Files
finance-dashboard/README.md
T

10 KiB
Raw Blame History

Finance Dashboard

Privates, schlankes Dashboard für passives Einkommen auf pinguAurora. FastAPI, Jinja2 und SQLite; Python 3.13 im Docker-Image. Kein Node, keine externen Finanz-APIs. Docker-Port 8081 → 8080, Daten-Volume ./data → /data, RAM-Limit 128 MiB bleiben bestehen.

Funktionen

  • Acht Kennzahlen, dynamischer Monats-/Jahresvergleich und drei Chart.js-Diagramme.
  • Zahlungen anlegen, bearbeiten, mit Bestätigung löschen; Positionen ergänzen.
  • Vollständige Historie unter /income, nach Jahr, Monat, Position und Kategorie filterbar, 100 Buchungen pro Seite.
  • CSV-Export, wiederholbarer XLSX-Import und getrennte Erfassung erwarteter/erhaltener Zahlungen.
  • Alle Summen basieren ausschließlich auf received=1. Der Jahresvergleich vergleicht das aktuelle Kalenderjahr mit dem gesamten Vorjahr, kein YTD-Vergleich. Bei Vorjahreswert 0 erscheint .
  • Jahres-Spalten entstehen aus den vorhandenen Buchungsjahren, dem aktuellen Jahr und dem nächsten Kalenderjahr als Ausblick; Lücken werden ergänzt. Es sind keine konkreten Jahreszahlen im Dashboard fest programmiert.

Lokale Entwicklung

python3.13 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
export FINANCE_DB_PATH="$PWD/data/finance.db"
uvicorn main:app --app-dir app --reload --host 127.0.0.1 --port 8081

requirements.txt enthält nur die Web-Abhängigkeiten, einschließlich python-multipart für HTML-Formulare. SQLite ist Teil von Python. openpyxl wird nur beim Import benötigt; httpx nur für Tests. Templates und statische Dateien werden relativ zum Anwendungscode gefunden, im Container also unter /app/templates und /app/static.

.venv/bin/python -m unittest discover -s tests -v
docker compose config

Tests verwenden ausschließlich temporäre Datenbanken. Die Entwicklungsbibliotheken werden nicht im Produktionsimage installiert.

Docker Build und Start

DOCKER_BUILDKIT=0 docker build \
  -t finance-dashboard-finance-dashboard:latest \
  .
docker compose up -d --no-build
curl --fail http://127.0.0.1:8081/health

Die App ist unter http://localhost:8081 erreichbar. /health liefert {"status":"ok"}. python:3.13-slim wird ohne feste CPU-Plattform verwendet; auf dem Raspberry wird nativ für ARM64 gebaut.

SQLite und Geldbeträge

Standardpfad: /data/finance.db, persistent auf dem Host als ./data/finance.db. Für lokale Entwicklung/Import ist FINANCE_DB_PATH oder beim Import --db verfügbar. Tabellen und die neun Grundpositionen werden beim Start automatisch angelegt.

income_entries.amount enthält ganze Cent (INTEGER), keine Euro-Floats. Python summiert Integer und berechnet Prozentwerte mit Decimal. Formulare akzeptieren 0,04, 0.04 und 28,00, ohne Tausendertrennzeichen. Mehr als zwei Nachkommastellen werden abgelehnt. Negative Beträge sind für Korrekturen erlaubt. Nur Chart.js verwendet für die grafische Anzeige JavaScript-Zahlen; das ändert keine Finanzwerte in SQLite.

SQLite nutzt Foreign Keys, WAL, kurze Transaktionen und fünf Sekunden Wartezeit bei Locks. Referenzierte Positionen können nicht physisch gelöscht werden; active=0 deaktiviert sie für neue Buchungen, vorhandene Historie bleibt erhalten. Bei temporären Datenbankproblemen antwortet die App mit HTTP 503. Schema-Version 1 wird über PRAGMA user_version geführt.

expected=1, received=0 bezeichnet eine offene/ausgefallene erwartete Zahlung. Der Betrag enthält dann die Erwartung, fließt aber nicht in tatsächliche Summen ein. Eine teilweise erhaltene Zahlung wird als erhaltene Buchung plus separate offene Restbuchung erfasst. Es gibt noch keine automatische Prognose oder Fälligkeitsverwaltung.

Excel-Import

SQLite ist die primäre Datenquelle. Excel wird nach dem Import nicht im laufenden Dashboard gelesen. Die XLSX-Datei bleibt lokal im ignorierten Verzeichnis import/.

source .venv/bin/activate
pip install -r requirements-import.txt
python scripts/import_excel.py import/tr_verbessert_2027_vergleiche_enbridge.xlsx \
  --db ./data/finance.db --dry-run
python scripts/import_excel.py import/tr_verbessert_2027_vergleiche_enbridge.xlsx \
  --db ./data/finance.db

Ohne --db verwendet das Script FINANCE_DB_PATH bzw. /data/finance.db:

python scripts/import_excel.py /pfad/datei.xlsx

Es wird nur das erste Tabellenblatt gelesen. Der Import sucht den Buchungskopf Datum, Art des Ertrags/Position, Betrag (€) und Kategorie. Dashboard-Zellen davor und weitere Blätter werden ignoriert. Ungültige Buchungen oder Formeln innerhalb der Buchungsfelder brechen den gesamten Import mit Zeilenangabe ab; es werden keine Teilimporte gespeichert. --dry-run rollt Buchungsänderungen zurück, legt jedoch bei Bedarf Datenbank und Seed an.

Bekannte Aliase werden normalisiert: MSC → Main Street Capital, CSW/CSWC → Capital Southwest, PC → Prospect Capital, Stoxx → STOXX Global Select Dividend 100, Air Baltic → airBaltic. Präfixe wie „Dividende“ werden entfernt. Groß-/Kleinschreibung, Leerzeichen und Satzzeichen führen nicht zu neuen Positionen. Unbekannte Positionen werden angelegt; unsichere Namen werden nicht anhand von Ähnlichkeit zusammengelegt. Historische airBaltic-Buchungen mit Dividendenkategorie werden als Anleihezinsen übernommen und mit einer Notiz gekennzeichnet.

Dublettenidentität: Datum, normalisierte Position, Kategorie, Centbetrag, Erwartet-/Erhalten-Status und Vorkommensnummer. Dadurch bleiben mehrere identische echte Zahlungen in einer Datei erhalten, während wiederholte Imports und passende manuelle Buchungen wiederverwendet werden. import_records merkt sich den Import auch nach Bearbeiten/Löschen einer Buchung; ein erneuter Import stellt gelöschte Buchungen nicht wieder her. Änderungen an identitätsbildenden Excel-Feldern gelten als neue Buchungen: Korrekturen nach dem Erstimport deshalb im Dashboard vornehmen. Die Excel anschließend als Archiv behandeln.

Die vorliegende Excel besitzt im Ertragsbuch keine Erwartet-/Erhalten-Spalten. Diese historischen Buchungen gelten als erhalten. Aus widersprüchlichen Dashboard-Texten werden keine zusätzlichen Zahlungen oder Ausfälle erfunden. Falls eine historische Buchung tatsächlich ausgefallen ist, ihren Status nach fachlicher Prüfung im Dashboard korrigieren. Optionale Importspalten Erwartet/Erhalten unterstützen Ja/Nein, true/false und 1/0.

Das Script zeigt einen Plausibilitätscheck: September 2026 3,71 €, einschließlich Enbridge am 02.09.2026 mit 0,04 €. Abweichungen werden gemeldet, nicht durch erfundene Buchungen ausgeglichen.

Falls data/ bereits durch Docker angelegt wurde und für deinen lokalen Benutzer nicht beschreibbar ist, kann der Import im temporären Container laufen. Die Importbibliothek wird dabei nicht im Produktionsimage gespeichert:

docker compose run --rm --no-deps -v "$PWD:/workspace:ro" finance-dashboard sh -c \
  'pip install --no-cache-dir --target /tmp/import-deps openpyxl && PYTHONPATH=/tmp/import-deps python /workspace/scripts/import_excel.py /workspace/import/tr_verbessert_2027_vergleiche_enbridge.xlsx'

CSV-Export

/export/income.csv exportiert alle Buchungen einschließlich offener Zahlungen: Datum, Position, Kategorie, Betrag, Notiz, Erwartet, Erhalten. UTF-8 mit BOM, Semikolon, Dezimalkomma und CRLF für Excel. Gefährliche Formelpräfixe in Textfeldern erhalten ein schützendes Apostroph. Export ist eine Buchungsliste, kein vollständiges Datenbankbackup.

Backup und Wiederherstellung

data/, SQLite-Dateien, Excel-Dateien und .env werden von Git ausgeschlossen. .dockerignore hält sie auch aus dem Build-Kontext fern. Nie nur die laufende SQLite-Hauptdatei kopieren: Im WAL-Modus können neuere Transaktionen noch in -wal liegen.

Konsistentes Backup während des Betriebs mit der SQLite-Backup-API:

mkdir -p backups
docker compose exec -T finance-dashboard python -c 'import sqlite3; src=sqlite3.connect("/data/finance.db"); dst=sqlite3.connect("/data/finance-backup.db"); src.backup(dst); dst.close(); src.close()'
cp data/finance-backup.db "backups/finance-$(date +%Y%m%d-%H%M%S).db"

Backup zusätzlich auf einem anderen Datenträger sichern. Wiederherstellung nur bei gestoppter App: vorhandenes data/ vollständig beiseite sichern (inkl. WAL/SHM), ein frisches data/ anlegen, die Backup-Datei dort als finance.db einsetzen und Dateirechte prüfen. Anschließend docker compose up -d --no-build. Das Deploy-Script führt keine Wiederherstellung durch und löscht keine Daten.

Deployment auf pinguAurora

Wegen des aktuellen Buildx-Versionskonflikts nicht docker compose up -d --build verwenden. Im bestehenden Checkout auf dem Raspberry:

git pull

DOCKER_BUILDKIT=0 docker build \
  -t finance-dashboard-finance-dashboard:latest \
  .

docker compose up -d --no-build

Oder im Checkout einfach:

./deploy.sh

Das ausführbare Script wechselt in sein eigenes Projektverzeichnis, nutzt git pull --ff-only, führt genau den klassischen Build und den Start ohne Build aus, zeigt den Containerstatus und prüft http://127.0.0.1:8081/health mit Wiederholungen. Bei einem Fehler bricht es ab. Es verändert keine Datenbankdateien. Zugriff: http://pinguAurora:8081.

Die lokale Datenbank wird nicht mit Git übertragen. Für die Erstübernahme auf dem Raspberry entweder die XLSX-Datei separat übertragen und das Import-Script dort in einer Python-Umgebung mit requirements-import.txt gegen ./data/finance.db ausführen, oder ein konsistentes SQLite-Backup vor dem ersten Start in das dortige data/ übernehmen. Vor einem Import in einen vorhandenen Datenbestand ein Backup erstellen.

Betrieb und Quellen

Nur für das private LAN, ohne Benutzerverwaltung. Validierung, SQL-Parameterbindung, Jinja-Autoescaping und Prüfung fremder Browser-Formularursprünge sind enthalten. Chart.js wird fest versioniert vom CDN geladen; ohne Internet funktionieren Buchungen, Kennzahlen und Tabellen weiter. Die Diagramme benötigen Zugang zum CDN.

Implementierungsreferenzen: FastAPI Templates und Chart.js Integration.