feat: Phase 5 – PDF/Bild-Beleg-Import (lokal, Tesseract-OCR)
Backend: - services/ocr: PDF-Text via PyMuPDF, OCR (Tesseract) für Scans/Bilder, heuristischer Parser (Datum + Betrag je Zeile, CH/DE/US-Beträge). - routers/documents: /documents/preview (Upload -> Text -> Kandidaten -> gemeinsame Prüf-/Buchungslogik des CSV-Imports). csv_import refaktoriert: detect_rows() von CSV und Beleg gemeinsam genutzt. - requirements: PyMuPDF, pytesseract, pillow; install.sh installiert tesseract-ocr (+ deu). pytest: Heuristik-Parser und PDF-Vorschau. Frontend: - Import-Seite: CSV/PDF-Umschalter; PDF-Modus mit Beleg-Auslesen, extrahiertem Text zur Kontrolle und Best-Effort-Hinweis; gleiche Prüfansicht + Buchen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
||||
fitz = pytest.importorskip("fitz")
|
||||
|
||||
|
||||
def _account(admin):
|
||||
r = admin.post("/api/accounts", json={"name": "Beleg", "type": "girokonto", "currency": "EUR"})
|
||||
assert r.status_code == 201
|
||||
return r.json()
|
||||
|
||||
|
||||
def _make_pdf() -> bytes:
|
||||
doc = fitz.open()
|
||||
page = doc.new_page()
|
||||
page.insert_text((50, 72), "15.03.2026 REWE Supermarkt Zuerich -50.00")
|
||||
page.insert_text((50, 96), "25.03.2026 Gehalt Firma AG 3000.00")
|
||||
page.insert_text((50, 120), "Kein Datum hier, nur Text 12.34")
|
||||
return doc.tobytes()
|
||||
|
||||
|
||||
def test_parse_transactions_heuristic():
|
||||
from app.services import ocr
|
||||
|
||||
text = (
|
||||
"Kontoauszug März 2026\n"
|
||||
"15.03.2026 REWE Supermarkt -50,00\n"
|
||||
"2026-03-25 Gehalt 3'000.00\n"
|
||||
"Zwischensumme\n"
|
||||
)
|
||||
rows = ocr.parse_transactions(text)
|
||||
assert len(rows) == 2
|
||||
assert rows[0]["date"] == "2026-03-15"
|
||||
assert rows[0]["amount_cents"] == -5000
|
||||
assert "REWE" in rows[0]["payee"]
|
||||
assert rows[1]["date"] == "2026-03-25"
|
||||
assert rows[1]["amount_cents"] == 300000 # Schweizer Tausender-Apostroph
|
||||
|
||||
|
||||
def test_pdf_document_preview(admin):
|
||||
acc = _account(admin)
|
||||
pdf = _make_pdf()
|
||||
r = admin.post(
|
||||
"/api/documents/preview",
|
||||
files={"file": ("beleg.pdf", io.BytesIO(pdf), "application/pdf")},
|
||||
data={"account_id": str(acc["id"])},
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert body["count"] == 2
|
||||
rows = body["rows"]
|
||||
kinds = {(row["type"], row["amount_cents"]) for row in rows}
|
||||
assert ("expense", -5000) in kinds
|
||||
assert ("income", 300000) in kinds
|
||||
Reference in New Issue
Block a user