[refactor] education.ts hand-rolls entry segmentation instead of using parseEntryBlocks — the repo's #1 complexity function
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
- Issue type
- Refactor
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
Research direction
Start by characterizing the education corpus fixtures and the batch:parser-education lane, then compare education.ts with parseEntryBlocks in src/lib/heuristics/entry-blocks.ts and its callers in experience.ts, projects.ts, and achievements.ts. Done means education.ts uses the shared primitive, the private chunker is gone, snapshots are unchanged or justified, fallow complexity improves, and npm run verify passes.
Written by the indexing model from the issue text.
Description
Problem
extractEducation is the highest-cognitive-complexity function in the repository, and the reason is that education.ts hand-rolls the entry segmentation that every other extractor gets from the shared parseEntryBlocks primitive.
fallow health (repo-wide, 14,244 functions analyzed) ranks it #1 and #5:
| rank | function | cognitive |
|---|---|---|
| 1 | extractEducation (src/lib/heuristics/extract/education.ts:1123) |
80 |
| 5 | educationFromChunk (src/lib/heuristics/extract/education.ts:921) |
42 |
src/lib/heuristics/entry-blocks.ts records the gap in its own prose:
education.tsdoes not use this primitive at all.
—src/lib/heuristics/entry-blocks.ts:279-280
The three other entry-shaped sections are thin callers of the shared primitive:
src/lib/heuristics/extract/experience.ts:44(and a second pass at:58)src/lib/heuristics/extract/projects.ts:39src/lib/heuristics/extract/achievements.ts:75
education.ts instead imports only two leaf predicates from it — isDateOnlyLine, isEntryHeaderShape (education.ts:22) — and re-implements the rest as a private "chunker": educationFromChunk (:921) joins a chunk with " | " and re-parses fields out of the joined string (:925), with the chunking gates spread across isRealEntryHeader (:258), isCourseworkContinuation (:290), isInlineDatedProgram (:362), isProgramLeadAt (:484) and isInstitutionLeadAt (:545).
The file carries 27 distinct issue references — tied for second-highest in the repo — which is what that hand-rolled path costs in accreted special cases.
Why this is the right shape
parseEntryBlocks is already a parameterized segmentation engine, not an experience-specific one. EntryBlockConfig (entry-blocks.ts:185-213) exposes exactly the axes education needs:
anchor— education entries anchor on an institution or degree line, not necessarily a date rangecollectBody— education entries may be header-onlyheaderLookback— theDegree\nInstitution <dates>stackdateParsing: "all" | "date_anchors_only"— education's dates ride the institution line
Projects and achievements were both onboarded onto it by supplying a config. Education is the one holdout.
Scope
Behaviour-preserving segmentation swap. education.ts keeps ownership of its education-specific field logic (degree/field parsing, GPA and honors via education-grade.ts, institution/location splitting, date precision) — the goal is to delete the private chunker, not the domain knowledge.
Expect this to need a new EntryAnchor variant or a widened anchor config; adding one is in the spirit of the primitive and benefits the other three callers.
Implementation plan
- Characterize the current chunker: what line sequences does it group, and on which fixtures? The education corpus fixtures plus the
batch:parser-educationlane are the reference set. - Express that grouping as an
EntryBlockConfig, extendingEntryAnchorif the institution-lead / degree-lead cues have no existing anchor. - Replace
educationFromChunk's chunk-assembly withparseEntryBlocksoutput, keeping the per-field extraction that runs on each block. - Delete the now-dead chunking gates, or demote any that survive as genuine field-level predicates.
- Re-run
fallow healthand record the new cognitive numbers for both functions.
Acceptance criteria
-
education.tsimports and callsparseEntryBlocks; the private chunk-assembly path is gone. - Corpus snapshots are byte-identical, or every diff is reviewed fixture-by-fixture and justified in the PR description.
-
extractEducationandeducationFromChunkboth drop below the fallow complexity threshold; the PR quotes before/after. -
npm run verifygreen; no newKNOWN_FAILURESentries. - Any new
EntryBlockConfigaxis is documented in the interface docblock the way the existing four are. - The PR description names the segmentation decision it made — which
EntryAnchorvariant orEntryBlockConfigaxis was added (or that none was needed), which education fixtures drove that choice, and which of the five private chunking gates (isRealEntryHeader,isCourseworkContinuation,isInlineDatedProgram,isProgramLeadAt,isInstitutionLeadAt) were deleted versus kept as field-level predicates, with one line of reason each. A choice made silently is a review finding.
Context
Surfaced by an architecture review of the "should we introduce a plugin architecture?" question. The answer was no — but this convergence was the one structural item the review found that epic #646 does not already cover, and fallow ranks it as the single highest-complexity function in the tree. Related: #646, #831 (batch:parser-education).
- Dominant language
- TypeScript
- Stars
- 11
- Forks
- 4
- PR merge metrics
- No merged PRs in 30d
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from offlinecv/OfflineCV
-
fix(heuristics): unit number before a Saint-named city drops "St." from the locationPossibly taken @gaal-agent claimed this 10 days ago. Openbug gaal ux:parsing
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Migrate Gaal implement/review/revise to the local gaal watch; keep mechanical workflows on ActionsOpenarchitecture chore github_actions needs-human ready-for-agent
Difficulty 5/5 Over a week Newbie friendliness 8/100
-
Parser: achievement year lost on re-import when '· YYYY' wrapped to its own linePossibly taken @gaal-agent claimed this 5 days ago. Openbug gaal ready-for-agent ux:parsing
Difficulty 3/5 1-2 days Newbie friendliness 25/100
-
Export: auto-bolded metric bullets extract one word per line in stream-order parsers (pypdf/Drive)Possibly taken @gaal-agent claimed this 5 days ago. Openbatch:export-standards bug gaal ready-for-agent ux:edit-export
Difficulty 3/5 1-2 days Newbie friendliness 25/100
-
bug duplicate improvement
Difficulty 3/5 Half a day Newbie friendliness 66/100
All issues in offlinecv/OfflineCV
Similar issues
-
[测试] 等待仓库加载后再记录多仓库排序基线Possibly taken @myot233 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
area:arcade p3-medium ready-for-agent testing type:docs
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
fderuiter/deruiter.dev#2369 ·
Maintainers usually reply within 1 day
-
area:devenv origin:agent type:bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
overengineeringstudio/effect-utils#1949 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
ccch1mneyyy/dsh-TUI#1631 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
MiniMax-AI/minimax-code#507 ·
Maintainers usually reply within 1 day