Files
ai-job-search-app/scripts/import-legacy.ts

170 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* One-time import of the legacy markdown job files + resumes into the DB.
* Usage: npx tsx scripts/import-legacy.ts
*/
import { PrismaClient } from "@prisma/client";
import matter from "gray-matter";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
const prisma = new PrismaClient();
const LEGACY = path.join(os.homedir(), "development/personal/ai-job-search");
function section(body: string, name: string): string | undefined {
const re = new RegExp(`##\\s*${name}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`, "i");
return body.match(re)?.[1]?.trim();
}
function firstMatch(body: string, re: RegExp): string | undefined {
return body.match(re)?.[1]?.trim();
}
async function importJobs() {
const dir = path.join(LEGACY, "jobs");
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".md"));
for (const file of files) {
const raw = fs.readFileSync(path.join(dir, file), "utf8");
const { data, content } = matter(raw);
const title = (data.title as string) ?? file.replace(/\.md$/, "");
const company = (data.company as string) ?? "Unknown";
const existing = await prisma.job.findFirst({ where: { title, company } });
if (existing) {
console.log(`skip (exists): ${file}`);
continue;
}
const url = firstMatch(content, /\*\*Link:\*\*\s*(\S+)/);
const applyUrl = firstMatch(content, /\*\*Apply(?: link)?:\*\*\s*(\S+)/);
const details = section(content, "Details");
const payLine = details ? firstMatch(details, /\*\*Pay(?:\s*Range)?:\*\*\s*(.+)/) : undefined;
const location = details ? firstMatch(details, /\*\*Location:\*\*\s*(.+)/) : undefined;
// crude pay parse: "$15-20/hr", "₱150-170k/mo", "$65-120/hr"
let payMin: number | undefined, payMax: number | undefined, payCurrency: string | undefined, payPeriod: string | undefined;
if (payLine) {
const m = payLine.match(/([$₱P])\s*([\d,.]+)\s*[-–]\s*([\d,.]+)(k?)\s*\/?\s*(hr|hour|mo|month|yr|year)?/i);
if (m) {
const mult = m[4] === "k" ? 1000 : 1;
payMin = parseFloat(m[2].replace(/,/g, "")) * mult;
payMax = parseFloat(m[3].replace(/,/g, "")) * mult;
payCurrency = m[1] === "$" ? "USD" : "PHP";
payPeriod = /hr|hour/i.test(m[5] ?? "") ? "hour" : /mo/i.test(m[5] ?? "") ? "month" : /yr|year/i.test(m[5] ?? "") ? "year" : undefined;
}
}
const phEligibility = /philippines|ph eligible|170\+ countries|worldwide/i.test(content) ? "yes" : "unclear";
const status = (data.status as string)?.replace(/\s+/g, "-") === "not-applied" ? "prospect" : "prospect";
await prisma.job.create({
data: {
title,
company,
source: file.replace(/\.md$/, ""),
url,
applyUrl,
payMin,
payMax,
payCurrency,
payPeriod,
location,
remoteType: "remote",
phEligibility,
description: section(content, "Description"),
requirements: section(content, "Requirements"),
notes: section(content, "Notes") ?? section(content, "Tailoring notes"),
status,
},
});
console.log(`imported: ${file}`);
}
}
async function importResumes() {
const base = path.join(LEGACY, "resume/resume.md");
if (fs.existsSync(base)) {
const md = fs.readFileSync(base, "utf8");
const exists = await prisma.resume.findFirst({ where: { kind: "base" } });
if (!exists) {
await prisma.resume.create({ data: { name: "Base Resume", kind: "base", markdown: md } });
console.log("imported: base resume");
}
}
const tailoredDir = path.join(LEGACY, "resume/tailored");
if (fs.existsSync(tailoredDir)) {
for (const f of fs.readdirSync(tailoredDir).filter((f) => f.endsWith(".md"))) {
const name = `Tailored: ${f.replace(/\.md$/, "")}`;
const exists = await prisma.resume.findFirst({ where: { name } });
if (exists) continue;
await prisma.resume.create({
data: { name, kind: "tailored", markdown: fs.readFileSync(path.join(tailoredDir, f), "utf8") },
});
console.log(`imported: tailored ${f}`);
}
}
}
async function seedProfile() {
const exists = await prisma.profile.findUnique({ where: { id: "main" } });
if (exists) return;
await prisma.profile.create({
data: {
id: "main",
fullName: "Joshua C. Sarmiento",
email: "joshuacsarmiento@gmail.com",
location: "Philippines",
headline: "Software Developer / AI Engineer — Prompt Engineering, Conversational AI, Backend Systems",
summary:
"Software developer with 8+ years of experience spanning enterprise IT, full-stack web development, and applied AI: building LLM-powered APIs and RAG systems with FastAPI and Amazon Bedrock.",
skills: JSON.stringify([
"Prompt engineering", "Amazon Bedrock", "RAG", "Conversational AI", "Python", "FastAPI",
"REST API design", "Active Directory", "Full-stack web development", "Bash/PowerShell automation",
]),
workHistory: JSON.stringify([
{
position: "Project Technical Specialist III",
company: "Philippine Institute of Volcanology and Seismology (PHIVOLCS)",
unit: "Geomatics and Hazard Assessment Services Section",
start: "2025-03", end: null, supervisor: "Eric Santos",
bullets: [
"Contributed to CLUP and CDRA systems",
"Gates Handa: AI APIs with FastAPI + Amazon Bedrock, chatbot, RAG document/report analyzer",
"Design system diagrams and plans; support project management",
],
},
{
position: "Computer Programmer II",
company: "Presidential Commission for the Urban Poor (PCUP)",
unit: "IT Unit",
start: "2019-08", end: "2024-02", supervisor: "Jason Ronidel",
bullets: [
"Developed major web apps: Attendance Monitoring, Service Workflow Management, UPO Accreditation, Reservation, Directory",
"Managed commission network infrastructure and devices",
"MS Access + VBA database applications",
],
},
{
position: "IT Associate",
company: "Manulife Business Process Services",
unit: "Global Access Management (GAM) Division",
start: "2016-11", end: "2017-10", supervisor: "Emory Lopez",
bullets: [
"Automated Active Directory user management with scripts (.sh, .ps1, .bat)",
"Managed AD, mainframe, Oracle, FTP, and email accounts (Exchange, Lotus Notes, O365)",
"ServiceNow request handling; VPN token creation",
],
},
]),
},
});
console.log("seeded: profile");
}
async function main() {
await importJobs();
await importResumes();
await seedProfile();
console.log("done");
}
main().finally(() => prisma.$disconnect());