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); }); }