Files
recipe-app/.kilo/plans/1779641992740-glowing-sailor.md
T
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

8.9 KiB
Raw Blame History

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.