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

14 KiB
Raw Blame History

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 cookingSessionIdinventory_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.