5879712a7f
Co-authored-by: Copilot <copilot@github.com>
204 lines
7.4 KiB
Markdown
204 lines
7.4 KiB
Markdown
# 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.
|
||
|
||
## 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
|
||
|
||
---
|
||
|
||
## Features
|
||
|
||
### Quick-import (`POST /api/quick-import`)
|
||
- **URL-skrapning** — ICA.se (JSON-LD) och generisk parser. Extraherar `imageUrl` från receptbild.
|
||
- **OCR (bild)** — tesseract.js, svenska+engelska. Returnerar `source: 'image'`.
|
||
- **PDF-parsning** — pdf-parse för digitala PDFs, OCR-fallback för skannade.
|
||
- **Multipart** — Tar emot antingen JSON-body (`{ url }`) eller FormData (`file`).
|
||
|
||
### Parse-Markdown (`POST /api/recipes/parse-markdown`)
|
||
Tolkar Markdown-recept till strukturerat JSON utan databas.
|
||
|
||
### Kvittoparsning (`POST /api/receipt-import/parse`)
|
||
- Bild (JPEG/PNG/WebP/HEIC/HEIF) eller PDF
|
||
- **Modell:** `mistral-small-2603` (vision-kapabel) med retry-logik (3 försök vid 503/429)
|
||
- 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`)
|
||
Används av Docker-healthcheck i `recipe-app/compose.yml`. Returnerar `{ status: "ok" }`.
|
||
|
||
---
|
||
|
||
## Miljövariabler
|
||
|
||
| Variabel | Beskrivning | Standardvärde |
|
||
|---|---|---|
|
||
| `MISTRAL_API_KEY` | API-nyckel för Mistral AI | (krävs för kvittoparsning) |
|
||
| `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
|
||
|
||
- **Git Repo** — Gitea på `192.168.50.2:2222/nilsjohan/microservice-importer`
|