Rend opérationnel le squelette TechStep/TechStepMapping/Step.techStepId présent dans le schéma depuis le premier commit mais jamais implémenté : - TechStep gagne un `key` unique (camelCase, même convention que Diet/Unit) ; TechStepMapping gagne un `locale` pour pouvoir porter plusieurs jeux de règles de matching par langue. - Catalogue statique de 25 techniques françaises courantes (Cuire, Frire, Déglacer, Mijoter, ...), chacune associée à une ou plusieurs expressions régulières + un poids, seedées de façon idempotente dans reference-seed-data.ts. - Nouveau moteur de matching (apps/api/src/lib/tech-step-matcher.ts) : normalisation accents/casse (NFD) puis test des expressions, résolution du meilleur match par poids. Pur et testé unitairement. - Câblé dans recipe.service.ts : à la création/modification d'une recette, chaque étape voit son techStepId calculé automatiquement à partir de sa description (locale "fr" en dur pour l'instant, faute de préférence de langue utilisateur dans l'app). - Reste backend-only : StepView n'expose pas encore techStepId, conformément au commentaire existant. - Endpoint GET /reference/tech-steps + TechStepView, en cohérence avec les autres catalogues de référence. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
import { expect } from "chai";
|
|
import {
|
|
type TechStepMappingRule,
|
|
matchTechStep,
|
|
normalizeText,
|
|
} from "../src/lib/tech-step-matcher.js";
|
|
|
|
describe("tech-step-matcher", () => {
|
|
describe("normalizeText", () => {
|
|
it("lowercases and strips accents", () => {
|
|
expect(normalizeText("Déglacer AU FOUR")).to.equal("deglacer au four");
|
|
});
|
|
});
|
|
|
|
describe("matchTechStep", () => {
|
|
const simmer: TechStepMappingRule = {
|
|
techStepId: 1,
|
|
expression: "\\bmijot(er|ez|e|ant|é)\\b",
|
|
weight: 15,
|
|
};
|
|
const cook: TechStepMappingRule = {
|
|
techStepId: 2,
|
|
expression: "\\bcui(re|sez|sant|sson)\\b|\\bcuit(e|es|s)?\\b",
|
|
weight: 10,
|
|
};
|
|
const bake: TechStepMappingRule = {
|
|
techStepId: 3,
|
|
expression:
|
|
"\\bcuire au four\\b|\\bcuisson au four\\b|\\benfourn(er|ez|é|ée|ées)\\b|\\bau four\\b",
|
|
weight: 25,
|
|
};
|
|
|
|
it("matches an exact expression", () => {
|
|
expect(matchTechStep("Faire mijoter à feu doux", [simmer])).to.equal(1);
|
|
});
|
|
|
|
it("is case- and accent-insensitive", () => {
|
|
expect(matchTechStep("FAIRE MIJOTER", [simmer])).to.equal(1);
|
|
expect(matchTechStep("faire mijoter", [simmer])).to.equal(1);
|
|
});
|
|
|
|
it("returns null when nothing matches", () => {
|
|
expect(matchTechStep("Servir immédiatement", [simmer, cook, bake])).to.be.null;
|
|
});
|
|
|
|
it("picks the highest-weight match when several mappings match", () => {
|
|
// "Cuire au four" matches both `cook` (weight 10) and `bake` (weight 25).
|
|
expect(matchTechStep("Cuire au four pendant 30 minutes", [cook, bake])).to.equal(3);
|
|
// Order-independent.
|
|
expect(matchTechStep("Cuire au four pendant 30 minutes", [bake, cook])).to.equal(3);
|
|
});
|
|
|
|
it("breaks a weight tie by lowest techStepId", () => {
|
|
const a: TechStepMappingRule = { techStepId: 5, expression: "\\bmelanger\\b", weight: 10 };
|
|
const b: TechStepMappingRule = { techStepId: 2, expression: "\\bmelanger\\b", weight: 10 };
|
|
expect(matchTechStep("Mélanger les ingrédients", [a, b])).to.equal(2);
|
|
});
|
|
});
|
|
});
|