src/programs/snap-negative/seed-generator.test.ts
Linked repository file from this build.
import { validateVariancePairs } from "./field-catalog";
import { queryErcOutcomes } from "@/features/reviews/downstream";
import { describe, expect, test } from "bun:test";
import { parseSnapNegativeBatch } from "@/lib/import/snap-negative";
import { FIRST_NAMES, LAST_NAMES } from "./seed-generator-names";
import { PA_LOCATIONS } from "./seed-generator-locations";
import {
currentSampleMonth,
ffySampleMonths,
generateCaseNumber,
generateSeedBatch,
seedReviewNumber,
supervisorForExaminer,
} from "./seed-generator";
describe("seed generator data", () => {
test("reference lists meet minimum size targets", () => {
expect(FIRST_NAMES.length).toBe(100);
expect(LAST_NAMES.length).toBe(100);
expect(PA_LOCATIONS.length).toBeGreaterThanOrEqual(67);
expect(new Set(PA_LOCATIONS.map((location) => location.county)).size).toBe(67);
});
});
describe("seed generator", () => {
test("numbering spans the complete closed and rejected ranges", () => {
expect(seedReviewNumber("closed_suspended", 1)).toBe("60001");
expect(seedReviewNumber("closed_suspended", 999)).toBe("60999");
expect(seedReviewNumber("closed_suspended", 1000)).toBe("61000");
expect(seedReviewNumber("closed_suspended", 1999)).toBe("61999");
expect(() => seedReviewNumber("closed_suspended", 2000)).toThrow("1999 cases per FFY");
expect(seedReviewNumber("rejected", 1)).toBe("650001");
expect(seedReviewNumber("rejected", 9999)).toBe("659999");
expect(seedReviewNumber("rejected", 10000)).toBe("660000");
expect(seedReviewNumber("rejected", 19999)).toBe("669999");
expect(() => seedReviewNumber("rejected", 20000)).toThrow("19999 cases per FFY");
expect(() => seedReviewNumber("rejected", 0)).toThrow();
});
test("a larger fiscal-year batch crosses the closed-case prefix without a gap", () => {
const batch = generateSeedBatch({ ffyFrom: 2026, ffyTo: 2026, perMonth: 100 });
const closed = batch.reviews.filter((review) => review.facts.negativeAction !== "Denial");
expect(closed.map((review) => review.reviewNumber)).toEqual(
Array.from({ length: 1200 }, (_, index) => String(60001 + index)),
);
expect(() => generateSeedBatch({ ffyFrom: 2026, ffyTo: 2026, perMonth: 167 })).toThrow(
"1999 cases per FFY",
);
});
test("FFY 2026 sample months run Oct 2025 through Sep 2026", () => {
expect(ffySampleMonths(2026)).toEqual([
"2025-10",
"2025-11",
"2025-12",
"2026-01",
"2026-02",
"2026-03",
"2026-04",
"2026-05",
"2026-06",
"2026-07",
"2026-08",
"2026-09",
]);
});
test("currentSampleMonth uses the calendar month from the reference date", () => {
expect(currentSampleMonth(new Date("2026-09-08T12:00:00Z"))).toBe("2026-09");
});
test("sampleMonths option limits generation to one month", () => {
const batch = generateSeedBatch({
ffyFrom: 2026,
ffyTo: 2026,
perMonth: 2,
seed: 1,
sampleMonths: ["2026-09"],
asOf: new Date("2026-09-08T12:00:00Z"),
preserveReviewNumbers: [],
});
expect(batch.reviews.length).toBe(4);
expect(new Set(batch.reviews.map((review) => review.sampleMonth))).toEqual(
new Set(["2026-09"]),
);
});
test("case numbers use 10-59 prefixes and 1-9999999 suffixes", () => {
const rng = {
next: () => 0.5,
int: (min: number, max: number) => Math.floor((min + max) / 2),
pick: <T>(items: readonly T[]) => items[0]!,
chance: () => true,
};
const caseNumber = generateCaseNumber(rng);
expect(caseNumber).toMatch(/^(1\d|[2-5]\d)[/-]\d{1,7}$/);
});
test("generates review numbers in supported prefix ranges", () => {
const batch = generateSeedBatch({
ffyFrom: 2026,
ffyTo: 2026,
perMonth: 2,
seed: 7,
asOf: new Date("2026-09-08T12:00:00Z"),
preserveReviewNumbers: [],
});
expect(batch.reviews.length).toBe(48);
expect(batch.reviews.find((review) => review.reviewNumber === "60001")?.examiner).toBe("101");
for (const review of batch.reviews) {
expect(review.reviewNumber).toMatch(/^(60|61)\d{3}$|^(65|66)\d{4}$/);
expect(review.facts.members.length).toBeGreaterThanOrEqual(1);
expect(review.facts.members.length).toBeLessThanOrEqual(20);
expect(review.facts.answers.telephone).toMatch(/^\(\d{3}\) 555-\d{4}$/);
expect(review.facts.answers.actualAddress.length).toBeGreaterThan(0);
expect(review.facts.answers.dateAssigned.length).toBeGreaterThan(0);
expect(review.address).toMatch(/, PA \d{5}$/);
}
});
test("completed non-drop seeds have current results, checks and reporting release", () => {
const options = {
ffyFrom: 2026,
ffyTo: 2026,
perMonth: 5,
seed: 11,
asOf: new Date("2026-09-08T12:00:00Z"),
preserveReviewNumbers: [],
};
const batch = generateSeedBatch(options);
const complete = batch.reviews.filter(
(review) => review.outcome === "clean" || review.outcome === "error",
);
expect(complete.length).toBeGreaterThan(0);
for (const review of complete) {
expect(review.facts.answers.disposition).toBe("1");
expect(review.facts.final?.inputsRevision).toBe(review.facts.inputsRevision);
expect(review.validation?.revision).toBe(review.revision);
expect(review.validation?.findings).toEqual([]);
expect(validateVariancePairs(review.facts.answers.variances)).toEqual([]);
if (review.outcome === "error")
expect(review.facts.answers.variances.length).toBeGreaterThan(0);
else expect(review.facts.answers.variances).toEqual([]);
expect(review.facts.answers.finding).toBe(review.outcome === "error" ? "2" : "1");
expect(review.facts.answers.noticeReasonAccuracy).toBe(
review.outcome === "error" ? "21" : "11",
);
}
const counts = queryErcOutcomes(batch.reviews);
expect(counts.cleanReviewCount).toBe(
batch.reviews.filter((review) => review.errorSummaryReleased && review.outcome === "clean")
.length,
);
expect(counts.errorReviewCount).toBe(
batch.reviews.filter((review) => review.errorSummaryReleased && review.outcome === "error")
.length,
);
expect(counts.ineligibleApprovedCount).toBe(
batch.reviews.filter((review) => review.memoApproved && !review.errorSummaryReleased).length,
);
expect(counts.cleanReviewCount + counts.errorReviewCount).toBeGreaterThan(0);
});
test("generated BIS rows parse through the intake batch parser", () => {
const batch = generateSeedBatch({
ffyFrom: 2026,
ffyTo: 2026,
perMonth: 1,
seed: 3,
preserveReviewNumbers: [],
});
const parsed = parseSnapNegativeBatch({
closedSuspended: [
`# SYNTHETIC QCCRM SNAP-NEGATIVE FIXTURE`,
...batch.closedSuspendedBisRows,
].join("\n"),
rejected: [`# SYNTHETIC QCCRM SNAP-NEGATIVE FIXTURE`, ...batch.rejectedBisRows].join("\n"),
});
expect(parsed.findings).toEqual([]);
expect(parsed.cases.length).toBe(batch.reviews.length);
});
test("examiner staff ids 101-104 report to 201 and 105-108 report to 202", () => {
expect(supervisorForExaminer("101")).toBe("201");
expect(supervisorForExaminer("104")).toBe("201");
expect(supervisorForExaminer("105")).toBe("202");
expect(supervisorForExaminer("108")).toBe("202");
expect(() => supervisorForExaminer("109")).toThrow("Unknown examiner staff id");
const batch = generateSeedBatch({
ffyFrom: 2026,
ffyTo: 2026,
perMonth: 20,
seed: 17,
asOf: new Date("2026-09-08T12:00:00Z"),
preserveReviewNumbers: [],
});
const examiners = new Set(batch.reviews.map((review) => review.examiner));
expect([...examiners].sort()).toEqual(["101", "102", "103", "104", "105", "106", "107", "108"]);
for (const review of batch.reviews) {
expect(review.supervisor).toBe(supervisorForExaminer(review.examiner));
expect(["201", "202"]).toContain(review.supervisor);
expect(["101", "102", "103", "104", "105", "106", "107", "108"]).not.toContain(
review.supervisor,
);
}
});
test("child-only households list the child as the sole member and use collateral contact", () => {
const batch = generateSeedBatch({
ffyFrom: 2026,
ffyTo: 2026,
perMonth: 50,
seed: 99,
asOf: new Date("2026-09-08T12:00:00Z"),
preserveReviewNumbers: [],
});
const childOnly = batch.reviews.filter(
(review) =>
review.facts.members.length === 1 && review.facts.members[0]?.relationship === "Child",
);
expect(childOnly.length).toBeGreaterThan(0);
for (const review of childOnly) {
expect(review.facts.members).toEqual([
expect.objectContaining({ line: "01", relationship: "Child" }),
]);
expect(review.clientName).toBe(review.facts.members[0]!.name);
expect(review.facts.members.some((member) => member.relationship === "Legal Guardian")).toBe(
false,
);
expect(review.facts.answers.actualAddress).toContain("Collateral/HH contact:");
if (review.facts.answers.disposition === "1") {
expect(["02", "04", "06"]).toContain(review.facts.answers.contactResponse);
}
}
});
});