Files
Cibello-app/packages/recommendation-engine/src/provenance-templates.ts
T
Sven (AAMOS AI) c32a7e33c7
CI / Typecheck, test & build (push) Failing after 2s
ci: trigga på master + formatfix inför Gitea Actions
2026-08-13 17:25:14 +07:00

174 lines
8.2 KiB
TypeScript

import type { ProvenanceEntry } from "./types.js";
/**
* Mall-baserade provenienssträngar för personalisering (S1).
* Ingen fri AI-text till användaren — alla strängar byggs från grundade fakta.
* R7/stödjande ton: positiv framing, aldrig restriktiv eller skam.
*/
export type LanguageTag =
"sv" | "en" | "es" | "it" | "de" | "fr" | "da" | "nb" | "fi" | "nl" | "pl" | "pt";
const TEMPLATES: Record<string, Record<LanguageTag, string>> = {
favoriteCuisine: {
sv: "För att du har berättat att du gillar {{cuisine}} mat.",
en: "Because you said you like {{cuisine}} food.",
es: "Porque dijiste que te gusta la comida {{cuisine}}.",
it: "Perché hai detto che ti piace la cucina {{cuisine}}.",
de: "Weil du gesagt hast, dass du {{cuisine}} Essen magst.",
fr: "Parce que vous avez dit que vous aimiez la cuisine {{cuisine}}.",
da: "Fordi du har sagt, at du kan lide {{cuisine}} mad.",
nb: "Fordi du har sagt at du liker {{cuisine}} mat.",
fi: "Koska kerroit pitäväsi {{cuisine}} ruoasta.",
nl: "Omdat je hebt gezegd dat je {{cuisine}} eten lekker vindt.",
pl: "Ponieważ powiedziałeś, że lubisz kuchnię {{cuisine}}.",
pt: "Porque disseste que gostas de comida {{cuisine}}.",
},
cookedOften: {
sv: "För att du lagat {{recipe}} {{count}} gånger den senaste månaden.",
en: "Because you've cooked {{recipe}} {{count}} times in the past month.",
es: "Porque has cocinado {{recipe}} {{count}} veces el último mes.",
it: "Perché hai cucinato {{recipe}} {{count}} volte nell'ultimo mese.",
de: "Weil du {{recipe}} {{count}} Mal im letzten Monat gekocht hast.",
fr: "Parce que vous avez cuisiné {{recipe}} {{count}} fois le mois dernier.",
da: "Fordi du har lavet {{recipe}} {{count}} gange den seneste måned.",
nb: "Fordi du har laget {{recipe}} {{count}} ganger den siste måneden.",
fi: "Koska olet kokannut {{recipe}} {{count}} kertaa viimeisen kuukauden aikana.",
nl: "Omdat je {{recipe}} {{count}} keer hebt gekookt afgelopen maand.",
pl: "Ponieważ gotowałeś {{recipe}} {{count}} razy w ostatnim miesiącu.",
pt: "Porque cozinhaste {{recipe}} {{count}} vezes no último mês.",
},
expiringIngredient: {
sv: "För att ni har {{count}} {{ingredient}} som bör användas inom {{days}} dagar.",
en: "Because you have {{count}} {{ingredient}} to use within {{days}} days.",
es: "Porque tenéis {{count}} {{ingredient}} para usar en {{days}} días.",
it: "Perché avete {{count}} {{ingredient}} da usare entro {{days}} giorni.",
de: "Weil ihr {{count}} {{ingredient}} habt, die binnen {{days}} Tagen verwendet werden sollten.",
fr: "Parce que vous avez {{count}} {{ingredient}} à utiliser dans {{days}} jours.",
da: "Fordi I har {{count}} {{ingredient}}, der bør bruges inden for {{days}} dage.",
nb: "Fordi dere har {{count}} {{ingredient}} som bør brukes innen {{days}} dager.",
fi: "Koska sinulla on {{count}} {{ingredient}}, jotka tulisi käyttää {{days}} päivän kuluessa.",
nl: "Omdat jullie {{count}} {{ingredient}} hebben die binnen {{days}} dagen gebruikt moeten worden.",
pl: "Ponieważ macie {{count}} {{ingredient}}, które należy zużyć w ciągu {{days}} dni.",
pt: "Porque tens {{count}} {{ingredient}} para usar em {{days}} dias.",
},
fitsProteinGoal: {
sv: "För att det passar ditt proteinmål i dag.",
en: "Because it fits your protein goal for today.",
es: "Porque encaja con tu objetivo de proteína de hoy.",
it: "Perché si adatta al tuo obiettivo proteico di oggi.",
de: "Weil es zu deinem Protein-Ziel für heute passt.",
fr: "Parce que cela correspond à votre objectif de protéines pour aujourd'hui.",
da: "Fordi det passer til dit proteinmål i dag.",
nb: "Fordi det passer proteinmålet ditt i dag.",
fi: "Koska se sopii päivän proteiinitavoitteesi.",
nl: "Omdat het past bij je eiwitdoel voor vandaag.",
pl: "Ponieważ pasuje do dzisiejszego celu białkowego.",
pt: "Porque se adequa à tua meta de proteína para hoje.",
},
fitsVegetableGoal: {
sv: "För att det bidrar till dina grönsaker i dag.",
en: "Because it contributes to your vegetables for today.",
es: "Porque contribuye a tus verduras de hoy.",
it: "Perché contribuisce alle tue verdure di oggi.",
de: "Weil es zu deinem Gemüse für heute beiträgt.",
fr: "Parce que cela contribue à vos légumes d'aujourd'hui.",
da: "Fordi det bidrager til dine grøntsager i dag.",
nb: "Fordi det bidrar til grønnsakene dine i dag.",
fi: "Koska se lisää päivän kasvissi saantia.",
nl: "Omdat het bijdraagt aan je groenten voor vandaag.",
pl: "Ponieważ przyczynia się do dzisiejszego spożycia warzyw.",
pt: "Porque contribui para os teus vegetais de hoje.",
},
tastePreference: {
sv: "För att det matchar din smakprofil ({{axis}}).",
en: "Because it matches your taste profile ({{axis}}).",
es: "Porque coincide con tu perfil de sabor ({{axis}}).",
it: "Perché corrisponde al tuo profilo di gusto ({{axis}}).",
de: "Weil es zu deinem Geschmacksprofil passt ({{axis}}).",
fr: "Parce que cela correspond à votre profil de goût ({{axis}}).",
da: "Fordi det matcher din smagsprofil ({{axis}}).",
nb: "Fordi det matcher smaksprofilen din ({{axis}}).",
fi: "Koska se sopii makuprofiilisi ({{axis}}).",
nl: "Omdat het bij je smaakprofiel past ({{axis}}).",
pl: "Ponieważ pasuje do Twojego profilu smakowego ({{axis}}).",
pt: "Porque corresponde ao teu perfil de sabor ({{axis}}).",
},
usesStapleYouFinish: {
sv: "För att ni brukar äta upp {{ingredient}} när ni lagar det.",
en: "Because you usually finish {{ingredient}} when you cook it.",
es: "Porque soléis acabar {{ingredient}} cuando lo cocináis.",
it: "Perché di solito finite {{ingredient}} quando lo cucinate.",
de: "Weil ihr {{ingredient}} normalerweise aufesset, wenn ihr es kocht.",
fr: "Parce que vous finissez habituellement {{ingredient}} quand vous le cuisinez.",
da: "Fordi I plejer at spise {{ingredient}} op, når I laver det.",
nb: "Fordi dere pleier å spise opp {{ingredient}} når dere lager det.",
fi: "Koska yleensä syöt {{ingredient}} loppuun, kun kokkaat sitä.",
nl: "Omdat jullie {{ingredient}} meestal opeten als jullie het koken.",
pl: "Ponieważ zwykle zjadacie {{ingredient}}, gdy to gotujecie.",
pt: "Porque normalmente acabas {{ingredient}} quando cozinhas.",
},
};
export function renderProvenance(entries: ProvenanceEntry[], languageTag: string): string {
const lang = (languageTag.split("-")[0] ?? "sv") as LanguageTag;
const parts: string[] = [];
for (const entry of entries) {
const tmpl = TEMPLATES[entry.key];
if (!tmpl) continue;
let s = tmpl[lang] ?? tmpl.sv;
for (const [k, v] of Object.entries(entry.args)) {
s = s.replaceAll(`{{${k}}}`, String(v));
}
parts.push(s);
}
return parts.join(" ");
}
export function renderProvenanceList(entries: ProvenanceEntry[], languageTag: string): string[] {
const lang = (languageTag.split("-")[0] ?? "sv") as LanguageTag;
return entries
.map((entry) => {
const tmpl = TEMPLATES[entry.key];
if (!tmpl) return null;
let s = tmpl[lang] ?? tmpl.sv;
for (const [k, v] of Object.entries(entry.args)) {
s = s.replaceAll(`{{${k}}}`, String(v));
}
return s;
})
.filter((s): s is string => s != null);
}
/** Språk som varje proveniensmall är översatt till. */
export function provenanceTemplateLanguages(): Record<string, LanguageTag[]> {
const result: Record<string, LanguageTag[]> = {};
for (const [key, t] of Object.entries(TEMPLATES)) {
result[key] = Object.keys(t) as LanguageTag[];
}
return result;
}
/** Förbjudna ord/fraser i proveniens/copy — R3 (ingen skam) + R7 (välmående). */
export const FORBIDDEN_COPY_PATTERNS = [
/överskrid/i,
/bara .* kvar/i,
/begränsa/i,
/undvik/i,
/skärp/i,
/skäms/i,
/dålig/i,
/för mycket/i,
/för lite/i,
/borde inte/i,
/får inte/i,
/måste sluta/i,
/överdriv/i,
/kalori/i, // näringsmål ska ramas positivt, aldrig kcal-centrerat
];
/** Kontrollera att en sträng inte innehåller förbjuden copy. */
export function containsForbiddenCopy(text: string): boolean {
return FORBIDDEN_COPY_PATTERNS.some((re) => re.test(text));
}