b04d157915
- 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
8.9 KiB
8.9 KiB
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 (
signalsJSON)
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)
FlyerItem.categoryIdsätts tillnulli parse-flödet trotscategoryHint.- Flyer-matchning använder enklare strategi än receipt-import (färre regler/signalvikter).
- Ingen strukturerad lagring av ursprung/etiketter (t.ex. Sverige, Eko) i flyer.
- Bundleinformation finns men exponeras inte tydligt som detaljnamn i payload.
- 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,brandweight,bundleWeight,isBundle,bundleItemsprice,priceUnit,comparisonPrice,comparisonUnitcategoryHint,categoryIdmatchedProductId,matchedProductName,matchedVia,matchConfidence,matchReasonssignals(JSON):originCountries: string[]labels: string[](ekologisk, laktosfri, etc)qualityFlags: string[](normaliserade flaggor, execo)variant: string | nullpackaging: string | null
displayNameDetailed(beräknat fält, kan persistas eller beräknas vid response)
Faser och implementation
Fas 1: Datamodell och migration
- Uppdatera
backend/prisma/schema.prisma:- Lägg till
signals Json?påFlyerItem - Lägg till
displayNameDetailed String?påFlyerItem
- Lägg till
- Skapa Prisma-migration.
- Säkerställ bakåtkompatibilitet:
- Nullabla fält
- Ingen ändring av befintliga constraints/index som bryter drift
- (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
- Skapa gemensam utility-modul i backend, exempel:
backend/src/import-common/import-item.types.tsbackend/src/import-common/import-signals.util.tsbackend/src/import-common/import-display-name.util.ts
- Implementera signal-extraktion från textfält (
rawName,brand,offerText):- Ursprungsländer till
originCountries - Etiketter/märkningar till
labels/qualityFlags - Pack-format till
packaging
- Ursprungsländer till
- Normalisera utan att förlora information:
- Ta bort signalord från primär matchsträng men spara i
signals - Ex:
Fläskytterfilé (Sverige)-> matchsträngflaskytterfile,signals.originCountries=["Sverige"]
- Ta bort signalord från primär matchsträng men spara i
- Implementera
displayNameDetailed:- Bundle: inkludera
bundleItemsi visningsnamn - Ex:
Kaptenens Favoriter (Chumlax 3x100g + Alaska pollock 3x100g)
- Bundle: inkludera
Acceptanskriterier fas 2
- Signals extraheras deterministiskt för kända mönster (Sverige/Tyskland/Eko/Ekologiskt).
displayNameDetailedgenereras för bundles.
Fas 3: Kategoriupplösning i flyer (paritet med kvitto)
- Extrahera/återanvänd kategori-regelmotorn från receipt-import till gemensam tjänst:
- Ex:
backend/src/import-common/category-resolver.service.ts
- Ex:
- Använd den i flyer-import efter normalisering:
categoryHint+ signaltext + regler ->categoryId
- Prioritet:
- Produktmatchad kategori (om säkert matchad produkt har kategori) kan väga högst
- Annars regelbaserad kategori
- Annars behåll
categoryHintutancategoryId
- Specifika regler för kött/fläskytterfilé verifieras.
Acceptanskriterier fas 3
Fläskytterfiléfår korrektcategoryIdi flyer-session.categoryIdsätts automatiskt för en betydande andel rader med tydlig signal.
Fas 4: Matchningsparitet flyer <-> kvitto
- Bryt ut matchning till gemensam matcher (eller harmonisera algoritm):
- alias exact
- canonical/normalized exact
- token/fuzzy
- bonus för brand/weight/signalträffar
- Matchning ska använda signalrensad namnsträng + metadata:
- Länder och eco-etiketter ska inte sabotera namnmatch
- Standardisera reason codes mellan flöden (så långt möjligt utan brytande API):
alias_exact,normalized_exact,token_overlap:*,no_match
- Behåll strikt policy: ingen auto-create produkt.
Acceptanskriterier fas 4
- Färre
no_matchpå samma flyer-input jämfört med baseline. - Matchningsorsaker blir mer förklarbara och konsekventa.
Fas 5: API/DTO och persistens
- Uppdatera flyer DTO:
backend/src/flyer-import/dto/flyer-import.response.ts- Lägg till
signalsochdisplayNameDetailed.
- Uppdatera persistens i
flyer-import.service.ts:- Spara
signals,displayNameDetailed,categoryId.
- Spara
- Säkerställ att
getSession,getLatestSession,updateSessionItemreturnerar nya fält. - 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)
- Uppdatera domänmodeller i Flutter:
flutter/lib/features/import/domain/flyer_import_item.dart- ev. session/result-objekt
- Visa
displayNameDetaileddär tillgängligt, annars fallbackrawName. - Visa
bundleItemstydligt i list-/detaljrad. - Visa badge/metadata för signaler (
Sverige,Ekologisk) utan att skriva över produktnamn. - 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
- extraktion av
- Ny kategori-resolver-spec
Fläskytterfilé-> köttkategori
flyer-import.service.spec.tscategoryIdsätts vid tydlig signalsignalsochdisplayNameDetailedpersisteras/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
- Lägg till enkel före/efter-mätning i logg/trace:
- andel
no_match - andel med satt
categoryId
- andel
- Soft rollout via feature flag (om möjligt), annars stegvis release.
- Utvärdera verkliga flyer-sessioner innan vidare automatisering.
Konkreta filer att ändra (planerad)
backend/prisma/schema.prismabackend/src/flyer-import/flyer-import.service.tsbackend/src/flyer-import/services/flyer-normalizer.service.tsbackend/src/flyer-import/dto/flyer-import.response.tsbackend/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.dartflutter/lib/features/import/data/flyer_import_session.dartflutter/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)
- Fas 1–2 (schema + signals + utilities)
- Fas 3 (kategoriupplösning flyer)
- Fas 4 (matchningsparitet)
- Fas 5 (DTO/persistens)
- Fas 6 (frontend)
- 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
signalsoch tydligare produktrepresentation (displayNameDetailed, bundle-innehåll). categoryIdsätts automatiskt i flyer när tillräcklig signal finns (inkl. fläskytterfilé-fall).- Ingen automatisk produktskapning sker.
- Tester uppdaterade och gröna.