/** * ATS fidelity test fixtures — three ResumeData shapes for the Chromium vs * WeasyPrint PDF comparison (scripts/ats-fidelity.ts). * * These conform to the same `ResumeData` the React HTML templates consume * (src/components/builder/use-resume.ts), NOT `RenderableResume`. The * difference matters: `ResumeData` lifts `projects`/`certifications`/`languages` * to top-level typed arrays and uses `templateConfigs` (plural, keyed by * templateId), where `RenderableResume` nests them under `sections` and uses * a single `templateConfig`. The React templates (`*-doc.tsx`) read the * top-level shape, so the fixtures match what real users edit. * * Three fixtures cover the three ATS failure modes that distinguish Chromium * print-to-PDF from purpose-built PDF engines: * * - STANDARD: the canonical happy path. Catches bulk character loss. * - UNICODE: non-ASCII names/locations + smart punctuation. Catches * encoding round-trip errors and ligature/run fragmentation. * - LONG: 3-4 pages of content forcing cross-page breaks. Catches the * classic Chromium PDF bug where a word straddling a page * boundary gets split into separate text runs. * * The string values here are the GROUND TRUTH the metrics check against * (scripts/ats-fidelity/metrics.ts). Don't paraphrase them — the metrics do * exact substring matching. */ import type { ResumeData } from "@/components/builder/use-resume"; /** Base shape shared by all three fixtures — every field is overridden per * fixture; this just provides the boilerplate (`id`, empty containers). */ function base(overrides: Partial): ResumeData { return { id: "fixture", templateId: "classic", firstName: "", lastName: "", headline: null, contact: { links: [] }, summary: null, experience: [], education: [], skills: [], projects: [], certifications: [], languages: [], sections: {}, templateConfigs: {}, draftState: {}, ...overrides, }; } /** * STANDARD — the canonical senior-engineer profile. * * Mirrors src/lib/demo-resume.ts content (the same resume every gallery * visitor sees), restructured into the React template's `ResumeData` shape. * Pure ASCII, normal-length fields, two experience entries + one education. * This is the "everything should work" baseline. */ export const STANDARD: ResumeData = base({ templateId: "classic", firstName: "Ada", lastName: "Lovelace", headline: "Senior Software Engineer", contact: { email: "ada@example.com", phone: "+1 (555) 123-4567", location: "San Francisco, CA", links: [ { type: "github", url: "https://github.com/ada", label: "GitHub" }, { type: "linkedin", url: "https://linkedin.com/in/ada", label: "LinkedIn" }, ], }, summary: "Senior engineer with 8+ years building distributed systems and developer platforms. Led the redesign of a payments pipeline processing $2B annually, cutting latency 40% and cost 30%. Passionate about systems that scale cleanly and teams that ship calmly.", experience: [ { id: "e1", role: "Senior Software Engineer", company: "Anthropic", location: "San Francisco", startDate: "2023", endDate: "Present", bullets: [ "Led the design of a streaming inference pipeline serving 1M+ daily requests at p99 < 80ms.", "Cut inference cost 38% by re-architecting the model router on a tiered GPU fleet.", "Mentored 4 engineers; established the team's on-call runbooks and incident review process.", ], }, { id: "e2", role: "Software Engineer", company: "Stripe", location: "Remote", startDate: "2020", endDate: "2023", bullets: [ "Shipped the payment-intents retry engine, reducing failed-charge leakage by 22%.", "Owned the idempotency layer across 4 services handling $2B+ in annual volume.", ], }, ], education: [ { id: "ed1", institution: "Stanford University", degree: "B.S. Computer Science", field: "Systems & AI", startDate: "2012", endDate: "2016", }, ], skills: [ { category: "Languages", items: ["TypeScript", "Go", "Python", "Rust"] }, { category: "Infrastructure", items: ["Postgres", "Kubernetes", "Terraform", "Redis"] }, { category: "Domains", items: ["Distributed Systems", "LLM Infra", "Payments"] }, ], projects: [ { id: "p1", name: "OpenMetrics", description: "Open-source Prometheus-compatible metrics aggregator.", company: "Side Project", startDate: "2022", endDate: "Present", bullets: ["1.2k GitHub stars; adopted by 3 mid-size companies."], }, ], certifications: [{ id: "c1", name: "AWS Solutions Architect — Professional" }], languages: [ { id: "l1", name: "English", level: "Native" }, { id: "l2", name: "Mandarin", level: "Professional" }, ], }); /** * UNICODE — non-ASCII names, smart punctuation, em-dashes. * * The string values the metrics check are in `UNICODE_TOKENS` below — keep * them in sync with the fixture body if you edit either. * * Catches: * - UTF-8 round-trip errors (the most common silent PDF failure) * - Smart-quote and em-dash normalization (some PDFs replace these with ?) * - Ligature run fragmentation on accented characters */ export const UNICODE: ResumeData = base({ templateId: "classic", firstName: "José", lastName: "Müller", headline: "Ingénieur Logiciel Senior", contact: { email: "jose.muller@example.com", phone: "+41 44 555 12 34", location: "Zürich, Switzerland", links: [{ type: "website", url: "https://jose-muller.example", label: "Portfolio" }], }, summary: "Ingénieur logiciel avec 8 ans d'expérience en systèmes distribués — “the right tool for the job” — spécialisé dans les plateformes à fort trafic.", experience: [ { id: "e1", role: "Ingénieur Principal", company: "Müller GmbH", location: "Zürich", startDate: "2020", endDate: "Présent", bullets: [ "Conception d'un pipeline traitant 1M+ requêtes/jour — latence p99 < 80ms.", "Réduction des coûts d'infrastructure de 38% via une architecture tiered GPU.", "Mentorat de 4 ingénieurs ; mise en place des runbooks d'astreinte.", ], }, ], education: [ { id: "ed1", institution: "ETH Zürich", degree: "M.Sc. Informatik", field: "Systèmes distribués", startDate: "2012", endDate: "2016", }, ], skills: [ { category: "Langues de programmation", items: ["TypeScript", "Go", "Python"] }, { category: "Infrastructure", items: ["Postgres", "Kubernetes", "Terraform"] }, ], projects: [ { id: "p1", name: "ÜberMetrics", description: "Agrégateur de métriques open-source — compatible Prometheus.", startDate: "2022", endDate: "Présent", bullets: ["1.2k étoiles GitHub ; adopté par 3 entreprises."], }, ], certifications: [{ id: "c1", name: "AWS Solutions Architect — Professional" }], languages: [ { id: "l1", name: "Français", level: "Natif" }, { id: "l2", name: "Deutsch", level: "Courant" }, { id: "l3", name: "English", level: "Full Professional" }, ], }); /** Tokens the metrics must find verbatim in the UNICODE fixture's extracted * text. If any of these is missing or mangled, Unicode fidelity failed. * Keep in sync with `UNICODE` above. */ export const UNICODE_TOKENS: string[] = [ "José", "Müller", "Zürich", "—", // em-dash "“", // left smart quote "”", // right smart quote "Ingénieur", "d'expérience", "Présent", ]; /** A unique marker prepended to the cross-page test bullet so the metrics * can locate it in extracted text even after Chromium has fragmented it. * Must not appear anywhere else in any fixture. Declared before `longContent` * because the LONG fixture embeds it. */ export const CROSS_PAGE_MARKER = "CROSSPAGE-X9K2"; /** * LONG — 3-4 pages of content forcing cross-page breaks. * * Catches the classic Chromium PDF bug: a bullet that straddles a page * boundary gets split into two text runs, and `pdftotext` reads them as * separate fragments. The metrics check whether a specific long bullet * (marked with CROSS_PAGE_MARKER) survives extraction as one continuous * string. * * Built by replicating STANDARD's content 6× — enough to overflow 2 A4 * pages reliably across all five templates (page capacity varies by * template density; 6× guarantees overflow even in the densest). */ function longContent(): ResumeData { const baseExperience = STANDARD.experience[0]; const longExperience = Array.from({ length: 6 }, (_, i) => ({ ...baseExperience, id: `e${i + 1}`, company: `${baseExperience.company} (Tenure ${i + 1})`, bullets: [ // A long bullet — the kind most likely to straddle a page break. The // CROSS_PAGE_MARKER prefix lets metrics.ts locate this specific bullet // in extracted text even if Chromium splits it across text runs/pages. `${CROSS_PAGE_MARKER} Led a multi-quarter initiative to redesign the request pipeline from ingress through the model router, eliminating three legacy proxies, consolidating four telemetry systems into one, and reducing end-to-end p99 latency from 240ms to 78ms while cutting monthly infrastructure spend by $340K and improving developer velocity measurably across the org.`, ...baseExperience.bullets, ], })); return base({ templateId: "classic", firstName: STANDARD.firstName, lastName: STANDARD.lastName, headline: STANDARD.headline, contact: STANDARD.contact, summary: STANDARD.summary, experience: longExperience, education: [...STANDARD.education, ...STANDARD.education], skills: [ ...STANDARD.skills, { category: "Frontend", items: ["React", "Next.js", "Tailwind", "Vite"] }, { category: "Cloud", items: ["AWS", "GCP", "Cloudflare", "Fly.io"] }, ], projects: [...STANDARD.projects, ...STANDARD.projects, ...STANDARD.projects], certifications: [ ...STANDARD.certifications, { id: "c2", name: "Certified Kubernetes Administrator (CKA)" }, { id: "c3", name: "Google Cloud Professional Cloud Architect" }, ], languages: STANDARD.languages, }); } export const LONG: ResumeData = longContent(); /** All fixtures keyed by name for iteration in the runner. */ export const FIXTURES: Record = { standard: STANDARD, unicode: UNICODE, long: LONG, };