749 lines
21 KiB
Markdown
749 lines
21 KiB
Markdown
# Session 2026-05-06: Refaktor och user-scoped AI
|
|
|
|
Denna session har genomfört:
|
|
- **User-scoped AI-fallback:** AI-förslag för ingrediens- och kategorimatchning är nu individuellt aktiverbara per användare (premium).
|
|
- **Admin-toggles:** Backend och UI har stöd för att admin kan slå på/av AI per användare.
|
|
- **Premium-scope:** Flutter och backend respekterar premium-flagga och AI-tillgång i alla flöden.
|
|
- **Rematch och manuell produkt:** Flutter har stöd för ommatchning och manuell produkt vid import.
|
|
- **Lessons learned:**
|
|
- Nullable propagation i Prisma och DTO:er kräver noggrannhet.
|
|
- Fallback-first AI och tydlig separation av analyskontrakt ger robustare flöden.
|
|
- User-scoped features kräver ownerId/userId-filter i all logik.
|
|
- Manuella migrationer kan krävas vid DB-problem (se migrering-MSI.md).
|
|
|
|
Se även:
|
|
- [TEKNISK_BESKRIVNING.md](TEKNISK_BESKRIVNING.md) för teknisk genomgång.
|
|
- [AI-FUNKTIONER.md](_archive/microservice-ai/AI-FUNKTIONER.md) för AI-översikt.
|
|
|
|
# Plan för omarbetning av receptimport
|
|
|
|
# 2026-05-07: Säkerhets- och deployförbättringar
|
|
|
|
- **Inventory är nu user-scopad:** Alla inventory-operationer kräver och filtrerar på userId i backend (schema, migration, service, controller, tester).
|
|
- **IDOR-skydd för inventory:** Det är nu omöjligt för användare att läsa eller ändra andras inventarieposter. Tester verifierar att åtkomst nekas vid försök till IDOR.
|
|
- **.gitignore och deploy-hygien:** backend/dist och backend/tsconfig.tsbuildinfo ignoreras och är ej längre spårade i git. .env och .env.* ignoreras, men .env.example finns och är uppdaterad.
|
|
- **CI/CD-härdning:** npm audit och prisma validate körs i pipeline. Alla tester och byggen måste passera.
|
|
|
|
## Bakgrund
|
|
|
|
Nuvarande importflöde blandar ihop två olika ansvar:
|
|
|
|
1. Att importera och spara receptet så troget källan som möjligt.
|
|
2. Att matcha receptets ingredienser mot interna produkter.
|
|
|
|
Det som fungerar bra i dag är:
|
|
- webskrapning
|
|
- import med text och bild
|
|
- presentation av receptbild och text när receptet väl är sparat
|
|
|
|
Det som fungerar sämre är:
|
|
- ingrediensimporten
|
|
- tidig produktmatchning i importsteget
|
|
- att receptets innehåll förvrängs eller tappar ingredienser om matchning saknas
|
|
|
|
## Målbild
|
|
|
|
Importsteget ska bara göra detta:
|
|
- hämta receptets titel, bild, beskrivning, instruktioner och ingrediensrader
|
|
- strukturera ingrediensraderna så gott det går
|
|
- spara receptet även när ingen produktmatchning finns
|
|
- låta användaren granska receptet utan att behöva välja produkter
|
|
|
|
Ett senare analyssteg ska göra detta:
|
|
- jämföra receptets ingredienser med inventory och pantry
|
|
- avgöra exakt träff, trolig ersättningsvara eller saknad vara
|
|
- ge underlag för shoppinglista
|
|
- ge underlag för AI-förslag och substitutionsförslag
|
|
|
|
## Arkitekturprincip
|
|
|
|
Receptet ska vara källtroget.
|
|
Produktmatchning ska vara ett separat lager ovanpå receptet.
|
|
|
|
Det innebär att vi behöver separera:
|
|
- receptets råa ingredienser
|
|
- användarens lager och skafferi
|
|
- matchnings- och analyslogik
|
|
|
|
## Föreslagen datamodell
|
|
|
|
### Nuvarande problem
|
|
|
|
I dag kräver `RecipeIngredient` i Prisma:
|
|
- `productId`
|
|
- `quantity`
|
|
- `unit`
|
|
|
|
Det gör att en ingrediens inte kan sparas om vi inte redan vet vilken intern produkt den motsvarar.
|
|
|
|
### Ny målmodell
|
|
|
|
Inför två nivåer för ingredienser:
|
|
|
|
1. Receptets egen ingrediensrad
|
|
2. Matchning/analys mot interna produkter
|
|
|
|
### Förslag A: utöka befintlig `RecipeIngredient`
|
|
|
|
Det enklaste spåret är att behålla `RecipeIngredient`, men göra den receptcentrisk i stället för produktcentrisk.
|
|
|
|
Nya eller ändrade fält:
|
|
- `rawLine String?`
|
|
- `rawName String`
|
|
- `productId Int?` i stället för required
|
|
- `quantity Decimal?`
|
|
- `unit String?`
|
|
- `note String?`
|
|
- `matchConfidence Float?`
|
|
- `matchSource String?` (`heuristic`, `ai`, `manual`)
|
|
- `analysisStatus String?` (`unmatched`, `exact`, `substitutable`, `missing`)
|
|
|
|
Behåll:
|
|
- `alternativeProductIds Json?`
|
|
- `recipeId`
|
|
|
|
### Förslag B: dela upp i två tabeller
|
|
|
|
Mer robust på sikt men större ombyggnad:
|
|
- `RecipeIngredient` blir rå ingrediensrad
|
|
- `RecipeIngredientMatch` blir separat matchningslager
|
|
|
|
Första implementationen bör använda Förslag A för lägre risk.
|
|
|
|
## Rekommenderad genomförandeordning
|
|
|
|
1. Gör datamodellen tolerant för omatchade ingredienser.
|
|
2. Ändra backend så att import och sparning fungerar utan produktmatchning.
|
|
3. Förenkla frontendens importgranskning.
|
|
4. Lägg till separat analyssteg mot inventory och pantry.
|
|
5. Lägg till AI-stöd först när grundflödet är stabilt.
|
|
|
|
---
|
|
|
|
# Fil-för-fil-plan
|
|
|
|
## 1. Databas och Prisma
|
|
|
|
### Fil: `backend/prisma/schema.prisma`
|
|
|
|
### Ändringar
|
|
- Gör `RecipeIngredient.productId` optional.
|
|
- Gör `RecipeIngredient.product` optional relation.
|
|
- Gör `quantity` optional.
|
|
- Gör `unit` optional.
|
|
- Lägg till `rawName`.
|
|
- Lägg till `rawLine`.
|
|
- Lägg till `matchConfidence`.
|
|
- Lägg till `matchSource`.
|
|
- Lägg till `analysisStatus`.
|
|
|
|
### Exempel på målmodell
|
|
|
|
```prisma
|
|
model RecipeIngredient {
|
|
id Int @id @default(autoincrement())
|
|
recipe Recipe @relation(fields: [recipeId], references: [id])
|
|
recipeId Int
|
|
product Product? @relation(fields: [productId], references: [id])
|
|
productId Int?
|
|
rawName String
|
|
rawLine String? @db.Text
|
|
quantity Decimal? @db.Decimal(10, 2)
|
|
unit String?
|
|
note String?
|
|
alternativeProductIds Json?
|
|
matchConfidence Float?
|
|
matchSource String?
|
|
analysisStatus String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
```
|
|
|
|
### Leverabler
|
|
- Prisma-schema uppdaterat
|
|
- ny migration skapad
|
|
- Prisma Client regenererad
|
|
|
|
### Risk
|
|
- All kod som i dag antar att `productId`, `quantity` och `unit` alltid finns måste uppdateras
|
|
|
|
---
|
|
|
|
## 2. DTO:er för receptskapande
|
|
|
|
### Fil: `backend/src/recipes/dto/create-recipe.dto.ts`
|
|
|
|
### Nuvarande problem
|
|
DTO:n kräver att varje ingrediens har:
|
|
- `productId`
|
|
- `quantity`
|
|
- `unit`
|
|
|
|
Det blockerar trogen import.
|
|
|
|
### Ändringar
|
|
- Gör `productId` optional.
|
|
- Gör `quantity` optional.
|
|
- Gör `unit` optional.
|
|
- Lägg till `rawName` som required.
|
|
- Lägg till `rawLine` som optional.
|
|
- Lägg till `matchConfidence` och `matchSource` som optional om vi vill bevara preliminär heuristik.
|
|
|
|
### Mål
|
|
Backend ska kunna acceptera ett recept där ingrediensen bara är:
|
|
- `rawName`
|
|
- eventuellt `rawLine`
|
|
- eventuellt `quantity`, `unit`, `note`
|
|
|
|
### Konsekvens
|
|
`ArrayMinSize(1)` kan sannolikt vara kvar, men valideringen måste vara råingrediens-baserad i stället för produkt-baserad.
|
|
|
|
---
|
|
|
|
## 3. DTO för parse-resultat
|
|
|
|
### Fil: `backend/src/recipes/dto/parse-markdown.dto.ts`
|
|
|
|
### Ändringar
|
|
Ingen stor strukturändring behövs här om endpointen fortsatt bara tar emot markdown.
|
|
|
|
### Eventuell komplettering
|
|
Om importen senare ska stödja flera källor mer explicit kan ett `sourceType` läggas till:
|
|
- `markdown`
|
|
- `web`
|
|
- `ocr`
|
|
|
|
Detta är inte nödvändigt i första omgången.
|
|
|
|
---
|
|
|
|
## 4. Receptservice: parse ska sluta göra för mycket
|
|
|
|
### Fil: `backend/src/recipes/recipes.service.ts`
|
|
|
|
### Nuvarande problem
|
|
`parseMarkdown()` gör i dag flera saker:
|
|
- anropar importer-api eller lokal parser
|
|
- hämtar alla produkter
|
|
- kör heuristisk matchning med normalisering och Levenshtein
|
|
- returnerar suggestions
|
|
|
|
Det gör att importsteget implicit blir ett matchningssteg.
|
|
|
|
### Rekommenderad ändring i etapp 1
|
|
Behåll parse men ändra dess ansvar:
|
|
- parse ska alltid returnera råingredienser
|
|
- suggestions får vara optional och sekundära
|
|
- frontend ska inte vara beroende av suggestions för att kunna spara
|
|
|
|
### Konkret refaktorering
|
|
Dela upp i privata metoder:
|
|
- `parseRecipeContent(markdown)`
|
|
- `buildIngredientSuggestions(parsedIngredients)`
|
|
- `createRecipeFromImport(dto, userId)`
|
|
|
|
### Ändringar i `create()`
|
|
- sluta kräva att alla ingredienser har `productId`
|
|
- kör `assertProductsActive()` bara för ingredienser som faktiskt har `productId`
|
|
- spara `rawName` även när produktmatchning finns
|
|
- spara `rawLine` när det finns
|
|
|
|
### Ny metod att lägga till
|
|
- `analyzeRecipeIngredients(recipeId, userId)`
|
|
|
|
Ansvar för `analyzeRecipeIngredients`:
|
|
- ladda receptets råingredienser
|
|
- jämför mot inventory
|
|
- jämför mot pantry
|
|
- returnera analysstatus per ingrediens
|
|
- returnera förslag på ersättningsvaror och inköp
|
|
|
|
### Ny privat logik i service
|
|
- `findExactInventoryMatches(...)`
|
|
- `findPantryMatches(...)`
|
|
- `findSubstituteCandidates(...)`
|
|
- `buildShoppingNeeds(...)`
|
|
|
|
### Viktig designregel
|
|
`parseMarkdown()` får gärna returnera suggestions, men `create()` får inte vara beroende av att de finns.
|
|
|
|
---
|
|
|
|
## 5. Receptcontroller: separera import från analys
|
|
|
|
### Fil: `backend/src/recipes/recipes.controller.ts`
|
|
|
|
### Ändringar
|
|
Behåll:
|
|
- `POST /recipes/parse-markdown`
|
|
- `POST /recipes`
|
|
|
|
Lägg till:
|
|
- `GET /recipes/:id/analysis`
|
|
- eventuellt `POST /recipes/:id/rematch`
|
|
|
|
### Förslag på ansvar
|
|
`GET /recipes/:id/analysis`
|
|
- returnerar en användarspecifik analys av receptet mot inventory och pantry
|
|
|
|
`POST /recipes/:id/rematch`
|
|
- kör ny matchning om användaren har ändrat inventory/pantry eller om heuristiken förbättrats
|
|
|
|
### Mål
|
|
Importen blir en egen sak.
|
|
Analysen blir en egen endpoint.
|
|
|
|
---
|
|
|
|
|
|
---
|
|
|
|
## 7. Alias-strategi och matchedVia (2026-05-07)
|
|
|
|
- Alias-strategin är nu fullt implementerad:
|
|
- Backend och Flutter stödjer user-alias och globala alias.
|
|
- matchedVia-badge visas i UI (Alias/Ordmatch/AI).
|
|
- Användare kan spara egna alias, admins kan spara globala.
|
|
- Profilsidan har alias-lista för användare, admin-panelen visar produkt-ID och radstruktur.
|
|
- Backend har 3 nya tester för matchedVia, totalt 66 tester gröna.
|
|
|
|
### Fil: `backend/src/recipes/recipes.module.ts`
|
|
|
|
### Ändringar
|
|
Lägg till eventuella nya providers när analyslogik bryts ut:
|
|
- `RecipeAnalysisService`
|
|
- eventuellt `RecipeMatchingService`
|
|
|
|
### Rekommendation
|
|
Om analysen växer, bryt ut den från `RecipesService` för att undvika att samma service blir för stor.
|
|
|
|
---
|
|
|
|
## 7. Ny analysservice
|
|
|
|
### Ny fil: `backend/src/recipes/recipe-analysis.service.ts`
|
|
|
|
### Ansvar
|
|
- ta ett sparat recept som indata
|
|
- analysera varje ingrediens mot inventory och pantry
|
|
- ta fram:
|
|
- exakt träff
|
|
- täcks av pantry
|
|
- möjlig ersättning
|
|
- saknas
|
|
|
|
### Förslag på output
|
|
|
|
```ts
|
|
{
|
|
recipeId: 123,
|
|
ingredients: [
|
|
{
|
|
ingredientId: 1,
|
|
rawName: "gul lök",
|
|
quantity: 1,
|
|
unit: "st",
|
|
status: "exact_match",
|
|
matchedProductId: 456,
|
|
matchedProductName: "Gul lök",
|
|
source: "inventory"
|
|
}
|
|
],
|
|
summary: {
|
|
exactCount: 3,
|
|
pantryCount: 2,
|
|
substituteCount: 1,
|
|
missingCount: 4
|
|
},
|
|
shoppingListCandidates: []
|
|
}
|
|
```
|
|
|
|
### Nytta
|
|
Detta blir grunden för:
|
|
- "kan jag laga detta?"
|
|
- shoppinglista
|
|
- AI-förslag
|
|
|
|
---
|
|
|
|
## 8. Ny matchningstjänst
|
|
|
|
### Ny fil: `backend/src/recipes/recipe-matching.service.ts`
|
|
|
|
### Ansvar
|
|
- matcha rå ingrediens mot produktkatalog
|
|
- inte spara något själv
|
|
- kunna användas av både import och analys
|
|
|
|
### Matchningsnivåer
|
|
1. Exakt match på normaliserat namn
|
|
2. Synonym/alias-match
|
|
3. Kategori- eller taggmatchning
|
|
4. AI-fallback
|
|
|
|
### Varför separat tjänst
|
|
Det gör att heuristik, aliaslogik och AI-stöd inte behöver ligga inbyggt i `parseMarkdown()`.
|
|
|
|
---
|
|
|
|
## 9. AI-integration för ingredienstolkning
|
|
|
|
### Fil: `backend/src/ai/ai.service.ts`
|
|
|
|
### Nuvarande läge
|
|
AI används redan för kategorisering.
|
|
|
|
### Nytt användningsområde
|
|
Lägg till en separat metod, till exempel:
|
|
- `suggestIngredientMatches(rawIngredient, candidates)`
|
|
- `suggestSubstitutions(rawIngredient, availableProducts)`
|
|
|
|
### Viktig regel
|
|
AI ska inte styra om receptet går att spara.
|
|
AI ska bara förbättra analys och förslag efteråt.
|
|
|
|
### Mål
|
|
AI används där osäkerhet är acceptabel:
|
|
- substitutionsförslag
|
|
- kompletterande matchning
|
|
- shoppingförslag
|
|
|
|
---
|
|
|
|
## 10. Importskärm i Flutter
|
|
|
|
### Fil: `flutter/lib/features/recipes/presentation/create_recipe_screen.dart`
|
|
|
|
### Nuvarande problem
|
|
Skärmen gör i dag detta i review-steget:
|
|
- checkbox per ingrediens
|
|
- produktval via suggestions
|
|
- manuellt tillagda ingredienser med produktdropdown
|
|
- vid sparning ignoreras ingredienser utan `productId`
|
|
|
|
Det gör att användaren i praktiken förväntas produktmatcha importen.
|
|
|
|
### Målbild för skärmen
|
|
Importgranskningen ska fokusera på receptet, inte lagerkoppling.
|
|
|
|
### Ändringar
|
|
Ta bort eller dölja i importläget:
|
|
- produktdropdownar
|
|
- krav på produktmatchning
|
|
- manuell ingrediensskapning via produktlista
|
|
|
|
Behåll eller lägg till:
|
|
- justera titel
|
|
- justera beskrivning vid behov
|
|
- justera servings
|
|
- visa importerade ingrediensrader som text
|
|
- tillåt enklare rättningar av mängd, enhet, notering och namn
|
|
- inkludera/uteslut ingrediensrad
|
|
|
|
### Ny save-logik
|
|
Vid `_save()` ska frontend skicka:
|
|
- `rawName`
|
|
- `rawLine`
|
|
- `quantity`
|
|
- `unit`
|
|
- `note`
|
|
- `productId` bara om en automatisk eller manuell matchning redan finns
|
|
|
|
### Rekommenderad UI-förändring
|
|
Dela upp skärmen i två modes:
|
|
- `import review`
|
|
- `manual recipe editing`
|
|
|
|
Import review ska vara avsevärt enklare än manuell receptredigering.
|
|
|
|
---
|
|
|
|
## 11. Parsed recipe-domain i Flutter
|
|
|
|
### Fil: `flutter/lib/features/recipes/domain/parsed_recipe.dart`
|
|
|
|
### Ändringar
|
|
Lägg till fält som tydligare representerar rå importdata:
|
|
- `rawLine`
|
|
- `matchState`
|
|
- `isParsedSafely`
|
|
|
|
### Rekommendation
|
|
`ParsedIngredient` ska behandlas som importdata, inte som nästan färdig `RecipeIngredient`.
|
|
|
|
### Ny riktning
|
|
`suggestions` ska vara optional hjälpdata i UI, inte en förutsättning för att spara.
|
|
|
|
---
|
|
|
|
## 12. Flutter-repository för recept
|
|
|
|
### Fil: `flutter/lib/features/recipes/data/recipe_repository.dart`
|
|
|
|
### Ändringar
|
|
- uppdatera `createRecipe()` så att request body tillåter råingredienser utan `productId`
|
|
- lägg till metod:
|
|
- `fetchRecipeAnalysis(int id)`
|
|
- lägg till eventuell metod:
|
|
- `rematchRecipeIngredients(int id)`
|
|
|
|
### Mål
|
|
Frontend ska kunna:
|
|
- spara troget importerat recept
|
|
- senare hämta analys mot inventory/pantry
|
|
|
|
---
|
|
|
|
## 13. API-paths i Flutter
|
|
|
|
### Fil: `flutter/lib/core/api/api_paths.dart`
|
|
|
|
### Ändringar
|
|
Lägg till:
|
|
- `static String analysis(int id) => '/recipes/$id/analysis';`
|
|
- eventuellt `static String rematch(int id) => '/recipes/$id/rematch';`
|
|
|
|
---
|
|
|
|
## 14. Receptdomän i Flutter
|
|
|
|
### Fil: `flutter/lib/features/recipes/domain/recipe.dart`
|
|
|
|
### Nuvarande problem
|
|
Nuvarande domän ser sannolikt ingredienser som produktkopplade objekt.
|
|
|
|
### Ändringar
|
|
Gör att `RecipeIngredient` klarar:
|
|
- `productId == null`
|
|
- `productName == null`
|
|
- `rawName` finns alltid
|
|
- `quantity` och `unit` kan vara null eller tomma
|
|
|
|
### Viktigt
|
|
UI för receptdetalj ska kunna visa rå ingredienstext även när ingen produktmatchning finns.
|
|
|
|
---
|
|
|
|
## 15. Receptdetaljskärm i Flutter
|
|
|
|
### Fil: `flutter/lib/features/recipes/presentation/recipe_detail_screen.dart`
|
|
|
|
### Ändringar
|
|
Ingredienslistan ska rendera enligt följande prioritet:
|
|
1. `rawName` och råa fält
|
|
2. matchat produktnamn om det finns
|
|
|
|
### Exempel
|
|
Om produktmatchning saknas ska användaren ändå se:
|
|
- `2 msk olivolja`
|
|
- `1 gul lök`
|
|
- `400 g krossade tomater`
|
|
|
|
### Lägg till ny sektion
|
|
- "Har du hemma?"
|
|
|
|
Den sektionen ska bygga på `GET /recipes/:id/analysis` i stället för att anta att receptingredienser redan är perfekt produktmatchade.
|
|
|
|
---
|
|
|
|
## 16. Ny analysdomän i Flutter
|
|
|
|
### Ny fil: `flutter/lib/features/recipes/domain/recipe_analysis.dart`
|
|
|
|
### Ansvar
|
|
Representera analysresultat från backend.
|
|
|
|
### Förslag på typer
|
|
- `RecipeAnalysis`
|
|
- `RecipeIngredientAnalysis`
|
|
- `RecipeIngredientAvailabilityStatus`
|
|
|
|
### Statusvärden
|
|
- `exactMatch`
|
|
- `coveredByPantry`
|
|
- `substitutable`
|
|
- `missing`
|
|
|
|
---
|
|
|
|
## 17. Ny provider för analys
|
|
|
|
### Fil: `flutter/lib/features/recipes/data/recipe_providers.dart`
|
|
|
|
### Ändringar
|
|
Lägg till provider:
|
|
- `recipeAnalysisProvider`
|
|
|
|
### Ansvar
|
|
- ladda analys för ett recept
|
|
- kunna invalidateras när inventory eller pantry ändras
|
|
|
|
---
|
|
|
|
## 18. Manual recipe editing ska vara separat
|
|
|
|
### Fil: `flutter/lib/features/recipes/presentation/recipe_edit_screen.dart`
|
|
|
|
### Rekommendation
|
|
Behåll denna skärm för riktig redigering av receptets struktur.
|
|
|
|
### Viktig skillnad mot import
|
|
- import review = bevara originalet
|
|
- recipe edit = medveten redigering av receptet
|
|
|
|
### Praktisk åtgärd
|
|
Undvik att återanvända fullständig edit-UI direkt i importflödet.
|
|
|
|
---
|
|
|
|
## 19. Alias- och produktmatchning
|
|
|
|
### Relevanta filer
|
|
- `backend/src/receipt-import/...`
|
|
- `backend/src/products/...`
|
|
- `backend/src/recipes/recipes.service.ts`
|
|
|
|
### Åtgärd
|
|
Flytta all generell ingrediensmatchning till en central tjänst i stället för att ha den dold i `parseMarkdown()`.
|
|
|
|
### Mål
|
|
Samma logik ska kunna användas av:
|
|
- receptimport
|
|
- analys mot inventory/pantry
|
|
- AI-förslag
|
|
- eventuellt framtida shoppinglista
|
|
|
|
---
|
|
|
|
## 20. Migration av befintliga data
|
|
|
|
### Vad behöver hanteras
|
|
Befintliga recept har ingredienser som redan är produktkopplade.
|
|
|
|
### Strategi
|
|
- behåll gamla värden
|
|
- fyll `rawName` med bästa tillgängliga namn från produkt eller befintlig text
|
|
- sätt `matchSource = 'legacy'` för migrerade rader
|
|
- sätt `analysisStatus = null` initialt
|
|
|
|
### Mål
|
|
Gamla recept fortsätter fungera utan att behöva byggas om manuellt.
|
|
|
|
---
|
|
|
|
# Implementationsfaser
|
|
|
|
## Fas 1: Gör receptingredienser omatchningsbara
|
|
|
|
### Filer
|
|
- `backend/prisma/schema.prisma`
|
|
- `backend/src/recipes/dto/create-recipe.dto.ts`
|
|
- `backend/src/recipes/recipes.service.ts`
|
|
- `flutter/lib/features/recipes/data/recipe_repository.dart`
|
|
- `flutter/lib/features/recipes/domain/recipe.dart`
|
|
|
|
### Resultat
|
|
Ett recept kan sparas även när inga ingredienser är produktmatchade.
|
|
|
|
---
|
|
|
|
## Fas 2: Förenkla importgranskningen
|
|
|
|
### Filer
|
|
- `flutter/lib/features/recipes/presentation/create_recipe_screen.dart`
|
|
- `flutter/lib/features/recipes/domain/parsed_recipe.dart`
|
|
|
|
### Resultat
|
|
Användaren granskar receptet som recept, inte som lagerobjekt.
|
|
|
|
---
|
|
|
|
## Fas 3: Lägg till analys mot inventory/pantry
|
|
|
|
### Filer
|
|
- `backend/src/recipes/recipe-analysis.service.ts`
|
|
- `backend/src/recipes/recipes.controller.ts`
|
|
- `backend/src/recipes/recipes.module.ts`
|
|
- `flutter/lib/core/api/api_paths.dart`
|
|
- `flutter/lib/features/recipes/domain/recipe_analysis.dart`
|
|
- `flutter/lib/features/recipes/data/recipe_repository.dart`
|
|
- `flutter/lib/features/recipes/data/recipe_providers.dart`
|
|
- `flutter/lib/features/recipes/presentation/recipe_detail_screen.dart`
|
|
|
|
### Resultat
|
|
Appen kan svara på:
|
|
- vad har jag exakt
|
|
- vad täcks av pantry
|
|
- vad kan ersättas
|
|
- vad saknas
|
|
|
|
---
|
|
|
|
## Fas 4: Förbättra matchning och AI
|
|
|
|
### Filer
|
|
- `backend/src/recipes/recipe-matching.service.ts`
|
|
- `backend/src/ai/ai.service.ts`
|
|
- eventuellt fler UI-filer för ersättningsförslag och shoppinglista
|
|
|
|
### Resultat
|
|
AI blir en förbättrare, inte ett krav för importen.
|
|
|
|
---
|
|
|
|
# Beslut som bör tas innan implementation
|
|
|
|
## 1. Ska `RecipeIngredient` byggas om eller delas upp?
|
|
|
|
Rekommendation:
|
|
- börja med att bygga om `RecipeIngredient`
|
|
- dela upp i fler tabeller först om behovet blir tydligt senare
|
|
|
|
## 2. Ska suggestions visas under import?
|
|
|
|
Rekommendation:
|
|
- ja, men bara som passiv hjälp
|
|
- inte som något användaren måste välja för att kunna spara
|
|
|
|
## 3. Ska användaren kunna redigera ingrediensrader under import?
|
|
|
|
Rekommendation:
|
|
- ja, men bara lätt redigering
|
|
- ingen tung produktkoppling i detta steg
|
|
|
|
## 4. När ska AI användas?
|
|
|
|
Rekommendation:
|
|
- efter att receptet är sparat
|
|
- för matchning, substitutioner och shoppingstöd
|
|
- inte för att avgöra om receptet får sparas
|
|
|
|
---
|
|
|
|
# Definition av klart
|
|
|
|
Denna omarbetning är klar när följande gäller:
|
|
|
|
1. Ett importerat recept kan sparas utan att någon ingrediens är kopplad till en intern produkt.
|
|
2. Receptets ingredienslista visas troget även utan produktmatchning.
|
|
3. Importskärmen kräver inte produktval.
|
|
4. Receptdetaljen kan analysera receptet mot inventory och pantry i ett separat steg.
|
|
5. Analysen kan särskilja exakt träff, pantry-träff, ersättningsbar och saknad ingrediens.
|
|
6. AI används bara som hjälplager ovanpå detta.
|
|
|
|
---
|
|
|
|
# Rekommenderat första arbetsblock
|
|
|
|
Om implementationen ska påbörjas direkt är detta bästa första block:
|
|
|
|
1. Uppdatera `schema.prisma` för råingrediensstöd.
|
|
2. Skapa migration.
|
|
3. Uppdatera `create-recipe.dto.ts`.
|
|
4. Uppdatera `recipes.service.ts#create()` så att recept kan sparas utan `productId`.
|
|
5. Uppdatera `create_recipe_screen.dart` så att `_save()` skickar råingredienser i stället för att kasta bort omatchade rader.
|
|
|
|
Det blocket ger störst effekt med lägst risk, eftersom det löser kärnproblemet: att importen inte längre förstör receptets innehåll.
|