From f8e00c5060516f6e262f3dabc8dd33ee0f36b627 Mon Sep 17 00:00:00 2001 From: "Sven (AAMOS AI)" Date: Mon, 10 Aug 2026 02:32:44 +0700 Subject: [PATCH] =?UTF-8?q?docs:=20personalisering=20Sp=C3=A5r=201=20?= =?UTF-8?q?=E2=80=93=20implementationsplan=20(Del=2034)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- docs/34-personalisering-implementation.md | 274 ++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 docs/34-personalisering-implementation.md diff --git a/docs/34-personalisering-implementation.md b/docs/34-personalisering-implementation.md new file mode 100644 index 0000000..6fc5a15 --- /dev/null +++ b/docs/34-personalisering-implementation.md @@ -0,0 +1,274 @@ +# 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 3–12, 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` på `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:** 3–4 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` på `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:** 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-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 (R1–R6) ä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 |