Skip to content

Releases: robeertm/DocuSort

v0.34.8 — Receipt-Editor: Auto-Recalc Line + Gesamtsumme

09 May 19:04

Choose a tag to compare

Changed — Stückzahl ändern → Line-Summe und Gesamtsumme rechnen sich neu

Wenn du im Kassenzettel-Editor die Menge oder den Stückpreis änderst, wird jetzt:

  • Die Zeilen-Summe automatisch neu berechnet (Menge × Stückpreis)
  • Die Gesamtsumme unten folgt der Summe aller Zeilen
  • Das "Summe weicht ab"-Banner verschwindet automatisch nach Korrektur

Funktioniert so:

  • Menge oder Stückpreis-Input → Zeilen-Summe wird neu berechnet
  • Jede Item-Änderung (auch Position hinzufügen / entfernen) → Gesamtsumme = Summe aller Zeilen
  • Wenn du die Zeilen-Summe direkt überschreibst, übernimmt das die Gesamtsumme genauso
  • Die Gesamtsumme ist weiter manuell editierbar — wenn du nach allen Item-Edits noch was anderes hinschreibst, wird das gespeichert (nächste Item-Änderung würde wieder syncen, also am besten Header-Override zuletzt machen)

Verifiziert mit Headless-Chromium: Joghurt qty 2 → 5 bei 1,29 €/Stk → Zeile springt 2,58 → 6,45 €, Gesamtsumme zieht entsprechend nach.

Upgrade

git pull && sudo systemctl restart docusort

v0.34.7 — psm=4 OCR für Bons + keine Token-Limits mehr

09 May 18:21

Choose a tag to compare

Changed — Tesseract --psm 4 für schmale Thermobons

Bons (ALDI, EDEKA, Kaufland, Cineplex …) sind hoch + schmal. Tesseracts Default --psm 3 (auto + OSD) erkennt die schmale Spalte als wäre es ein Multi-Column-Layout und verquirlt benachbarte Zeilen zu einer:

LEERGUTRUCKNAHME 19% -3FANTA/SPRITE/MEZZOMIX 1

(zwei Bonzeilen, eine OCR-Zeile → LLM kann die nicht mehr als zwei Items lesen)

Lösung: für jedes Dokument prüfe ich die Seitenverhältnisse der ersten Seite (PDF) bzw. des Bilds. Bei Höhe/Breite >= 2.0 (passt für Thermo-Bons, nicht für A4-Briefe) gibt's --tesseract-pagesegmode 4 ("Single column of variable-sized text") an ocrmypdf bzw. --psm 4 an pytesseract. Briefe und Multi-Column-Docs laufen weiterhin mit Default psm.

Erkennung via Aspect-Ratio statt Doc-Klassifikation, weil's vor der OCR-Phase passiert (Doc ist noch nicht klassifiziert).

Changed — Keine künstlichen Token-Limits mehr

Der Receipt-Extractor schluckte vorher max 12 000 OCR-Zeichen Input und gab max 4 000 Tokens Output raus (in v0.34.1 schon auf 16 k angehoben). Bei einem ALDI-Großeinkauf mit 80+ Items reicht das nicht — Items hinter dem Cut werden stillschweigend gedroppt.

Jetzt limit-frei:

  • Receipt-Extractor: 200 000 Input-Chars, 100 000 Output-Tokens
  • Main-Classifier: 200 000 Input-Chars, 100 000 Output-Tokens

Du läufst lokal auf Ollama, daher ist Token-Kosten kein Thema. Der einzige echte Bound ist das num_ctx deines lokalen Models — wenn der Bon zu groß für dein Ollama-Setup ist, musst du OLLAMA_NUM_CTX oder den Modelfile entsprechend hochsetzen. Aber DocuSort schneidet nicht mehr selbst ab.

200 k Input deckt realistisch jedes einzelne Dokument ab und schützt nur noch vor pathologischem OCR-Müll (5 MB).

Upgrade

git pull && sudo systemctl restart docusort

Dann auf /analytics → "Bons neu analysieren" klicken. Die existierenden Bons werden mit der besseren OCR + ohne Limits neu durchgejagt. Wichtig: das funktioniert nur für neue OCR-Läufe. Die existierenden Doc-Dateien wurden mit dem alten psm=3 ge-OCR'd und das ist im Library-PDF eingebrannt. Für die vorhandenen ALDI-Bons müsstest du ggf.:

  1. Den Bon nochmal in den /inbox legen → Re-OCR mit psm=4 → neue saubere OCR-Daten
  2. Oder mit den aktuellen OCR-Daten leben und die fehlenden Items manuell im Receipt-Editor ergänzen

Für neue Bons ab jetzt: psm=4 läuft automatisch sobald du sie scannst und reinwirfst.

v0.34.6 — 180 Zeilen toter JS-Code crashte JEDEN Alpine-Widget

09 May 18:04

Choose a tag to compare

Fixed — Echte Root Cause für alle "Button tut nichts"-Reports

Im v0.33.0 Kontoauszug-PDF-Refactor wurden die function statementWidget() und function transactionEditor() Deklarationen aus document.html entfernt, aber ihre Method-Bodies blieben als 180 Zeilen freischwebender Code stehen — beginnend mit }, _scheduleTick() { ... }, loadPreview() { ... }.

JavaScript parste den <script>-Block, warf Unexpected token '}' beim ersten verirrten } — und damit war JEDE Helper-Funktion in dem Block undefined:

  • diagnosticsPanel → Diagnose-Button leer
  • editForm → Kategorie-Dropdown defaulted auf "Rechnungen" (Alpine x-model schlug fehl)
  • receiptEditor → Save / Add-Item / Re-Extract tot
  • retryWidget → Retry-Button tot
  • trashWidget → Trash-Button tot

Alle deine Beschwerden seit v0.34.0 — Diagnose-Button leer, Bons als "Rechnungen" angezeigt, Items nicht gespeichert, kein Klick — waren ein und derselbe Bug. Symptome unterschiedlich, Ursache ein einziger Syntaxfehler.

Wie ich's diagnostiziert habe (statt zu raten)

Hatte recht: ich hätte das vor drei Releases per Browser testen müssen. Diesmal wirklich gemacht:

.venv/bin/python -m playwright install chromium
.venv/bin/python  # spinned headless Chromium up
page.on("pageerror", ...)
page.goto("http://127.0.0.1:8919/document/1")

Erste Console-Message: Unexpected token '}'. Bracket-Balancer auf den Script-Inhalt → Tiefe geht bei Zeile 123 ins Negative. Source-Mapping → Zeile 658 in document.html. 180 Zeilen verwaister Refactor-Code, alles weg.

Verifiziert end-to-end: 0 Page-Errors, 0 Console-Warnings, Diagnose-Panel füllt sich nach Klick mit allen 19 Feldern, Kategorie-Dropdown zeigt "Kassenzettel" korrekt.

Bonus

approveJob.done in der Topbar nutzt jetzt Optional Chaining (?.), damit es nicht warnt wenn kein Bulk-Job läuft.

Upgrade

git pull && sudo systemctl restart docusort

Dann sollten ALLE Buttons funktionieren — Diagnose, Receipt-Save, Add-Item, Trash, Retry. Und der Kategorie-Dropdown zeigt endlich den echten DB-Wert.

Sorry für die Schleife. Der Bug war seit v0.33.0 latent in der Codebase — Alpine schluckte den Syntaxfehler still bis ich neue Components dazugepackt hab und es auffiel.

v0.34.5 — Drei unsichtbare Buttons (tote Tailwind-Klassen)

09 May 17:47

Choose a tag to compare

Fixed — Drei Buttons waren komplett unsichtbar

Tailwind wird hier precompiled — die fertige tailwind.css enthält nur Klassen, die zum Build-Zeitpunkt in den Templates standen. Bei den Buttons aus v0.34.1–v0.34.3 hatte ich bg-emerald-600, bg-amber-600 und text-white benutzt — alle drei waren nie im gebauten Stylesheet. Resultat: Buttons rendern ohne Hintergrund, Default-Textfarbe entspricht dem dunklen Card-Hintergrund → leeres Pill ohne sichtbaren Text, nicht klickbar.

Betroffen:

  • /analytics "Bons neu analysieren" Reload-Button (v0.34.1)
  • Salvage-Banner "Nach Kassenzettel verschieben" Button (v0.34.2)
  • Document-Detail Diagnose "Status prüfen" Button (v0.34.3)

Klassen umgestellt auf bg-emerald-500 / bg-amber-500 (existieren bereits) plus text-ink-950 für lesbaren dunklen Text auf hellem Fill. Plus scripts/build/build-css.sh neu gelaufen — die neu eingeführten hover:bg-emerald-300 / hover:bg-amber-400 / disabled:opacity-50 Varianten sind jetzt im kompilierten CSS drin.

Diagnostik und Salvage funktionieren korrekt seit v0.34.2/.3 — die Buttons waren nur das fehlende Frontend-Tor zum funktionierenden Backend. Lokal mit echtem Server end-to-end verifiziert: jede Klasse im Button-HTML kommt im ausgelieferten CSS vor, der Diagnostik-Endpunkt liefert sauberes JSON.

Mein Fehler

Hätte ich beim Einführen der Buttons einmal selbst durch den Browser geklickt oder den CSS-Build neu laufen lassen, wäre das nicht drei Releases lang im Live-System gestanden. Sorry für die Schleife.

Upgrade

git pull && sudo systemctl restart docusort

Dann sollte:

  • /analytics einen sichtbaren grünen "Bons neu analysieren"-Button rechts oben haben
  • Das Amber-Salvage-Banner einen sichtbaren "Nach Kassenzettel verschieben"-Button haben
  • Document-Detail eine sichtbare grüne "Status prüfen"-Schaltfläche im Diagnose-Card haben

v0.34.4 — Fix: Diagnose-Button leer + Dropdown defaulted falsch

09 May 17:37

Choose a tag to compare

Fixed — Diagnose-Button leer

In v0.34.3 hatte ich den Button-Text in zwei verschachtelte <span x-show> gepackt. Wenn Alpine für diesen Panel-Init ein Race-Problem hatte (anderer Komponent auf der Seite errort first), wurden beide Spans unsichtbar — User sah ein leeres Pill ohne Klick-Möglichkeit.

Jetzt: einfacher Button mit Plain-Text als Fallback, x-text für Lokalisierung, bg-emerald-600 damit er unübersehbar ist. Inline-Text rendert IMMER, auch wenn Alpine nie aufwacht.

Fixed — Kategorie-Dropdown zeigte "Rechnungen" auf Kassenzettel-Docs

Der Dropdown hat nur x-model für die Auswahl genutzt. Wenn Alpine nicht initialisiert (Race-Bedingung, JS-Fehler in einem anderen Komponent), fällt der Browser auf die ERSTE Option zurück → "Rechnungen" laut categories.yaml. Resultat: ein Kassenzettel-Doc sah aus, als wäre es Rechnungen, obwohl die DB stimmte.

Lösung: Jinja setzt jetzt zusätzlich selected aufs passende Option-Tag. Browser zeigt schon vor Alpine die richtige Auswahl. Alpine übernimmt danach für Änderungen wie gewohnt.

Upgrade

git pull && sudo systemctl restart docusort

Dann den ALDI-Bon nochmal öffnen:

  • Kategorie-Dropdown sollte jetzt korrekt anzeigen, was wirklich in der DB steht (vermutlich ist es Kassenzettel, nicht Rechnungen — das war nur der Anzeige-Bug)
  • Diagnose-Karte hat jetzt einen sichtbaren grünen "Status prüfen"-Button. Klick → Output zeigt mir was wirklich los ist.

v0.34.3 — Diagnose-Panel pro Dokument

09 May 17:21

Choose a tag to compare

Added — Diagnose-Panel auf der Document-Detail-Seite

Das Salvage-Banner aus v0.34.2 zeigt 0 Kandidaten, obwohl die User noch "Rechnungen"-gelabelte Bons sehen. Bisher gab's keinen Weg, um zu prüfen, an welcher Stelle die Pipeline klemmt: Classifier? Salvage-Regex? OCR-Text leer? Receipt-Row ohne Items?

Jede Document-Detail-Seite hat jetzt eine Diagnose-Karte. Klick auf "Status prüfen" → GET /api/document//diagnostics → folgendes wird angezeigt:

  • DB-Kategorie + Subcategory + Status — Vergleich mit dem angezeigten Dropdown möglich (Dropdown zeigt "Rechnungen"? Diagnose zeigt "Kassenzettel"? Dann ist's ein Frontend-Bug.)
  • OCR-Text-Länge + 1500-Zeichen-Preview (in einklappbarem <details>)
  • Receipt-Row: existiert ja/nein, Item-Anzahl, Total, Shop
  • Salvage-Signale: Liste der gematchten Pattern-Namen (bon_nr, terminal_id, kontaktlos girocard, …)
  • Invoice-Blocker: Anzahl Rechnungs-typischer Patterns (Rechnungsnummer + Fälligkeit + IBAN für Überweisung)
  • Verdict in Klartext: würde dieses Doc im /analytics-Salvage-Banner auftauchen, und wenn nein warum nicht

Keine Mutations, kein LLM-Call — purer Read.

Workflow für die Diagnose

git pull && sudo systemctl restart docusort

Dann:

  1. Öffne den ALDI-Bon der bei "Rechnungen" klemmt
  2. Scroll zur Diagnose-Karte (rechte Spalte, unter "Claude-Begründung")
  3. Klick "Status prüfen" → schick mir den Output (Screenshot reicht)

Damit sehe ich genau, ob:

  • die DB-Kategorie wirklich "Rechnungen" oder doch "Kassenzettel" ist
  • die OCR-Länge zu kurz ist (kein Text → Salvage hat nichts zu matchen)
  • die Salvage-Regex die Signale verfehlt (dann ergänze ich die Pattern)
  • die Receipt-Row da ist aber 0 Items hat (dann brauchst du nur Re-Extract)

v0.34.2 — Classifier-Härtung + Salvage-Banner für falsch eingestufte Bons

09 May 16:41

Choose a tag to compare

Changed — Classifier-Prompt: Kassenzettel vs. Rechnungen sauber trennen

Der Hauptclassifier hat Thermo-Bons öfter als "Rechnungen" eingestuft, sobald der OCR-Text "GmbH" + "USt-IdNr" + eine SUMME enthielt — selbst wenn die Doc eindeutig ein Kassenbon war. Zwei Anpassungen:

  • Rechnungen verlangt jetzt explizit mindestens zwei konkrete Rechnungs-Signale (Rechnungsnummer mit ID, Fälligkeit / Zahlungsziel / IBAN für Überweisung, Adressblock, Leistungs-/Abrechnungszeitraum). Preise + SUMME allein reichen nicht.
  • Kassenzettel listet jetzt explizit die typischen Bon-Signale (Bon-Nr., TA-Nr, BNr, Terminal-ID, Kasse N, kontaktlos girocard, EC-Cash, Telecash, K-U-N-D-E-N-B-E-L-E-G, MwSt 0=19,00%, TSE-Signatur, ZU ZAHLEN, PAYBACK, EMV-AID, Pfand/Leergut, sowie ALDI-/Deichmann-/Cineplex-Artikel-Patterns).
  • Vier neue Few-shot-Beispiele decken konkrete Fehlklassifikationen ab: ALDI Großeinkauf, Deichmann Schuhbon, Cineplex Snackbar, standalone Kartenzahlungsbeleg.

Added — Salvage-Banner für falsch klassifizierte Bons auf /analytics

Ein heuristischer Scanner (kein LLM-Call) durchsucht alle Docs in Rechnungen / Sonstiges / Bank / Vertraege / Versand und matcht den OCR-Text gegen 21 Receipt-Signale. Docs mit ≥ 2 verschiedenen Signal-Treffern UND ohne klare Invoice-Blocker (Rechnungsnummer + Fälligkeit / IBAN für Überweisung) erscheinen oben auf /analytics in einem Amber-Banner mit Checkbox-Tabelle:

  • Pro Kandidat siehst du die erkannten Signale (z.B. terminal_id, bnr, kontaktlos girocard, kundenbeleg).
  • Auswahl prüfen, False Positives abwählen, Rest mit "Nach Kassenzettel verschieben" bulk-promoten.
  • Status wird auf review gesetzt, damit jeder verschobene Doc nochmal durch deine Review-Queue läuft.

Endpoints:

  • GET /api/receipts/salvage/scan liefert Kandidaten
  • POST /api/receipts/salvage/promote mit {doc_ids: [...]} verschiebt sie

Nach dem Verschieben den "Bons neu analysieren"-Button (v0.34.1) auf /analytics drücken — der zieht jetzt die Items raus.

Upgrade

git pull && sudo systemctl restart docusort

Dann auf /analytics:

  1. Amber-Banner oben sollte deine misklassifizierten Bons zeigen.
  2. Auswahl prüfen → "Nach Kassenzettel verschieben".
  3. "Bons neu analysieren" klicken — extrahiert die Items mit dem aktualisierten v0.34.0/v0.34.1 Receipt-Prompt.

v0.34.1 — Bons neu analysieren, ALDI- & Cineplex-Few-shots

09 May 16:03

Choose a tag to compare

Added — "Bons neu analysieren"-Button auf /analytics

--reextract-receipts aus dem CLI funktionierte, gab aber bis zum Ende keine Ausgabe und sah deshalb aus wie "läuft nicht". Es gibt jetzt einen Button auf /analytics, der denselben Job in einem Hintergrund-Thread startet und Live-Progress zeigt (current/total + zuletzt verarbeiteter Shop). Single-instance: der Button ist gesperrt während ein Lauf aktiv ist; wer wegnavigiert und zurückkommt, sieht den laufenden Job.

  • POST /api/receipts/reextract?force=true startet den Job
  • GET /api/receipts/reextract/status liefert running/current/total/ok/failed/last_shop/last_error für Polling

Added — Live-Progress-Logging während Receipt-Backfill

backfill_receipts() loggt jetzt eine Zeile pro Bon ([12/87] doc 4231 OK shop='ALDI' items=53 total=149.11) und akzeptiert einen optionalen progress_cb, damit das Web-UI dieselben Daten spiegeln kann.

Changed — Receipt-Prompt: mehr Few-shot-Examples für knifflige Layouts

Drei neue Beispiele decken Real-World-Bons ab, die vorher schlecht extrahiert wurden:

  • ALDI Zwei-Zeilen-Layout — Menge/Gewicht auf einer Zeile ÜBER dem Item-Namen (2 x 1,19 € dann SKYR 2,38 € 1, 0,463 kg x 11,99 €/kg dann SCHWEINEFILET 5,55 € 1). Die abschließende 1 / 2 ist die USt-Klasse — wird ignoriert. Selbes Beispiel zeigt LEERGUTRÜCKNAHME (negativ, pfand) und PFANDWERT 1,50 (positiv, getraenke) auf einem Bon.
  • Cineplex/Kino-Menü — Sub-Items mit 1 * Prefix beschreiben den Menü-Inhalt und haben KEINE eigenen Preise. Wird zu einem Item mit dem Menüpreis zusammengefasst.
  • Kartenzahlungsbeleg ohne Items — der Stand-alone-Beleg, den manche Shops zusätzlich zum Kassenzettel drucken. Gibt items=[] mit total_amount aus der Betrag-Zeile zurück, statt Items zu erfinden.

Plus verschärfte Layout-Noise-Regeln: PAYBACK-Zeilen, EMV-Daten, TSE-/Terminal-Daten und K-U-N-D-E-N-B-E-L-E-G-Trenner sind jetzt explizit als droppable gelistet.

Upgrade

git pull && sudo systemctl restart docusort

Dann auf /analytics → "Bons neu analysieren" klicken (oder weiterhin docusort --reextract-receipts aus dem CLI).

v0.34.0 — Pfand-Aufschlag vs. Pfand-Rückgabe sauber getrennt

09 May 13:50

Choose a tag to compare

Changed — Pfand semantics on receipts: charge vs. refund cleanly separated

Receipts mixed two very different things under one pfand bucket: the deposit you PAY when buying a bottled drink (positive PFAND 0,25 line) and the deposit you GET BACK at the Leergutautomat (negative LEERGUT -1,50 line). The analytics view summed both, so "Pfand" looked like a refund category but actually contained the deposits you paid.

  • The pfand item category is now reserved for negative Pfand-Rückgabe / Leergut-Rücknahme lines only.
  • Positive Pfand-Aufschlag lines are folded into the related drink purchase as getraenke, where they belong.
  • Translation labels follow the new meaning: DE "Pfand" → "Pfand-Rückgabe", EN "Deposit" → "Deposit refund", plus ES/FR/IT.
  • Receipt prompt has explicit Pfand handling rules and a new Leergutautomat few-shot example so the model learns both cases.

Added — --reextract-receipts CLI flag

--backfill-receipts only processed Kassenzettel docs that didn't have an extraction yet. After a prompt update that fixes systematic mis-classifications, you need to overwrite existing data — the new --reextract-receipts flag does exactly that. Costs LLM tokens proportional to receipt count.

Changed — Receipt extractor max_output_tokens 2000 → 4000

Long supermarket bons (50+ items) hit the 2000-token output cap and got truncated mid-stream, leaving items missing or with broken JSON. 4000 gives enough headroom for the longest receipts.

Upgrade

git pull && sudo systemctl restart docusort
# clean up existing receipts (costs LLM tokens):
docusort --reextract-receipts

v0.33.3 — iPhone viewport fixes

07 May 18:28

Choose a tag to compare

Fixes content getting cut off / horizontal scrollbars on iPhone-sized viewports (375-414px).

  • Global overflow-x: clip on html/body and .card so inner scroll regions never push the whole page past 100vw.
  • /finance stat tiles: smaller base font, min-w-0 + break-all so multi-thousand-Euro numbers fit the 2-column mobile grid.
  • /finance accounts list: now wraps cleanly on narrow screens — bank name + balance on row 1, IBAN suffix + tx count on row 2.
  • /analytics stat tiles: same shrink + truncate treatment.