fix(heuristics): drop empty-title (date-only) entry blocks before scoring (#145) - #161
Conversation
s-annam
left a comment
There was a problem hiding this comment.
PR Review: fix(heuristics): drop empty-title (date-only) entry blocks before scoring (#145)
Summary
Correct root-cause fix. A date-only anchor reduces to an empty label after stripDateRange, producing a phantom { title: "" } / { name: "" } score-0 entry that also diluted section confidence via avgScore. The new shared finalizeEntries helper drops the phantom and packages survivors as the standard { value, confidence } pair.
Highlights
- DRY: one helper replaces the duplicated
blocks.length === 0 → avgScore(...)tail across all three entry extractors. - Filtering on the built entry's label (not on empty
headerLines) also catches URL-only headers thatliftHeaderLabelcollapses to an empty label — covers every anchor path at once. - Behavior-preserving for the empty case (empty
built→avgScore([])→ 0), so no regression vs the old early return. - Predicates match the returned label fields (
title,name,title || companyfor experience).
Verification
npm run typecheckcleannpm run test— 723 passed (+5 new cases incl. no-dilution regression guard)- Corpus unchanged vs main
Verdict
Action: APPROVE
Rationale: No findings. Targeted, well-tested fix.
…ring (#145) A header/anchor line whose entire text is a date leaves nothing after stripDateRange, so parseEntryBlocks emits a block with empty headerLines. The three extractors mapped that straight through to a {title:""}/{name:""} entry with score 0 — a title-less item in output that also diluted section confidence. Add a shared finalizeEntries() helper that drops empty-label entries (filtering on the built entry's label, so a URL-only header is caught too) and packages the survivors; route extractAchievements/extractProjects/extractExperience through it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4a7796d to
1158431
Compare
s-annam
left a comment
There was a problem hiding this comment.
Re-approve after rebase onto main + conflict resolution in experience.ts (import-line only: kept #159's US_LOCATION_RE/INTL_LOCATION_RE, swapped avgScore→finalizeEntries; avgScore now unused in file). Gates green post-rebase: typecheck clean, 732 tests passing.
s-annam
left a comment
There was a problem hiding this comment.
Re-approve after updating branch to base (160 merged; disjoint files, clean update — no conflict). Gates green on prior rebase: typecheck clean, 732 tests.
…ring (#145) (#161) A header/anchor line whose entire text is a date leaves nothing after stripDateRange, so parseEntryBlocks emits a block with empty headerLines. The three extractors mapped that straight through to a {title:""}/{name:""} entry with score 0 — a title-less item in output that also diluted section confidence. Add a shared finalizeEntries() helper that drops empty-label entries (filtering on the built entry's label, so a URL-only header is caught too) and packages the survivors; route extractAchievements/extractProjects/extractExperience through it. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Srinivas Annam <annam@annam.org>
…ring (#145) (#161) A header/anchor line whose entire text is a date leaves nothing after stripDateRange, so parseEntryBlocks emits a block with empty headerLines. The three extractors mapped that straight through to a {title:""}/{name:""} entry with score 0 — a title-less item in output that also diluted section confidence. Add a shared finalizeEntries() helper that drops empty-label entries (filtering on the built entry's label, so a URL-only header is caught too) and packages the survivors; route extractAchievements/extractProjects/extractExperience through it. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Srinivas Annam <annam@annam.org>
Summary
A header/anchor line whose entire text is a date leaves nothing after
stripDateRange, soparseEntryBlocksemits a block with emptyheaderLines. All three extractors mapped that straight through to a{ title: "" }/{ name: "" }entry with score 0 — a title-less item in output that also diluted section confidence viaavgScore. (Surfaced during #144 review; pre-existing and shared across the extractors.)Adds a shared
finalizeEntries(built, hasLabel)helper inextract/shared.tsthat drops empty-label entries and packages the survivors as the standard{ value, confidence }pair — keeping the score-0 phantom out of theavgScoredenominator. RoutesextractAchievements/extractProjects/extractExperiencethrough it (predicates:title,name, andtitle || companyfor experience). Filtering on the built entry's label rather than on emptyheaderLinesalso catches a URL-only header, whichliftHeaderLabelcollapses to an empty label — so every anchor path (first_lineflat-list,first_linebuildEntryBlock,date_range) is covered at once. Per-block mappers and scoring are unchanged.Closes #145
Test plan
npm run typecheckcleannpm run testgreen (723 passed; +5 newextract-fieldscases: date-only bullet, date-only header, date-only project, date-only experience, and a no-dilution regression guard)main(confirmed via--ignore-cr-at-eol); no real résumé carried a phantom empty entry, matching the issue's expectationeslintclean on changed files