Une seule feature livree en une seule PR, en 5 phases : - Phase 1 : enrichit le corpus NLP (tech-step-training-data.ts) et ajoute un harness d'evaluation (precision/rappel/F1) avec un jeu de test etiquete - la premiere metrique objective de qualite pour ce classifieur. - Phase 2 : schema Prisma (StepTechStepCorrection, TechStepTrainingSuggestion) + endpoints utilisateur (POST/GET corrections, ouverts a tout viewer, pas seulement l'auteur) + endpoints internes /internal/tech-steps/* proteges par secret partage (requireInternalWorker). - Phase 3 : UI de highlight/correction cote web (selection de texte -> association a une technique, ou clic sur un highlight existant pour le corriger/supprimer) - verifiee via Cypress (component + e2e, en Chrome reel). - Phase 4 : worker LLM autonome (services/tech-step-llm-worker, hors du monorepo pnpm comme experiments/llm-tech-step-poc) qui audite les clauses a faible confiance et transforme les corrections utilisateur en suggestions d'entrainement, sans jamais toucher le chemin interactif. - Phase 5 : script retrain-tech-steps.ts (gate de regression F1 + backfill) et list-pending-training-suggestions.ts pour la revue humaine avant application au corpus. Verification effectuee cette session : tsc/biome sur l'ensemble du repo, build complet (pnpm build), suite Cypress complete (component 39/39, e2e 75/76 - le seul echec est preexistant et sans rapport, cote recipe-form.feature/ingredient-picker), tests unitaires du worker (6/6) et son install/typecheck reels contre node-llama-cpp. Les tests Mocha d'apps/api (Phases 1 et 2) n'ont pas pu etre executes dans cette session (pas de Postgres local disponible) - a lancer avant merge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
70 lines
2.9 KiB
TypeScript
70 lines
2.9 KiB
TypeScript
import { prisma } from "../db/prisma.js";
|
|
import { techStepClassifier } from "../lib/recipe-matching/tech-step-matcher.js";
|
|
|
|
/**
|
|
* Recomputes every existing `Step`'s `StepTechStep` sequence against the
|
|
* *current* classifier (`tech-step-matcher.ts`/`tech-step-training-data.ts`),
|
|
* the same way `updateRecipe` does when a user resaves a recipe through the
|
|
* UI — always `"fr"` (`DEFAULT_TECH_STEP_LOCALE` in `recipe.service.ts`;
|
|
* there's no persisted per-recipe locale to recover for a step that already
|
|
* exists, so this matches real resave behavior exactly rather than
|
|
* guessing).
|
|
*
|
|
* Needed because tech-step detection only ever runs at create/update time
|
|
* (`recipe.service.ts`'s `matchStepsTechSteps`), never retroactively — a
|
|
* step saved before a classifier/corpus change (new vocabulary, or the
|
|
* `contextStart`/`contextEnd` columns a previous session added) keeps
|
|
* whatever it was matched with at the time until it's next resaved.
|
|
*
|
|
* Exported (not just called from this file's own CLI guard below) so
|
|
* `retrain-tech-steps.ts` can run it as one step of its own larger
|
|
* maintainer workflow, without shelling out to a second process.
|
|
*
|
|
* Safe to re-run: each step's technique sequence is fully replaced (delete
|
|
* + recreate) from the classifier's current output, same as a real edit —
|
|
* running it twice in a row with no corpus change in between is a no-op.
|
|
*/
|
|
export async function backfillTechSteps(): Promise<{ total: number; changed: number }> {
|
|
const steps = await prisma.step.findMany({ select: { id: true, description: true } });
|
|
console.info(`Recomputing tech steps for ${steps.length} step(s)...`);
|
|
|
|
let changed = 0;
|
|
for (const step of steps) {
|
|
const matches = await techStepClassifier.matchTechStepSpans(step.description, "fr");
|
|
await prisma.$transaction([
|
|
prisma.stepTechStep.deleteMany({ where: { stepId: step.id } }),
|
|
prisma.stepTechStep.createMany({
|
|
data: matches.map((match, order) => ({
|
|
stepId: step.id,
|
|
techStepId: match.techStepId,
|
|
order,
|
|
start: match.start,
|
|
end: match.end,
|
|
contextStart: match.contextStart,
|
|
contextEnd: match.contextEnd,
|
|
})),
|
|
}),
|
|
]);
|
|
changed += 1;
|
|
}
|
|
|
|
console.info(`Done — ${changed} step(s) recomputed.`);
|
|
return { total: steps.length, changed };
|
|
}
|
|
|
|
// Only runs when this file is executed directly (`tsx
|
|
// src/scripts/backfill-tech-steps.ts`), not when `retrain-tech-steps.ts`
|
|
// imports `backfillTechSteps` above — the standard ESM "is this the entry
|
|
// module" check, first needed in this codebase by that new script; every
|
|
// prior script here (`seed-runtime.ts`) was always only ever run directly,
|
|
// never imported.
|
|
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
if (isMainModule) {
|
|
backfillTechSteps()
|
|
.then(() => prisma.$disconnect())
|
|
.catch(async (err) => {
|
|
console.error(err);
|
|
await prisma.$disconnect();
|
|
process.exit(1);
|
|
});
|
|
}
|