101 lines
2.9 KiB
TypeScript
101 lines
2.9 KiB
TypeScript
import type { Cuisine } from "@app/shared-types";
|
|
|
|
export interface ParsedCraving {
|
|
tags: string[];
|
|
cuisine?: Cuisine | undefined;
|
|
maxKcal?: number | undefined;
|
|
keywords: string[];
|
|
}
|
|
|
|
/**
|
|
* Deterministisk grundtolkning av "Jag är sugen på …" (spec §19).
|
|
* Täcker vanliga svenska uttryck utan AI-anrop; AAMOS kan förfina tolkningen
|
|
* (GENERATE_RECIPE_OPTIONS) men denna bas fungerar alltid, även offline-nära.
|
|
*/
|
|
const CUISINE_KEYWORDS: Array<[Cuisine, string[]]> = [
|
|
["thai", ["thai", "thailändskt", "asiatiskt"]],
|
|
["japanese", ["japanskt", "sushi", "ramen"]],
|
|
["chinese", ["kinesiskt", "wok"]],
|
|
["korean", ["koreanskt", "kimchi"]],
|
|
["vietnamese", ["vietnamesiskt", "pho"]],
|
|
["indian", ["indiskt", "curry"]],
|
|
["italian", ["italienskt", "pasta", "pizza"]],
|
|
["french", ["franskt"]],
|
|
["spanish", ["spanskt", "tapas", "paella"]],
|
|
["greek", ["grekiskt", "gyros", "tzatziki"]],
|
|
["mexican", ["mexikanskt", "tacos", "tex mex", "texmex"]],
|
|
["american", ["amerikanskt", "burgare", "hamburgare"]],
|
|
["turkish", ["turkiskt", "kebab"]],
|
|
["lebanese", ["libanesiskt", "meze"]],
|
|
["moroccan", ["marockanskt", "tagine"]],
|
|
["swedish", ["svenskt", "husman", "husmanskost"]],
|
|
["nordic", ["nordiskt"]],
|
|
];
|
|
|
|
const TAG_KEYWORDS: Array<[string, string[]]> = [
|
|
["creamy", ["krämigt", "krämig", "gräddigt"]],
|
|
["spicy", ["starkt", "stark", "hett", "chili"]],
|
|
["fresh", ["fräscht", "fräsch", "lätt", "somrigt"]],
|
|
["comfort", ["comfort", "mysmat", "husman", "tröstmat"]],
|
|
["luxury", ["lyxigt", "lyx", "fest"]],
|
|
["quick", ["snabbt", "snabb", "enkelt", "enkel"]],
|
|
["kid_friendly", ["barnvänligt", "barnvänlig", "barn"]],
|
|
["high_protein", ["proteinrikt", "protein"]],
|
|
["vegetarian", ["vegetariskt", "vegetarisk", "vego"]],
|
|
["vegan", ["veganskt", "vegansk"]],
|
|
["soup", ["soppa"]],
|
|
["stew", ["gryta", "långkok"]],
|
|
["grill", ["grill", "grillat"]],
|
|
];
|
|
|
|
export function parseCraving(text: string): ParsedCraving {
|
|
const lower = text.toLowerCase();
|
|
const tags: string[] = [];
|
|
let cuisine: Cuisine | undefined;
|
|
|
|
for (const [c, words] of CUISINE_KEYWORDS) {
|
|
if (words.some((w) => lower.includes(w))) {
|
|
cuisine = c;
|
|
break;
|
|
}
|
|
}
|
|
for (const [tag, words] of TAG_KEYWORDS) {
|
|
if (words.some((w) => lower.includes(w))) tags.push(tag);
|
|
}
|
|
|
|
let maxKcal: number | undefined;
|
|
const kcalMatch = lower.match(/under\s+(\d{2,4})\s*(?:kcal|kalorier)/);
|
|
if (kcalMatch?.[1]) maxKcal = Number(kcalMatch[1]);
|
|
|
|
const STOPWORDS = new Set([
|
|
"jag",
|
|
"är",
|
|
"sugen",
|
|
"på",
|
|
"vill",
|
|
"ha",
|
|
"något",
|
|
"lite",
|
|
"och",
|
|
"med",
|
|
"gärna",
|
|
"känner",
|
|
"för",
|
|
"mig",
|
|
"idag",
|
|
"ikväll",
|
|
"kväll",
|
|
"mat",
|
|
"äta",
|
|
"laga",
|
|
"göra",
|
|
"en",
|
|
"ett",
|
|
]);
|
|
const keywords = lower
|
|
.split(/[^a-zåäö0-9]+/)
|
|
.filter((w) => w.length >= 3 && !STOPWORDS.has(w));
|
|
|
|
return { tags, cuisine, maxKcal, keywords };
|
|
}
|