Files
microservice-importer/README.md
T

204 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`