Skip to content

Refactor: split extract-fields.ts god-module (1060 LOC) into per-field modules #126

Description

@s-annam

Problem

src/lib/heuristics/extract-fields.ts is 1060 LOC — the largest module in the repo — and holds all 10 field extractors plus ~20 private helpers in one file:

extractName            (l.174)
extractContact         (l.292)   + ContactExtractionResult interface
extractSummary         (l.496)
extractSkills          (l.538)
extractExperience      (l.575)
extractProjects        (l.650)
extractAchievements    (l.744)
disambiguateCompanyTitle (l.932)
extractEducation       (l.988)

Each extractor is now independently complex (name scoring, contact band/annotation logic, education chunking, experience entry-blocking, column-skill splitting). The file has ~15 issue/template-specific heuristic anchors and keeps growing — PR #125 alone added +400 lines to it. This is a god-module: hard to navigate, hard to review a single extractor's change in isolation, and a magnet for unrelated churn.

This is a behavior-preserving refactor — no parsing logic changes, no score changes, no snapshot changes.

Why now

The deterministic Tier-1 extractor is where most template-fix PRs land, so the file's growth rate is high. Splitting now (before more accretion) keeps each extractor reviewable and makes the next round of fixes touch a ~150-LOC file instead of a 1060-LOC one.

Current structure facts (verified)

  • Sole non-test importer: src/lib/heuristics/openresume.ts:44 imports extractName, extractContact, extractSummary, extractSkills, extractExperience, extractProjects, extractAchievements, extractEducation from ./extract-fields.ts. (disambiguateCompanyTitle is exported but consumed internally by extractExperience.)
  • Cross-extractor private helpers:
    • looksLikeContactLink — used by extractSkills (token + cell rejection).
    • disambiguateCompanyTitle — used by extractExperience (and exported).
    • parseEducationDates / educationDateFields — education-only.
    • isBulletLine, stripBullet (imported from ./sections.ts), mergeItemText (imported from ./sections.ts), matchSectionHeader (from ./regex.ts) — already external, stay as imports.

Implementation plan

  1. Create per-field modules under src/lib/heuristics/extract/ (new dir):
    • name.tsextractName + isSingleNameWord, looksLikeMononymName, findContactClusterY, looksLikeDocTitleBoilerplate.
    • contact.tsextractContact, ContactExtractionResult, isLinkedinProfileUrl, LINKEDIN_NONPROFILE_RE, normalizeUrl, annotation/band helpers.
    • summary.tsextractSummary.
    • skills.tsextractSkills + isSkillToken, splitColumnCells, looksLikeContactLink, PROFILE_LABEL_RE/PROFILE_HOST_RE/URLISH_RE.
    • experience.tsextractExperience, disambiguateCompanyTitle, looksLikeTitle, looksLikeCompany, TITLE_KEYWORDS_RE.
    • education.tsextractEducation, educationFromChunk, isDateOnlyLine, parseEducationDates, educationDateFields.
    • projects.tsextractProjects.
    • achievements.tsextractAchievements.
  2. Shared helper placement: put a helper in the one module that uses it. If two modules need one (e.g. looksLikeContactLink if it ever spreads), promote it to a small extract/shared.ts — but only on real second use, not speculatively.
  3. Keep src/lib/heuristics/extract-fields.ts as a barrel that re-exports the public surface, so openresume.ts:44 imports are unchanged:
    export { extractName } from "./extract/name.ts";
    export { extractContact, type ContactExtractionResult } from "./extract/contact.ts";
    // …etc
    export { disambiguateCompanyTitle } from "./extract/experience.ts";
    (Optionally update openresume.ts to import from the new paths and delete the barrel — but the barrel is the lower-risk default; decide during implementation.)
  4. Move the co-located tests the same way, or keep extract-fields.test.ts importing through the barrel — either is fine as long as the suite stays green.
  5. Preserve SPDX headers on every new file (the 3-line Apache-2.0 header).
  6. Run npm run typecheck && npm run test && npm run lint.

Acceptance criteria

  • extract-fields.ts is ≤ ~60 LOC (barrel re-exports only) or removed with openresume.ts importing the new modules directly.
  • No single new extractor module exceeds ~250 LOC.
  • npm run test green with zero changes to any *.expected.json snapshot (proves behavior-preserving).
  • npm run typecheck and npm run lint clean.
  • openresume.ts produces byte-identical HeuristicResult for the existing fixture corpus (snapshot stability is the proof).
  • Every new .ts file carries the SPDX 3-line header.
  • No new dependency added.

Out of scope

  • Any change to extraction behavior (that's the heuristic PRs' lane).
  • The scorer / segmentation rework (tracked separately — see the section-segmentation issue).

Activity

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

Metadata

Metadata

Assignees

Labels

refactorCode restructuring without behavior change

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions