feat(ai): enhance AI trace warnings and reason codes system
- Added structured warning system with `AdminAiWarning` type in backend and Flutter - Implemented detailed reason descriptors with `FlyerReasonDescriptor` for parse and match operations - Added `legacyWarnings` field to maintain backward compatibility - Enhanced AI trace service to collect and format warnings with item-level context - Updated flyer import services to include detailed reason descriptions in responses - Added Swedish diacritic preservation for cheese variants (Prästost, Herrgårdsost, Grevéost) - Implemented UTF-8 content validation for AI responses - Added new reason code definitions in `reason-codes.ts` - Updated Flutter UI to display structured warnings with severity indicators - Added error report generation and copy functionality in admin panel - Added comprehensive test coverage for new warning system and cheese normalization BREAKING CHANGE: AI trace warnings are now structured objects instead of simple strings
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
# Plan: Flyerimport - specialtecken, beskrivande felmeddelanden, synlig prompt
|
||||
|
||||
## Mål
|
||||
Tre sammanhängande förbättringar av flyerimport-pipelinen så att användaren får begripligt feedback och korrekt data:
|
||||
|
||||
1. Säkerställ att svenska tecken (ä, å, ö, é) bevaras i produktnamn som "Prästost", "Herrgårdsost", "Grevéost".
|
||||
2. Ersätt opaka koder som `parse:ai_parsed` och `match:no_match` med människovänliga svenska förklaringar som beskriver "vad" som hände och "var" det hände.
|
||||
3. Visa promptens innehåll (inte bara output) för operatörer/admins och vid behov i importflödet vid varningar.
|
||||
|
||||
Allt arbete ska respektera nuvarande arkitektur: NestJS backend (`backend/src/flyer-import`, `backend/src/ai`), Flutter web frontend (`flutter/lib/features/import`, `flutter/lib/features/admin`).
|
||||
|
||||
---
|
||||
|
||||
## Verifierad nulägesbild (källkodsbevis)
|
||||
|
||||
### Specialtecken
|
||||
- `ai-flyer-parser.service.ts:189-293` skickar prompt utan diakritiska tecken (skriver "Prastost", "Herrgardsost", "Greveost", "ARLA KO", "ä" undviks medvetet i prompt-instruktionerna). Detta gör att modellen tränas/uppmuntras att returnera namnen utan ä/å/é.
|
||||
- `ai-flyer-parser.service.ts:358-364` (`normalizeName`) tar bort allt som inte är `[a-zåäö0-9\s]` men `rawName` skickas vidare som det är, så ä/å bevaras tekniskt om AI returnerar dem.
|
||||
- `flyer-normalizer.service.ts:26-30` har en hårdkodad mappning för cheese variants:
|
||||
- `prast: 'Prästost'`
|
||||
- `herrgard: 'Herrgårdsost'`
|
||||
- `greve: 'Greveost'` (saknar `é`! Bör vara `Grevéost`)
|
||||
- `flyer-normalizer.service.ts:196-227` (`expandCheeseVariants`) gör `stripDiacritics` på rawName innan den jämför med token-list. Detta funkar för matchning men producerar diakrit-fria varianter om mappningen inte är korrekt.
|
||||
- `flyer-normalizer.service.ts:140-146` (`normalizeName`) fungerar med `[a-zåäö0-9\s]` så svenska tecken behålls i normaliserat namn när inputen har dem.
|
||||
|
||||
### "parse:ai_parsed" och "match:no_match"
|
||||
- Konstanter genereras som "reason codes" i koden:
|
||||
- `ai-flyer-parser.service.ts:354`: `reasonCodes: ['ai_parsed']` - sätts på alla AI-parsade items.
|
||||
- `flyer-import.service.ts:496`: `reasons: ['no_match']` - sätts när inget produktnamn matchar.
|
||||
- Reason codes prefixas med `parse:` eller `match:` av `ai-trace.service.ts:435-452` (`collectWarnings`):
|
||||
```ts
|
||||
warnings.add(`parse:${text}`);
|
||||
warnings.add(`match:${text}`);
|
||||
```
|
||||
- Dessa visas i admin AI-traces-vyn (`admin_ai_panel.dart:355-362`, `_WarningsCard`) och möjligen i import-UI:t via `_buildWarningsPanel` (`flyer_import_tab.dart:497-539`) om `_result.warnings` innehåller dem.
|
||||
- Slutsats: koderna är inte fel - de är obegripliga utan översättning.
|
||||
|
||||
### Promptens synlighet
|
||||
- Admin AI-panelen (`admin_ai_panel.dart:444-508`, `_PromptCard`) visar redan prompt med expand/copy-knappar.
|
||||
- Importflödet (`flyer_import_tab.dart`, `receipt_import_tab.dart`) visar idag bara `warnings`-listan. Ingen promptvisning, ingen mappning från reason codes till begripligt språk, ingen länk till AI-trace.
|
||||
- AI-trace lagras alltid i DB (`flyer-import.service.ts:148-164`, `persistFlyerTrace`). Promptens innehåll finns alltså redan tillgängligt.
|
||||
|
||||
---
|
||||
|
||||
## Strategi och faser
|
||||
|
||||
Tre paralleliserbara delprojekt med varsin egen fas. Slutlig sammanflätning verifieras med befintliga tester och ny smoke-test.
|
||||
|
||||
---
|
||||
|
||||
## Fas A: Specialtecken i produktnamn (lågriskfix, hög nytta)
|
||||
|
||||
### A1. Korrigera hårdkodad cheese-mappning
|
||||
- `backend/src/flyer-import/services/flyer-normalizer.service.ts:26-30`
|
||||
- Ändra `greve: 'Greveost'` till `greve: 'Grevéost'`.
|
||||
- Behåll `prast: 'Prästost'` och `herrgard: 'Herrgårdsost'`.
|
||||
|
||||
### A2. Skydda diakritiska tecken hela vägen från AI-svar till klient
|
||||
- `backend/src/flyer-import/services/ai-flyer-parser.service.ts`
|
||||
- Säkerställ att Mistralklientens response.choices[0].message.content tolkas som UTF-8. Logga hex-dump i debug-läge om tecken förvanskas.
|
||||
- I `sanitizeJsonResponse`/`normalizeAiItem`: säkerställ att `rawName` inte normaliseras eller stripps innan `normalize`-steget.
|
||||
- Uppdatera prompten:
|
||||
- Lägg till explicit instruktion: `Behåll svenska diakritiska tecken (ä, å, ö, é) i produktnamn. Returnera "Prästost", "Herrgårdsost", "Grevéost" - inte ASCII-versioner.`
|
||||
- Uppdatera exempel-utdata i prompten (rad 256-286) från "Prastost"/"Herrgardsost" till "Prästost"/"Herrgårdsost"/"Grevéost" så modellen tränas att returnera korrekt.
|
||||
- Behåll fortfarande `prast/herrgard/greve` som tokens i instruktion 9 men kräv normalisering till diakrit-versionen i utdatan.
|
||||
- `backend/src/flyer-import/services/flyer-normalizer.service.ts`
|
||||
- I `expandCheeseVariants` (rad 196-227): efter `stripDiacritics`-tokenisering, mappa via `CHEESE_VARIANT_TO_NAME` så slutnamnet alltid har korrekta tecken.
|
||||
- I `fixKnownOcrTypos` (rad 250-262): lägg till regel som korrigerar "Greveost" -> "Grevéost" (men bara när det är klart att det är ostnamnet, inte personnamn). Använd kontext: bara om `category` antyder hårdost/ost eller `rawName` slutar på `ost`.
|
||||
|
||||
### A3. Säkra Caddy/HTTP-respons för UTF-8
|
||||
- Verifiera att `Content-Type: application/json; charset=utf-8` returneras från NestJS (default i `@nestjs/platform-express`, men kontrollera att ingen middleware skriver om).
|
||||
- Caddy gör inte byteomvandling som standard, men dubbelkolla att `flutter/Caddyfile` inte har någon `replace`-direktiv (det har det inte i nuläget).
|
||||
|
||||
### A4. Test
|
||||
- Utöka `flyer-normalizer.service.spec.ts` med:
|
||||
- Test som matar in rawName "PRAST" och kontrollerar att outputens rawName blir "Prästost" (inte "Prastost").
|
||||
- Test som matar in rawName "GREVE" och kontrollerar att outputens rawName blir "Grevéost".
|
||||
- Test som verifierar att `é`-tecken bevaras i hela pipen.
|
||||
- Snapshot-test för prompten i `ai-flyer-parser.service.spec.ts` så att ändringar i prompten är medvetna.
|
||||
|
||||
### Acceptanskriterier Fas A
|
||||
- En flyer som innehåller "PRAST, HERRGARD, GREVE" producerar rader med rawName `Prästost`, `Herrgårdsost`, `Grevéost`.
|
||||
- Ingen befintlig test går sönder.
|
||||
- UI:t (Flutter) visar de korrekta tecknen utan encoding-artifacts.
|
||||
|
||||
### Risker
|
||||
- Mistral kan ignorera diakritiska tecken i instruktioner. Motåtgärd: post-normalisering i `flyer-normalizer.service.ts` är hårda fallback-regeln.
|
||||
|
||||
---
|
||||
|
||||
## Fas B: Beskrivande felmeddelanden ersätter `parse:ai_parsed` och `match:no_match`
|
||||
|
||||
### B1. Centraliserad reason-code-katalog (backend)
|
||||
- Skapa `backend/src/flyer-import/services/reason-codes.ts` (eller `backend/src/ai/reason-codes.ts` om det är delat med receipt) som exporterar:
|
||||
- `ParseReasonCode = 'ai_parsed' | 'split_cheese_variants' | 'normalized' | 'low_confidence' | ...`
|
||||
- `MatchReasonCode = 'no_match' | 'alias_exact' | 'normalized_exact' | 'token_overlap' | 'alias_points_to_missing_product' | 'empty_name'`
|
||||
- Funktion `describeParseReason(code, context?): { title, message, severity, location? }`
|
||||
- Funktion `describeMatchReason(code, context?): { title, message, severity }`
|
||||
- Exempelmappning:
|
||||
- `ai_parsed` -> `severity: 'info', title: 'AI-tolkad rad', message: 'Raden tolkades av AI utan att en deterministisk regel matchade.'`
|
||||
- `split_cheese_variants` -> `severity: 'info', title: 'Variant-split', message: 'AI-svarade en gruppannons som expanderades till individuella ostvarianter.'`
|
||||
- `low_confidence` -> `severity: 'warning', title: 'Låg parsningskvalitet', message: 'Modellens säkerhet är låg, granska raden manuellt.'`
|
||||
- `no_match` -> `severity: 'warning', title: 'Ingen produktmatchning', message: 'Vi kunde inte hitta någon befintlig produkt som matchar texten på flyern.'`
|
||||
- `alias_points_to_missing_product` -> `severity: 'error', title: 'Trasig alias-koppling', message: 'Ett alias pekar på en produkt som inte längre finns.'`
|
||||
- `empty_name` -> `severity: 'error', title: 'Tomt produktnamn', message: 'Raden saknar tolkbart produktnamn.'`
|
||||
- Inkludera `location` när relevant: t ex `'Steg: AI-parser, chunk N/M'` eller `'Steg: matchning mot dina produkter'`.
|
||||
|
||||
### B2. Returnera strukturerade reasons i API
|
||||
- Utöka `backend/src/flyer-import/dto/flyer-import.response.ts`:
|
||||
```ts
|
||||
export type FlyerReasonDescriptor = {
|
||||
code: string; // 'ai_parsed', 'no_match', ...
|
||||
kind: 'parse' | 'match';
|
||||
title: string; // Människovänlig titel
|
||||
message: string; // Förklarande text
|
||||
severity: 'info' | 'warning' | 'error';
|
||||
location: string | null; // T ex 'Steg: matchning mot dina produkter'
|
||||
};
|
||||
```
|
||||
- Lägg till på `FlyerImportItem`:
|
||||
- `parseReasonsDetailed: FlyerReasonDescriptor[]`
|
||||
- `matchReasonsDetailed: FlyerReasonDescriptor[]`
|
||||
- Behåll befintliga `parseReasons: string[]` och `matchReasons: string[]` för bakåtkompatibilitet.
|
||||
- I `flyer-import.service.ts:110-144` (där `FlyerImportItem` byggs): mappa via `describeParseReason`/`describeMatchReason`.
|
||||
- Detsamma för session-läs-paths (`toFlyerImportItem` rad 721-799 och `toFlyerImportResponseFromSession`).
|
||||
|
||||
### B3. Uppdatera AI-trace warnings (admin)
|
||||
- `backend/src/ai/ai-trace.service.ts:435-452` (`collectWarnings`):
|
||||
- Ändra till att returnera strukturerade objekt istället för `parse:xxx`/`match:xxx`-strängar:
|
||||
```ts
|
||||
type AdminAiWarning = {
|
||||
code: string;
|
||||
kind: 'parse' | 'match';
|
||||
title: string;
|
||||
message: string;
|
||||
severity: 'info' | 'warning' | 'error';
|
||||
itemIndex?: number; // Pekar på vilken rad i sessionen
|
||||
};
|
||||
```
|
||||
- Uppdatera `AdminAiTraceDetail.warnings` schema till strukturerat format.
|
||||
- Bibehåll en `legacyWarnings: string[]` med gamla formatet ifall någon klient ännu inte uppdaterats.
|
||||
|
||||
### B4. Uppdatera Flutter-modeller och vyer
|
||||
- `flutter/lib/features/import/domain/flyer_import_item.dart`:
|
||||
- Lägg till `parseReasonsDetailed: List<FlyerReasonDescriptor>` och `matchReasonsDetailed: List<FlyerReasonDescriptor>` med `fromJson`/`toJson`.
|
||||
- Skapa `flutter/lib/features/import/domain/flyer_reason_descriptor.dart`:
|
||||
- `class FlyerReasonDescriptor { final String code; final String kind; final String title; final String message; final String severity; final String? location; ... }`
|
||||
- `flutter/lib/features/import/presentation/flyer_import_tab.dart`:
|
||||
- I `_buildWarningsPanel` (rad 497-539): visa enbart sessionens egentliga warnings (existerande) men gör dem klickbara så de kan kopieras.
|
||||
- Per rad: visa en summerande badge "X varningar" som expanderar till en lista med titel + message istället för tekniska koder. Använd ikoner per severity (info/warning/error).
|
||||
- Behåll befintlig kvalitetsbadge (Hög/Medel/Låg).
|
||||
- `flutter/lib/features/admin/presentation/admin_ai_panel.dart`:
|
||||
- `_WarningsCard` (rad 583+): byt ut SelectableText med rå-strängar mot strukturerad rendering: titel (fet), message (vanlig), severity-färg, eventuellt itemIndex som länk.
|
||||
- Behåll copy-funktion - kopierar då en formaterad sträng `"[severity] title: message"`.
|
||||
|
||||
### B5. Test
|
||||
- Backend: enhetstest för `describeParseReason`/`describeMatchReason` som täcker alla codes.
|
||||
- Backend: integrationstest som verifierar att `FlyerImportResponse` innehåller `parseReasonsDetailed` med rätt fält.
|
||||
- Flutter: widget-test för warnings-panel som verifierar att `parse:ai_parsed` ALDRIG visas, utan ersätts av "AI-tolkad rad".
|
||||
- Uppdatera `flutter/test/features/admin/presentation/admin_ai_panel_test.dart` som idag verifierar `find.text('parse:low_confidence')` - testet ska istället leta efter "Låg parsningskvalitet".
|
||||
|
||||
### Acceptanskriterier Fas B
|
||||
- Inga `parse:xxx`- eller `match:xxx`-strängar visas i UI:t (varken admin eller import).
|
||||
- Varje warning har: titel, beskrivande text, severity-ikon, och om relevant en location ("Steg: ...").
|
||||
- API:et returnerar både legacy- och nytt format, så ingen klient bryts.
|
||||
|
||||
### Risker
|
||||
- Översättning av code -> human text måste hållas i sync mellan backend och Flutter. Motåtgärd: sätt textmappningen ENBART i backend och returnera färdig string. Flutter renderar bara.
|
||||
|
||||
---
|
||||
|
||||
## Fas C: Synlig prompt i import-flödet och vid varningar
|
||||
|
||||
### C1. Bestäm synlighetspolicy
|
||||
- Operatörer/admins ska kunna se prompten direkt i admin AI-panelen (finns redan, fortsätter fungera).
|
||||
- Vanliga användare ska kunna se prompten **efter behov** för att felsöka eller rapportera fel - men inte by default eftersom prompten är teknisk.
|
||||
- Föreslagen UX: när en rad har varningar (parse eller match), visa knapp "Visa AI-detaljer" som öppnar modal med:
|
||||
- Använd modell + retry/chunk-info
|
||||
- Prompten (expanderbar)
|
||||
- AI-svar (raw output, expanderbar)
|
||||
- Lista över alla parse/match-reasons med deras `describeXxxReason`-output
|
||||
- Promptvisning i adminpanelen redan komplett (`_PromptCard`) - bara verifiera att det fortsätter funka efter Fas B.
|
||||
|
||||
### C2. Backend-ändringar
|
||||
- Ny endpoint `GET /api/flyer-import/sessions/:sessionId/ai-trace` som returnerar:
|
||||
```ts
|
||||
{
|
||||
sessionId: number;
|
||||
model: string;
|
||||
prompt: string;
|
||||
rawOutput: string;
|
||||
chunkCount: number | null;
|
||||
retryCount: number | null;
|
||||
durationMs: number | null;
|
||||
status: 'success' | 'warning' | 'error';
|
||||
warnings: AdminAiWarning[];
|
||||
}
|
||||
```
|
||||
- Återanvänd existerande `aiTrace`-tabellen. Auth: kräv att `userId` ägar sessionen (samma policy som `getSessionSource`).
|
||||
- Eventuellt feature-flagga visa-prompt-för-användare (`FLYER_AI_USER_PROMPT_VISIBLE` env). Default: `true` för admin, `true` för användare som äger sessionen.
|
||||
|
||||
### C3. Flutter-ändringar
|
||||
- `flutter/lib/features/import/data/import_repository.dart`: ny metod `getFlyerSessionAiTrace(sessionId, token)`.
|
||||
- `flutter/lib/features/import/domain/`: ny modell `FlyerAiTrace`.
|
||||
- `flutter/lib/features/import/presentation/flyer_import_tab.dart`:
|
||||
- Lägg till en "AI-detaljer"-knapp i headern (efter Importera-knappen) eller i varje rads expanderingspanel.
|
||||
- Visa prompt + rawOutput i en modal/expanderbar Card med samma look-and-feel som admin (`_PromptCard` + `_OutputJsonCard` kan extraheras till delad widget under `flutter/lib/features/import/presentation/widgets/ai_trace_view.dart`).
|
||||
- Lägg till samma åtgärd för `receipt_import_tab.dart` om motsvarande backend-stöd finns (det finns - receipts har också AI-trace).
|
||||
|
||||
### C4. Säkerhet och PII
|
||||
- Innan prompt visas till slutanvändare: kör `maskSensitiveText` (finns redan i `ai-trace.service.ts`).
|
||||
- Alla loggade prompts/rawOutputs ska redan vara maskerade i nuvarande pipeline. Verifiera att det fortsätter gälla.
|
||||
|
||||
### C5. Test
|
||||
- Backend: e2e-test för nya endpointen, inklusive 403 för icke-ägare och 200 för ägare.
|
||||
- Flutter: widget-test för att modalen öppnas och visar prompten.
|
||||
- Manuell QA: Importera en flyer, klicka "AI-detaljer", verifiera att prompten visas och att kopiera-knappen fungerar.
|
||||
|
||||
### Acceptanskriterier Fas C
|
||||
- Användare kan se prompten som skickades vid sin egen flyerimport.
|
||||
- Adminpanelen visar fortfarande prompten oförändrat.
|
||||
- PII-mask appliceras innan prompten skickas till klienten.
|
||||
|
||||
### Risker
|
||||
- Prompten är lång (>5000 tecken). Motåtgärd: använd existerande expand/collapse-mönster (`_PromptCard.expanded`).
|
||||
- Prompten kan innehålla användardata. Motåtgärd: maskning + tydlig "Detta innehåller text från din flyer"-disclaimer.
|
||||
|
||||
---
|
||||
|
||||
## Prioriterad genomförandeordning
|
||||
1. **Fas A** (specialtecken) - lågrisk, snabb seger för UX.
|
||||
2. **Fas B** (mänskliga felmeddelanden) - medelarbete, hög UX-impact.
|
||||
3. **Fas C** (prompt-synlighet) - mer komplex pga ny endpoint + UI, men oberoende av A och B.
|
||||
|
||||
Faserna kan implementeras i parallella PR:er om så önskas; Fas A och B berör delvis samma kodvägar i `flyer-normalizer.service.ts` och `flyer-import.service.ts`, och bör samordnas så att en PR mergas före nästa.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
- "Prästost", "Herrgårdsost", "Grevéost" och `é`/`å`/`ä` syns korrekt i flyerimport-UI.
|
||||
- Inga råa `parse:ai_parsed`/`match:no_match`-strängar visas för användare eller admin.
|
||||
- Användare och admin kan se vilken prompt som skickades till AI.
|
||||
- Befintliga tester passerar; nya tester täcker varje fas separat.
|
||||
- Inga regressioner i Docker-bygget (`backend/Dockerfile` kör `npm test -- --runInBand`).
|
||||
|
||||
---
|
||||
|
||||
## Konkreta filer som berörs
|
||||
|
||||
Backend:
|
||||
- `backend/src/flyer-import/services/flyer-normalizer.service.ts` (Fas A)
|
||||
- `backend/src/flyer-import/services/ai-flyer-parser.service.ts` (Fas A, eventuellt B)
|
||||
- `backend/src/flyer-import/services/reason-codes.ts` (ny, Fas B)
|
||||
- `backend/src/flyer-import/dto/flyer-import.response.ts` (Fas B)
|
||||
- `backend/src/flyer-import/flyer-import.service.ts` (Fas B, Fas C)
|
||||
- `backend/src/flyer-import/flyer-import.controller.ts` (Fas C, ny endpoint)
|
||||
- `backend/src/ai/ai-trace.service.ts` (Fas B)
|
||||
- `backend/src/flyer-import/services/flyer-normalizer.service.spec.ts` (Fas A)
|
||||
- `backend/src/flyer-import/services/ai-flyer-parser.service.spec.ts` (Fas A, B)
|
||||
|
||||
Flutter:
|
||||
- `flutter/lib/features/import/domain/flyer_import_item.dart` (Fas B)
|
||||
- `flutter/lib/features/import/domain/flyer_reason_descriptor.dart` (ny, Fas B)
|
||||
- `flutter/lib/features/import/domain/flyer_ai_trace.dart` (ny, Fas C)
|
||||
- `flutter/lib/features/import/data/import_repository.dart` (Fas C)
|
||||
- `flutter/lib/features/import/presentation/flyer_import_tab.dart` (Fas A-C)
|
||||
- `flutter/lib/features/import/presentation/widgets/ai_trace_view.dart` (ny, Fas C)
|
||||
- `flutter/lib/features/admin/domain/admin_ai_trace_detail.dart` (Fas B)
|
||||
- `flutter/lib/features/admin/presentation/admin_ai_panel.dart` (Fas B)
|
||||
- `flutter/test/features/admin/presentation/admin_ai_panel_test.dart` (Fas B)
|
||||
|
||||
---
|
||||
|
||||
## Riskanalys och rollback
|
||||
|
||||
| Risk | Sannolikhet | Motåtgärd |
|
||||
| --- | --- | --- |
|
||||
| Mistral returnerar fortfarande ASCII-versioner | Medel | Hård post-normalisering i `flyer-normalizer.service.ts` (Fas A2) |
|
||||
| Strukturerade reasons bryter befintlig admin-vy | Låg | Behåll legacy-format parallellt under en release |
|
||||
| Promptens längd försämrar mobil-UX | Låg | Default collapsed med expand-knapp |
|
||||
| AI-trace exponerar PII oavsiktligt | Medel | Återanvänd befintlig `maskSensitiveText` + tydlig disclaimer |
|
||||
| Ändrade reason codes bryter andra konsumenter (t ex flyer-selection-matcher) | Låg | Sökning visar att `reasonCodes` används endast i flyer-import + ai-trace; uppdatera samtliga callsites i samma PR |
|
||||
|
||||
---
|
||||
|
||||
## Beslut tagna med dig
|
||||
|
||||
1. **Promptens synlighet**: endast admin. Vanliga användare ser inte prompten - bara översatta reasons. Detta förenklar Fas C avsevärt: ingen ny användarendpoint, ingen PII-mask för slutanvändare, ingen ny användar-UI.
|
||||
2. **Reason-codes översätts i backend**: backend returnerar färdig svensk text (`title`, `message`) i `FlyerReasonDescriptor`. Frontend renderar bara strängarna utan översättning. Lang-parameter förbereds för framtida flerspråksstöd.
|
||||
3. **Kopiera felrapport-knapp**: ja. Lägg till "Kopiera felrapport"-knapp i admin AI-panelens detail-vy som producerar formaterad text:
|
||||
```
|
||||
[AI-trace flyer-123]
|
||||
Modell: ministral-8b-2512
|
||||
Status: warning (3 varningar)
|
||||
Tid: 2026-05-23T20:12:00
|
||||
|
||||
Varningar:
|
||||
- [warning] Ingen produktmatchning (rad 5): Vi kunde inte hitta...
|
||||
- [info] AI-tolkad rad (rad 7): Raden tolkades av AI...
|
||||
|
||||
Prompt:
|
||||
...
|
||||
|
||||
Raw output:
|
||||
...
|
||||
```
|
||||
4. **Stavning**: `Grevéost` med `é` (Arlas officiella stavning).
|
||||
|
||||
## Konsekvenser av besluten på faserna
|
||||
|
||||
### Fas A (oförändrad)
|
||||
- `Grevéost` används i mappningen.
|
||||
|
||||
### Fas B (oförändrad)
|
||||
- Backend översätter och returnerar färdig svensk text i `title`/`message`.
|
||||
- Behåll `code`-fältet så frontend kan filtrera/rendera olika per typ.
|
||||
|
||||
### Fas C (förenklad)
|
||||
- **Tas bort**: Ny användarendpoint `GET /api/flyer-import/sessions/:id/ai-trace`.
|
||||
- **Tas bort**: Ny Flutter-modell `FlyerAiTrace` och repository-metod för användarvy.
|
||||
- **Tas bort**: Ny widget `ai_trace_view.dart` för importflödet.
|
||||
- **Behålls**: Adminpanelens promptvisning (`_PromptCard`) - finns redan, fungerar.
|
||||
- **Läggs till**: "Kopiera felrapport"-knapp i adminpanelens detail-vy. Genererar formaterad text enligt mallen ovan.
|
||||
- **Användarflöde uppdateras**: i flyer/receipt-import-tabben visa endast översatta reasons via Fas B; ingen prompt-knapp för användare.
|
||||
|
||||
## Uppdaterade konkreta filer (efter beslut)
|
||||
|
||||
Backend:
|
||||
- `backend/src/flyer-import/services/flyer-normalizer.service.ts` (Fas A)
|
||||
- `backend/src/flyer-import/services/ai-flyer-parser.service.ts` (Fas A)
|
||||
- `backend/src/flyer-import/services/reason-codes.ts` (ny, Fas B)
|
||||
- `backend/src/flyer-import/dto/flyer-import.response.ts` (Fas B)
|
||||
- `backend/src/flyer-import/flyer-import.service.ts` (Fas B)
|
||||
- `backend/src/ai/ai-trace.service.ts` (Fas B)
|
||||
- `backend/src/flyer-import/services/flyer-normalizer.service.spec.ts` (Fas A)
|
||||
- `backend/src/flyer-import/services/ai-flyer-parser.service.spec.ts` (Fas A)
|
||||
- *Inte längre*: ny endpoint i `flyer-import.controller.ts` (utgår med beslut 1).
|
||||
|
||||
Flutter:
|
||||
- `flutter/lib/features/import/domain/flyer_import_item.dart` (Fas B)
|
||||
- `flutter/lib/features/import/domain/flyer_reason_descriptor.dart` (ny, Fas B)
|
||||
- `flutter/lib/features/import/presentation/flyer_import_tab.dart` (Fas A-B)
|
||||
- `flutter/lib/features/admin/domain/admin_ai_trace_detail.dart` (Fas B)
|
||||
- `flutter/lib/features/admin/presentation/admin_ai_panel.dart` (Fas B + felrapportknapp)
|
||||
- `flutter/test/features/admin/presentation/admin_ai_panel_test.dart` (Fas B)
|
||||
- *Inte längre*: `flyer_ai_trace.dart`, `ai_trace_view.dart`, repository-metod (utgår med beslut 1).
|
||||
Reference in New Issue
Block a user