Summary
The Tier 1 heuristic parser has one real "dated entry block" parser — extractExperience — and every other section is bespoke or missing. Experience, projects, and achievements are structurally identical (a header line + optional date/date-range + a bullet body), yet:
extractExperience (src/lib/heuristics/extract-fields.ts:523) is the only one that anchors on DATE_RANGE_RE, splits entries, and parses a real date range.
- Projects has no extractor at all (see child issue: Projects as a first-class section).
- Achievements is demoted to the
other sink bucket and never extracted (see child issue: Achievements).
- Education re-implements date handling badly (first-year only) instead of reusing
parseDateRange (see child issue: Education date-range).
This is the architectural root behind the three reported parser bugs: section logic is "implemented in multiple different ways," so fixing one section doesn't help the next.
Goal
Extract the common machinery out of extractExperience into a single shared primitive — tentatively parseEntryBlocks() — that all entry-style sections consume with a small per-section config. This is the enabler epic; the projects/achievements/education child issues build on it.
Proposed shape
// src/lib/heuristics/entry-blocks.ts (new)
interface EntryBlockConfig {
anchor: "date_range" | "institution" | "first_line";
// which header line becomes title vs subtitle (company / institution / project name)
// whether bullets are collected into a description body
}
function parseEntryBlocks(section: PdfSection, cfg: EntryBlockConfig): EntryBlock[];
extractExperience becomes a thin caller of parseEntryBlocks({ anchor: "date_range", ... }). Reuse the existing parseDateRange / stripDateRange / isBulletLine / stripBullet helpers — don't duplicate them.
Acceptance criteria
Scope notes
Milestone: M1 · Parser Hardening. Parent of the projects / achievements / education child issues.
Summary
The Tier 1 heuristic parser has one real "dated entry block" parser —
extractExperience— and every other section is bespoke or missing. Experience, projects, and achievements are structurally identical (a header line + optional date/date-range + a bullet body), yet:extractExperience(src/lib/heuristics/extract-fields.ts:523) is the only one that anchors onDATE_RANGE_RE, splits entries, and parses a real date range.othersink bucket and never extracted (see child issue: Achievements).parseDateRange(see child issue: Education date-range).This is the architectural root behind the three reported parser bugs: section logic is "implemented in multiple different ways," so fixing one section doesn't help the next.
Goal
Extract the common machinery out of
extractExperienceinto a single shared primitive — tentativelyparseEntryBlocks()— that all entry-style sections consume with a small per-section config. This is the enabler epic; the projects/achievements/education child issues build on it.Proposed shape
extractExperiencebecomes a thin caller ofparseEntryBlocks({ anchor: "date_range", ... }). Reuse the existingparseDateRange/stripDateRange/isBulletLine/stripBullethelpers — don't duplicate them.Acceptance criteria
parseEntryBlocks()primitive with unit tests.extractExperiencerefactored to consume it — behavior-preserving: existingcorpus.testsnapshots andextract-fields.testunchanged (or only mechanically re-baked with justification).Scope notes
Milestone: M1 · Parser Hardening. Parent of the projects / achievements / education child issues.