20 KiB
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 (allaorigin,paused,verifiedByUser).taste_signals: strukturerade smakpreferenser per axel (ursprunguser_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örUPDATE_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 iuserConsents). 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/:idsamtDELETE /v1/me/memory). - Användarkorrigering ändrar
origintilluser_statedochconfidencetill1.
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
localeContextochmemory-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 motsvarandewhyEn,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 valfriprovenancearray.
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.mdoch förankra R1–R7 explicit mot varje avsnitt i chartern. - Säkerställ att
userConsentsförpersonalizationläses korrekt överallt. Enkeltflaggatpersonalization-samtycke är OK för Spår 1 (ingen granulär uppdelning av personalisering ännu). - Uppdatera
packages/recommendation-engine/src/types.tsmedprovenanceochmemorySignalIdspåScoredRecommendation. - 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.tsmed tre nya delpoäng som läser befintliga tabeller:memoryFit: positiv boost om recept matcharmemory_items(t.ex. favoritkök, gillade rätter).tasteFit: boost/penalty fråntaste_signalsper axel.cookingAssumptionFit: boost om recept använder ingredienser därcooking_assumption_profilesvisar att hushållet brukar äta upp.
- Lägg till
provenancearray i resultatet med motiveringar på svenska + andra språk. - Uppdatera
explain.tsså proveniens visas iwhySv. - Uppdatera
apps/api/src/routes/recommendations.tsatt läsa inmemory_items,taste_signalsochcooking_assumption_profilesför aktuell användare/hushåll. - Hård krav:
personalization-samtycke måste varagranted; annars används nuvarande beteende.
Testplan:
- Enhetstest: en kandidat med
taste_signals(positiv/negativ) får rätttasteFit. - Enhetstest:
memoryFitboostas avverifiedByUser-minne mer änai_inferred. - Enhetstest:
cookingAssumptionFitboostar recept som använder ingredienser med högaverageEatenPortions. - Integrationstest:
GET /v1/recommendations/what-to-eatmed/utan samtycke ger olika resultat. - UI/Copy-test: inga skam-fraser i 100 genererade
whySv.
Estimat: 3–4 dagar.
Skiva S2 — Proaktiva puffar (opt-in)
Innehåll:
- Ny jobbtyp
SEND_PERSONALIZED_NUDGEi worker (BullMQ-schemaläggare, t.ex. kl 11:00 på helger och kl 16:00 på vardagar). - Logik:
- Hämta hushåll med
personalization-samtycke. - Hämta utgående varor (
inventory-engine/expiry.ts). - Hämta recept som använder dessa varor och som matchar
taste_signals/memory_items. - Generera max 1 puff per hushåll per dag; undvik upprepning inom 7 dagar.
- Skriv notis i
notifications(inte push direkt; push kräver separat opt-in).
- Hämta hushåll med
- 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/memoryså 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/impactsom 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.
- Använd samma budgettak + kostnadsräkning som skanning (
Testplan:
- Enhetstest:
buildMemoryOverviewgrupperar rätt och visar pausade poster. - Integrationstest:
PATCH /v1/me/memory/:idändrarorigintilluser_stated. - Mock-test:
UPDATE_USER_MEMORYavvisar fabricerat minne (t.ex. påhittad favoriträtt). - GDPR-test:
DELETE /v1/me/memoryraderar allt;DELETE /v1/meraderar äventaste_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|pantrypåGET /v1/recommendations/what-to-eat. - Varje vy använder samma
rankAllmen med fördefinieradeScoringWeights:- Taste: högre
taste,craving,memoryFit,rating; lägrenutritionFit. - Health: högre
nutritionFit; boosta protein-/fiber-/kcal-mål; lägrebudget. - Pantry: högre
coverage,expiry; lägrevariety,weather.
- Taste: högre
- Spara vikterna i
packages/recommendation-engine/src/types.tssom 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_itemssomuser_stated-poster.taste_signalsfö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_itemsochtaste_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/renderMemorySummarymed nya value-former. - Lägg till AI-eval-fall i
ai_eval_casesfö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
whySvper språk.
Estimat: 2–3 dagar.
Totalt estimerat: 16–20 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 (R1–R7) ä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 |