Releases: robeertm/DocuSort
v0.34.8 — Receipt-Editor: Auto-Recalc Line + Gesamtsumme
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
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.:
- Den Bon nochmal in den /inbox legen → Re-OCR mit psm=4 → neue saubere OCR-Daten
- 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
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 leereditForm→ Kategorie-Dropdown defaulted auf "Rechnungen" (Alpine x-model schlug fehl)receiptEditor→ Save / Add-Item / Re-Extract totretryWidget→ Retry-Button tottrashWidget→ 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)
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:
/analyticseinen 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
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
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:
- Öffne den ALDI-Bon der bei "Rechnungen" klemmt
- Scroll zur Diagnose-Karte (rechte Spalte, unter "Claude-Begründung")
- 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
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
reviewgesetzt, damit jeder verschobene Doc nochmal durch deine Review-Queue läuft.
Endpoints:
GET /api/receipts/salvage/scanliefert KandidatenPOST /api/receipts/salvage/promotemit{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:
- Amber-Banner oben sollte deine misklassifizierten Bons zeigen.
- Auswahl prüfen → "Nach Kassenzettel verschieben".
- "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
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=truestartet den JobGET /api/receipts/reextract/statusliefert 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 €dannSKYR 2,38 € 1,0,463 kg x 11,99 €/kgdannSCHWEINEFILET 5,55 € 1). Die abschließende1/2ist die USt-Klasse — wird ignoriert. Selbes Beispiel zeigtLEERGUTRÜCKNAHME(negativ,pfand) undPFANDWERT 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
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
pfanditem 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
Fixes content getting cut off / horizontal scrollbars on iPhone-sized viewports (375-414px).
- Global
overflow-x: clipon html/body and.cardso inner scroll regions never push the whole page past 100vw. - /finance stat tiles: smaller base font,
min-w-0+break-allso 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.