Go to file
Codex 5d81016ece Skip unchanged Excel report uploads 2026-07-31 08:03:30 +02:00
deploy/systemd Initial local PDF renaming pipeline 2026-07-23 19:08:36 +02:00
src/orc_renaming Skip unchanged Excel report uploads 2026-07-31 08:03:30 +02:00
tests Skip unchanged Excel report uploads 2026-07-31 08:03:30 +02:00
.env.example Initial local PDF renaming pipeline 2026-07-23 19:08:36 +02:00
.gitignore Initial local PDF renaming pipeline 2026-07-23 19:08:36 +02:00
Dockerfile Initial local PDF renaming pipeline 2026-07-23 19:08:36 +02:00
README.md Skip unchanged Excel report uploads 2026-07-31 08:03:30 +02:00
compose.yaml Initial local PDF renaming pipeline 2026-07-23 19:08:36 +02:00
config.example.yaml Repair malformed Ollama JSON responses 2026-07-26 21:11:42 +01:00
pyproject.toml Skip unchanged Excel report uploads 2026-07-31 08:03:30 +02:00

README.md

ORC Renaming

Datenschutzfreundliche Verarbeitung gescannter Eingangspost:

  • PDFs per Nextcloud-WebDAV herunterladen
  • vorhandenen OCR-Text lokal auslesen
  • bei Bedarf die erste PDF-Seite lokal rendern und visuell analysieren
  • Rechnungen und Korrespondenz mit Regeln und optional lokalem Ollama klassifizieren
  • kontrollierte Dateinamen erzeugen
  • Originalname, Ergebnis, Sicherheit und Fehler in SQLite und Excel protokollieren
  • Ergebnisse zurück nach Nextcloud übertragen

Die Schreibweise orc-renaming folgt dem Namen des bestehenden Repositorys. Inhaltlich geht es um OCR-Dokumente.

Dateinamensregeln

yymmdd_RE_Firma-Immobilie.pdf
yymmdd_RE_Firma-priv.pdf
yymmdd_Firma-Topic.pdf

Das verwendete Datum ist bei Rechnungen das Rechnungsdatum, ansonsten das Brief-/Dokumentdatum. Kann das System ein Pflichtfeld nicht sicher bestimmen, kommt das Dokument in manuell-pruefen. Eine optionale Privat-Fallback-Regel greift nur, wenn kein Immobilienmerkmal gefunden wurde und keine Firmenregel dies verbietet.

Sicherheitsprinzip

dry_run ist in der Beispielkonfiguration eingeschaltet. In diesem Modus werden PDFs heruntergeladen und analysiert, aber keine PDFs auf Nextcloud hochgeladen und keine Originale verschoben. Für eine einfache Diagnose wird nur protokoll.xlsx nach Nextcloud geladen, sofern upload_excel_in_dry_run: true gesetzt ist. Das Protokoll entsteht zusätzlich lokal unter state/protokoll.xlsx.

Erst nach Prüfung der Namensvorschläge sollte in config.yaml stehen:

processing:
  dry_run: false

Zugangsdaten gehören ausschließlich in Umgebungsvariablen. Für Nextcloud empfiehlt sich ein eigenes App-Passwort mit Zugriff nur auf den vorgesehenen Bereich. Umgebungsvariablen für HTTP-Proxys werden standardmäßig ignoriert (trust_env: false), damit interne Dokumentdaten nicht unbeabsichtigt über einen Proxy laufen.

Ablauf

  1. Die oberste Ebene von input_folder wird nach PDFs durchsucht.
  2. Jede Datei wird lokal in einem temporären Arbeitsverzeichnis verarbeitet.
  3. Regeln suchen Rechnungsmerkmale, Datum, Betreff, Topics, Firmen und Immobilien.
  4. Sind Angaben unvollständig oder unsicher, wird zunächst das schnelle lokale Ollama-Modell mit OCR-Text und einem Bild der ersten PDF-Seite befragt.
  5. Bleibt dessen Ergebnis unsicher, wird optional ein größeres Fallback-Modell genutzt.
  6. Nur vollständige Ergebnisse oberhalb von confidence_threshold werden automatisch nach output_folder geladen.
  7. Unsichere Ergebnisse landen mit dem Präfix PRUEFEN_ in review_folder.
  8. Nach erfolgreichem Upload wird das Original optional nach archive_folder verschoben.
  9. protokoll.xlsx wird im Ausgabeordner aktualisiert.

SHA-256-Prüfsummen und SQLite verhindern, dass erfolgreich verarbeitete Inhalte erneut verarbeitet werden. Bestehende Zieldateien werden nicht überschrieben; bei Kollisionen wird _02, _03 usw. ergänzt. Eine Prozesssperre verhindert parallele Läufe auf derselben Installation. Entsteht während eines Laufs kein neuer Protokolleintrag, wird protokoll.xlsx weder lokal neu erzeugt noch erneut zu Nextcloud hochgeladen.

Installation auf einer Linux-VM

Voraussetzungen:

  • Debian/Ubuntu oder vergleichbares Linux
  • Python 3.11 oder neuer
  • Netzwerkzugriff auf Nextcloud
  • optional Netzwerkzugriff auf den internen Ollama-Host
git clone https://gitea.muehlberger.net/JMORG/orc-renaming.git
cd orc-renaming
python3 -m venv .venv
.venv/bin/pip install .
cp config.example.yaml config.yaml
cp .env.example .env

config.yaml mit den Nextcloud-Pfaden, Immobilien, Firmen und dem Ollama-Host ausfüllen. Anschließend das Nextcloud-Passwort setzen:

export NEXTCLOUD_PASSWORD='NEXTCLOUD-APP-PASSWORT'
.venv/bin/orc-renaming --config config.yaml check-config
.venv/bin/orc-renaming --config config.yaml run

Die Shell-History kann Passwörter speichern. Im Dauerbetrieb deshalb die Environment-Datei aus dem systemd-Beispiel verwenden und auf Dateirechte 0600 setzen.

Ollama

Voreingestellt ist:

ollama:
  enabled: true
  base_url: "http://ollama.intern:11434"
  model: "qwen3.5:4B"
  fallback_model: "qwen3.5:9b"
  think: false
  keep_alive: "10m"
  context_tokens: 8192
  max_output_tokens: 700
  json_repair_attempts: 1
  json_repair_model: "qwen3.5:4B"
  vision_enabled: true
  vision_dpi: 144
  vision_image_format: "jpeg"
  vision_jpeg_quality: 80
  max_text_characters: 16000

Die Anwendung nutzt /api/chat, deaktiviertes Thinking, Temperatur 0 und ein festes JSON-Schema. Das Schema wird sowohl im API-Feld format als auch ausdrücklich im Prompt übergeben. Reichen die Regeln nicht aus, wird die erste PDF-Seite mit 144 DPI als JPEG gerendert und zusammen mit dem OCR-Text an das lokale Ollama übertragen. Der OCR-Text dient dabei als Ergänzung; das Bild bewahrt Briefkopf, Spalten und räumliche Zuordnung. Reichen die Regeln bereits für ein vollständiges, sicheres Ergebnis aus, wird Ollama nicht aufgerufen und es wird auch kein Seitenbild erzeugt.

Antwortet das Modell trotzdem mit Fließtext oder einem fremden JSON-Schema, wird seine Antwort einmal ohne Bild durch das schnelle json_repair_model in das verbindliche Schema überführt. Erst wenn auch das nicht zu einem vollständigen, hinreichend sicheren Ergebnis führt, wird das 9B-Fallback gestartet.

Unvollständige JSON-Antworten werden nicht mehr als technischer Totalausfall behandelt: vorhandene Felder werden mit reduzierter Sicherheit übernommen und mit sicheren Regeltreffern kombiniert.

Wenn der Ollama-Host nicht erreichbar ist, wird die Datei nicht falsch benannt: Unvollständige Ergebnisse gehen in die manuelle Prüfung und der Fehler erscheint im Protokoll.

Stammdaten und Privat-Fallback

Kurze, eindeutige Marker sind robuster gegen OCR-Umbrüche als vollständige Anschriften:

properties:
  - id: "WH1"
    name: "Wohnhaus Musterstraße"
    markers:
      - "Musterstraße 12"
      - "47110815"
      - "DE000123456"

Firmen können eine Standardzuordnung erhalten. Eine immobilientypische Firma kann einen zwingenden Objektmarker verlangen:

companies:
  - name: "Hausverwaltung-Muster"
    aliases:
      - "Hausverwaltung Muster GmbH"
    default_scope: "property"
    default_property_id: null
    require_property_match: true

recipient_markers enthält die normale Privatadresse. Sie ist allein kein Beweis für private Post, bestätigt aber den konfigurierbaren Fallback, wenn kein Objektmarker vorhanden ist. private_markers darf deshalb nur exklusive private Vertrags-, Versicherungs- oder Kundennummern enthalten.

Wiederkehrende Topics können ebenfalls ohne LLM erkannt werden:

topics:
  - name: "Eigentuemerversammlung"
    markers:
      - "Eigentümerversammlung"
      - "Einladung zur Eigentümerversammlung"

Das Excelprotokoll dokumentiert neben der Entscheidungsmethode auch, welche LLM-Modelle tatsächlich verwendet wurden.

Docker

Nach dem Anlegen von .env und config.yaml:

mkdir -p state work
sudo chown 10001:10001 state work
docker compose build
docker compose run --rm app

Der Container führt genau einen Lauf aus. Für einen regelmäßigen Betrieb kann ein systemd-Timer docker compose run --rm app aufrufen. Auf einer kleinen VM ist die direkte Python-/systemd-Installation meist übersichtlicher.

systemd-Timer

Die Dateien in deploy/systemd/ sind Vorlagen. Sie gehen von folgenden Pfaden aus:

  • Anwendung: /opt/orc-renaming
  • Konfiguration: /etc/orc-renaming.yaml
  • Geheimnisse: /etc/orc-renaming.env
  • Zustandsdaten: /var/lib/orc-renaming

In der produktiven Konfiguration deshalb setzen:

processing:
  state_directory: "/var/lib/orc-renaming"
  work_directory: "/var/lib/orc-renaming/work"

Danach als root:

useradd --system --home /var/lib/orc-renaming --create-home orc-renaming
install -m 0644 deploy/systemd/orc-renaming.service /etc/systemd/system/
install -m 0644 deploy/systemd/orc-renaming.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now orc-renaming.timer

Status und Logs:

systemctl list-timers orc-renaming.timer
journalctl -u orc-renaming.service

Entwicklung

python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/ruff check .
.venv/bin/pytest

Noch nicht Teil des ersten MVP:

  • OCR für bildbasierte PDFs ohne eingebettete Textebene
  • visuelle Analyse einzelner Seiten durch ein multimodales Modell
  • Weboberfläche für Freigaben
  • automatische Benachrichtigungen