Skip to content

refactor(#133): pool anon scorer bullets from accomplishment sections - #139

Merged
s-annam merged 1 commit into
mainfrom
gh-133
Jun 21, 2026
Merged

refactor(#133): pool anon scorer bullets from accomplishment sections#139
s-annam merged 1 commit into
mainfrom
gh-133

Conversation

@s-annam

@s-annam s-annam commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Sources the anonymous scorer's experience-bullet pool from the accomplishment sections (experience / projects / achievements) of SectionedResume via a new extractBulletsFromSections, instead of walking all of rawText and subtracting a skills-line set. Skills are now excluded by construction (skills is not an accomplishment section), retiring extractBulletsFromText, buildSkillsExclusion, and stripBulletMarker — the "pool everything, subtract" shape that forced the #30 side-channel. The experience-completeness check now asks "is there a non-empty experience section?" rather than guessing from the global bullet count (spike §1.5). The #82 live bullet-edit path is threaded so an edit re-grades Specificity/Structure: applyOverrides now takes and returns the edited SectionedResume (new replaceBulletInSections), and App.tsx feeds the edited sections into the scorer.

This is a deliberate scoring change: ATS_SCORE_ALGO_VERSION bumped 1.2 → 1.3 with a changelog entry.

Stacked on #138 (refactor-137-132-fallow-sectioned, the SectionedResume proof PR). Based against that branch, not main — GitHub will auto-retarget to main once #138 merges. Review/merge #138 first.

Resolves #133
Refs #109, #127, #82

Goldens movement (regenerated + spot-audited fixture-by-fixture)

19 of 24 fixtures are version-stamp-only (their bullets were already inside accomplishment sections — unchanged, as expected). 5 moved, all narrowing (totalBullets dropped; none increased → no double-counting):

fixture totalBullets overall why
google-docs-skia-proxy-two-column 18→13 63→74 5 bulleted CONTACT lines in the profile sidebar (• jane@…, • (312)…) were falsely pooled as experience bullets; removed → ratios rise
weasyprint-cairo-two-column 18→13 53→60 same 5 bulleted-contact-sidebar lines leave the pool
awesome-cv-cv 59→52 58→60 6 certifications + 1 education bullet leave the pool
awesome-cv-resume 31→30 67→68 1 education bullet leaves the pool
openresume-react-pdf 12→8 84→84 3 education bullets + 1 project bullet leave the pool (the project bullet lands under a mis-segmented singular "PROJECT" header → education; #109 territory, pre-existing)

Only lossy keys/counts *.expected.json snapshots changed — no fixture binaries touched, so no PII surface.

Test plan

  • npm run typecheck clean
  • npm run test green (616 tests, 41 files; 24 corpus fixtures match regenerated goldens)
  • npm run lint (eslint) clean

@rohithgollapalli

Copy link
Copy Markdown
Collaborator

Review — Approve ✅ (stacked on #138 — merge that first)

A well-scoped, well-documented deliberate scoring change. It removes the "pool all of rawText, then subtract a skills set" anti-pattern and instead pools bullets from the accomplishment sections, making skills exclusion structural rather than a side-channel. The #82 live-edit path is correctly re-threaded through the section view. Verified locally on gh-133.

Verification performed

  • npm run typecheck clean
  • npm run lint clean
  • npm run test616/616 pass, 41 files (24 corpus fixtures match the regenerated goldens)
  • ✅ No orphaned references to the retired extractBulletsFromText / buildSkillsExclusion / stripBulletMarker — remaining hits are only in docs/segmentation-spike.md
  • App.tsx useMemo deps are complete (state covers state.result.sections; edit overrides listed)

What I checked closely

  • Skills-by-construction. extractBulletsFromSections walks only accomplishmentSections (experience/projects/achievements) in policy order; skills can never enter the pool, so the subtraction step and its side-channel are genuinely gone. The reworked unit tests pin this by showing the same lines pool when in experience and don't when in skills.
  • Edit → re-lint: make parse overrides authoritative (or honest) #82 edit path. replaceBulletInSections mirrors replaceBulletInRawText (first normalized-match wins, marker preserved), is immutable (clones the map + only the mutated section's array, leaves the input untouched — explicitly asserted in the test), and App.tsx now feeds the edited sections into re-grading. Matching is by normalized text, so the bullet-index reorder (now section order, not rawText order) doesn't affect override application.
  • Goldens. All 5 non-stamp movements are narrowing (totalBullets drop, none rise → no double-counting), and the audit table's explanations check out (bulleted contact-sidebar lines and education/cert bullets leaving the pool).

Notes (non-blocking)

  1. New invariant coupling in the lone-bullet merge. extractBulletsFromLines dropped the blank-skipping loop and now does j = i + 1, relying on "section lines are already trimmed & non-empty" from toSectionedResume. That holds for both producers today (toSectionedResume and replaceBulletInSections, since empty edits are skipped), and it's documented — but it's a real coupling: any future code that puts a blank line into a byName array would make a lone glyph adopt "" as its text. Worth keeping in mind if the section view gains other producers.
  2. Cross-section lone glyph. A marker-only line stranded at the end of a section no longer merges with text in the following section (it breaks within the section). Negligible and arguably more correct, just flagging the semantic shift from the whole-document walk.
  3. openresume-react-pdf drops a genuine project bullet due to a mis-segmented singular "PROJECT" header landing under education (Generalize resume section recognition (layered recognizer) #109, pre-existing). The PR rightly calls this out — but it's worth noting the new "pool by section membership" model makes pre-existing segmentation bugs score-visible in a way the old rawText walk masked. Raises the value of Generalize resume section recognition (layered recognizer) #109.
  4. Experience-completeness predicate has no corpus coverage for its changed branch. passed: expEntries.length > 0 || (experience section non-empty) replaces the old || bullets.length > 0. In all 5 moved fixtures completeness stayed at 27, so the divergent case (bullets only in projects/achievements, no experience section) is exercised only by the flags missing sections unit test, not the corpus. Fine, just no fixture pins it.

None block merge. Clean structural win.

🤖 Generated with Claude Code

@s-annam
s-annam deleted the branch main June 21, 2026 16:14
@s-annam s-annam closed this Jun 21, 2026
@s-annam s-annam reopened this Jun 21, 2026
@s-annam
s-annam changed the base branch from refactor-137-132-fallow-sectioned to main June 21, 2026 16:18
@s-annam
s-annam dismissed rohithgollapalli’s stale review June 21, 2026 16:18

The base branch was changed.

Pool the anonymous scorer's experience-bullet set from the
accomplishment sections (experience/projects/achievements) via
SectionedResume instead of walking all of rawText and subtracting
skills. Skills are excluded by construction.

- Retire extractBulletsFromText + buildSkillsExclusion +
  stripBulletMarker; add extractBulletsFromSections +
  extractBulletsFromLines.
- Experience-completeness check now asks "is there a non-empty
  experience section?" instead of "any bullet anywhere".
- Bump ATS_SCORE_ALGO_VERSION 1.2 -> 1.3 (deliberate scoring change)
  with changelog entry.
- Thread the edited SectionedResume through applyOverrides (new
  replaceBulletInSections + sections param/return) and App.tsx so
  live bullet edits re-grade Specificity/Structure (#82 integration).
- Regenerated + spot-audited corpus goldens: 19 fixtures
  version-stamp-only (bullets already inside accomplishment sections);
  5 moved (totalBullets dropped) because contact/summary/education/
  un-segmented bullets correctly left the pool — all narrowing, none
  added.

Resolves #133
Refs #109

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GstfZc8CpvugDY85bS541Y
@s-annam

s-annam commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main now that #138 is merged. The PR was auto-closed when its stacked base branch (refactor-137-132-fallow-sectioned) was deleted on #138's squash-merge; reopened and retargeted to main. Since #138 landed as a squash commit, this branch's copy of the #137/#132 work conflicted — dropped it via git rebase --onto origin/main d09fca2, leaving a single clean commit (a466107) on top of main.

Gates green on the rebased branch: npm run typecheck clean, npm run lint clean, npm run test 616/616. Mergeable is now clean (was CONFLICTING).

On the four non-blocking review notes: all are flagging semantic shifts / pre-existing conditions, not change requests, so leaving code as-is. Note 4 (no corpus fixture pins the changed experience-completeness branch) is the only one with a possible follow-up — the flags missing sections unit test covers it; deferring a dedicated fixture as out of scope for this structural refactor.

@s-annam
s-annam requested a review from rohithgollapalli June 21, 2026 16:21
@s-annam
s-annam merged commit 106c897 into main Jun 21, 2026
2 checks passed
@s-annam
s-annam deleted the gh-133 branch June 21, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pool anon scorer bullets from accomplishment sections (retire rawText walk + buildSkillsExclusion)

2 participants