Files
Cibello-app/docs/34-personalisering-implementation.md
T
Sven (AAMOS AI) f8e00c5060 docs: personalisering Spår 1 – implementationsplan (Del 34)
- Yt-inventering av AI-nära ytor.
- Motor-mappning mot befintliga data/motorer (anti-dubblett).
- Charter-regler R1–R6 inbyggda.
- Skivindelning S0–S6 med testplaner.
- Risker + test av särskilt fabricering, användarstyrt minne, samtycke.
- Inväntar godkännande innan skiva 1 kodas.
2026-08-10 02:32:44 +07:00

275 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Del 34 Personalisering: implementationsplan (Spår 1)
> Status: **plan för granskning**. Ingen funktionskod får skrivas förrän planen är godkänd.
> Omfattning: Staging endast. Prod/AAMOS förblir orörd.
> Mål: mappa persona-chartern (se `docs/31-persona-nordstjärnan.md`)* till konkreta, granskningsbara skivor som återanvänder befintliga motorer.
\* *Dokumentet `docs/31-persona-nordstjärnan.md` saknas för närvarande i repot. Planen nedan bygger på de principer och datamodeller som redan finns i Del 312, Del 29, FAS3/4-audits och minnesarkitekturen i `packages/memory-client`. Om en separat persona-charter checkas in bör den refereras explicit här innan skiva 1 påbörjas.*
---
## 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. | 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 visas aldrig utan att användaren kan se att det är ett "mönster vi har observerat" och kan pausa/radera.
- 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`).
- "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.
- Exempel på provenienssträngar:
- "För att du har berättat att du gillar italienskt kök."
- "För att ni har tre ägg som bör användas inom två dagar."
- "För att du lagat kycklinggryta tre gånger den senaste månaden."
- "För att det passar ditt återstående proteinbehov i dag."
- Proveniens lagras/transporteras i `ScoredRecommendation.parts` + en ny valfri `provenance` array.
---
## 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` om den finns separat; annars lyft in charter-reglerna ovan i Del 34.
- Säkerställ att `userConsents` för `personalization` läses korrekt överallt.
- Uppdatera `packages/recommendation-engine/src/types.ts` med `provenance` och `memorySignalIds``ScoredRecommendation`.
- Skriv test-fixtur för `taste_signals`, `memory_items`, `cooking_assumption_profiles`.
**Testplan:**
- Typecheck och enhetstester gröna.
- Fixturen kan skapa en användare med samtycken, minnen, smaksignaler och matlagningshistorik.
**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:
- 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|pantry``GET /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-nordstjärnan.md`) checkas in eller refereras korrekt.
- [ ] 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 (R1R6) är tydliga och testbara.
- [ ] Skivindelningen är tillräckligt liten; varje skiva har testplan.
- [ ] Risker och testmetoder accepteras.
- [ ] Ingen kod skrivs förrän denna plan är godkänd.
---
## Revisionslogg
| Datum | Revision | Författare |
| --- | --- | --- |
| 2026-08-10 | Initial plan för granskning | Sven |