Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 170 additions & 23 deletions src/lib/webllm/preserve-numbers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,19 +197,13 @@ describe("checkNumbersPreserved", () => {
// `projects` isn't a people noun, so `Managed 5 projects` is a verb
// pointing at a non-people object, not a headcount claim.
expect(
checkNumbersPreserved(
["Owned 5 projects."],
["Managed 5 projects."],
),
checkNumbersPreserved(["Owned 5 projects."], ["Managed 5 projects."]),
).toEqual({ ok: true, dropped: [], added: [] });
expect(
checkNumbersPreserved(["Built 8 features."], ["Led 8 features."]),
).toEqual({ ok: true, dropped: [], added: [] });
expect(
checkNumbersPreserved(
["Delivered 4 programs."],
["Ran 4 programs."],
),
checkNumbersPreserved(["Delivered 4 programs."], ["Ran 4 programs."]),
).toEqual({ ok: true, dropped: [], added: [] });
expect(
checkNumbersPreserved(
Expand All @@ -232,10 +226,7 @@ describe("checkNumbersPreserved", () => {

it("still claims a headcount when a verb-object noun IS a person", () => {
expect(
checkNumbersPreserved(
["Managed 5 engineers."],
["Led 5 engineers."],
),
checkNumbersPreserved(["Managed 5 engineers."], ["Led 5 engineers."]),
).toEqual({ ok: true, dropped: [], added: [] });
});

Expand Down Expand Up @@ -394,9 +385,9 @@ describe("checkNumbersPreserved", () => {
});

it("is case-insensitive on the multiplier — `10X` matches `10x`", () => {
expect(
checkNumbersPreserved(["Scaled 10X."], ["Scaled 10x."]).ok,
).toBe(true);
expect(checkNumbersPreserved(["Scaled 10X."], ["Scaled 10x."]).ok).toBe(
true,
);
});

it("catches a dropped `10+`", () => {
Expand Down Expand Up @@ -675,18 +666,174 @@ describe("checkNumbersPreserved", () => {
expect(result.dropped).toEqual(["12"]);
});

it("does NOT extend to year — a year has no unclaimed state to compare against (documented residual)", () => {
// Every 4-digit number in 1900-2099 auto-claims as a year regardless of
// context (see `bareIntegerClaim`), so "suite 1900" is itself always
// claimed — there is no unclaimed bucket for the count-aware guard to
// compare against, and this masking case survives. Catching it needs a
// context gate on year classification itself (mirroring headcount's
// verb/noun check), which is a separate, larger change than this fix;
// tracked as a follow-up rather than silently left unmentioned.
it("extends to year (#876) — catches a dropped year masked by an unrelated same-value digit", () => {
// With year context gating (#876), "suite 1900" is unclaimed while
// "in 1900" is claimed as a year. A genuinely dropped year is caught
// even when an unrelated 4-digit number of the same value survives.
const result = checkNumbersPreserved(
["Founded the program in 1900.", "Operated out of suite 1900."],
["Founded the program.", "Operated out of suite 1900."],
);
expect(result.ok).toBe(false);
expect(result.dropped).toEqual(["1900"]);
});

it("catches a dropped year date range across all 6 dash characters (#876)", () => {
const dashes = [
"-", // U+002D ASCII hyphen
"–", // U+2013 en dash
"—", // U+2014 em dash
"‒", // U+2012 figure dash
"‑", // U+2011 non-breaking hyphen
"−", // U+2212 minus sign
];
for (const dash of dashes) {
const input = [`Acme Corp 2019 ${dash} 2021 senior engineer.`];
const output = ["Acme Corp senior engineer."];
const result = checkNumbersPreserved(input, output);
expect(result.ok).toBe(false);
expect(result.dropped).toEqual(["2019", "2021"]);
}
});

it("catches dropped years with month abbreviations and date slashes (#876)", () => {
const result1 = checkNumbersPreserved(
["Sep. 2025 – Apr. 2026: Senior Staff Engineer."],
["Senior Staff Engineer."],
);
expect(result1.ok).toBe(false);
expect(result1.dropped).toEqual(["2025", "2026"]);

const result2 = checkNumbersPreserved(
["01/2019 - 02/2022: Lead Architect."],
["Lead Architect."],
);
expect(result2.ok).toBe(false);
expect(result2.dropped).toEqual(["2019", "2022"]);
});

it("catches dropped years across common resume forms (#876)", () => {
const cases = [
["Speaker at PyCon (2019).", "Speaker at PyCon.", "2019"],
["B.S. Computer Science, 2019.", "B.S. Computer Science.", "2019"],
[
"Awarded Employee of the Year 2021.",
"Awarded Employee of the Year.",
"2021",
],
["AWS Certified Architect 2019.", "AWS Certified Architect.", "2019"],
["Shipped the platform 2018.", "Shipped the platform.", "2018"],
["Worked at Acme Corp 2019.", "Worked at Acme Corp.", "2019"],
[
"Recipient of the 2019 Excellence Award.",
"Recipient of the Excellence Award.",
"2019",
],
["Winner, 2020 Innovation Award.", "Innovation Award winner.", "2020"],
["Presented at KubeCon 2022.", "Presented at KubeCon.", "2022"],
];
for (const [inputStr, outputStr, expectedYear] of cases) {
const result = checkNumbersPreserved([inputStr], [outputStr]);
expect(result.ok).toBe(false);
expect(result.dropped).toEqual([expectedYear]);
}
});

it("accepts surviving year in rephrased context (#876)", () => {
const result = checkNumbersPreserved(
["Presented at KubeCon in 2022.", "Refactored 2022 legacy modules."],
["KubeCon 2022 speaker; refactored legacy modules."],
);
expect(result.ok).toBe(true);
expect(result.dropped).toEqual([]);
});

it("does not falsely report dropped years on plain-English merges of 4-digit quantities (#876)", () => {
const mergePairs: [string[], string[]][] = [
[
["Delivered the 2000 units.", "Tracked 2000 tickets."],
["Delivered and tracked 2000 items."],
],
[
["Reduced by 2000 hours.", "Cut 2000 tickets."],
["Cut 2000 hours and tickets."],
],
[
["A total of 2000 records.", "Indexed 2000 rows."],
["Indexed 2000 records and rows."],
],
[
["Ran campaign for 2000 customers.", "Emailed 2000 leads."],
["Reached 2000 customers and leads."],
],
];
for (const [input, output] of mergePairs) {
const result = checkNumbersPreserved(input, output);
expect(result.ok).toBe(true);
expect(result.dropped).toEqual([]);
}
});

it("treats range endpoints uniformly across the 1900-2099 boundary (#876)", () => {
const result1 = checkNumbersPreserved(
["Processed 1000-2000 tickets.", "Closed 2000 escalations."],
["Processed 1000 to 2000 tickets."],
);
expect(result1.ok).toBe(true);

const result2 = checkNumbersPreserved(
["Processed 50-100 tickets.", "Closed 100 escalations."],
["Processed 50 to 100 tickets."],
);
expect(result2.ok).toBe(true);
});

it("accepts legitimate temporal reword of an attributive year (#876)", () => {
const result = checkNumbersPreserved(
["Recipient of the 2019 Excellence Award."],
["Won the Excellence Award in 2019."],
);
expect(result.ok).toBe(true);
expect(result.added).toEqual([]);
});

it("accepts a year date-anchor at the start of a bullet (#876)", () => {
const input = [
"2019: Founded the company and led initial product launch.",
];
expect(checkNumbersPreserved(input, input).ok).toBe(true);
const dropped = checkNumbersPreserved(input, [
"Founded the company and led initial product launch.",
]);
expect(dropped.ok).toBe(false);
expect(dropped.dropped).toEqual(["2019"]);
});

it("accepts a year with month/season prefix or range (#876)", () => {
const input = ["Spring 2021: Graduated with honors."];
expect(checkNumbersPreserved(input, input).ok).toBe(true);
const dropped = checkNumbersPreserved(input, ["Graduated with honors."]);
expect(dropped.ok).toBe(false);
expect(dropped.dropped).toEqual(["2021"]);
});

it("does not treat non-temporal 4-digit bare integers as year claims (#876)", () => {
// Bare 2000 units is a quantity with no temporal cue, not a year claim.
// Dropping it does not trigger a false year drop.
const result = checkNumbersPreserved(
["Delivered 2000 units to production."],
["Delivered units to production."],
);
expect(result.ok).toBe(true);
});

it("accepts legitimate reword of a sole year occurrence (#876)", () => {
// When "in 1900" is the only occurrence and rewords to an unclaimed digit,
// the new unclaimed count allows the reword.
const result = checkNumbersPreserved(
["Founded in 1900."],
["Operated as project 1900."],
);
expect(result.ok).toBe(true);
});

Expand Down
Loading
Loading