From ef068aa7648a1470a85bdfee6398c52c939c12f0 Mon Sep 17 00:00:00 2001 From: Vaishnavi Kale Date: Thu, 4 Jun 2026 14:50:10 -0700 Subject: [PATCH] fix(heuristics): reject resume-doc-title boilerplate as candidate name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #10 (mode 1). Adds partial defence against mode 2. The name selector in `extractName()` was scoring "first line in profile + largest font + title-case" up to 1.0, which lets a Microsoft-style "Functional Resume Sample" doc-title header win over the real candidate name on the line below it (reported by @sriyau64 during dogfooding). The data needed for a proper fix was already on `PdfLine` — y-coordinates of each profile line and the contact tokens — the selector just never consumed it. Fix is fully local to `extractName()`: - Hard rejection for resume-doc-title boilerplate. A line where ≥60% of tokens are in {resume, cv, curriculum vitae, sample, template, example, chronological, functional, combination, profile, biography, ...} is skipped before scoring. "Jane Smith Resume" still passes (1/3 boilerplate); "Functional Resume Sample", "Curriculum Vitae", "Resume Sample" all reject. - First-eligible-index reindex. When the literal first line is rejected as boilerplate, the next surviving candidate inherits the first-line +0.4 bonus — it is effectively in the header position. Without this, fixing the wrong-name pick would dial the runner-up's confidence to 0.45, below `ANON_CONTACT_CONFIDENCE_FLOOR = 0.5` in score.ts. The completeness scorer would then mark the (correctly-detected) name as "missing" — mode 2 of issue #10 manifesting inside the fix for mode 1. Reindex puts the surviving candidate over 0.5, so completeness scores the name as present. - Soft contact-cluster proximity bonus (+0.15) when the candidate's y is within ~80pt of the email/phone/linkedin line. Backup signal for ambiguous header layouts; doesn't override the first-line bonus. Note on mode 2 in general: the reporter described it as "separately, on another resume… real name detected only with low confidence — likely because it was set apart from the contact block." Without a specific upstream PDF, the broader mode 2 (Tier 1 fails entirely, Tier 1.5 falls back at 0.5) needs its own repro fixture to investigate properly. Filing as follow-up. Fixture: tests/fixtures/pdfs/latex/header-as-name-functional-resume.pdf, a synthetic single-page XeLaTeX export structured as: Functional Resume Sample (Huge, line 0 — the boilerplate) Jane Smith (Large, line 1 — the real name) 123 Example Way, ... (line 2 — contact starts) jane.smith@example.com · ... (line 3) SUMMARY / EXPERIENCE / EDUCATION (3 sections, 5 bullets total) PII-clean: synthetic persona only, no Author / Title / XMP metadata beyond XeTeX producer string. Pre-fix snapshot pinned `full_name = "Functional Resume Sample"` and flagged `name` as missing in completeness; post-fix `full_name = "Jane Smith"` and `name` clears the floor. Overall score moves 75 → 78. Unit tests added to extract-fields.test.ts for the selector: - header-above-name picks the real name (mode 1) - "Curriculum Vitae" rejected - "Resume Sample" rejected - "Jane Smith Resume" still picks (only 1/3 boilerplate) - no regression: top-line name still picked when no boilerplate - runner-up confidence ≥ 0.5 after boilerplate rejection (mode 2 in miniature — guards the threshold-crossing) Verification: - npm run typecheck: clean - npm run test: 174/174 (167 prior baseline + 6 extractName tests + 1 new corpus fixture) - 6 existing corpus snapshots: byte-identical, zero regression - pdftotext + pdfinfo on new fixture: no real-author tokens, no Author/Title metadata Regenerating the fixture: see the .tex source captured in commit body section "fixture source" below. Refs #10 ----- fixture source: header_as_name.tex (xelatex) ----- \documentclass[11pt]{article} \usepackage[margin=1in]{geometry} \usepackage{enumitem} \pagestyle{empty} \setlength{\parindent}{0pt} \begin{document} \begin{center} {\Huge\bfseries Functional Resume Sample}\\[1.5em] {\Large Jane Smith}\\[0.4em] 123 Example Way, Springfield, IL 62701\\ jane.smith@example.com $\cdot$ (555) 010-0123 $\cdot$ linkedin.com/in/janesmith \end{center} \vspace{1em} \textbf{SUMMARY}\\ Experienced software engineer with five years of full-stack development experience building scalable web services. \vspace{0.8em} \textbf{EXPERIENCE}\\ \textbf{Acme Corp} \hfill Jan 2022 -- Present\\ \emph{Senior Software Engineer} \begin{itemize}[leftmargin=*,topsep=2pt,itemsep=1pt] \item Built scalable web services with Node.js and Go, handling 50M requests/day. \item Led team of three engineers on the payments platform. \item Reduced p99 latency by 40\% through caching and query optimisation. \end{itemize} \textbf{Initech} \hfill 2020 -- 2022\\ \emph{Software Engineer} \begin{itemize}[leftmargin=*,topsep=2pt,itemsep=1pt] \item Migrated monolith to microservices using Kubernetes. \item Owned authentication and authorisation across all internal tools. \end{itemize} \vspace{0.5em} \textbf{EDUCATION}\\ B.S. Computer Science, Springfield State University, 2020 \end{document} --- src/lib/heuristics/extract-fields.test.ts | 80 +++++++++++++++++- src/lib/heuristics/extract-fields.ts | 79 ++++++++++++++++- ...er-as-name-functional-resume.expected.json | 68 +++++++++++++++ .../header-as-name-functional-resume.pdf | Bin 0 -> 21846 bytes 4 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/pdfs/latex/header-as-name-functional-resume.expected.json create mode 100644 tests/fixtures/pdfs/latex/header-as-name-functional-resume.pdf diff --git a/src/lib/heuristics/extract-fields.test.ts b/src/lib/heuristics/extract-fields.test.ts index 6d190169..da0290a1 100644 --- a/src/lib/heuristics/extract-fields.test.ts +++ b/src/lib/heuristics/extract-fields.test.ts @@ -2,7 +2,7 @@ // Copyright 2026 The resumelint Authors import { describe, it, expect } from "vitest"; -import { extractContact } from "./extract-fields.ts"; +import { extractContact, extractName } from "./extract-fields.ts"; import { groupIntoLines, splitIntoSections, findSection } from "./sections.ts"; import { US_LOCATION_RE } from "./regex.ts"; import type { PdfLinkAnnotation } from "./types.ts"; @@ -167,6 +167,84 @@ describe("extractContact — location no longer falls back to document-wide scan }); }); +describe("extractName — document-title boilerplate rejection (issue #10)", () => { + it("picks the real name when a 'Functional Resume Sample' header is above it", () => { + // Mode 1 of issue #10: a public Microsoft-style sample template renders + // the doc title in the largest font on the first profile line, which the + // original selector scored at 1.0 — choosing the boilerplate as the name. + const { profile } = buildContext([ + { text: "Functional Resume Sample", fontSize: 22 }, + { text: "Jane Smith", fontSize: 14 }, + { text: "jane.smith@example.com · (555) 010-0123", fontSize: 10 }, + { text: "" }, + { text: "EXPERIENCE", fontSize: 13 }, + ]); + const result = extractName(profile); + expect(result.value).toBe("Jane Smith"); + expect(result.confidence).toBeGreaterThan(0); + }); + + it("rejects 'Curriculum Vitae' as a name candidate", () => { + const { profile } = buildContext([ + { text: "Curriculum Vitae", fontSize: 22 }, + { text: "Jane Smith", fontSize: 14 }, + { text: "jane.smith@example.com", fontSize: 10 }, + ]); + expect(extractName(profile).value).toBe("Jane Smith"); + }); + + it("rejects 'Resume Sample' as a name candidate (all tokens are boilerplate)", () => { + const { profile } = buildContext([ + { text: "Resume Sample", fontSize: 22 }, + { text: "Jane Smith", fontSize: 14 }, + { text: "jane.smith@example.com", fontSize: 10 }, + ]); + expect(extractName(profile).value).toBe("Jane Smith"); + }); + + it("still picks 'Jane Smith Resume' (only 1 of 3 tokens is boilerplate)", () => { + // Conservative filter — a real name with the word "Resume" appended must + // still pass. Only ≥60% boilerplate triggers rejection. + const { profile } = buildContext([ + { text: "Jane Smith Resume", fontSize: 18 }, + { text: "jane.smith@example.com", fontSize: 10 }, + ]); + expect(extractName(profile).value).toBe("Jane Smith Resume"); + }); + + it("no regression: still picks a top-line name when no boilerplate is present", () => { + const { profile } = buildContext([ + { text: "Mohin Patel", fontSize: 18 }, + { text: "mohinp@uw.edu | 973-452-3653", fontSize: 10 }, + { text: "" }, + { text: "EDUCATION", fontSize: 13 }, + ]); + const result = extractName(profile); + expect(result.value).toBe("Mohin Patel"); + expect(result.confidence).toBeGreaterThan(0.8); + }); + + it("boilerplate-rejected name still picks up the contact-cluster proximity bonus on the runner-up", () => { + // Regression on the proximity signal itself: when the obvious first-line + // candidate is rejected as boilerplate, the proximity bonus must still + // fire for the surviving candidate. Otherwise we'd lose a confidence + // signal that's most useful precisely in the issue-10 scenario. + const { profile } = buildContext([ + { text: "Functional Resume Sample", fontSize: 22 }, + { text: "Jane Smith", fontSize: 14 }, + { text: "jane.smith@example.com", fontSize: 10 }, + ]); + const result = extractName(profile); + expect(result.value).toBe("Jane Smith"); + // Must clear ANON_CONTACT_CONFIDENCE_FLOOR (0.5) in score.ts — + // otherwise completeness scoring marks the (correctly-detected) name as + // "missing", which is mode 2 of issue #10 manifesting inside the fix + // for mode 1. Guarded so a future tuning regression on this threshold + // boundary fails loudly. + expect(result.confidence).toBeGreaterThanOrEqual(0.5); + }); +}); + describe("US_LOCATION_RE — preposition-phrase city rejection", () => { it("does not eat lowercase prepositions like 'and' / 'of' inside the city capture", () => { // Pre-fix the regex captured "CS and Engineering Seattle" (26 chars diff --git a/src/lib/heuristics/extract-fields.ts b/src/lib/heuristics/extract-fields.ts index 9827695b..a4a15161 100644 --- a/src/lib/heuristics/extract-fields.ts +++ b/src/lib/heuristics/extract-fields.ts @@ -64,6 +64,64 @@ function allMatches(re: RegExp, text: string): string[] { // ── Name ──────────────────────────────────────────────────────────────────── +/** + * Words that signal "this is a resume document title, not the candidate's name" + * — e.g. "Functional Resume Sample", "Chronological CV Template". Conservative: + * "Jane Smith Resume" still passes because only one of three words is boilerplate. + * See `looksLikeDocTitleBoilerplate` below for the rule. + */ +const NAME_BOILERPLATE_WORDS = new Set([ + "resume", + "résumé", + "cv", + "curriculum", + "vitae", + "sample", + "template", + "example", + "draft", + "chronological", + "functional", + "combination", + "profile", + "biography", +]); + +/** + * True when the line is mostly resume-document-title boilerplate rather than + * a person's name. Requires *all* tokens to be boilerplate (or ≥2 boilerplate + * tokens out of ≤3 total). Tuned so "Jane Smith" passes and "Resume" / "CV + * Sample" / "Functional Resume Sample" / "Curriculum Vitae" all reject. + */ +function looksLikeDocTitleBoilerplate(words: string[]): boolean { + const lowered = words.map((w) => w.toLowerCase().replace(/[^a-z]/g, "")); + const hits = lowered.filter((w) => NAME_BOILERPLATE_WORDS.has(w)).length; + if (hits === 0) return false; + if (hits === words.length) return true; + return words.length <= 3 && hits >= 2; +} + +/** y-position of the first line in `lines` matching any of the contact regexes, + * or undefined if no contact-bearing line is found. Used as a soft signal — + * candidate names close to this y get a small bonus. */ +function findContactClusterY(lines: PdfLine[]): number | undefined { + for (const line of lines) { + if ( + EMAIL_RE.test(line.text) || + PHONE_RE.test(line.text) || + LINKEDIN_RE.test(line.text) + ) { + // Reset lastIndex defensively; the constants are recompiled per call + // elsewhere in the file but test() with `g` flag mutates state. + EMAIL_RE.lastIndex = 0; + PHONE_RE.lastIndex = 0; + LINKEDIN_RE.lastIndex = 0; + return line.y; + } + } + return undefined; +} + /** * Resume names almost always appear at the very top, in the largest font, with * 2–4 words that are all letters (plus maybe a period or hyphen). Score: @@ -71,6 +129,10 @@ function allMatches(re: RegExp, text: string): string[] { * +0.3 font size larger than the rest of profile * +0.2 all-caps OR title-case * +0.1 2–4 words, 2–40 chars, no digits/emails + * +0.15 within ~80pt of the email/phone/linkedin line (contact-cluster proximity) + * + * Hard rejection: lines that are mostly resume-document-title boilerplate + * ("Functional Resume Sample", "Curriculum Vitae", etc.) — see issue #10. */ export function extractName( profile: PdfSection, @@ -80,8 +142,17 @@ export function extractName( const maxFontSize = Math.max(...profile.lines.map((l) => l.maxFontSize)); const averageFontSize = profile.lines.reduce((s, l) => s + l.maxFontSize, 0) / profile.lines.length; + const contactY = findContactClusterY(profile.lines); let best: { line: PdfLine; score: number } | null = null; + // Index of the first eligible candidate after rejections. When the literal + // first line is rejected as boilerplate (e.g. "Functional Resume Sample"), + // the next surviving line is effectively the header — it inherits the + // first-line bonus, which also keeps confidence above the scorer's + // contact-field floor (0.5). Without this, fixing the wrong-name pick + // would dial confidence down enough to mark the (correct) name as + // "missing" in completeness scoring. + let firstEligibleIdx: number | null = null; for (let i = 0; i < Math.min(profile.lines.length, 5); i++) { const line = profile.lines[i]; @@ -94,14 +165,20 @@ export function extractName( const letterRatio = text.replace(/[^A-Za-z]/g, "").length / Math.max(text.length, 1); if (letterRatio < 0.7) continue; + if (looksLikeDocTitleBoilerplate(words)) continue; + + if (firstEligibleIdx === null) firstEligibleIdx = i; let score = 0; - if (i === 0) score += 0.4; + if (i === firstEligibleIdx) score += 0.4; if (line.maxFontSize >= maxFontSize - 0.5) score += 0.3; if (line.maxFontSize > averageFontSize + 1) score += 0.1; const titleCase = words.every((w) => /^[A-Z][a-zA-Z.\-']*$/.test(w)); if (line.allCaps || titleCase) score += 0.2; if (words.length >= 2 && words.length <= 4) score += 0.1; + if (contactY !== undefined && Math.abs(line.y - contactY) < 80) { + score += 0.15; + } if (!best || score > best.score) best = { line, score }; } diff --git a/tests/fixtures/pdfs/latex/header-as-name-functional-resume.expected.json b/tests/fixtures/pdfs/latex/header-as-name-functional-resume.expected.json new file mode 100644 index 00000000..647d4137 --- /dev/null +++ b/tests/fixtures/pdfs/latex/header-as-name-functional-resume.expected.json @@ -0,0 +1,68 @@ +{ + "schemaVersion": 1, + "cascade": { + "confidence": 0.87, + "triggers": [], + "tiers": [ + "t0_layout", + "t1_openresume" + ], + "suggestedEscalation": "none", + "fieldsPopulated": [ + "current_company", + "current_title", + "education", + "email", + "experience", + "family_name", + "full_name", + "given_name", + "linkedin_url", + "location", + "phone", + "summary", + "website_url" + ], + "skillsCount": 0, + "experienceCount": 2, + "educationCount": 1, + "rawTextCharCount": 762, + "pageCount": 1, + "linkAnnotationCount": 0, + "hasMarkdown": true, + "sectionSource": "regex" + }, + "score": { + "overall": 78, + "preLayoutOverall": 78, + "specificity": { + "score": 27, + "max": 40, + "gradable": true, + "metricBullets": 2, + "totalBullets": 5 + }, + "structure": { + "score": 24, + "max": 30, + "gradable": true, + "goodBullets": 4, + "totalBullets": 5 + }, + "completeness": { + "score": 27, + "max": 30, + "gradable": true, + "missing": [ + "skills" + ] + }, + "layout": { + "triggers": [], + "multiplier": 1, + "scanned": false + }, + "bulletCount": 5, + "algoVersion": "1.0" + } +} diff --git a/tests/fixtures/pdfs/latex/header-as-name-functional-resume.pdf b/tests/fixtures/pdfs/latex/header-as-name-functional-resume.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a19775caae446eef7a1744263fa94aeb18b3fa7c GIT binary patch literal 21846 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