Files
recipe-app/.kilo/plans/1779641992740-glowing-sailor.md
Nils-Johan Gynther b04d157915
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Successful in 5m12s
Test Suite / flutter-quality (push) Failing after 2m8s
feat(flyer-import): add detailed product signals and display names
- Added `signals` and `displayNameDetailed` fields to FlyerItem model in Prisma schema
- Introduced `FlyerImportSignals` type with origin countries, labels, quality flags, variant, and packaging
- Added `displayNameDetailed` field to FlyerImportItem DTO and Flutter model
- Implemented utility functions for signal extraction and display name building
- Updated flyer import service to persist and return signals/category data
- Enhanced Flutter UI to display detailed product information including badges for signals
- Added new test coverage for signals persistence and display name generation
- Added new import-common module for shared import utilities
- Created database migration for new fields
- Added Kilo plan for feature development
2026-05-24 19:32:13 +02:00

199 lines
8.9 KiB
Markdown
Raw Permalink 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.
# Plan: Harmonisera flyer-import och kvitto-import
## Mål
Implementera en gemensam importmodell och matchningspipeline så att flyer-import och kvitto-import beter sig så likt som möjligt, med fokus på:
- Automatisk strukturering av namn/brand/vikt samt bundle-detaljer
- Automatiskt kategoriupplösning (`categoryHint -> categoryId`)
- Matchning mot befintliga produkter via normaliserade namn + signaler
- Ingen automatisk skapning av produkter
- Förberedelse för framtida automation via strukturerade signaler (`signals` JSON)
## Icke-mål (denna implementation)
- Ingen auto-create av produkter i produktkatalog
- Ingen ändring av övergripande UI-flöde (manuell import/validering kvar)
- Ingen full omskrivning av receipt-import; vi extraherar och återanvänder delar stegvis
## Nuvarande gap (från kodbasen)
1. `FlyerItem.categoryId` sätts till `null` i parse-flödet trots `categoryHint`.
2. Flyer-matchning använder enklare strategi än receipt-import (färre regler/signalvikter).
3. Ingen strukturerad lagring av ursprung/etiketter (t.ex. Sverige, Eko) i flyer.
4. Bundleinformation finns men exponeras inte tydligt som detaljnamn i payload.
5. Receipt och flyer använder olika “kontrakt” för mellanrepresentation.
## Övergripande design
Inför en gemensam intern domänmodell för importerade rader (backend), och låt både flyer- och kvittoflöde mappa till den innan kategori/matchning.
### Gemensam intern modell (ny)
`ImportedItemCandidate` (internt, ej API-brytande initialt):
- `rawName`, `normalizedName`, `brand`
- `weight`, `bundleWeight`, `isBundle`, `bundleItems`
- `price`, `priceUnit`, `comparisonPrice`, `comparisonUnit`
- `categoryHint`, `categoryId`
- `matchedProductId`, `matchedProductName`, `matchedVia`, `matchConfidence`, `matchReasons`
- `signals` (JSON):
- `originCountries: string[]`
- `labels: string[]` (ekologisk, laktosfri, etc)
- `qualityFlags: string[]` (normaliserade flaggor, ex `eco`)
- `variant: string | null`
- `packaging: string | null`
- `displayNameDetailed` (beräknat fält, kan persistas eller beräknas vid response)
## Faser och implementation
## Fas 1: Datamodell och migration
1. Uppdatera `backend/prisma/schema.prisma`:
- Lägg till `signals Json?``FlyerItem`
- Lägg till `displayNameDetailed String?``FlyerItem`
2. Skapa Prisma-migration.
3. Säkerställ bakåtkompatibilitet:
- Nullabla fält
- Ingen ändring av befintliga constraints/index som bryter drift
4. (Valfritt i samma fas) indexera vanligt använda JSON-signaler senare först efter verifierad nytta.
### Acceptanskriterier fas 1
- Migration appliceras lokalt utan dataförlust.
- Befintliga endpoints fungerar med gamla rader (`signals = null`).
## Fas 2: Gemensamma normaliserings-/signalverktyg
1. Skapa gemensam utility-modul i backend, exempel:
- `backend/src/import-common/import-item.types.ts`
- `backend/src/import-common/import-signals.util.ts`
- `backend/src/import-common/import-display-name.util.ts`
2. Implementera signal-extraktion från textfält (`rawName`, `brand`, `offerText`):
- Ursprungsländer till `originCountries`
- Etiketter/märkningar till `labels`/`qualityFlags`
- Pack-format till `packaging`
3. Normalisera utan att förlora information:
- Ta bort signalord från primär matchsträng men spara i `signals`
- Ex: `Fläskytterfilé (Sverige)` -> matchsträng `flaskytterfile`, `signals.originCountries=["Sverige"]`
4. Implementera `displayNameDetailed`:
- Bundle: inkludera `bundleItems` i visningsnamn
- Ex: `Kaptenens Favoriter (Chumlax 3x100g + Alaska pollock 3x100g)`
### Acceptanskriterier fas 2
- Signals extraheras deterministiskt för kända mönster (Sverige/Tyskland/Eko/Ekologiskt).
- `displayNameDetailed` genereras för bundles.
## Fas 3: Kategoriupplösning i flyer (paritet med kvitto)
1. Extrahera/återanvänd kategori-regelmotorn från receipt-import till gemensam tjänst:
- Ex: `backend/src/import-common/category-resolver.service.ts`
2. Använd den i flyer-import efter normalisering:
- `categoryHint` + signaltext + regler -> `categoryId`
3. Prioritet:
- Produktmatchad kategori (om säkert matchad produkt har kategori) kan väga högst
- Annars regelbaserad kategori
- Annars behåll `categoryHint` utan `categoryId`
4. Specifika regler för kött/fläskytterfilé verifieras.
### Acceptanskriterier fas 3
- `Fläskytterfilé` får korrekt `categoryId` i flyer-session.
- `categoryId` sätts automatiskt för en betydande andel rader med tydlig signal.
## Fas 4: Matchningsparitet flyer <-> kvitto
1. Bryt ut matchning till gemensam matcher (eller harmonisera algoritm):
- alias exact
- canonical/normalized exact
- token/fuzzy
- bonus för brand/weight/signalträffar
2. Matchning ska använda signalrensad namnsträng + metadata:
- Länder och eco-etiketter ska inte sabotera namnmatch
3. Standardisera reason codes mellan flöden (så långt möjligt utan brytande API):
- `alias_exact`, `normalized_exact`, `token_overlap:*`, `no_match`
4. Behåll strikt policy: ingen auto-create produkt.
### Acceptanskriterier fas 4
- Färre `no_match` på samma flyer-input jämfört med baseline.
- Matchningsorsaker blir mer förklarbara och konsekventa.
## Fas 5: API/DTO och persistens
1. Uppdatera flyer DTO:
- `backend/src/flyer-import/dto/flyer-import.response.ts`
- Lägg till `signals` och `displayNameDetailed`.
2. Uppdatera persistens i `flyer-import.service.ts`:
- Spara `signals`, `displayNameDetailed`, `categoryId`.
3. Säkerställ att `getSession`, `getLatestSession`, `updateSessionItem` returnerar nya fält.
4. Behåll kompatibilitet mot klient:
- Nya fält adderas utan att ta bort befintliga.
### Acceptanskriterier fas 5
- Response innehåller tydlig bundle-info och signaler per rad.
- Inga regressions i existerande frontend-parsing.
## Fas 6: Frontend (flyer import-tab)
1. Uppdatera domänmodeller i Flutter:
- `flutter/lib/features/import/domain/flyer_import_item.dart`
- ev. session/result-objekt
2. Visa `displayNameDetailed` där tillgängligt, annars fallback `rawName`.
3. Visa `bundleItems` tydligt i list-/detaljrad.
4. Visa badge/metadata för signaler (`Sverige`, `Ekologisk`) utan att skriva över produktnamn.
5. Säkerställ att manuellt urval till inköpslista fortsätter fungera.
### Acceptanskriterier fas 6
- Bundle-rader är tydligare i UI.
- Ursprung/eko syns som metadata.
## Fas 7: Teststrategi
### Backend enhetstester
- `flyer-normalizer.service.spec.ts`
- extraktion av `signals` (origin/labels)
- bundle-detaljnamn
- Ny kategori-resolver-spec
- `Fläskytterfilé` -> köttkategori
- `flyer-import.service.spec.ts`
- `categoryId` sätts vid tydlig signal
- `signals` och `displayNameDetailed` persisteras/returneras
- Matchningstester
- namn med land/eko matchar korrekt produkt
### Integrationstester
- End-to-end parseAndMatch med representativ flyer-fixture.
- Verifiera att inga produkter auto-skaps.
- Verifiera att shopping-list insertion fungerar med/utan `matchedProductId`.
### Frontendtester
- Serialisering av nya fält i import-session.
- Rendering av `displayNameDetailed` + `bundleItems`.
## Fas 8: Mätning och rollout
1. Lägg till enkel före/efter-mätning i logg/trace:
- andel `no_match`
- andel med satt `categoryId`
2. Soft rollout via feature flag (om möjligt), annars stegvis release.
3. Utvärdera verkliga flyer-sessioner innan vidare automatisering.
## Konkreta filer att ändra (planerad)
- `backend/prisma/schema.prisma`
- `backend/src/flyer-import/flyer-import.service.ts`
- `backend/src/flyer-import/services/flyer-normalizer.service.ts`
- `backend/src/flyer-import/dto/flyer-import.response.ts`
- `backend/src/receipt-import/receipt-import.service.ts` (endast för extraktion/återanvändning av gemensamma delar)
- Nya gemensamma filer under `backend/src/import-common/*`
- `flutter/lib/features/import/domain/flyer_import_item.dart`
- `flutter/lib/features/import/data/flyer_import_session.dart`
- `flutter/lib/features/import/presentation/flyer_import_tab.dart`
- Relevanta spec/test-filer i backend + flutter
## Risker och mitigering
- Risk: API-kontraktsändringar bryter klient.
- Mitigering: endast additive fält, fallback på gamla fält.
- Risk: Felkategori vid aggressiva regler.
- Mitigering: regelprioritet + reason-codes + tester för edge cases.
- Risk: Övermatchning av produkter.
- Mitigering: tröskelvärden + konservativ confidence för fuzzy.
## Leveransordning (rekommenderad)
1. Fas 12 (schema + signals + utilities)
2. Fas 3 (kategoriupplösning flyer)
3. Fas 4 (matchningsparitet)
4. Fas 5 (DTO/persistens)
5. Fas 6 (frontend)
6. Fas 78 (tester + mätning/rollout)
## Definition of Done
- Flyer och kvitto använder samma centrala regler för kategorisering/matchning där möjligt.
- Flyer-rader innehåller `signals` och tydligare produktrepresentation (`displayNameDetailed`, bundle-innehåll).
- `categoryId` sätts automatiskt i flyer när tillräcklig signal finns (inkl. fläskytterfilé-fall).
- Ingen automatisk produktskapning sker.
- Tester uppdaterade och gröna.