Files
Cibello-app/docs/34-personalisering-implementation.md
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

25 KiB
Raw Permalink Blame History

Del 34 Personalisering: implementationsplan (Spår 1)

Status: S1 implementerad och testad. Plan godkänd av Johan 2026-08-10; S1-kod commitad (0885f5b).
Omfattning: Staging endast. Prod/AAMOS förblir orörd.
Mål: mappa persona-chartern (docs/31-persona-personalisering.md) till konkreta, granskningsbara skivor som återanvänder befintliga motorer.


1. YT-INVENTERING — vilka AI-nära ytor ska personaliseras

Yta Nuvarande läge Persona-effekt efter Spår 1 Varför denna yta?
A. "Vad ska vi äta?" / GET /v1/recommendations/what-to-eat Byggd. Rankar recept efter täckning, utgångsdatum, näringsfit, smak, betyg, säsong, högtid, tid, budget, variation, väder, craving. Recept rangordnas med tydlig proveniens som inkluderar användarens egna minnen ("Du lagade kycklinggryta tre tisdagar i rad — här är en annan variant"), hushållsförbrukningsmönster och utgångs-varor. Appens viktigaste surface; här märks personliga förslag mest.
B. Grupperade sök-svar / recept-sök 🔧 Delvis byggd (/v1/recipes/search finns troligen; söksvar är grundade i katalogen). Sökresultat tonas av användarens smakprofil, hushållsbegränsningar och lager. Proaktivt utesluter allergener/undvikanden. Bygger PÅ befintligt grundat svar (lager + plats + trust-hedge); no-fabrication-grundningen bevaras. Sök är en aktiv användarintent; personlig rangordning måste vara förklarlig.
C. Proaktiva puffar 🔧 SEND_EXPIRY_NOTIFICATION körs dagligen kl 07. Matlådepåminnelser finns. Nya puffar: "Gurkan börjar se trött ut — tre recept du brukar gilla med gurka", "Ni har ätit risotto ofta på söndagar; vill du planera en?" Hög användarnytta, låg frekvens, kräver explicit opt-in.
D. Receptrangordning efter smak/hälsa/lager Motor finns (packages/recommendation-engine/src/scoring.ts). Skapa tre fördefinierade vyer: Smak (favoritkök + taste_signals), Hälsa (näring mot dagsmål + health_profile), Lager (täckning + utgår-snart). Användaren växlar; ingen hemlig viktning. Ger användaren kontroll och transparens.
E. Onboarding-relationen Onboarding med mål, allergier, favoritkök. Onboarding-klassifikationer skrivs som user_stated-minnen och taste_signals; de synkroniseras tillbaka till "Vad plattformen vet om mig". Tidigt förtroende: användaren ser att svaren används.

Out of scope för Spår 1 (sparas till senare spår):

  • Generering av helt nya recept utifrån minne.
  • Community/creator-personalisering.
  • Extern hälsointegration (HealthKit/Health Connect).
  • Butikserbjudanden/partnerships.
  • Bildtränings-personalisering.

2. MOTOR-MAPPNING — anti-dubblett: återbruk, inga parallella motorer

För varje yta anger tabellen vilken befintlig data/motor som är primär källa. Ingen ny AI-motor ska byggas för Spår 1; personalisering = att låta befintliga motorer läsa mer kontext.

Yta Primär befintlig data/motor Ny data vi läser in (ingen ny motor) Vad vi INTE bygger
A. Vad ska vi äta? recommendation-engine (scoreCandidate/rankAll) memory_items (smak/faktum/mönster), taste_signals (axelriktning), cooking_assumption_profiles (förbrukningsmönster), recipe_cooks (senast lagat), recipe_ratings (egna betyg). Ingen ny rankningsmotor.
B. Sök-svar Recept-katalog + recipe-engine (säkerhet/täckning) Samma som A plus userPreferences (favoriteCuisines, avoidIngredientIds, spiceLevelMax). Ingen separat sök-AI.
C. Proaktiva puffar inventory-engine/forecast.ts, reconciliation.ts, worker-schemaläggare memory_items.recipe_memory, recipe_cooks, taste_signals, userConsents (opt-in). Ingen ny notismotor.
D. Smak/Hälsa/Lager-vyer recommendation-engine + nutrition-engine userPreferences, userHealthProfiles, dagens meals, lager. Ingen ny vy-motor; bara fördefinierade vikter.
E. Onboarding Onboarding-flödet i apps/mobile Skriver till memory_items + taste_signals via befintliga API-routes. Ingen ny onboarding-backend.

Centrala datatabeller som används av flera ytor:

  • users + userPreferences + userHealthProfiles: mål, allergier, diet, kök, undvikanden, spice max, utrustning, portioner.
  • userConsents: personalization-flaggan är hård port för alla personliga ytor.
  • households + household_members: hushållsgemensamma begränsningar (strängaste gäller) och portionsfaktorer.
  • inventory_items + inventory_transactions: lager, utgångsdatum, förbrukningstakt.
  • canonical_ingredients + recipe_ingredients: säkerhetsfiltrering, täckning, allergener.
  • recipes + recipe_ratings + recipe_cooks + recipe_favorites: betyg, historik, variation.
  • memory_items: användarägda, transparenta minnen (alla origin, paused, verifiedByUser).
  • taste_signals: strukturerade smakpreferenser per axel (ursprung user_stated/observed/ai_inferred).
  • cooking_assumption_profiles: hushållsspecifika förbrukningsantaganden per ingrediens.
  • meals + meal_boxes: dagens näringsintag och rester.
  • domain_events: händelseflöde för UPDATE_USER_MEMORY.
  • season_events + weather (stub): säsong/högtid/väder.

Anti-dubblett-princip: Om en funktion kan uppnås genom att lägga till ett fält i en befintlig motor eller läsa en befintlig tabell, ska vi göra det. Endast om en yta kräver helt ny algoritmisk logik får en ny fil skapas — och då i packages/recommendation-engine med egna enhetstester.


3. CHARTER-REGLER inbyggda

Persona-chartern översätts till följande tekniska regler. Varje regel ska gå att granska i kod och test.

R1. Grundat, aldrig påhittat

  • Alla personliga påståenden måste ha ett spårbart ursprung i memory_items.origin (user_stated, observed, ai_inferred).
  • ai_inferred-poster:
    • Har låg startkonfidens (t.ex. 0.5).
    • Formuleras som "ett mönster vi sett", aldrig "du gillar".
    • Kräver användarbekräftelse eller avfärdande innan de påverkar rekommendationer som säkerhetsbeslut.
    • Driver aldrig säkerhetsbeslut (allergen/diet/exkludering).
  • Ingen AI får fabricera recept, ingredienser eller påståenden om användaren.

R2. Samtycke + synligt/redigerbart minne

  • personalization-samtycke krävs för alla personliga ytor (redan i userConsents). Enkeltflaggat samtycke är OK för Spår 1; granulär uppdelning skjuts till senare spår om behov uppstår.
  • "Vad plattformen vet om mig" (GET /v1/me/memory) visar alla minnen, inklusive ursprung och konfidens.
  • Användaren kan rätta (PATCH /v1/me/memory/:id), pausa (POST /v1/me/memory/pause-all) och radera (DELETE /v1/me/memory/:id samt DELETE /v1/me/memory).
  • Användarkorrigering ändrar origin till user_stated och confidence till 1.

R3. Ingen skam-ton

  • Förklaringar (whySv) får aldrig innehålla skam, moralisering eller värdeomdömen om matvanor.
  • Exempel på förbjuden copy: "Du borde äta mindre kött", "Din kost är obalanserad".
  • Tillåten copy: "Denna rätt ger 35 g protein, vilket matchar ditt mål", "Du har lagat risotto ofta på söndagar — här är en variation".
  • Enhetstester ska validera copy mot en förbudslista.

R4. Allergener/hälsa användarsatt

  • Allergener kommer från userPreferences.allergens + hushållsaggregering (strängaste gäller), aldrig från AI.
  • Hälsodata (userHealthProfiles) är frivillig och användarsatt; appen visar icke-medicinsk disclaimer.
  • Recept-allergener fortsätter att härledas deterministiskt från canonical_ingredients.allergens (se Del 33 / allergen-invariant-testet).

R5. i18n × 12

  • Alla nya användarvisningstexter ska gå via localeContext och memory-client/renderMemorySummary.
  • Proveniessatser genereras med språkspecifika mallar (sv/en/es/it/de/fr/da/nb/fi/nl/pl/pt).
  • Förklaringar (whySv) ska ha motsvarande whyEn, whyEs, … eller genereras dynamiskt från mallar.

R6. Proveniens

  • Varje personaliserat förslag ska kunna förklara varför det valdes.
  • Personaliseringstexter (proveniens, why, puffar) genereras från mallar med grundade fakta, inte fri AI-text. Mallarna är översatta, skam- och fabriceringsfria per konstruktion.
  • Exempel på mallar (svenska):
    • "För att du har berättat att du gillar {{cuisine}}."
    • "För att ni har {{count}} {{ingredient}} som bör användas inom {{days}} dagar."
    • "För att du lagat {{recipe}} {{count}} gånger den senaste månaden."
    • "För att det passar ditt återstående proteinbehov i dag."
  • Förbudslistan (R3) behålls som skyddsnät för mall-renderade strängar.
  • Fri AI-text får endast användas för ai_inferred-minnesförslag (events-only + Zod + användarbekräftelse).
  • Proveniens lagras/transporteras i ScoredRecommendation.parts + en ny valfri provenance array.

R7. Välmående — icke-restriktiv hälsa-framing

  • Hälso-vyn och all näringsmåls-framing ska vara stödjande och icke-restriktiv.
  • Tillåten copy: "Passar ditt proteinmål", "Bidrar till dina grönsaker i dag".
  • Förbjuden copy: "Du har överskridit ditt kalorimål", "Bara X kcal kvar", "Begränsa dig", "Undvik …" eller annan negativ/restriktiv framing.
  • Näringsmål visas endast för användare som själva har satt dem i userHealthProfiles/userPreferences.
  • Copy-granskning + eval-fall ska täcka välmående-framing.

4. SKIVINDELNING — små reviewbara skivor med testplan

Ingen skiva får påbörjas förrän föregående är godkänd. Varje skiva levererar: kod + enhetstester + integrationstest mot staging-DB + dokumentationsuppdatering.

Skiva S0 — förberedelse (inga användarfunktioner)

Innehåll:

  • Checka in docs/31-persona-nordstjärnan.md och förankra R1R7 explicit mot varje avsnitt i chartern.
  • Säkerställ att userConsents för personalization läses korrekt överallt. Enkeltflaggat personalization-samtycke är OK för Spår 1 (ingen granulär uppdelning av personalisering ännu).
  • Uppdatera packages/recommendation-engine/src/types.ts med provenance och memorySignalIdsScoredRecommendation.
  • Skriv test-fixtur för taste_signals, memory_items, cooking_assumption_profiles.
  • Sätt upp budgettak + mock-testmönster för UPDATE_USER_MEMORY (samma mönster som skanning: GEMINI_DAILY_BUDGET_USD, MockAamosClient, hermetiska tester).

Testplan:

  • Typecheck och enhetstester gröna.
  • Fixturen kan skapa en användare med samtycken, minnen, smaksignaler och matlagningshistorik.
  • UPDATE_USER_MEMORY-anrop kan mockas och kostnadsräknas utan att träffa AAMOS.

Estimat: 1 dag.


Skiva S1 — "Vad ska vi äta?" tonad mot lager + utgår-snart + smak + proveniens

Innehåll:

  • Utöka recommendation-engine/src/scoring.ts med tre nya delpoäng som läser befintliga tabeller:
    • memoryFit: positiv boost om recept matchar memory_items (t.ex. favoritkök, gillade rätter).
    • tasteFit: boost/penalty från taste_signals per axel.
    • cookingAssumptionFit: boost om recept använder ingredienser där cooking_assumption_profiles visar att hushållet brukar äta upp.
  • Lägg till provenance array i resultatet med motiveringar på svenska + andra språk.
  • Uppdatera explain.ts så proveniens visas i whySv.
  • Uppdatera apps/api/src/routes/recommendations.ts att läsa in memory_items, taste_signals och cooking_assumption_profiles för aktuell användare/hushåll.
  • Hård krav: personalization-samtycke måste vara granted; annars används nuvarande beteende.

Testplan:

  • Enhetstest: en kandidat med taste_signals (positiv/negativ) får rätt tasteFit.
  • Enhetstest: memoryFit boostas av verifiedByUser-minne mer än ai_inferred.
  • Enhetstest: cookingAssumptionFit boostar recept som använder ingredienser med hög averageEatenPortions.
  • Integrationstest: GET /v1/recommendations/what-to-eat med/utan samtycke ger olika resultat.
  • UI/Copy-test: inga skam-fraser i 100 genererade whySv.

Estimat: 34 dagar.


Skiva S2 — Proaktiva puffar (opt-in)

Innehåll:

  • Ny jobbtyp SEND_PERSONALIZED_NUDGE i worker (BullMQ-schemaläggare, t.ex. kl 11:00 på helger och kl 16:00 på vardagar).
  • Logik:
    1. Hämta hushåll med personalization-samtycke.
    2. Hämta utgående varor (inventory-engine/expiry.ts).
    3. Hämta recept som använder dessa varor och som matchar taste_signals/memory_items.
    4. Generera max 1 puff per hushåll per dag; undvik upprepning inom 7 dagar.
    5. Skriv notis i notifications (inte push direkt; push kräver separat opt-in).
  • Proveniens: "Gurkan håller på att bli slapp — här är ett recept du brukar gilla."

Testplan:

  • Enhetstest: puff genereras endast om det finns utgående vara + matchande recept + samtycke.
  • Enhetstest: max 1 puff per hushåll per dag.
  • Integrationstest: jobbet kör mot staging-DB och skapar rätt notis.
  • GDPR-test: avbryt vid personalization-revoke.

Estimat: 3 dagar.


Skiva S3 — Minnes-ytan + personliga grundade svar

Innehåll:

  • Förbättra GET /v1/me/memory så minnen visas med tydligare ursprung och användbara kategorier.
  • Koppla minnen till reella ytor: t.ex. "Du gillar italienskt kök" → visar vilka recept som påverkas.
  • Lägg till GET /v1/me/memory/impact som returnerar vilka rekommendationer som påverkas av ett specifikt minne (utan att exponera andras data).
  • Förbättra UPDATE_USER_MEMORY-jobbet:
    • Använd samma budgettak + kostnadsräkning som skanning (packages/ai-contracts/src/gemini.ts, GEMINI_DAILY_BUDGET_USD).
    • Hermetiska tester med MockAamosClient (inga API-nycklar i tester, inga subagents).
    • Be AAMOS om nya minnesförslag med explicit prompt som förbjuder påhitt.
    • Validera output mot Zod-kontraktet.
    • Avvisa förslag som saknar stöd i events.

Testplan:

  • Enhetstest: buildMemoryOverview grupperar rätt och visar pausade poster.
  • Integrationstest: PATCH /v1/me/memory/:id ändrar origin till user_stated.
  • Mock-test: UPDATE_USER_MEMORY avvisar fabricerat minne (t.ex. påhittad favoriträtt).
  • GDPR-test: DELETE /v1/me/memory raderar allt; DELETE /v1/me raderar även taste_signals.

Estimat: 3 dagar.


Skiva S4 — Smak / Hälsa / Lager-vyer i "Vad ska vi äta?"

Innehåll:

  • Lägg till query-param view=default|taste|health|pantryGET /v1/recommendations/what-to-eat.
  • Varje vy använder samma rankAll men med fördefinierade ScoringWeights:
    • Taste: högre taste, craving, memoryFit, rating; lägre nutritionFit.
    • Health: högre nutritionFit; boosta protein-/fiber-/kcal-mål; lägre budget.
    • Pantry: högre coverage, expiry; lägre variety, weather.
  • Spara vikterna i packages/recommendation-engine/src/types.ts som konstanter.

Testplan:

  • Enhetstest: samma kandidater ger olika ordning beroende på vy.
  • Integrationstest: API accepterar view-parametern och returnerar skilda topplistor.
  • Regressionstest: default är oförändrad gentemot idag.

Estimat: 2 dagar.


Skiva S5 — Onboarding → minne + smaksignaler

Innehåll:

  • När användaren slutför onboarding skrivs mål, allergier, favoritkök, undvikanden och spice max till:
    • userPreferences (redan idag).
    • memory_items som user_stated-poster.
    • taste_signals för favoritkök (axis=cuisine, direction=positiv) och undvikna ingredienser (axis=ingredient_avoid, direction=negativ).
  • Visa i onboarding: "Detta sparas i "Vad plattformen vet om mig" och du kan ändra det när som helst."

Testplan:

  • Integrationstest: efter onboarding finns motsvarande memory_items och taste_signals.
  • Enhetstest: onboarding-favoritkök skapar rätt taste_signals-poster.

Estimat: 2 dagar.


Skiva S6 — i18n, proveniens och eval-pipeline

Innehåll:

  • Säkerställ att alla nya provenienssträngar finns på 12 språk.
  • Uppdatera memory-client/renderMemorySummary med nya value-former.
  • Lägg till AI-eval-fall i ai_eval_cases för:
    • "Inga fabricerade minnen"
    • "Inga skam-fraser"
    • "Rätt språk i proveniens"
  • Kör evals mot AAMOS i staging.

Testplan:

  • Evals körbara via adminpanel eller worker-jobb.
  • Manuell granskning av 50 genererade whySv per språk.

Estimat: 23 dagar.


Totalt estimerat: 1620 arbetsdagar över 6 skivor, förutsatt att varje skiva godkänns innan nästa påbörjas.


5. RISKER + hur varje charter-regel testas

Risk Mitigering Test/verifiering
AI fabricerar minnen eller påhittar användarpreferenser UPDATE_USER_MEMORY tar bara events som input; output valideras mot Zod; ai_inferred-poster visas med tydlig etikett; användaren kan radera/pausa. Mock-test där AAMOS föreslår ett minne som inte stöds av events → avvisas. Invariant: alla memory_items måste ha origin satt.
Personliga data läcker till andra hushållsmedlemmar userHealthProfiles, memory_items, taste_signals är användarsatta; hushållsaggregering är begränsad till allergener/undvikanden/spice max. Integrationstest: medlem A kan inte läsa medlem B:s memory_items eller hälsoprofil.
Skam-ton eller värderande copy Förbudslista + språkgranskning i explain.ts och nudge-generering. Enhetstest som skannar 100+ genererade förklaringar mot förbudslista; eval-korpus med skam-exempel.
Användaren tappar kontroll över minnet Full CRUD + paus + radera allt; alla nya minnen kommer från användaren eller observerade events. Manuell test av "Vad plattformen vet om mig"-flödet; GDPR-raderingstest.
Personalisering körs utan samtycke Hård check på userConsents.kind='personalization' AND status='granted' i API och worker. Integrationstest: utan samtycke fallback till opersonligt beteende; inga memory_items skrivs.
Allergener eller hälsa härleds felaktigt av AI Allergener kommer från användarpreferenser + canonical-ingredienser; hälsa är användarsatt. AI används aldrig för säkerhetsfiltrering. Återanvänd befintliga säkerhetstester (recipe-engine/test/safety.test.ts) och allergen-invariant-testet.
i18n-försämring / fel språk i proveniens Alla nya texter via localeContext och språkspecifika mallar; eval per språk. Manuell granskning per språk; automatisk check att whySv inte blandas med whyEn.
Prestandaförsämring i "Vad ska vi äta?" Läsning av memory_items och taste_signals är indexerad; begränsa till aktuell användare/hushåll; cache lämpliga aggregeringar. Lasttest: 200 anrop/minut under 5 minuter; jämför svarstid före/efter.
Cirkulära beroenden Alla ändringar i recommendation-engine får inte bero på apps/api; memory-client får inte bero på recommendation-engine. pnpm typecheck och dependency-graf (turbo) grön.
Seed/reseed bryter personliga data Inga personliga data i seed; seed skrivs aldrig över användardata. Seed-test: efter db:test-setup finns inga memory_items eller taste_signals för testanvändare.

Godkännande-checklista för planen

  • Persona-charter (docs/31-persona-personalisering.md) checkas in och förankras.
  • Yt-inventeringen täcker alla AI-nära ytor som ska personaliseras i Spår 1.
  • Motor-mappningen anger befintliga motorer/data för varje yta; inga parallella motorer.
  • Charter-reglerna (R1R7) är tydliga och testbara.
  • Personaliseringstexter = mallar med grundade fakta, inte fri AI-text.
  • ai_inferred-minnen: låg konfidens, användarbekräftelse, driver inte säkerhetsbeslut.
  • UPDATE_USER_MEMORY: budgettak + mock-mönster som skanning.
  • Skivindelningen är tillräckligt liten; varje skiva har testplan.
  • Risker och testmetoder accepteras.
  • Ingen kod skrivs förrän denna plan är godkänd.
  • S0 får starta; S1 påbörjas efter godkännande.

Revisionslogg

Datum Revision Författare
2026-08-10 Initial plan för granskning Sven
2026-08-10 Godkänd med justeringar: R7 välmående, mall-baserade texter, ai_inferred-konfidens, UPDATE_USER_MEMORY-budget, docs/31 incheckad Sven / Johan
2026-08-10 S1 implementerat: "Vad ska vi äta?" med provenansmallar, samtyckesgrind, personliga delpoäng, integrationstester; commit 0885f5b Sven / Johan
2026-08-10 S2 implementerat: proaktiva puffar (expiring-ingredient) med dubbelgrind, 12-språksmallar, frekvens-/duplicate-gate, UTC-matte, integrationstester; commit f494b18 Sven / Johan