# SpaFu02 – Entwicklungsvorgaben
tags: [sparfuchs, spafu02, entwicklung, planung]
erstellt: 2026-06-13
aktualisiert: 2026-06-15
status: in Entwicklung
---
## Überblick
SpaFu02 ist die Nachfolgeversion von Sparfuchs Koblenz (SpaFu01). Ziel ist eine quellenunabhängige, öffentlich betriebene Preisvergleichs-Anwendung auf Basis von Discounter-Prospekten.
---
## Motivation
Die bisherige Datenquelle (marktguru API) ist nur für den privaten Gebrauch lizenziert und für einen öffentlichen Betrieb nicht geeignet. SpaFu02 ersetzt die API durch automatisierte Verarbeitung von Discounter-Newslettern/Prospekten.
---
## Systemarchitektur
```
┌─────────────────────────────┐
│ System "Gen" (lokal) │
│ - IMAP Newsletter lesen │
│ - PDF herunterladen │
│ - PyMuPDF + Claude API │
│ - SQLite schreiben │
└────────────┬────────────────┘
│ Sync bei Bedarf (Strategie offen)
┌────────────▼────────────────┐
│ System "Web" (Hetzner VPS) │
│ ⚠️ ausgeschaltet bis │
│ Anwendung fehlerfrei │
└─────────────────────────────┘
```
### System "Gen" (lokal) – LXC 106
- IP: 192.168.2.181, Port 8765
- Proxmox Host:
[email protected], SSH-Key: C:\Users\Joachim2026\.ssh\id_ed25519_proxmox
- Pfad: /opt/preisdb/
- DB: /opt/preisdb/data/preisdb.sqlite (Schema v6)
- PyMuPDF nur im venv → immer /opt/preisdb/venv/bin/python3
### System "Web" (Hetzner VPS) – ⚠️ ausgeschaltet
- Host:
[email protected], SSH-Key: ~/.ssh/id_ed25519
- Domain: sparfuchs-koblenz.com (Cloudflare Proxy, Full strict)
- Stack: Ubuntu 26.04, Python 3.14, Caddy v2.11.4, FastAPI Port 8000
---
## Deploy-Workflow (lokal → VPS) – zurückgestellt
```powershell
cat "C:\MoniBase\Anwendungen\Schnäppchen Jäger\main.py" | ssh -i ~/.ssh/id_ed25519
[email protected] "cat > /opt/preisdb/app/main.py"
ssh -i ~/.ssh/id_ed25519
[email protected] "sudo systemctl restart preisdb-api"
```
---
## Dateiversionen (Stand 2026-06-14)
| Datei | Version | Status |
|-------|---------|--------|
| `main.py` | v2.4 – PDF-Download-Endpoint | LXC ✅, VPS ⏳, commit eea4867 |
| `models.py` | NEU – Pydantic-Klassen ausgelagert | LXC ✅, VPS ⏳ |
| `index.html` | v2.8 – Download-Button Original-PDF | LXC ✅, VPS ⏳, commit eea4867 |
| `quality_check.py` | v1.0 – Schritt 6 QS | LXC ✅, commit eea4867 |
| `offer_extractor.py` | v1.4 – Claude Vision + API-Log | LXC ✅, commit 3a07b0e |
| `page_processor.py` | v1.2 – DB-Write + Prospekt-Status | LXC ✅, commit c6bf442 |
| `pdf_extractor.py` | v2.0 – newsletter/link/mail_render | LXC ✅, committed |
| `mail_classifier.py` | v1.0 – Claude Haiku | LXC ✅, committed |
| `imap_reader.py` | v1.0 | LXC ✅, committed |
| `importer.py` | v1.4 | ✅ |
| `run_import.py` | v1.5 | ✅ |
---
## Datensynchronisation Gen → Web
- **Erster Schritt:** komplette DB-Übertragung
- **Später:** nur geänderte Datensätze (Entscheidung offen)
- **Transfermethode:** offen (scp/rsync, Script, REST-API)
---
## Datenquelle: Discounter-Newsletter
- Postfach: `
[email protected]`
- IMAP: `imap.secureserver.net:993` (SSL/TLS)
### Ziel-Händler (Koblenz)
- Aldi Nord / Aldi Süd
- Lidl
- Penny
- Netto
---
## Verarbeitungslogik (Pipeline)
### Schritt 1: IMAP-Abruf (`imap_reader.py`) ✅
- Alle Mails abrufen (ALL), nach Download löschen
- `.eml`-Datei speichern unter `/opt/preisdb/data/mails/`
### Schritt 2: Händler-Erkennung (`mail_classifier.py`) ✅
- Claude Haiku (claude-haiku-4-5-20251001) analysiert `.eml`
- `sicher=false` → Warteschlange für manuelle Zuordnung im Admin
### Schritt 3: PDF-Extraktion (`pdf_extractor.py`) ✅
- Priorität: PDF-Anhang → Link-Download → HTML-Rendering (wkhtmltopdf)
- Speichern: `/opt/preisdb/data/prospekte/haendler_plz_datum.pdf`
- PDFs werden als **eine Datei pro Prospekt** gespeichert – keine Seiten-Dateien
- Prospekt-Kopfsatz in DB anlegen
### Schritt 4: Seitenverarbeitung (`page_processor.py` v1.2) ✅
- PyMuPDF im venv: `/opt/preisdb/venv/bin/python3`
- Seiten werden direkt aus Gesamt-PDF im RAM gerendert (`doc[seite_nr - 1].get_pixmap()`)
- Testlauf ALDI SÜD Prospekt 4: 7 Seiten verarbeitet ✅
### Schritt 5: Angebots-Extraktion (`offer_extractor.py` v1.4) ✅
- Claude Haiku Vision: PDF-Seite als PNG → JSON-Angebote
- Ergebnis Prospekt 4: 9 Angebote aus 5 von 7 Seiten (`qs_status='neu'`)
- API-Log: `/opt/preisdb/logs/claude_api.log`
### Schritt 6: QS-Verfahren (`quality_check.py` v1.0) ✅
- Höchste `cc_guete` gewinnt → `qs_status='freigegeben'`
- Aufruf: `python3 quality_check.py [prospekt_id]`
---
## Reset-Script (`reset_testdaten.py`) – geplant
Setzt die Testumgebung vollständig zurück für wiederholbare Tests.
**Dateisystem:**
- `/opt/preisdb/data/mails/*.eml` – alle löschen
- `/opt/preisdb/data/prospekte/*.pdf` – alle löschen
**Datenbank (Reihenfolge wegen Foreign Keys):**
```sql
DELETE FROM Angebot;
DELETE FROM Preishistorie;
DELETE FROM SeitenErgebnis;
DELETE FROM ProspektSeite;
DELETE FROM Prospekt;
DELETE FROM Produkt;
DELETE FROM Kategorie;
DELETE FROM Marke;
DELETE FROM Haendler;
```
**Nicht löschen:** ErkennungsSystem, SparfuchsGruppe, SparfuchsKategorie, Lebensmittelgruppe, Suchbegriff
**Aufruf:** `/opt/preisdb/venv/bin/python3 /opt/preisdb/app/reset_testdaten.py`
---
## QS-Oberfläche (Admin-Tab "QS / Prospekte") ✅
**FastAPI-Endpunkte (JWT-geschützt):**
- `GET /admin/prospekte` – Liste aller Prospekte
- `GET /admin/prospekte/{id}` – Kopfdaten + Seiten
- `GET /admin/prospekte/{id}/seiten/{seite_nr}` – Erkennungsergebnisse + Angebote
- `GET /admin/prospekte/{id}/seiten/{seite_nr}/bild` – PDF-Seite als PNG
- `GET /admin/prospekte/{id}/pdf` – Original-PDF-Download
---
## PDF-Verarbeitung: Erkennungssysteme (nach Priorität)
| Priorität | System | Typ | Beschreibung |
|-----------|--------|-----|--------------|
| 1 | `pymupdf` | lokal | PyMuPDF – Standard, 96% Qualität ✅ in DB |
| 2 | `pdfplumber` | lokal | Tabellenerkennung – Fallback ✅ in DB |
| 3 | `claude_api` | Cloud | Vision API – Bild-PDFs ✅ in DB |
| 4 | `mistral_ocr` | Cloud | Mistral OCR ✅ in DB |
| 5 | `google_dai` | Cloud | Google Cloud Document AI ✅ in DB |
| 6 | `azure_di` | Cloud | Azure AI Document Intelligence ✅ in DB |
| 7 | `nutrient_ocr` | Cloud | Nutrient OCR (ehem. PSPDFKit) ✅ in DB |
| 8 | `docupipe` | Cloud | DocuPipe ✅ in DB |
---
## Datenbankschema: schema_v6.sql
```
Prospekt (Kopfsatz, PLZ)
└── ProspektSeite (je Seite)
└── SeitenErgebnis ←── ErkennungsSystem
↓
Angebot (qs_status) ←── Produkt, Haendler
```
---
## Gesamtplan
### Sofort (Claude Code)
- [ ] `reset_testdaten.py` erstellen und testen
- [ ] `quality_check.py` auf Prospekt 4 ausführen (9 Angebote → freigeben)
- [ ] Prospekte 1 + 3 klären (status='fehler', 0 Seiten)
### Testrunde
- [ ] Reset → Newsletter einspielen → komplette Pipeline testen
- [ ] QS-Oberfläche im Admin prüfen
### Wenn Pipeline fehlerfrei
- [ ] Weitere Händler-Newsletter abonnieren (Lidl, Penny, Netto)
- [ ] VPS einschalten + Deploy (erst fragen!)
- [ ] Sync Gen→Web einrichten
### Features (parallel)
- [ ] Admin: manueller PDF-Upload
- [ ] Button-Text "Verwaltung" → "Gruppen anpassen" (`sfBtn-verwaltung` in index.html:427)
- [ ] Einkaufsliste nach LG sortieren beim Drucken
- [ ] index.html aufteilen: styles.css + app.js
- [ ] Standorte via Overpass/OSM
- [ ] sendmail auf VPS einrichten
---
## Dateien
- Schema: `C:\MoniBase\Anwendungen\Sparfuchs-Koblenz\schema_v6.sql`
- Migration: `C:\MoniBase\Anwendungen\Sparfuchs-Koblenz\migrate_v5_to_v6.sql`
- Lokale App-Dateien: `C:\MoniBase\Anwendungen\Schnäppchen Jäger\`
- OCR-Bewertung: `C:\MoniBase\Anwendungen\Sparfuchs-Koblenz\SpaFu02-Bewertung-OCR.md`
- Design PDF-Extraktion: `C:\MoniBase\Anwendungen\Sparfuchs-Koblenz\Design – PDF-Extraktion.md`
- LXC-Setup: `C:\MoniBase\Anwendungen\Sparfuchs-Koblenz\SpaFu02-LXC-Setup.md`
- VPS-Setup: `C:\MoniBase\Anwendungen\Sparfuchs-Koblenz\04 Sparfuchs-VPS-Setup bei Hetchner.md`
- Claude Code Memory: `C:\Users\Joachim2026\.claude\projects\C--Users-Joachim2026\memory\project_preisdb_koblenz.md`