fix: update documentation status and enhance technical description for microservice importer
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -1,218 +1,43 @@
|
|||||||
# Microservice Importer
|
# Microservice Importer
|
||||||
|
|
||||||
Intern import-tjänst (`importer-api`) för [recipe-app](../recipe-app). Hanterar URL-skrapning, OCR, PDF-parsning och AI-kvittoparsning utan databas. Körs som Docker-tjänst på det interna `recipe-internal`-nätverket — exponeras ej externt.
|
Intern import-tjänst (`importer-api`) för [recipe-app](../recipe-app). Den hanterar URL-skrapning, OCR, PDF-parsning och AI-kvittoparsning utan databas. Tjänsten körs som Docker-tjänst på det interna `recipe-internal`-nätverket och exponeras inte externt.
|
||||||
|
|
||||||
## Dokumentstatus (2026-05-03)
|
## Dokumentstatus (2026-05-10)
|
||||||
|
|
||||||
### Målgrupp
|
Det här dokumentet är skrivet för systemadministratörer och utvecklare som driftar eller vidareutvecklar importtjänsten. För arkitektur, drift och tekniska detaljer, se [TEKNISK_BESKRIVNING.md](TEKNISK_BESKRIVNING.md). För roadmap och prioriteringar, se [next_steps_MSImporter.md](next_steps_MSImporter.md).
|
||||||
Detta dokument är för systemadministratörer och utvecklare som driftar eller vidareutvecklar importtjänsten.
|
|
||||||
|
|
||||||
### Tillägg från senaste sessionerna
|
## Vad tjänsten gör
|
||||||
- Regelbaserad kvittotolkning har förbättrats för multipack, enheter och antaluttryck.
|
|
||||||
- Parserstödet för brödrelaterade produkter och guardrails mot felkategorisering har utökats.
|
|
||||||
- Integrationen med klienten för kvitto-session är fortfarande stateless i denna tjänst; ingen serverlagring av användarsession infördes.
|
|
||||||
- Kvittokategorisering: nya regler för pasta, grädde, ägg, juice, godis, och potatis samt justerad AI-guardrail.
|
|
||||||
- Testinfrastruktur: parametriserade enhetstester för kvittoimport (18 testfall) och CI/CD-pipeline med automatiserad testkörning på push.- **PDF-fix:** `pdf-parse` importeras nu med `require()` (CJS-kompatibilitet). `pdfjs-dist/legacy/build/pdf.js` används som fallback för att undvika `DOMMatrix is not defined` i Node.js-miljö.
|
|
||||||
- **Retry-förbättring:** Mistral 429/503 → väntar `3000 * attempt` ms innan nytt försök.
|
|
||||||
- **Reproducerbart bygge:** `package-lock.json` spåras i git; Dockerfile kör `npm ci`.
|
|
||||||
- **AI-skippning för icke-produktrader:** `looksLikeReceiptProductLine()` filtrerar PDF-rader utan siffra (header/footer/butiksinformation) — dessa skickas inte till Mistral.
|
|
||||||
## Viktigt!! Kod- och byggpraxis!
|
|
||||||
Säkerställ att inga absoluta Windows-sökvägar används i koden, för att stödja bygg och drift på Linux/Ubuntu
|
|
||||||
|
|
||||||
---
|
- Tar emot URL:er, filer och markdown för importflöden
|
||||||
|
- Skrapar receptsidor och extraherar `imageUrl` när det finns
|
||||||
|
- Kör OCR för bilder och skannade dokument
|
||||||
|
- Tolkar kvitton via Mistral AI
|
||||||
|
- Returnerar strukturerad data till recipe-app utan att lagra någon session eller databaspost
|
||||||
|
|
||||||
## Features
|
## Flöden
|
||||||
|
|
||||||
### Quick-import (`POST /api/quick-import`)
|
- `POST /api/quick-import` för URL-skrapning, bild-OCR och PDF-import
|
||||||
- **URL-skrapning** — ICA.se (JSON-LD) och generisk parser. Extraherar `imageUrl` från receptbild.
|
- `POST /api/recipes/parse-markdown` för markdown till strukturerat recept
|
||||||
- **OCR (bild)** — tesseract.js, svenska+engelska. Returnerar `source: 'image'`.
|
- `POST /api/receipt-import/parse` för kvittobild eller PDF till `ParsedReceiptItem[]`
|
||||||
- **PDF-parsning** — pdf-parse för digitala PDFs, OCR-fallback för skannade.
|
- `GET /api/health` för Docker healthcheck
|
||||||
- **Multipart** — Tar emot antingen JSON-body (`{ url }`) eller FormData (`file`).
|
|
||||||
|
|
||||||
### Parse-Markdown (`POST /api/recipes/parse-markdown`)
|
## Viktigt
|
||||||
Tolkar Markdown-recept till strukturerat JSON utan databas.
|
|
||||||
|
|
||||||
### Kvittoparsning (`POST /api/receipt-import/parse`)
|
- Inga absoluta Windows-sökvägar ska användas i kod eller scripts
|
||||||
- Bild (JPEG/PNG/WebP/HEIC/HEIF) eller PDF
|
- Tjänsten är stateless
|
||||||
- **Bild:** `mistral-small-2603` (vision-kapabel) med retry-logik (3 försök, `3000 * attempt` ms fördröjning vid 429/503)
|
- Ingen databas är konfigurerad i tjänsten
|
||||||
- **PDF:** `pdf-parse` → fallback `pdfjs-dist/legacy` → regelbaserad parsning → AI enbart för rader med namntext + siffra
|
- Host-port 3001 används av `wetty` på servern och får därför inte exponeras av importtjänsten
|
||||||
- Returnerar `ParsedReceiptItem[]` med fälten `rawName`, `quantity`, `unit`, `price`, `brand`, `origin`
|
|
||||||
- Inbyggda regler i AI-prompten styr tolkning av `quantity`/`unit` (se nedan)
|
|
||||||
|
|
||||||
### Health (`GET /api/health`)
|
## Kort faktadel
|
||||||
Används av Docker-healthcheck i `recipe-app/compose.yml`. Returnerar `{ status: "ok" }`.
|
|
||||||
|
|
||||||
---
|
- Runtime: Node.js 22-alpine
|
||||||
|
- Ramverk: NestJS 10 + TypeScript 5
|
||||||
## Miljövariabler
|
- OCR: `tesseract.js`
|
||||||
|
- PDF: `pdf-parse` med `pdfjs-dist/legacy` fallback
|
||||||
| Variabel | Beskrivning | Standardvärde |
|
- AI: `@mistralai/mistralai`
|
||||||
|---|---|---|
|
- Upload: `multer`
|
||||||
| `MISTRAL_API_KEY` | API-nyckel för Mistral AI | (krävs för kvittoparsning) |
|
- Alpine-paket: `tesseract-ocr`, `tesseract-ocr-data-swe`, `tesseract-ocr-data-eng`
|
||||||
| `PORT` | HTTP-port | `3001` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Arkitektur
|
|
||||||
|
|
||||||
### Backend (NestJS 10, TypeScript 5, Node.js 22-alpine)
|
|
||||||
**Port:** 3001 (intern, ej exponerad till host)
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── app.module.ts # Root module + HealthController (GET /api/health)
|
|
||||||
├── main.ts
|
|
||||||
├── common/
|
|
||||||
│ ├── filters/global-exception.filter.ts
|
|
||||||
│ └── utils/normalize-name.ts
|
|
||||||
├── web-scraping-service/ # Quick-import (URL + fil)
|
|
||||||
│ ├── web-scraping.module.ts
|
|
||||||
│ ├── controllers/quick-import.controller.ts # POST /api/quick-import
|
|
||||||
│ ├── services/quick-import.service.ts # Scraping, OCR, PDF
|
|
||||||
│ └── parsers/
|
|
||||||
│ ├── base.parser.ts # ParsedRecipe interface
|
|
||||||
│ ├── ica.parser.ts # ICA.se JSON-LD + imageUrl
|
|
||||||
│ └── generic.parser.ts
|
|
||||||
├── receipt-parsing/ # Kvittoparsning via Mistral AI
|
|
||||||
│ ├── receipt-parsing.module.ts
|
|
||||||
│ ├── receipt-parsing.controller.ts # POST /api/receipt-import/parse
|
|
||||||
│ └── receipt-parsing.service.ts
|
|
||||||
├── document-service/ # PDF-dokumentimport
|
|
||||||
│ ├── document-service.module.ts
|
|
||||||
│ ├── controllers/document-import.controller.ts
|
|
||||||
│ ├── services/document-import.service.ts
|
|
||||||
│ └── parsers/
|
|
||||||
│ ├── document.parser.ts
|
|
||||||
│ └── pdf.parser.ts
|
|
||||||
└── recipes/ # Markdown-tolkning
|
|
||||||
├── recipes.module.ts
|
|
||||||
├── recipes.controller.ts # POST /api/recipes/parse-markdown
|
|
||||||
├── recipes.service.ts
|
|
||||||
└── dto/parse-markdown.dto.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
**Viktigt:** Backend har _INGEN_ databaskonfiguration — stateless service.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Kvittoparsning — regler för quantity och unit
|
|
||||||
|
|
||||||
Följande regler är inbyggda i AI-prompten och styr hur Mistral tolkar mängd och enhet per produkt:
|
|
||||||
|
|
||||||
| Typ | Regel | Exempel |
|
|
||||||
|---|---|---|
|
|
||||||
| **Lösvikt** (kött, ost, frukt/grönt vägt i kassan) | `quantity` = faktisk vikt från kvittot, `unit` = `kg`/`g` | `BLANDFÄRS 20%` 0.997 kg → `quantity=0.997, unit="kg"` |
|
|
||||||
| **Förpackad vara med storlek i namn** (mejeri, dryck, konserver) | `quantity` = antal förpackningar, `unit` = `"förp"` | `MJÖLK 1,5L` × 3 → `quantity=3, unit="förp"` |
|
|
||||||
| **Multipack** (`NxYg`/`NxYml` i namn) | `quantity=1`, `unit="förp"` — räkna inte upp N | `BACON 3X120G` → `quantity=1, unit="förp"` |
|
|
||||||
| **Förpackat innehåll** (bröd, kex, chips) | `quantity` = antal förpackningar, `unit` = `"förp"` | `BRIOCHE SESAM` × 2 → `quantity=2, unit="förp"` |
|
|
||||||
| **Lösa styckvaror** (enstaka frukt/bröd per st) | `quantity` = antal, `unit` = `"st"` | `BANAN` × 1 → `quantity=1, unit="st"` |
|
|
||||||
|
|
||||||
Tillåtna enheter: `st`, `kg`, `g`, `l`, `dl`, `cl`, `ml`, `förp`, `pak`, `burk`, `flaska`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Parser-arkitektur
|
|
||||||
|
|
||||||
### Dokument-parsers (`document-service/parsers/`)
|
|
||||||
|
|
||||||
Abstrakt bas `DocumentParser` som alla dokumenttyp-specifika parsers ärver från.
|
|
||||||
|
|
||||||
### PDF Parser (`pdf.parser.ts`)
|
|
||||||
|
|
||||||
Hanterar textbaserade PDFs via `pdf-parse`. Skannade PDFs varnas.
|
|
||||||
|
|
||||||
### Webb-parsers (`web-scraping-service/parsers/`)
|
|
||||||
|
|
||||||
Abstrakt bas `RecipeParser` med `canHandle(url)` + `parse(html)`. Implementationer:
|
|
||||||
|
|
||||||
- **`ica.parser.ts`** — ICA.se, prioriterar JSON-LD structured data, extraherar `imageUrl`
|
|
||||||
- **`generic.parser.ts`** — Fallback för alla webbplatser
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
`importer-api` byggs och startas via `recipe-app/compose.yml` — **ej via sin egen compose-fil**.
|
|
||||||
|
|
||||||
**Serverstruktur:**
|
|
||||||
```
|
|
||||||
/opt/containers/
|
|
||||||
microservice-importer/ ← klonas separat, pullas vid deploy
|
|
||||||
recipe-app/
|
|
||||||
compose.yml ← definierar importer-api-tjänsten
|
|
||||||
deploy.sh ← kör docker compose build + up
|
|
||||||
```
|
|
||||||
|
|
||||||
**Deploy:**
|
|
||||||
```bash
|
|
||||||
# 1. Uppdatera importer (om ändringar gjorts)
|
|
||||||
cd /opt/containers/microservice-importer && git pull
|
|
||||||
|
|
||||||
# 2. Bygg och starta alla containers
|
|
||||||
cd /opt/containers/recipe-app && git pull && ./deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Loggar:**
|
|
||||||
```bash
|
|
||||||
docker logs importer-api -f
|
|
||||||
```
|
|
||||||
|
|
||||||
**Hälsokontroll:**
|
|
||||||
```bash
|
|
||||||
docker exec importer-api wget -qO- http://localhost:3001/api/health
|
|
||||||
# → {"status":"ok"}
|
|
||||||
```
|
|
||||||
|
|
||||||
**OBS:** Host-port 3001 används av `wetty` på servern. `importer-api` exponeras **aldrig** utanför Docker-nätverket — anropas via `http://importer-api:3001` från `recipe-api`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tekniska detaljer
|
|
||||||
|
|
||||||
### Backend Stack
|
|
||||||
- **NestJS** 10 — REST API & modular architecture
|
|
||||||
- **TypeScript** 5 — Type safety
|
|
||||||
- **Node.js** 22-alpine — Runtime (Alpine Linux)
|
|
||||||
- **pdf-parse** — PDF text extraction
|
|
||||||
- **tesseract.js** — OCR (bild och skannade PDFs, svenska + engelska)
|
|
||||||
- **@mistralai/mistralai** — AI-kvittoparsning (`mistral-small-2603`)
|
|
||||||
- **multer** — Multipart file upload handling
|
|
||||||
- **Ingen databas** — Stateless service
|
|
||||||
|
|
||||||
### Systempaket (Alpine)
|
|
||||||
Installerade i Dockerfile runner-stage:
|
|
||||||
```
|
|
||||||
tesseract-ocr
|
|
||||||
tesseract-ocr-data-swe
|
|
||||||
tesseract-ocr-data-eng
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
- Centraliserad `GlobalExceptionFilter` (svenska meddelanden)
|
|
||||||
- Konsistent JSON-responsformat: `{ statusCode, message, timestamp, path }`
|
|
||||||
- HTTP status codes: 200, 400, 503
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Framtida utbyggnader
|
|
||||||
|
|
||||||
- [x] PDF-import — textbaserad extraction
|
|
||||||
- [x] OCR för skannade bild-PDFs (tesseract.js + Alpine-paket)
|
|
||||||
- [x] Kvittoparsning via Mistral AI
|
|
||||||
- [x] ICA-receptbildsextraktion (`imageUrl` i `ParsedRecipe`)
|
|
||||||
- [ ] Fler webbplats-parsers (Arla, Tasteline, Köket.se)
|
|
||||||
- [ ] Word (.docx) import
|
|
||||||
- [ ] Swagger/OpenAPI-dokumentation
|
|
||||||
- [ ] Rate limiting / Caching
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Licens
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- **Git Repo** — Gitea på `192.168.50.2:2222/nilsjohan/microservice-importer`
|
- Git repo: Gitea på `192.168.50.2:2222/nilsjohan/microservice-importer`
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
# Teknisk beskrivning av Microservice Importer
|
||||||
|
|
||||||
|
## Dokumentstatus (2026-05-10)
|
||||||
|
|
||||||
|
Detta dokument riktar sig till utvecklare och driftansvariga för microservice-importer. Det beskriver arkitektur, drift och tekniska beslut för den interna importtjänsten.
|
||||||
|
|
||||||
|
## Roll och ansvar
|
||||||
|
|
||||||
|
`importer-api` är en stateless intern tjänst för [recipe-app](../recipe-app). Den hanterar URL-skrapning, OCR, PDF-parsning, markdown-parsning och AI-kvittoparsning utan databas eller användarsessioner.
|
||||||
|
|
||||||
|
## Arkitektur
|
||||||
|
|
||||||
|
### Körmiljö
|
||||||
|
|
||||||
|
- NestJS 10
|
||||||
|
- TypeScript 5
|
||||||
|
- Node.js 22-alpine
|
||||||
|
- Port `3001` internt
|
||||||
|
- Exponeras bara på `recipe-internal`-nätverket
|
||||||
|
|
||||||
|
### Moduler
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── app.module.ts # Root module + HealthController (GET /api/health)
|
||||||
|
├── main.ts
|
||||||
|
├── common/
|
||||||
|
│ ├── filters/global-exception.filter.ts
|
||||||
|
│ └── utils/normalize-name.ts
|
||||||
|
├── web-scraping-service/
|
||||||
|
│ ├── web-scraping.module.ts
|
||||||
|
│ ├── controllers/quick-import.controller.ts # POST /api/quick-import
|
||||||
|
│ ├── services/quick-import.service.ts # Scraping, OCR, PDF
|
||||||
|
│ └── parsers/
|
||||||
|
│ ├── base.parser.ts # ParsedRecipe interface
|
||||||
|
│ ├── ica.parser.ts # ICA.se JSON-LD + imageUrl
|
||||||
|
│ └── generic.parser.ts
|
||||||
|
├── receipt-parsing/
|
||||||
|
│ ├── receipt-parsing.module.ts
|
||||||
|
│ ├── receipt-parsing.controller.ts # POST /api/receipt-import/parse
|
||||||
|
│ └── receipt-parsing.service.ts
|
||||||
|
├── document-service/
|
||||||
|
│ ├── document-service.module.ts
|
||||||
|
│ ├── controllers/document-import.controller.ts
|
||||||
|
│ ├── services/document-import.service.ts
|
||||||
|
│ └── parsers/
|
||||||
|
│ ├── document.parser.ts
|
||||||
|
│ └── pdf.parser.ts
|
||||||
|
└── recipes/
|
||||||
|
├── recipes.module.ts
|
||||||
|
├── recipes.controller.ts # POST /api/recipes/parse-markdown
|
||||||
|
├── recipes.service.ts
|
||||||
|
└── dto/parse-markdown.dto.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
| Endpoint | Funktion |
|
||||||
|
|---|---|
|
||||||
|
| `POST /api/quick-import` | URL-skrapning, bild-OCR och PDF-import |
|
||||||
|
| `POST /api/recipes/parse-markdown` | Markdown till strukturerat recept utan databas |
|
||||||
|
| `POST /api/receipt-import/parse` | Kvittobild/PDF till `ParsedReceiptItem[]` via Mistral AI |
|
||||||
|
| `GET /api/health` | Hälsokontroll för Docker healthcheck |
|
||||||
|
|
||||||
|
## Kvittoparsning
|
||||||
|
|
||||||
|
### Modell och pipeline
|
||||||
|
|
||||||
|
- Vision-input använder `mistral-small-2603`
|
||||||
|
- PDF-flödet kör `pdf-parse` eller `pdfjs-dist/legacy/build/pdf.js` som fallback
|
||||||
|
- Regelbaserad parsning körs före AI när det är möjligt
|
||||||
|
- `looksLikeReceiptProductLine()` filtrerar bort rader utan siffra så att AI bara används för sannolika produktrader
|
||||||
|
|
||||||
|
### Mängd- och enhetsregler
|
||||||
|
|
||||||
|
Följande regler är inbyggda i kvitto-prompten:
|
||||||
|
|
||||||
|
| Typ | Regel | Exempel |
|
||||||
|
|---|---|---|
|
||||||
|
| Lösvikt | `quantity` = faktisk vikt, `unit` = `kg`/`g` | `BLANDFÄRS 20%` 0.997 kg |
|
||||||
|
| Förpackad vara med storlek i namn | `quantity` = antal förpackningar, `unit` = `förp` | `MJÖLK 1,5L` × 3 |
|
||||||
|
| Multipack | `quantity=1`, `unit=förp` | `BACON 3X120G` |
|
||||||
|
| Förpackat innehåll | `quantity` = antal förpackningar, `unit` = `förp` | `BRIOCHE SESAM` × 2 |
|
||||||
|
| Lösa styckvaror | `quantity` = antal, `unit` = `st` | `BANAN` × 1 |
|
||||||
|
|
||||||
|
Tillåtna enheter: `st`, `kg`, `g`, `l`, `dl`, `cl`, `ml`, `förp`, `pak`, `burk`, `flaska`.
|
||||||
|
|
||||||
|
### Retry och stabilitet
|
||||||
|
|
||||||
|
- Mistral 429/503 backas av med `3000 * attempt` ms
|
||||||
|
- PDF-flödet använder fallback för CJS- och Node Alpine-kompatibilitet
|
||||||
|
- `GlobalExceptionFilter` ger konsekventa felobjekt
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
`importer-api` byggs och startas via [recipe-app/compose.yml](../recipe-app/compose.yml) och inte via egen compose-fil.
|
||||||
|
|
||||||
|
### Serverlayout
|
||||||
|
|
||||||
|
```text
|
||||||
|
/opt/containers/
|
||||||
|
microservice-importer/
|
||||||
|
recipe-app/
|
||||||
|
compose.yml
|
||||||
|
deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Driftsekvens
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/containers/microservice-importer && git pull
|
||||||
|
cd /opt/containers/recipe-app && git pull && ./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hälsokontroll
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec importer-api wget -qO- http://localhost:3001/api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tekniska detaljer
|
||||||
|
|
||||||
|
### Byggberoenden
|
||||||
|
|
||||||
|
- `pdf-parse`
|
||||||
|
- `tesseract.js`
|
||||||
|
- `@mistralai/mistralai`
|
||||||
|
- `multer`
|
||||||
|
|
||||||
|
### Alpine-paket
|
||||||
|
|
||||||
|
- `tesseract-ocr`
|
||||||
|
- `tesseract-ocr-data-swe`
|
||||||
|
- `tesseract-ocr-data-eng`
|
||||||
|
|
||||||
|
### Viktiga tekniska beslut
|
||||||
|
|
||||||
|
- Tjänsten är stateless och saknar databaskonfiguration
|
||||||
|
- Importer exponeras aldrig externt, bara internt via Docker-nätverket
|
||||||
|
- Host-port 3001 är upptagen av `wetty` och får därför inte användas av tjänsten
|
||||||
|
|
||||||
|
## Parser-arkitektur
|
||||||
|
|
||||||
|
### Dokument-parsers
|
||||||
|
|
||||||
|
Abstrakt bas `DocumentParser` används för dokumenttypsspecifik parsing.
|
||||||
|
|
||||||
|
### PDF Parser
|
||||||
|
|
||||||
|
`pdf.parser.ts` hanterar textbaserade PDFs. Skannade PDFs varnas och kan falla tillbaka på OCR-vägen.
|
||||||
|
|
||||||
|
### Webb-parsers
|
||||||
|
|
||||||
|
- `ica.parser.ts` prioriterar JSON-LD och extraherar `imageUrl`
|
||||||
|
- `generic.parser.ts` är fallback för webbplatser utan specialparser
|
||||||
|
|
||||||
|
## Framtida utbyggnader
|
||||||
|
|
||||||
|
- Fler webbplats-parsers som Arla, Tasteline och Köket.se
|
||||||
|
- Word/import av `.docx`
|
||||||
|
- Swagger/OpenAPI-dokumentation
|
||||||
|
- Caching av skrapade sidor om belastningen mot externa webbplatser blir ett problem
|
||||||
|
|
||||||
|
## Referenser
|
||||||
|
|
||||||
|
- [README.md](README.md)
|
||||||
|
- [NEXT_STEPS.md](next_steps_MSImporter.md)
|
||||||
+35
-60
@@ -1,21 +1,21 @@
|
|||||||
# Plan för vidareutveckling av Microservice Importer
|
# Plan för vidareutveckling av Microservice Importer
|
||||||
|
|
||||||
## Dokumentstatus (2026-05-03)
|
## Dokumentstatus (2026-05-10)
|
||||||
|
|
||||||
Detta dokument riktar sig till utvecklare och driftansvariga för microservice-importer.
|
Detta dokument riktar sig till utvecklare och driftansvariga för microservice-importer. Teknisk referens finns i [TEKNISK_BESKRIVNING.md](TEKNISK_BESKRIVNING.md).
|
||||||
|
|
||||||
### Senast avklarat i angränsande flöden
|
### Senast avklarat i angränsande flöden
|
||||||
- Regelbaserad kvittotolkning har stärkts för multipack/enheter och svårare radformat.
|
- Regelbaserad kvittotolkning har stärkts för multipack, enheter och svårare radformat.
|
||||||
- Bröd-/rostbrödklassning har utökade guardrails för att minska felaktig kategorisering.
|
- Bröd- och rostbrödklassning har utökade guardrails för att minska felaktig kategorisering.
|
||||||
- Klientens granskningsflöde och sessionpersistens i Flutter är implementerat, vilket minskar avbrott mellan parse och spara.
|
- Klientens granskningsflöde och sessionpersistens i Flutter är implementerat, vilket minskar avbrott mellan parse och spara.
|
||||||
- Kvittokategorisering: nya regler för pasta, grädde, ägg, juice, godis, och potatis samt justerad AI-guardrail.
|
- Kvittokategorisering: nya regler för pasta, grädde, ägg, juice, godis och potatis samt justerad AI-guardrail.
|
||||||
- Testinfrastruktur: parametriserade enhetstester för kvittoimport (18 testfall) och CI/CD-pipeline med automatiserad testkörning på push.
|
- Testinfrastruktur: parametriserade enhetstester för kvittoimport och CI/CD-pipeline med automatiserad testkörning på push.
|
||||||
- **PDF-parsning stabiliserad:** `pdf-parse` använder nu `require()` för CJS-kompatibilitet; `pdfjs-dist/legacy/build/pdf.js` används som fallback för att undvika `DOMMatrix`-fel i Node.js-miljö.
|
- PDF-parsning stabiliserad med `require()` och `pdfjs-dist/legacy/build/pdf.js` som fallback.
|
||||||
- **Retry-logik förbättrad:** Mistral-anrop vid 429/503 väntar nu `3000 * attempt` ms (3s, 6s, 9s) i stället för fast 1s.
|
- Retry-logik förbättrad för 429/503.
|
||||||
- **Reproducerbart bygge:** `package-lock.json` är nu spårat i repot; Dockerfile använder `npm ci`.
|
- Reproducerbart bygge via `package-lock.json` och `npm ci`.
|
||||||
- **AI-optimering implementerad:** `looksLikeReceiptProductLine()` filtrerar bort PDF-rader utan siffra (header/footer/butiksinformation) innan Mistral-anrop. Minskar drastiskt antal onödiga AI-anrop vid kvittoimport.
|
- `looksLikeReceiptProductLine()` filtrerar bort PDF-rader utan siffra innan Mistral-anrop.
|
||||||
|
|
||||||
## Status (2026-05-03) — Driftsatt och integrerad med recipe-app
|
## Status (2026-05-10) — Driftsatt och integrerad med recipe-app
|
||||||
|
|
||||||
`microservice-importer` körs som intern tjänst (`importer-api`) i `recipe-app/compose.yml`. Alla importflöden är delegerade och driftsatta.
|
`microservice-importer` körs som intern tjänst (`importer-api`) i `recipe-app/compose.yml`. Alla importflöden är delegerade och driftsatta.
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ cd /opt/containers/recipe-app && git pull && ./deploy.sh
|
|||||||
### Medel prioritet
|
### Medel prioritet
|
||||||
- **Fler webbplats-parsers** — Specifika parsers för t.ex. Tasteline, Köket.se, Arla
|
- **Fler webbplats-parsers** — Specifika parsers för t.ex. Tasteline, Köket.se, Arla
|
||||||
- **Swagger/OpenAPI** — Automatisk API-dokumentation via `@nestjs/swagger`
|
- **Swagger/OpenAPI** — Automatisk API-dokumentation via `@nestjs/swagger`
|
||||||
- **Testtäckning** — Utökad enhetstesttäckning för parsers och `receipt-parsing.service.ts` (18 testfall för kvittoimport)
|
- **Testtäckning** — Utökad enhetstesttäckning för parsers och `receipt-parsing.service.ts`
|
||||||
|
|
||||||
### Låg prioritet / Framtida
|
### Låg prioritet / Framtida
|
||||||
- **Caching** — Cacha skrapade sidor för att minska belastning på externa webbplatser
|
- **Caching** — Cacha skrapade sidor för att minska belastning på externa webbplatser
|
||||||
@@ -62,77 +62,52 @@ cd /opt/containers/recipe-app && git pull && ./deploy.sh
|
|||||||
|
|
||||||
## AI-optimering: Mistral-modell och pipeline
|
## AI-optimering: Mistral-modell och pipeline
|
||||||
|
|
||||||
**Nuläge:** `mistral-small-2603` används för bildinput (vision). För PDF-flödet extraheras text först via `pdf-parse`/`pdfjs-dist`; sedan regelbaserad parsning; sedan AI enbart för återstående rader.
|
Nuläget är att `mistral-small-2603` används för bildinput (vision). För PDF-flödet extraheras text först via `pdf-parse` eller `pdfjs-dist`; därefter körs regelbaserad parsning och AI bara för kvarvarande rader.
|
||||||
|
|
||||||
### Implementerad optimering: AI sist i pipeline (PDF)
|
### Implementerad optimering: AI sist i pipeline (PDF)
|
||||||
|
|
||||||
Kvittopipelinen för PDF ser nu ut:
|
Kvittopipelinen för PDF ser nu ut:
|
||||||
|
|
||||||
```
|
```text
|
||||||
PDF → pdf-parse / pdfjs-dist → preprocessPdfLines → isIgnoredReceiptLine → ruleBasedParseLine → looksLikeReceiptProductLine → AI
|
PDF → pdf-parse / pdfjs-dist → preprocessPdfLines → isIgnoredReceiptLine → ruleBasedParseLine → looksLikeReceiptProductLine → AI
|
||||||
```
|
```
|
||||||
|
|
||||||
**`looksLikeReceiptProductLine(line)`** filtrerar bort rader som saknar siffra (butiksnamn, datum, välkomsttext m.m.) innan Mistral-anropet. Enbart rader med namnliknande text OCH minst ett tal skickas till AI.
|
`looksLikeReceiptProductLine(line)` filtrerar bort rader som saknar siffra innan Mistral-anropet. Enbart rader med namnliknande text och minst ett tal skickas till AI.
|
||||||
|
|
||||||
**Fördelar:**
|
|
||||||
- Regelbaserad parsning hanterar standardfall gratis och snabbt (t.ex. "MJÖLK 1,5L", "BLANDFÄRS 997G")
|
|
||||||
- AI anropas bara för rader som regelverket inte kan tolka entydigt
|
|
||||||
- Möjlighet att använda en mindre/billigare modell för enklare tolkningsuppgifter
|
|
||||||
|
|
||||||
### Modellval för olika deluppgifter
|
### Modellval för olika deluppgifter
|
||||||
|
|
||||||
| Uppgift | Rekommenderad modell | Motivering |
|
| Uppgift | Rekommenderad modell | Motivering |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Kvittoparsning (hela bilden, nuläge) | `mistral-small-2603` | Vision-förmåga krävs för bild-input |
|
| Kvittoparsning (hela bilden) | `mistral-small-2603` | Vision-förmåga krävs för bild-input |
|
||||||
| Tolka OCR-text (textbaserad input) | `mistral-small-latest` eller mindre | Enklare uppgift när text redan extraherats |
|
| Tolka OCR-text | `mistral-small-latest` eller mindre | Enklare uppgift när text redan extraherats |
|
||||||
| Kategorisering av enskild produktrad | `open-mistral-nemo` (7B) | Klassificering, ej vision — kan vara mycket liten |
|
| Kategorisering av enskild produktrad | `open-mistral-nemo` (7B) | Klassificering utan vision |
|
||||||
|
|
||||||
**OBS:** För bild-input (JPEG/PNG/HEIC/WebP) krävs alltid en vision-kapabel modell. Optimering med mindre modell är bara möjlig när Tesseract/pdf-parse redan har extraherat text.
|
För bild-input krävs alltid en vision-kapabel modell.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Framtida förbättringar
|
## Framtida förbättringar
|
||||||
|
|
||||||
### Schemalagd Uppdatering av Kategorier
|
- Schemalagd uppdatering av kategorier om det visar sig nödvändigt igen.
|
||||||
- **Mål:** Implementera en schemalagd uppdatering av kategorierna en gång i veckan för att säkerställa att cachen alltid är uppdaterad.
|
|
||||||
- **Metod:** Använda `cron` för att schemalägga ett anrop till `POST /receipt-import/refresh-categories` en gång i veckan.
|
|
||||||
|
|
||||||
---
|
## Nuvarande implementering
|
||||||
|
|
||||||
## Nuvarande Implementering
|
- Ingen separat manuell kategoriuppdatering via Flutter-UI finns längre.
|
||||||
|
- Kvittoparsningen använder i stället nuvarande regler, retry-logik och filtrering i backend.
|
||||||
### Manuell Uppdatering av Kategorier
|
|
||||||
- **Mål:** Låta användaren manuellt uppdatera kategorierna via Flutter-UI.
|
|
||||||
- **Implementering:**
|
|
||||||
- En knapp i Flutter-UI:n som låter användaren trigga uppdateringen.
|
|
||||||
- Anropa `POST /receipt-import/refresh-categories` från Flutter-UI:n när användaren klickar på knappen.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
// Exempel på hur du kan anropa endpointen från Flutter
|
|
||||||
Future<void> refreshCategories() async {
|
|
||||||
final response = await http.post(
|
|
||||||
Uri.parse('http://YOUR_API_URL/receipt-import/refresh-categories'),
|
|
||||||
headers: {'Authorization': 'Bearer YOUR_JWT_TOKEN'},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Kategorier har uppdaterats.')),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Misslyckades med att uppdatera kategorier.')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Arkitektur-noteringar
|
## Arkitektur-noteringar
|
||||||
|
|
||||||
- Tjänsten är **helt stateless** — ingen databas, ingen session
|
- Tjänsten är helt stateless och saknar databas
|
||||||
- Kommunicerar **aldrig direkt** med internet-klienter — exponeras bara på `recipe-internal`-nätverket
|
- Den exponeras bara på `recipe-internal`-nätverket
|
||||||
- `MISTRAL_API_KEY` injiceras via env (samma nyckel som `recipe-api` använder)
|
- `MISTRAL_API_KEY` injiceras via env
|
||||||
- Alpine Docker-image: systempaket `tesseract-ocr`, `tesseract-ocr-data-swe`, `tesseract-ocr-data-eng` installerade via `apk`
|
- Alpine Docker-image använder `tesseract-ocr`, `tesseract-ocr-data-swe`, `tesseract-ocr-data-eng`
|
||||||
- Host-port 3001 är upptagen av `wetty` på servern — `importer-api` exponeras aldrig till host
|
- Host-port 3001 är upptagen av `wetty`, så `importer-api` exponeras aldrig till host
|
||||||
|
|
||||||
|
## Referenser
|
||||||
|
|
||||||
|
- [README.md](README.md)
|
||||||
|
- [TEKNISK_BESKRIVNING.md](TEKNISK_BESKRIVNING.md)
|
||||||
|
|
||||||
|
## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
|
||||||
|
|||||||
Reference in New Issue
Block a user