import { expect } from "chai"; import { prisma } from "../../src/db/prisma.js"; import { MIN_OVERALL_F1, runTechStepEvalSuite, } from "../../src/lib/recipe-matching/tech-step-eval-runner.js"; import { resetDatabase } from "../../test-support/reset-db.js"; /** * Regression gate for `TECH_STEP_TRAINING_DATA` — every change to that * corpus (including a maintainer applying suggestions from * `TechStepTrainingSuggestion`, see `scripts/retrain-tech-steps.ts`) must * keep this suite green. Runs {@link runTechStepEvalSuite} (the real * trained classifier against `tech-step-eval-dataset.ts`) and asserts the * aggregate F1 doesn't fall below {@link MIN_OVERALL_F1}. * * `MIN_OVERALL_F1` (`tech-step-eval-runner.ts`) is a provisional floor, * not a target: most of the dataset's cases are built around a * technique's own registered synonym, which `_classifyClause` always * resolves correctly via its NER-anchor fallback even when the intent * classifier itself scores under `CONFIDENCE_THRESHOLD` (see * `tech-step-matcher.ts`'s doc comment, point 3) — so a healthy run should * land well above this floor. It's set low enough to tolerate the residual * uncertainty in a dataset authored without being able to run it against a * live trained classifier first (no local Postgres was reachable in the * session that introduced this file — see this feature's plan document). * Once this suite has actually run once (locally or in CI) and produced * real numbers, tighten that constant to just below the observed F1, so a * real future regression still fails loudly instead of hiding under a * floor that's too forgiving. */ describe("tech-step-eval", () => { beforeEach(async () => { await resetDatabase(); }); after(async () => { await prisma.$disconnect(); }); it(`scores at least ${MIN_OVERALL_F1} aggregate F1 against the labeled evaluation set`, async () => { const { overall, byKey } = await runTechStepEvalSuite(); expect( overall.f1, `aggregate F1 ${overall.f1.toFixed(3)} (precision ${overall.precision.toFixed(3)}, recall ${overall.recall.toFixed(3)}) fell below the ${MIN_OVERALL_F1} floor — per-technique breakdown: ${JSON.stringify(byKey)}`, ).to.be.at.least(MIN_OVERALL_F1); }); });