# 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?` på `FlyerItem` - Lägg till `displayNameDetailed String?` på `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 1–2 (schema + signals + utilities) 2. Fas 3 (kategoriupplösning flyer) 3. Fas 4 (matchningsparitet) 4. Fas 5 (DTO/persistens) 5. Fas 6 (frontend) 6. Fas 7–8 (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.