Files
Cibello-app/docs/FAS3-COOKING-SESSIONS-AUDIT.md
T

239 lines
14 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.
# Fas 3 Cooking Sessions: REUSE/GAP-matris och stegplan
> Levereras före kod. Styrande dokument: utbyggnadsprompten §6 + `docs/19-beslutslogg.md` + anti-dubblettkartan.
---
## Hermetiska testregler
Alla integrationstester ska vara hermetiska:
- De får inte bero på innehållet i `.env` (värden som `AAMOS_MODE`, `EMAIL_MODE`, `S3_MODE` skrivs över i `setup-env.ts`).
- De får inte bero på förkonfigurerad konfiguration eller externa tjänster.
- De får inte bero på databasinnehåll som skapats utanför testet (exempelvis seedade recept från en tidigare körning). Testkörningen ansvarar själv för att testdatabasen är migrerad **och** seedad innan testerna startar; seeden är idempotent.
- Körning: `pnpm --filter @app/database run db:test-setup` (migrate + seed) ska alltid föregå API-testerna.
## 1. REUSE/GAP-matris mot befintligt matlagningsflöde
| §6-krav / område | Befintlig implementation | Status | Rekommenderad åtgärd |
|------------------|-------------------------|--------|----------------------|
| **Lifecycle PLANNED → STARTED → COMPLETED/CANCELLED** | Saknas. `recipe_cooks` är en enda rad skapad vid `/cook` ("COMPLETED" implicit). Ingen separat sessionstabell. | **Saknas** | Inför `cooking_sessions` (se nedan). Behåll `recipe_cooks` som aggregerad historik för rekommendationer/variation. |
| **FEFO-avdrag från inventory (§6)** | `/v1/recipes/:id/cook` kör `allocateFefo()` och skriver `inventory_transactions.type = 'cook_use'` med `refType='recipe_cook'`. | **Finns** | Återanvänd oförändrad. Lägg till `cookingSessionId` i transaktionen för att kunna ångra en hel session. |
| **portioner / portionsCooked** | `cookRecipeInputSchema.portionsCooked` (124). Skalning i receptdetalj. | **Finns** | Återanvänd. |
| **Individuell förbrukning per ätare** | `eaters[]` med `portionFraction` loggas i `meals`. | **Finns** | Återanvänd. |
| **Minimala efterfrågor efteråt (§6.2)** | Efterflödet i `cooking/[id].tsx` frågar idag bara antal matlådor. Inga profiler för "hur mycket blev det kvar?" | **Delvis** | Inför 23 snabba frågor som extraheras till `cooking_sessions.actualPortionsEaten`, `leftoverEstimate` och `cookingSessionId` på meals. |
| **Partiell förbrukning (§6.3)** | Saknas. `/cook` drar allt på en gång. Användaren kan efteråt manuellt justera via `/v1/inventory/items/:id/transactions`. | **Saknas** | Låt `/cook` skapa `cook_use`-transaktioner med `undoUntil = cookingSessionId`; vid efterfrågan skriv reverseringstransaktioner för det som inte åts. |
| **undo_until (ångra hel session)** | Transaktionsmodellen stöder bara enskilda nya transaktioner. Ingen koppling tillbaka till en session. | **Saknas** | Lägg `cookingSessionId``inventory_transactions`, `meals`, `meal_boxes` och tillåt `POST /v1/cooking-sessions/:id/undo`. |
| **Rester till BEFINTLIGA meal_boxes (§6.4)** | `meal_boxes` finns. `/cook` kan skapa EN ny matlåda per tillfälle. | **Delvis** | Utöka så att rester kan läggas i en **befintlig** `meal_box` (matcha på recipeId + hushåll + `status=available`) istället för att alltid skapa ny. |
| **Anti-dubblettkartan: ingen prepared_food_batches-tabell** | Finns ingen sådan tabell. | **OK** | Behåll. Använd `meal_boxes` som enda restbehållare. Motivering: samma koncept (portioner kvar, ät-senast-datum, näringssnapshot), sparar dubblettlogik. |
| **Näringssnapshot på rester** | `meal_boxes.nutritionPerPortion` sparas vid skapande. | **Finns** | Återanvänd. |
| **recommendations läser recipe_cooks** | `recommendations.ts` gör `SELECT recipeId, max(cookedAt) FROM recipe_cooks GROUP BY recipeId`. | **Finns** | **Risk:** om vi ändrar semantiken i `recipe_cooks` måste denna fråga vara bakåtkompatibel. Rekommendation ska fortsätta se sista tillfället ett recept lagades. |
| **Testprotokoll steg 5** | "Laga nu → cooking mode → klart → logga → ingredienser dras från lagret; måltiden syns i Min dag". | **Finns** | Måste förbli sant. Nytt flöde får inte kräva fler steg än idag; nya frågor ska vara valbara/skipbara. |
| **Testprotokoll steg 8** | "Matlådor: laga recept med portioner till lådor → lådan syns med ät-senast-datum". | **Finns** | Måste förbli sant. Nya rest-flödet ska INTE bryta skapandet av nya lådor. |
| **Alla strängar ×12** | i18next + 12 locales. | **Finns** | Lägg till nya nycklar i alla 12 språk. |
| **Hermetiska tester** | `apps/api/test/*` använder `TEST_DATABASE_URL` och setup-env. | **Finns** | Fortsätt samma mönster. |
| **Mjölkprincipen i texter** | Alla UI-strängar undviker "kassera automatiskt" och skiljer bäst-före/sista-förbrukningsdag. | **Finns** | Fortsätt. Inga texter får garantera matsäkerhet. |
### Slutsats av matrisen
Cooking Sessions är en **utbyggnad**, inte ett parallellsystem:
- **Kärnan** (`recipe_cooks`, FEFO-avdrag, meals, meal_boxes) finns redan.
- **Det som saknas** är en session-boog (`cooking_sessions`) som knyter samman påbörjat/klart/ångrat, plus möjligheten att (a) skjuta upp delar av förbrukningen till efterfrågan och (b) lägga rester i befintliga matlådor.
---
## 2. Anti-dubblettkartan varför INGEN `prepared_food_batches`-tabell
Befintlig `meal_boxes` kan redan representera:
- Portioner som återstår
- Receptursprung (`recipeId`)
- Näringsvärden per portion (`nutritionPerPortion`)
- Förvaringsplats och frystillstånd
- Rekommenderat ät-senast-datum
- Status (`available`/`consumed`/`discarded`)
- Reservering för användare
Att lägga till `prepared_food_batches` skulle skapa:
- Dubbla konsumtions-endpoints
- Dubbla "ät senast"-logiker
- Dubbla måltidsloggningsvägar
- Extra migrering och index
- Förvirring i UI: "är detta en matlåda eller en batch?"
**Beslut:** Utöka `meal_boxes` med ett fält `leftoverSource` (`cook_session`) och en kolumn `cookingSessionId`. Då är resterna fortfarande matlådor ur användarens perspektiv, men spårbart till en cooking session.
---
## 3. STEGPLAN
### Steg 3a Lifecycle + planned usage ovanpå befintlig `/cook`
**Mål:** En cooking session kan startas, slutföras eller avbrytas utan att bryta dagens `/cook`-beteende.
**Ändringar:**
1. Ny tabell `cooking_sessions`:
- `id`, `recipeId`, `householdId`, `startedByUserId`
- `status`: `planned`, `started`, `completed`, `cancelled`
- `plannedPortions`, `plannedMealType`
- `startedAt`, `completedAt`, `cancelledAt`
- `cancelReason` (frivillig text, max 200)
2. Lägg `cookingSessionId` nullable på:
- `inventory_transactions`
- `meals`
- `meal_boxes`
- `recipe_cooks` (för spårbarhet)
3. Nytt API:
- `POST /v1/recipes/:id/cook/start` → skapar `cooking_session` med status `started`.
- `POST /v1/cooking-sessions/:id/complete` → kör dagens `/cook`-logik (FEFO, meals, mealBoxes) men länkar allt till `cookingSessionId`.
- `POST /v1/cooking-sessions/:id/cancel` → sätter status `cancelled`, ingen lagerpåverkan.
4. **Bakåtkompatibilitet:** Behåll `POST /v1/recipes/:id/cook` som ett kortkommando som skapar session + complete i samma anrop (så testprotokoll steg 5 fortsätter fungera).
**Berörda filer:**
- `packages/database/src/schema/recipes.ts` (cooking_sessions, recipe_cooks-kolumn)
- `packages/database/src/schema/meals.ts` (meal_boxes-kolumn)
- `packages/database/src/schema/inventory.ts` (inventory_transactions-kolumn)
- `packages/validation/src/recipes.ts` (nya schemas)
- `apps/api/src/routes/recipes.ts` (nytt `/cook/start`, refactor `/cook`)
- `apps/api/src/routes/cooking-sessions.ts` (ny)
- `apps/api/src/server.ts`
- `apps/mobile/src/app/cooking/[id].tsx` (starta session)
- `apps/mobile/src/app/recipe/[id].tsx` ("Laga nu" kan gå via start)
- `apps/mobile/src/locales/*/common.json` (+12)
**Migration:** 0011_cooking_sessions.sql
**Testplan:**
- POST /cook/start → status started
- POST /cooking-sessions/:id/complete → samma resultat som idag: inventory dras, meals skapas, mealBox skapas
- POST /recipes/:id/cook (gammalt) fortfarande grönt
- recipe_cooks.fas3_verifierat: rekommendationer ser fortfarande `max(cookedAt)` korrekt
---
### Steg 3b Minimala efterfrågor §6.2 + antagandeprofiler
**Mål:** Efter "klart" ställs max 23 snabba frågor. Appen kan fylla i svar automatiskt från profiler (t.ex. "vi brukar äta alla portioner" / "vi brukar ha 2 lådor över").
**Ändringar:**
1. Lägg kolumner på `cooking_sessions`:
- `actualPortionsEaten` (nullable int)
- `leftoverEstimatePortions` (nullable int)
- `leftoverNote` (text)
- `postCookProfile` (jsonb med senaste svaren)
2. Lägg `postCookQuestions` i `user_preferences` eller `households`:
- Profil: alltid samma svar, t.ex. `"we_eat_all"` eller `"leftovers_n"`.
3. App-skärm efter cooking mode:
- "Hur många portioner åt ni?" (default = portionsCooked - mealBoxPortions)
- "Vill du spara rester som matlådor?" (default från profil)
- Frågorna ska kunna skippas med ett tryck (default antas).
**Berörda filer:**
- `packages/database/src/schema/recipes.ts` (kolumner)
- `packages/validation/src/recipes.ts`
- `apps/api/src/routes/cooking-sessions.ts`
- `apps/mobile/src/app/cooking/[id].tsx` (efterflöde)
- `apps/mobile/src/locales/*/common.json`
**Migration:** 0012_cooking_questions.sql
**Testplan:**
- Profil sparas och återanvänds nästa session
- Skippa frågor → defaults används
- Validering: `actualPortionsEaten + leftoverEstimatePortions ≤ portionsCooked`
---
### Steg 3c Partiell förbrukning §6.3 + undo_until
**Mål:** Om användaren säger att de bara åt 3 av 4 portioner, ska FEFO-avdraget justeras så att motsvarande råvaror återstår.
**Ändringar:**
1. Vid complete: använd `actualPortionsEaten` för att räkna om råvaror.
- Exempel: 4 planerade portioner → 3 ätna = använd 75 % av varje ingrediens.
- Om `actualPortionsEaten` är null, använd `portionsCooked` (dagens beteende).
2. Lagra ursprungligt FEFO-avdrag i `cooking_sessions.plannedDeductions` (jsonb).
3. `POST /v1/cooking-sessions/:id/undo`:
- Status måste vara `completed`.
- För varje `inventory_transactions` med `cookingSessionId`: skapa en `correction` med motsatt delta.
- För varje `meal` med `cookingSessionId`: radera (eller markera som cancelled).
- För varje `meal_box` med `cookingSessionId`: sätt status `discarded`.
- Sätt `cooking_sessions.status = 'cancelled'`.
4. Lägg `cookingSessionId` i inventory item detail så användaren kan ångra enskilda transaktioner därifrån också.
**Berörda filer:**
- `apps/api/src/routes/cooking-sessions.ts`
- `apps/api/src/routes/inventory.ts` (visa cookingSessionId i transaktionslistan)
- `packages/inventory-engine/src/fefo.ts` (ev. helper för omräkning)
- `apps/mobile/src/app/cooking/[id].tsx`
**Migration:** ingår i 0011 (kolumn cookingSessionId)
**Testplan:**
- Laga 4 portioner, ät 3 → lager innehåller 25 % kvar av varje ingrediens
- Undo → allt återställs
- Invariant: `computeBalance(transaktioner) === item.quantity` efter complete och efter undo
---
### Steg 3d Rester §6.4 via befintliga meal_boxes
**Mål:** Efterfrågade rester hamnar i antingen (1) befintlig available matlåda med samma `recipeId`, eller (2) ny matlåda.
**Ändringar:**
1. Vid complete, om `leftoverEstimatePortions > 0`:
- Sök `meal_boxes` där `recipeId = recipe.id`, `householdId`, `status = 'available'`, `frozen = false` (eller matcha valt frystillstånd), sorterat på `recommendedUseBy`.
- Om träff: öka `portions` och `portionsRemaining` med `leftoverEstimatePortions`, uppdatera `recommendedUseBy` till det tidigare av de två datumen (mjölkprincipen: behåll kortaste hållbarhet).
- Om ingen träff: skapa ny matlåda (dagens logik).
2. Lägg `leftoverSource = 'cook_session'` och `cookingSessionId` på meal_box.
3. Frysval: om användaren väljer frys, skapa/utöka fryst matlåda med `frozen=true`, useBy=90 dagar.
**Berörda filer:**
- `packages/database/src/schema/meals.ts`
- `apps/api/src/routes/recipes.ts` eller `cooking-sessions.ts`
- `apps/mobile/src/app/cooking/[id].tsx`
- `apps/mobile/src/app/meal-boxes.tsx` (visa source-ikon om det är rester)
**Migration:** 0013_meal_box_leftover_source.sql
**Testplan:**
- Två cooking sessions med samma recept → en matlåda har ökade portioner
- Frysta rester → separat fryst låda
- Ingen dubblett i rekommendationer: matlådor-först-sektionen räknar fortfarande `portionsRemaining > 0`
---
## 4. RISKER
| Risk | Påverkan | Minskning |
|------|----------|-----------|
| `recommendations.ts` använder `recipe_cooks` för "senast lagat". Om `cookedAt` ändras semantik bryts variation. | Medel | Behåll `recipe_cooks.cookedAt` som tidpunkt för färdig session. Lägg separata tidsstämplar på `cooking_sessions`. |
| Testprotokoll steg 5 kräver att "Laga nu → klart → logga" fortsätter fungera utan extra kranar. | Hög | Behåll `/recipes/:id/cook` som kortkommando. Nya frågor ska vara skipbara med tydliga defaults. |
| Testprotokoll steg 8 kräver att nya matlådor skapas. | Hög | Nya rest-flödet ska inte blockera skapandet; befintlig-låda-utökning är opt-in. |
| Partiell förbrukning kan leda till brutna invarianter om omräkning blir fel. | Hög | Invariantstest efter varje complete/undo; använd samma enhetskonvertering som FEFO. |
| Undo kan radera måltider som andra flöden refererar till. | Medel | Använd soft-delete (`deletedAt` på meals) eller markera som `cancelled` istället för hård radering. |
| UI-blödning: nya frågor kan kännas som ett formulär. | Medel | Designa som kort med +/- och "kom ihåg mitt val". |
| Matsäkerhetstexter kan osynligt bli för skarpa. | Medel | Varje ny i18n-nyckel granskas: aldrig "säkert att äta", alltid "lukta och smaka" / "rekommenderas före". |
| Hermetiska tester kan läcka `.env`. | Låg | Fortsätt `setup-env.ts` + `vitest.config.ts`. |
---
## 5. Översiktliga berörda tabeller
| Tabell | Förändring |
|--------|------------|
| `cooking_sessions` | **Ny** |
| `recipe_cooks` | `cookingSessionId` nullable |
| `inventory_transactions` | `cookingSessionId` nullable |
| `meals` | `cookingSessionId` nullable, ev. `deletedAt` |
| `meal_boxes` | `cookingSessionId` nullable, `leftoverSource` text |
| `users` / `households` | post-cook-profil jsonb (steg 3b) |
---
## 6. Leveransordning
1. Steg 3a: lifecycle + bakåtkompatibel `/cook`
2. Steg 3b: efterfrågor + profiler
3. Steg 3c: partiell förbrukning + undo
4. Steg 3d: rester till befintliga meal_boxes
Per steg: `brand-guard`, `typecheck`, `test`, `build`, `first-deploy staging`, patch + bundle.
---
*Rapport färdig. Inväntar godkännande innan kod påbörjas.*