fix(skan+sok): robust kvitto-enheter, titel-/fuzzy-sok, hogtidsfilter

Kvitto-skanning (rapporterad krasch 'svar matchar inte schema … invalid value'):
- AI-skan-enheter (kvitto/kyl/maltid) normaliseras nu fore validering:
  'st'->COUNT, 'kg'->KILOGRAM, 'forp'/'pase'->PACKAGE osv. Okand enhet -> null
  i stallet for att falla hela svaret. Skarpt kvitto-prompt med exakt enhetslista.
  17 nya tester bevisar normaliseringen.

Sok pa 'Vad ska vi ata':
- Endast TITELN (inte beskrivningen) -> 'pannkaka' ger inte langre appelkompott
  (som bara namnde pannkakor i beskrivningen).
- Stavningstolerant via trigram-similarity: 'pankakor' hittar pannkakor.

Hogtidsknapparna (kraftskiva/grill/jul ...):
- Filtrerar nu pa recept-holidayTag (slug) i stallet for en titelsok som gav
  tomt. Har event inga recept an visas ett arligt tomt-lage.

Full typecheck gron, ai-contracts 24 tester grona.
This commit is contained in:
Claude
2026-08-19 14:09:45 +00:00
parent dc5df13f25
commit de279a7c01
6 changed files with 225 additions and 37 deletions
+12 -6
View File
@@ -125,15 +125,19 @@ export async function recommendationRoutes(app: FastifyInstance) {
and(
eq(schema.recipes.status, "published"),
// Titelsök (t.ex. "pannkaka") söker BLAND ALLA recept, oavsett flik.
// Endast TITELN (inte beskrivningen) för relevans, och trigram-
// similarity gör söket stavningstolerant ("pankakor" hittar pannkakor).
// Utan sökterm filtreras i stället på flikens tagg/måltidstyp.
q.search
? or(
ilike(schema.recipes.titleSv, `%${q.search}%`),
ilike(schema.recipes.descriptionSv, `%${q.search}%`),
sql`similarity(${schema.recipes.titleSv}, ${q.search}) > 0.3`,
)
: q.tag
? sql`${q.tag} = ANY(${schema.recipes.tags})`
: sql`${q.mealType} = ANY(${schema.recipes.mealTypes})`,
: q.holiday
? sql`${q.holiday} = ANY(${schema.recipes.holidayTags})`
: q.tag
? sql`${q.tag} = ANY(${schema.recipes.tags})`
: sql`${q.mealType} = ANY(${schema.recipes.mealTypes})`,
),
)
.limit(200);
@@ -211,7 +215,6 @@ export async function recommendationRoutes(app: FastifyInstance) {
isEventActive({ dateRule: e.dateRule, leadDays: e.leadDays }, today),
);
const activeHolidayTags = activeEvents.map((e) => e.slug); // slugs → matchar recept-holidayTags (scoring)
const activeHolidayNames = activeEvents.map((e) => e.nameLocalized || e.nameSv); // lokaliserat namn, fallback nameSv
// --- 6. Senast lagat (variation) + hushållsbetyg ---
const cooks = householdId
@@ -504,7 +507,10 @@ export async function recommendationRoutes(app: FastifyInstance) {
context: {
persons: ctx.persons,
season: ctx.currentSeason,
activeHolidays: activeHolidayNames,
activeHolidays: activeEvents.map((e) => ({
slug: e.slug,
name: e.nameLocalized || e.nameSv,
})),
remainingKcal: ctx.remainingKcal,
remainingProteinG: ctx.remainingProteinG,
craving: craving ?? null,