d73ea5ef7c
Test Suite / test (24.15.0) (push) Has been cancelled
Co-authored-by: Copilot <copilot@github.com>
219 lines
11 KiB
Markdown
219 lines
11 KiB
Markdown
# Migrering: Import-funktion → microservice-importer
|
||
|
||
## Status: ✅ GENOMFÖRD 2026-04-30
|
||
|
||
## Dokumentstatus (2026-05-03)
|
||
|
||
### Målgrupp
|
||
Detta dokument är för systemadministratörer och utvecklare som ansvarar för integrationen mellan recipe-app och microservice-importer.
|
||
|
||
### Tillägg efter genomförd migrering
|
||
- Kvittoparsningens regelbaserade tolkning har förbättrats för multipack och enheter.
|
||
- Brödrelaterade kategoriregler och contradiction guards har utökats för högre träffsäkerhet.
|
||
- Klientens kvittosession i Flutter är nu persistent utan att ändra backendkontrakt eller införa serverlagring av sessionen.
|
||
- Kategoriträdet i seed-data har utökats med `Korvbröd` under `Fastfoodbröd`.
|
||
- **PDF-fix (2026-05-03):** `pdf-parse` använder `require()` istället för ESM-import; `pdfjs-dist/legacy/build/pdf.js` är fallback vid parsningsfel — löser `DOMMatrix is not defined` i Node.js Alpine-miljö.
|
||
- **Felkods-forwarding (2026-05-03):** `receipt-import.service.ts` returnerar nu `ServiceUnavailableException` (503) vid 503/429 från importer-api istället för `BadRequestException` (400).
|
||
- **Reproducerbart bygge (2026-05-03):** `package-lock.json` spåras i git; Dockerfile för importer-api kör `npm ci`.
|
||
- **AI-optimering (2026-05-03):** `looksLikeReceiptProductLine()` filtrerar PDF-rader utan siffra innan Mistral-anrop — minskar onödiga API-anrop vid kvittoimport.
|
||
|
||
- **Scope:** quick-import, parse-markdown, receipt-import
|
||
- **Arkitektur:** Backend-till-backend — recipe-app NestJS-backend anropar microservice-importer internt via HTTP. Frontend ändras inte.
|
||
- **OCR:** Implementerat i microservice-importer (tesseract.js + Alpine apk-paket)
|
||
- **Infra:** `importer-api`-tjänst i `recipe-app/compose.yml`, port 3001 intern
|
||
- **Driftsatt:** 2026-04-30, alla containers Healthy
|
||
|
||
---
|
||
|
||
## Fas 1 — Utöka microservice-importer ✅
|
||
|
||
**1. OCR-stöd och multipart i quick-import** ✅
|
||
- `QuickImportService.importFromUpload()` tillagd — hanterar PDF (pdf-parse) och bild (tesseract.js)
|
||
- `quick-import.controller.ts` utökat med `FileInterceptor`, `@HttpCode(200)`
|
||
- `Dockerfile` uppdaterad: `apk add tesseract-ocr tesseract-ocr-data-swe tesseract-ocr-data-eng`
|
||
|
||
**2. `imageUrl` i quick-import-svaret** ✅
|
||
- `imageUrl?: string` tillagd i `ParsedRecipe`-interfacet (`base.parser.ts`)
|
||
- ICA-parsern extraherar nu `recipe.image` (string/array/objekt-varianter)
|
||
- `QuickImportResult` utökad med `imageUrl?`, `imageWarning?`, source `'image'`
|
||
- `normalizeImageUrl()` hanterar protokollrelativa URL:er (`//cdn.ica.se/...`)
|
||
|
||
**3. Ny `ReceiptParsingModule`** ✅
|
||
- `backend/src/receipt-parsing/receipt-parsing.service.ts` — Mistral AI-parsning av kvitto (bild/PDF)
|
||
- `backend/src/receipt-parsing/receipt-parsing.controller.ts` — `POST /api/receipt-import/parse`, `@HttpCode(200)`, tillåter `application/octet-stream`
|
||
- `backend/src/receipt-parsing/receipt-parsing.module.ts`
|
||
- Registrerad i `app.module.ts`
|
||
|
||
**4. Health-endpoint** ✅
|
||
- `GET /api/health` → `{status: "ok"}` inline i `app.module.ts`
|
||
|
||
**5. Bugfixar i document-service** ✅
|
||
- `document-service.module.ts`: korrigerade importvägar + klassnamn (`DocumentImportModule` → `DocumentServiceModule`)
|
||
- `services/document-import.service.ts`: parsersökväg `./parsers/` → `../parsers/`
|
||
- Borttagna dubbletter: `services/web-scraping.module.ts`, `services/document-service.module.ts`
|
||
|
||
---
|
||
|
||
## Fas 2 — Anpassa recipe-app backend ✅
|
||
|
||
**5. Refaktorera `QuickImportService`** ✅
|
||
- All lokal parsning (ICA, pdf-parse, tesseract) borttagen
|
||
- Delegerar URL-import: `POST importer-api:3001/api/quick-import` (JSON)
|
||
- Delegerar filuploading: `POST importer-api:3001/api/quick-import` (FormData, `new Uint8Array(file.buffer)`)
|
||
- `downloadAndOptimizeImage()` behålls lokalt (körs efter microservice returnerat `imageUrl`)
|
||
- `IMPORTER_SERVICE_URL` env-variabel med fallback `http://importer-api:3001`
|
||
|
||
**6. Refaktorera `ReceiptImportService`** ✅
|
||
- AI-parsning (Mistral, pdf-parse) borttagen ur recipe-app
|
||
- Delegerar till `POST importer-api:3001/api/receipt-import/parse` (FormData)
|
||
- `matchProducts()` och `enrichWithAiCategories()` behålls (DB-krav)
|
||
- `RECEIPT_IMPORT_MODEL`-konstanten flyttad till `ai.controller.ts` (lokal konstant)
|
||
|
||
**7. Refaktorera `RecipesService.parseMarkdown()`** ✅
|
||
- Delegerar markdown-parsning till `POST importer-api:3001/api/recipes/parse-markdown`
|
||
- Fallback till lokal `parseRecipeMarkdown()` vid driftavbrott
|
||
- Levenshtein-produktmatchning behålls lokalt
|
||
|
||
---
|
||
|
||
## Fas 3 — Infrastruktur ✅
|
||
|
||
**8. `importer-api` i `recipe-app/compose.yml`** ✅
|
||
- Build-context: `../microservice-importer`, dockerfile `backend/Dockerfile`
|
||
- Image: `recipe-importer-api:local`, `pull_policy: never`
|
||
- Nätverk: `recipe-internal` (ej exponerad externt)
|
||
- Env: `MISTRAL_API_KEY`, `PORT=3001`
|
||
- Healthcheck: `wget -qO- http://127.0.0.1:3001/api/health`
|
||
- `recipe-api` får `depends_on: importer-api: condition: service_healthy`
|
||
|
||
---
|
||
|
||
## Relevanta filer som ändrades
|
||
|
||
| Fil | Förändring |
|
||
|---|---|
|
||
| `microservice-importer/backend/src/web-scraping-service/parsers/base.parser.ts` | `imageUrl?` i `ParsedRecipe` |
|
||
| `microservice-importer/backend/src/web-scraping-service/parsers/ica.parser.ts` | Extraherar `recipe.image` |
|
||
| `microservice-importer/backend/src/web-scraping-service/services/quick-import.service.ts` | Omskriven: OCR, PDF, imageUrl, importFromUpload |
|
||
| `microservice-importer/backend/src/web-scraping-service/controllers/quick-import.controller.ts` | FileInterceptor, HttpCode(200) |
|
||
| `microservice-importer/backend/src/web-scraping-service/web-scraping.module.ts` | Fixade importvägar + klassnamn |
|
||
| `microservice-importer/backend/src/document-service/document-service.module.ts` | Fixade importvägar + klassnamn |
|
||
| `microservice-importer/backend/src/document-service/services/document-import.service.ts` | Fixad parsersökväg |
|
||
| `microservice-importer/backend/src/receipt-parsing/` | Ny modul (service, controller, module) |
|
||
| `microservice-importer/backend/src/app.module.ts` | ReceiptParsingModule + HealthController |
|
||
| `microservice-importer/backend/Dockerfile` | apk add tesseract-ocr |
|
||
| `recipe-app/backend/src/quick-import/quick-import.service.ts` | Delegerar till importer-api |
|
||
| `recipe-app/backend/src/receipt-import/receipt-import.service.ts` | AI-del delegeras, matchning behålls |
|
||
| `recipe-app/backend/src/recipes/recipes.service.ts` | parseMarkdown delegeras, matchning behålls |
|
||
| `recipe-app/backend/src/ai/ai.controller.ts` | RECEIPT_IMPORT_MODEL lokal konstant |
|
||
| `recipe-app/compose.yml` | importer-api-tjänst tillagd |
|
||
|
||
---
|
||
|
||
## Avgränsningar (oförändrade)
|
||
|
||
- **Frontend ändras inte** — samma proxy-routes, samma API-kontrakt
|
||
- **Auth stannar i recipe-app backend** — microservice-importer exponeras bara internt
|
||
- **Bildoptimering** behålls i recipe-app (`downloadAndOptimizeImage` vid `RecipesService.create()`)
|
||
- `matchProducts()` och `enrichWithAiCategories()` stannar i recipe-app (DB-krav)
|
||
|
||
|
||
---
|
||
|
||
## Fas 1 — Utöka microservice-importer
|
||
|
||
*Steg 1–3 är oberoende och kan utföras parallellt.*
|
||
|
||
**1. Lägg till OCR-stöd (tesseract.js)**
|
||
Ny `ImageParser` i `backend/src/web-scraping-service/parsers/`. Controllern
|
||
`quick-import.controller.ts` utökas att acceptera `multipart/form-data` för
|
||
bilder vid sidan av JSON-body för URL-anrop.
|
||
|
||
**2. Lägg till `imageUrl` i quick-import-svaret**
|
||
`quick-import.service.ts` returnerar idag `{ markdown, source }`. Komplettera
|
||
med `imageUrl?` (original-URL från skrapad sida).
|
||
|
||
**3. Ny `ReceiptParsingModule` – stateless kvittoparsning**
|
||
Ny modul `backend/src/receipt-parsing/` med endpoint `POST /api/receipt-import/parse`.
|
||
- PDF → text via `pdf-parse`; bild → base64
|
||
- Anropar Mistral AI med kvitto-prompt
|
||
- Returnerar: `[{ rawName, quantity, unit, price, brand, origin }]`
|
||
- Ingen databaskoppling — rent stateless
|
||
|
||
---
|
||
|
||
## Fas 2 — Anpassa recipe-app backend
|
||
|
||
*Beror på Fas 1. Steg 5–7 kan utföras parallellt.*
|
||
|
||
**4. Lägg till `HttpModule` + `IMPORTER_SERVICE_URL`**
|
||
recipe-app backend registrerar NestJS:s `HttpModule` (axios-wrapper).
|
||
`IMPORTER_SERVICE_URL` sätts som env-variabel (`http://importer-api:3001` i Docker).
|
||
|
||
**5. Refaktorera `QuickImportService`**
|
||
Ta bort lokal ICA-parsning, pdf-parse och tesseract — anropa istället
|
||
microservice-importer `POST /api/quick-import` (eller `POST /api/document-import`
|
||
för PDF). `QuickImportModule` behåller sin controller och DTO (API-kontrakt oförändrat).
|
||
|
||
**6. Refaktorera `ReceiptImportService`**
|
||
- AI-parsning → delegeras till `POST $IMPORTER_URL/api/receipt-import/parse`
|
||
- Produktmatchning (Levenshtein mot `Product`, `ReceiptAlias`) — behålls i recipe-app (DB-krav)
|
||
- Slår ihop och returnerar samma svar som idag till frontend
|
||
|
||
**7. Refaktorera `RecipesService.parseMarkdown()`**
|
||
- Anropar `POST $IMPORTER_URL/api/recipes/parse-markdown` → `{ name, ingredients[], ... }`
|
||
- Kör befintlig Levenshtein-produktmatchning mot `Product`-tabellen i recipe-app
|
||
- Returnerar sammansatt svar — API-kontraktet mot frontend oförändrat
|
||
|
||
**8. Ta bort lokala parsningsberoenden**
|
||
Ta bort `pdf-parse`, `tesseract.js`, `node-fetch` etc. ur recipe-app backend
|
||
`package.json` när steg 5–7 är verifierade.
|
||
|
||
---
|
||
|
||
## Fas 3 — Infrastruktur
|
||
|
||
*Kan påbörjas parallellt med Fas 1.*
|
||
|
||
**9. Länka microservice-importer i recipe-app:s Docker Compose**
|
||
Lägg till `importer-api`-tjänst i `recipe-app/compose.yml` (byggs från
|
||
`../microservice-importer/backend`). Delar `recipe-network` med recipe-app
|
||
backend. Sätt `IMPORTER_SERVICE_URL=http://importer-api:3001` i recipe-app
|
||
backend-tjänstens env.
|
||
|
||
---
|
||
|
||
## Relevanta filer
|
||
|
||
| Fil | Förändring |
|
||
|---|---|
|
||
| `microservice-importer/backend/src/web-scraping-service/` | Ny ImageParser, imageUrl i svar |
|
||
| `microservice-importer/backend/src/` | Ny `receipt-parsing/` modul |
|
||
| `recipe-app/backend/src/quick-import/quick-import.service.ts` | Ersätt lokal parsning med HTTP-anrop |
|
||
| `recipe-app/backend/src/receipt-import/receipt-import.service.ts` | AI-del delegeras, matchning behålls |
|
||
| `recipe-app/backend/src/recipes/recipes.service.ts` | parseMarkdown delegeras, matchning behålls |
|
||
| `recipe-app/backend/src/app.module.ts` | Registrera HttpModule |
|
||
| `recipe-app/backend/package.json` | Ta bort pdf-parse, tesseract.js |
|
||
| `recipe-app/compose.yml` | Lägg till importer-api tjänst |
|
||
| `recipe-app/frontend/` | **Ändras inte** |
|
||
|
||
---
|
||
|
||
## Verifiering
|
||
|
||
1. `POST /api/quick-import` (recipe-app backend) med ICA-URL → samma svar som idag
|
||
2. `POST /api/quick-import` med PDF-fil → samma svar
|
||
3. `POST /api/recipes/parse-markdown` med markdown → ingredienser med produkt-ID:n
|
||
4. `POST /api/receipt-import` med kvittobild → matchade items med DB-produkt-ID:n
|
||
5. Autentisering fungerar (hanteras av recipe-app backend som tidigare)
|
||
6. `docker compose up` startar microservice-importer som intern tjänst
|
||
|
||
---
|
||
|
||
## Avgränsningar
|
||
|
||
- **Frontend ändras inte** — samma proxy-routes, samma API-kontrakt
|
||
- **Auth stannar i recipe-app backend** — microservice-importer exponeras bara internt på Docker-nätverket
|
||
- **Bildoptimering vid sparande** behålls i recipe-app (sker vid `RecipesService.create()`, inte vid import)
|
||
- `receipt-import` splittad: AI-del → microservice, produktmatchning + DB → recipe-app backend
|