138 lines
10 KiB
Markdown
138 lines
10 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
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`.
|
||
|
||
```bash
|
||
.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
|
||
|
||
```bash
|
||
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/`.
|
||
|
||
```bash
|
||
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`:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
git pull
|
||
|
||
DOCKER_BUILDKIT=0 docker build \
|
||
-t finance-dashboard-finance-dashboard:latest \
|
||
.
|
||
|
||
docker compose up -d --no-build
|
||
```
|
||
|
||
Oder im Checkout einfach:
|
||
|
||
```bash
|
||
./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](https://fastapi.tiangolo.com/advanced/templates/) und [Chart.js Integration](https://www.chartjs.org/docs/latest/getting-started/integration.html).
|