/** * 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());