Skip to content

Cascade inconsistency: bullets counted but experienceCount: 0 for non-standard experience headings (laverne fixture) #19

Description

@Vaishnavi1709

Problem

The openresume-laverne-word-quartz corpus fixture exposes a scoring inconsistency for student and volunteer-heavy resumes:

cascade.experienceCount   → 0       (parsed.experience[].length)
score.bulletCount         → 6       (extractBulletsFromText over rawText)
score.completeness.missing → ["work experience", "phone", "LinkedIn"]

The resume contains sections headed "VOLUNTEER EXPERIENCE" and "ON CAMPUS INVOLVEMENT" with 6 experience-shaped bullet points. The parser scores those 6 bullets for Specificity and Structure (document-scoped), but experienceCount stays 0 and Completeness deducts a full check for missing "work experience" — because experience extraction is section-scoped on SECTION_KEYWORDS.experience (regex.ts:96–105) while bullet counting runs document-scoped in extractBulletsFromText (score.ts:508).

Current snapshot: tests/fixtures/pdfs/word/openresume-laverne-word-quartz.expected.json

"experienceCount": 0,
"bulletCount": 6,
"completeness": {
  "missing": ["LinkedIn", "phone", "work experience"]
}

Root cause

Two layers in the pipeline treat "experience" differently:

  1. Tier 1 parser (openresume.ts:107): findSection(sections, "experience") returns null because matchSectionHeader (regex.ts:172) tests each heading against SECTION_KEYWORDS.experience (regex.ts:96–105). That list contains only canonical professional-experience synonyms ("experience", "work experience", "professional experience", "employment", "employment history", "work history", "career", "career history"). Headings like "VOLUNTEER EXPERIENCE" and "ON CAMPUS INVOLVEMENT" match nothing, so extractExperience(null) returns [] and parsed.experience stays empty.

  2. Anonymous scorer (score.ts:530): extractBulletsFromText(input.rawText) scans the raw text string without any section boundary awareness. It finds all 6 bullet lines regardless of their parent heading. The Completeness check at score.ts:567–571 tests expEntries.length > 0 (where expEntries = input.parsed.experience ?? []), labels the failure "work experience", and deducts a full check slot.

The three keywords "volunteer", "volunteering", and "activities" currently live in SECTION_KEYWORDS.other (regex.ts:123–139), whose sole purpose is to act as a section boundary terminator — nothing renders an other section. Headings containing those words terminate the preceding section but do not open an experience section.

Decided fix — Compound A + C

Decision: student/volunteer headings count toward experienceCount AND drop the "work experience" Completeness deduction when bulletCount > 0.

Part A — Expand SECTION_KEYWORDS.experience (regex.ts:96–105)

Add student and non-standard synonyms so matchSectionHeader routes those headings into the experience section:

"volunteer experience"
"community service"
"leadership"
"involvement"
"on campus involvement"
"campus involvement"
"extracurricular"
"extracurricular activities"
"activities"          ← moved from other
"volunteer"           ← moved from other
"volunteering"        ← moved from other

Keyword move: "volunteer", "volunteering", and "activities" are removed from SECTION_KEYWORDS.other and added to SECTION_KEYWORDS.experience. They previously served as boundary terminators; after the move they open an experience section instead. Accept this trade-off (see Risks).

Part C — Adjust the Completeness scorer (score.ts:567–571)

De-emphasize the "work experience" check when the document has bullets but no parsed experience entries — indicating that extraction missed content rather than that the resume has none:

// score.ts ~line 567
completenessChecks.push({
  key: "experience",
  passed: expEntries.length > 0 || bullets.length > 0,
  label: "work experience",
});

This lets the bullet signal (already computed at line 530) serve as a fallback pass condition for the experience completeness check. The scorer still rewards resumes that produce parsed experience entries (enabling the dates sub-check at lines 580–587), but stops penalizing resumes where extraction failed to section-scope the content.

Acceptance criteria

  1. After Part A: experienceCount ≥ 1 for the laverne fixture (at least one of "VOLUNTEER EXPERIENCE" or "ON CAMPUS INVOLVEMENT" is parsed into parsed.experience).
  2. After Part C: "work experience" is absent from score.completeness.missing for the laverne fixture.
  3. The laverne expected.json snapshot is updated to reflect both changes:
    • "experienceCount": ≥ 1
    • "work experience" removed from completeness.missing
    • Overall score will increase (exact value to be captured in the updated snapshot run).
  4. Existing fixtures whose experienceCount > 0 are unaffected — the keyword expansion only adds new match paths, it does not change existing ones.
  5. The google-docs-skia-proxy-nonstandard-headers and weasyprint-cairo-nonstandard-headers fixtures (which also have experienceCount: 0 and "work experience" in missing) should be evaluated for whether their headings now match; if they do, those snapshots update too. If they don't, that confirms the fix scope is bounded.
  6. npm run test passes (including corpus snapshot verification) with UPDATE=1 re-run to bake the new snapshots.

Risks

Two-column boundary bleed. "volunteer", "volunteering", and "activities" previously terminated the preceding section in SECTION_KEYWORDS.other. Moving them to experience means a two-column resume with a sidebar "VOLUNTEER" label will now open an experience section instead of closing the preceding one, potentially bleeding sidebar content into the experience entries. Mitigations:

  • The isTwoColumn layout probe (from LayoutProbes) is already in scope; a follow-up could gate the expanded keywords behind a !isTwoColumn condition if bleed is observed in practice.
  • The existing two-column corpus fixtures (google-docs-skia-proxy-two-column, weasyprint-cairo-two-column, chromium-asymmetric-sidebar) must be re-run with UPDATE=1 and their snapshots eyeballed for regression before merging.

False-positive "leadership" / "involvement". A resume that uses "Leadership" as a sidebar widget label rather than a section heading could bleed into experience. The split-letter recovery already in matchSectionHeader (regex.ts:172) does not change this risk; it applies equally to any added keyword.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingimprovementEnhancing existing functionality

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions