JARVIS Finance Dashboard

- complementary
  - paragraph
    - StaticText "JARVIS"
  - heading "Finance" [level=1, ref=e1]
  - paragraph
    - StaticText "PrimeVue Aura Light"
  - navigation
    - link "Command Center" [ref=e2]
    - link "Portfolio" [ref=e3]
    - link "Crypto" [ref=e4]
    - link "Aktien & ETFs" [ref=e5]
    - link "Wallets" [ref=e6]
    - link "Position hinzufügen" [ref=e7]
    - link "Reports" [ref=e8]
    - paragraph
    - link "Budget Übersicht" [ref=e9]
    - link "Budgetstatus" [ref=e10]
    - link "Budgetplanung" [ref=e11]
    - link "Fixkosten & Abos" [ref=e12]
    - link "Budget vs Ist" [ref=e13]
    - link "Monatsbudget" [ref=e14]
    - link "Jahresbudget" [ref=e15]
    - link "Budgetpläne" [ref=e16]
    - paragraph
    - link "Monatsimport" [ref=e17]
    - link "Effektive Ausgaben" [ref=e18]
    - link "Effektive Einnahmen" [ref=e19]
    - link "Buchungen prüfen" [ref=e20]
    - link "Transfers" [ref=e21]
    - paragraph
    - link "Daten-Explorer" [ref=e22]
    - link "Kategorieanalyse" [ref=e23]
    - link "Monatsvergleich" [ref=e24]
    - paragraph
    - link "Konten" [ref=e25]
    - link "Kategorien & Tags" [ref=e26]
    - link "Excel übernehmen / Seed Review" [ref=e27]
  - sectionfooter
    - paragraph
    - paragraph
    - paragraph
      - StaticText "Schreibseiten: Preview/Confirm/Audit."
- main
  - sectionheader
    - paragraph
      - StaticText "USER DASHBOARD"
    - heading "Einkünfte" [level=1, ref=e28]
    - paragraph
      - StaticText "Lokale Finance-Ansicht · externe Provider nur per expliziter Aktion"
    - button "? Hilfe" [ref=e29]
      - StaticText "? Hilfe"
    - StaticText "Read-only View"
  - generic
    - sectionheader
      - paragraph
        - StaticText "BUDGET · EINKÜNFTE"
      - heading "Einkünfte planen" [level=1, ref=e30]
      - paragraph
        - StaticText "Geplante Haushaltseinkünfte, effektive Einnahmen und Plan-vs-Ist. Jede Schreibaktion läuft über Preview → Confirm → Audit."
      - combobox [expanded=false, ref=e31]: Budgetjahr 2026
        - MenuListPopup
          - option "Budgetjahr 2026" [selected, ref=e55]
          - option "Referenzjahr 2025" [ref=e56]
    - paragraph
      - StaticText "GEPLANT MONAT"
    - paragraph
      - StaticText "CHF 11’047.00"
    - paragraph
      - StaticText "laufende Monatswerte"
    - paragraph
      - StaticText "GEPLANT JAHR"
    - paragraph
      - StaticText "CHF 132’564.00"
    - paragraph
      - StaticText "MONATSDURCHSCHNITT"
    - paragraph
      - StaticText "CHF 11’047.00"
    - paragraph
      - StaticText "inkl. Jahres-/Einmalzahlungen"
    - paragraph
      - StaticText "IST AKTUELLER MONAT"
    - paragraph
      - StaticText "CHF 10’334.75"
    - paragraph
      - StaticText "IST JAHR BISHER"
    - paragraph
      - StaticText "CHF 52’136.70"
    - paragraph
      - StaticText "PLAN-IST-ABWEICHUNG"
    - paragraph
      - StaticText "CHF -80’427.30"
    - navigation
      - button "Geplante Einkünfte" [ref=e32]
      - button "Effektive Einnahmen" [ref=e33]
      - button "Plan vs Ist" [ref=e34]
    - generic
      - heading "Einkommens-Templates" [level=2, ref=e36]
      - paragraph
        - StaticText "Prüft/ergänzt die Soll-Liste ohne Beträge und ohne Duplikate."
      - button "Templates prüfen" [ref=e37]
      - button "Templates anlegen" [disabled, ref=e38]
      - generic
        - heading "Einkunft hinzufügen" [level=2, ref=e39]
        - paragraph
          - StaticText "Erst Preview, dann Confirm. Keine stillen Mutationen."
        - button "Preview" [ref=e40]
        - button "Confirm" [disabled, ref=e41]
        - textbox "Name" [ref=e42]
        - combobox [expanded=false, ref=e43]: Marcel
          - MenuListPopup
            - option "Marcel" [selected, ref=e57]
            - option "Melanie" [ref=e58]
            - option "Haushalt" [ref=e59]
            - option "Sonstige" [ref=e60]
        - textbox "Arbeitgeber/Quelle optional" [ref=e44]
        - combobox [expanded=false, ref=e45]: Lohn
          - MenuListPopup
            - option "monatlich" [ref=e61]
            - option "jährlich" [ref=e62]
            - option "einmalig" [ref=e63]
            - option "unregelmässig" [ref=e64]
            - option "Lohn" [selected, ref=e65]
            - option "13. Monatslohn" [ref=e66]
            - option "Bonus" [ref=e67]
            - option "Rückvergütung" [ref=e68]
            - option "Rückerstattung" [ref=e69]
        - combobox [expanded=false, ref=e46]: monatlich
          - MenuListPopup
            - option "monatlich" [selected, ref=e70]
            - option "jährlich" [ref=e71]
            - option "einmalig" [ref=e72]
            - option "unregelmässig" [ref=e73]
        - textbox "Fälligkeitsmonat optional" [ref=e47]
        - textbox "Planwert" [ref=e48]
        - combobox [expanded=false, ref=e49]: Planwert ist Monatswert
          - MenuListPopup
            - option "Planwert ist Monatswert" [selected, ref=e74]
            - option "Planwert ist Jahres-/Einmalwert" [ref=e75]
        - textbox [disabled, ref=e50]: CHF
          - StaticText "CHF"
        - combobox [expanded=false, ref=e51]: Lohn Marcel
          - MenuListPopup
            - option "Kategorie" [ref=e76]
            - option "Lohn Marcel" [selected, ref=e77]
            - option "Lohn Melanie" [ref=e78]
            - option "Gratifikation" [ref=e79]
            - option "Sonstige Einnahmen" [ref=e80]
            - option "Rückerstattungen" [ref=e81]
        - textbox "Notiz" [ref=e52]
      - generic
        - heading "Geplante Einkünfte" [level=2, ref=e53]
        - paragraph
          - StaticText "Planwerte, Monatsdurchschnitt und Ist-Abgleich je Einkommensposition. Reihenfolge kann gespeichert werden."
        - button "Reihenfolge speichern" [ref=e54]
        - table
          - rowgroup
            - row
              - columnheader "REIHENFOLGE" [ref=e212]
              - columnheader "NAME" [ref=e213]
              - columnheader "PERSON/QUELLE" [ref=e214]
              - columnheader "TYP" [ref=e215]
              - columnheader "FÄLLIGKEIT" [ref=e216]
              - columnheader "MONATSWERT" [ref=e217]
              - columnheader "JAHRESWERT" [ref=e218]
              - columnheader "MONATSDURCHSCHNITT" [ref=e219]
              - columnheader "IST YTD" [ref=e220]
              - columnheader "PROGNOSE JAHR" [ref=e221]
              - columnheader "ABWEICHUNG" [ref=e222]
              - columnheader "STATUS" [ref=e223]
              - columnheader "AKTIONEN" [ref=e224]
          - row
            - cell "↑ ↓" [ref=e82]
              - button "↑" [ref=e225]
              - button "↓" [ref=e226]
            - cell "13. Monatslohn Marcel" [ref=e83]
            - cell "Marcel" [ref=e84]
            - cell "13. Monatslohn" [ref=e85]
            - cell "Dezember" [ref=e86]
            - cell "CHF 0.00" [ref=e87]
            - cell "CHF 0.00" [ref=e88]
            - cell "CHF 0.00" [ref=e89]
            - cell "CHF 50’884.50" [ref=e90]
            - cell "CHF 0.00" [ref=e91]
            - cell "CHF 50’884.50" [ref=e92]
            - cell "Noch keine Planwerte" [ref=e93]
            - cell "Planwert bearbeiten tatsächliche Einnahme Archivieren Verlauf" [ref=e94]
              - button "Planwert bearbeiten" [ref=e227]
              - button "tatsächliche Einnahme" [ref=e228]
              - button "Archivieren" [ref=e229]
              - button "Verlauf" [ref=e230]
          - row
            - cell "↑ ↓" [ref=e95]
              - button "↑" [ref=e231]
              - button "↓" [ref=e232]
            - cell "Lohn Marcel (ERNE)" [ref=e96]
            - cell "Marcel · ERNE" [ref=e97]
            - cell "Lohn" [ref=e98]
            - cell "monatlich" [ref=e99]
            - cell "CHF 11’047.00" [ref=e100]
            - cell "CHF 132’564.00" [ref=e101]
            - cell "CHF 11’047.00" [ref=e102]
            - cell "CHF 50’884.50" [ref=e103]

[... 170 more lines truncated, use browser_snapshot for full content]


(()=>{const t=document.body.innerText.toLowerCase(); return {route:'income', ok:t.includes('einkünfte planen') && t.includes('preview') && t.includes('confirm') && t.includes('keine stillen mutationen')}})()


Browser-Sanity ist sauber, keine JS-Fehler. Ich committe und pushe jetzt; davor finaler Git-Safety-Check auf Artefakte.

terminal

set -euo pipefail
cd /home/agent/.hermes/repos/FinanceManager
if git status --short | grep -E '(^| )[AM?].*(finance\.sqlite3|jarvis_runtime|secrets/|\.env|\.csv|\.xlsx|\.pdf|frontend/dist/)'; then echo 'unsafe artifact in working tree'; exit 1; fi
git status --short --branch
git diff --stat


Output:
## feat/budget-monthly-import-rule-learning-v1
 M frontend/src/api/budget.ts
 M frontend/src/components/SidebarNav.vue
 M frontend/src/pages/BudgetRulesPage.vue
 M frontend/src/pages/BudgetUxV2.test.ts
 M frontend/src/router/index.ts
 M src/jarvis_finance/api/routers/budget.py
 M src/jarvis_finance/storage/migrations.py
 M tests/unit/test_budget_phase1.py
 M tests/unit/test_budget_phase11.py
 M tests/unit/test_budget_phase12_seed_review.py
 M tests/unit/test_budget_phase16_user_rules.py
 M tests/unit/test_fixed_costs_subscriptions_v1.py
 M tests/unit/test_schema.py
?? frontend/src/pages/BudgetMonthlyImportPage.test.ts
?? frontend/src/pages/BudgetMonthlyImportPage.vue
?? src/jarvis_finance/services/budget_monthly_import.py
?? tests/unit/test_budget_monthly_import_rule_learning_v1.py
 frontend/src/api/budget.ts                      | 12 +++++
 frontend/src/components/SidebarNav.vue          |  2 +-
 frontend/src/pages/BudgetRulesPage.vue          | 15 +++++--
 frontend/src/pages/BudgetUxV2.test.ts           |  8 +++-
 frontend/src/router/index.ts                    |  2 +
 src/jarvis_finance/api/routers/budget.py        | 52 ++++++++++++++++++++++
 src/jarvis_finance/storage/migrations.py        | 59 ++++++++++++++++++++++++-
 tests/unit/test_budget_phase1.py                |  2 +-
 tests/unit/test_budget_phase11.py               |  2 +-
 tests/unit/test_budget_phase12_seed_review.py   |  2 +-
 tests/unit/test_budget_phase16_user_rules.py    |  2 +-
 tests/unit/test_fixed_costs_subscriptions_v1.py |  2 +-
 tests/unit/test_schema.py                       |  2 +-
 13 files changed, 149 insertions(+), 13 deletions(-)


set -euo pipefail
cd /home/agent/.hermes/repos/FinanceManager
git add frontend/src/api/budget.ts frontend/src/components/SidebarNav.vue frontend/src/pages/BudgetRulesPage.vue frontend/src/pages/BudgetUxV2.test.ts frontend/src/router/index.ts frontend/src/pages/BudgetMonthlyImportPage.vue frontend/src/pages/BudgetMonthlyImportPage.test.ts src/jarvis_finance/api/routers/budget.py src/jarvis_finance/storage/migrations.py src/jarvis_finance/services/budget_monthly_import.py tests/unit/test_budget_monthly_import_rule_learning_v1.py tests/unit/test_budget_phase1.py tests/unit/test_budget_phase11.py tests/unit/test_budget_phase12_seed_review.py tests/unit/test_budget_phase16_user_rules.py tests/unit/test_fixed_costs_subscriptions_v1.py tests/unit/test_schema.py
git commit -m "feat: add monthly budget import workflow" >/dev/null
printf 'commit='; git rev-parse HEAD


Commit erstellt. Ich pushe mit dem vorhandenen GitHub-Token und verifiziere den Remote-Hash.

set -euo pipefail
cd /home/agent/.hermes/repos/FinanceManager
TOKEN_FILE="$HOME/jarvis_runtime/finance-system/secrets/Github_token_FinanceManager.csv"
ASKPASS=$(mktemp)
python - "$TOKEN_FILE" "$ASKPASS" <<'PY'
import csv, pathlib, stat, sys
src=pathlib.Path(sys.argv[1]); ask=pathlib.Path(sys.argv[2])
token=''
for row in csv.reader(src.read_text().splitlines()):
    for cell in row:
        cell=cell.strip()
        if cell.startswith(('github_pat_','ghp_')):
            token=cell; break
    if token: break
if not token:
    raise SystemExit('no token found')
ask.write_text('#!/bin/sh\ncase "$1" in *Username*) printf "%s" "x-access-token";; *) printf "%s" "'+token+'";; esac\n')
ask.chmod(0o700)
PY
cleanup(){ rm -f "$ASKPASS"; }
trap cleanup EXIT
export GIT_ASKPASS="$ASKPASS"
export GIT_TERMINAL_PROMPT=0
git push -u origin feat/budget-monthly-import-rule-learning-v1 >/tmp/fm_push_mi1.log 2>&1 || { sed 's/github_pat_[A-Za-z0-9_]*/[REDACTED]/g; s/ghp_[A-Za-z0-9_]*/[REDACTED]/g' /tmp/fm_push_mi1.log; exit 1; }
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git ls-remote origin refs/heads/feat/budget-monthly-import-rule-learning-v1 | awk '{print $1}')
printf 'local=%s\nremote=%s\nmatch=%s\n' "$LOCAL" "$REMOTE" "$(test "$LOCAL" = "$REMOTE" && echo yes || echo no)"


Output:
local=7170d47ae99f338af23dbf164656cbcb81b7a032
remote=7170d47ae99f338af23dbf164656cbcb81b7a032
match=yes


Push/Remote-Hash sauber. Ich prüfe final den Working Tree und markiere den Sprint abgeschlossen.

set -euo pipefail
cd /home/agent/.hermes/repos/FinanceManager
printf 'branch='; git branch --show-current
printf 'head='; git rev-parse HEAD
printf '\nstatus=\n'; git status --short
printf 'backend_pid='; ss -ltnp 2>/dev/null | awk '$4 ~ /:8000/ {print $0}' | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | sort -u | paste -sd, -
printf '\nfrontend_pid='; ss -ltnp 2>/dev/null | awk '$4 ~ /:5173/ {print $0}' | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | sort -u | paste -sd, -
printf '\n'


Output:
branch=feat/budget-monthly-import-rule-learning-v1
head=7170d47ae99f338af23dbf164656cbcb81b7a032

status=
backend_pid=629230

frontend_pid=629266


todo

Restart/Tailscale Browser sanity, Commit, Push, Abschlussbericht


Preflight: Branch erstellen, bestehende Import-/Candidate-/Rule-/Drive-Strukturen inventarisieren

RED-Tests: Monatsimport, Idempotenz, Klassifizierung, Rule Learning, UI, Safety

Backend: Import Sessions/History, Drive Scan Adapter, Source Profile/Dry Run, Fingerprints/Idempotenz, Rule Suggestions APIs

Frontend: Monatsimport-Seite, Import Dashboard/History, Review-Links, Regelvorschläge in Regeln

Verifikation: compile, pytest, npm test/build, secret scans, Git-Safety, diff-check


## Budget Monthly Import Workflow & Rule Learning v1 — abgeschlossen

**Branch:** `feat/budget-monthly-import-rule-learning-v1`
**Commit:** `7170d47ae99f338af23dbf164656cbcb81b7a032`
**Push:** ja, Remote-Hash verifiziert
**PR-Link:** https://github.com/Gamexgit/FinanceManager/pull/new/feat/budget-monthly-import-rule-learning-v1

## Ergebnis — Ja/Nein

- **Monatsimport-Seite umgesetzt:** ja
  Route: `http://100.85.29.67:5173/planning/budget/monthly-import`

- **Google Drive Scan umgesetzt:** ja, als read-only Metadaten-/Adapter-Workflow
  Der Backend-Adapter scannt `Finanzen/03 Budget` über `gog`. Echte CSV-Dateien bleiben runtime/temp-only und werden nicht ins Git geschrieben. Keine produktive Mutation durch Scan.

- **Import Sessions / Historie umgesetzt:** ja
  Neue Import-Session-/History-Struktur mit:
  - Importdatum
  - Quelle
  - Dateiname
  - Profil
  - Zeilen total
  - neue Kandidaten
  - bekannte/processed Einträge
  - Duplikate
  - ignored / covered_by_source
  - Fehler
  - Status

- **Idempotenz gehärtet:** ja
  Fingerprints und Import-Line-Tracking für:
  - VISA
  - Migros
  - AKB
  - Raiffeisen

  Wiederholter Import erzeugt keine doppelten Kandidaten/Buchungen. Bereits bestätigte, ignorierte und `covered_by_migros`-Einträge werden entsprechend erkannt.

- **Auto-Klassifizierung beim Import:** ja
  Implementiert für:
  - Migros Bon → Essen & Haushalt / ready
  - VISA Migros → `covered_by_migros`
  - Apple / Netflix / Disney / Google / Amazon Prime → regel-/merchant-artige Vorzuordnung
  - Galaxus/Digitec → Review
  - Lohn → income candidate
  - AKB/Raiffeisen intern → transfer candidate
  - True Wealth → investment_transfer
  - Kreditkartenausgleich → credit_card_payment

- **Rule Learning umgesetzt:** ja
  Manuelle Kategorieänderung kann Regelvorschläge erzeugen. Vorschläge enthalten:
  - Merchant/Pattern
  - Match-Typ
  - Quelle
  - Kategorie
  - optional recurring_type
  - Confidence
  - Preview-Zähler betroffener offener Kandidaten

- **Regelvorschläge-Seite umgesetzt:** ja
  In `Budget > Regeln` ergänzt:
  `http://100.85.29.67:5173/planning/budget/rules`

  Aktionen:
  - übernehmen & anwenden
  - ablehnen
  - später prüfen

- **Review-Übergang mit Filter umgesetzt:** ja
  Monatsimport und Import-History verlinken nach:
  `Buchungen prüfen` mit Source/Status/Import-Session-tauglichen Review-URLs.

- **Keine automatische Produktivbuchung:** bestätigt
  Monatsimport darf:
  - Kandidaten erzeugen
  - Auto-Kategorie setzen
  - `ready_for_confirm` / `needs_review` setzen
  - Rule Suggestions erstellen

  Monatsimport darf nicht und tut nicht:
  - Budgettransaktion final erzeugen
  - Bulk Confirm automatisch ausführen
  - Einkommen final buchen
  - Transfers final buchen

## Neue/erweiterte Hauptbereiche

### Backend

- Schema-Version auf **31** erhöht.
- Neue Tabellen:
  - `budget_import_sessions`
  - `budget_import_line_items`
  - `budget_rule_suggestions`
- Neue Service-Datei:
  - `src/jarvis_finance/services/budget_monthly_import.py`
- Neue API-Endpunkte:
  - `GET /api/budget/monthly-import/dashboard`
  - `GET /api/budget/monthly-import/history`
  - `POST /api/budget/monthly-import/drive-scan`
  - `POST /api/budget/monthly-import/dry-run`
  - `POST /api/budget/monthly-import/confirm-candidates`
  - `GET /api/budget/rules/suggestions`
  - `POST /api/budget/rules/suggestions/{id}/apply`
  - `POST /api/budget/rules/suggestions/{id}/reject`

### Frontend

- Neue Seite:
  - `BudgetMonthlyImportPage.vue`
- Neue Tests:
  - `BudgetMonthlyImportPage.test.ts`
- Navigation ergänzt:
  - `Budget > Monatsimport`
- Regel-Seite erweitert:
  - Sektion `Regelvorschläge`

## Verifikation

- **Python compile:** OK
- **Backend pytest:** `467 passed`
- **Frontend Vitest:** `83 passed`
- **Frontend build:** OK
- **Source secret scan:** OK
- **Frontend build secret scan:** OK
- **Git-Safety:** OK
- **git diff --check:** OK
- **Runtime-DB im Git:** nein
- **CSV-Dateien im Git:** nein
- **Secrets im Git:** nein
- **Frontend dist im Git:** nein

## Browser Sanity über Tailscale

Geprüft und OK:

- **Monatsimport:**
  `http://100.85.29.67:5173/planning/budget/monthly-import`

- **Buchungen prüfen:**
  `http://100.85.29.67:5173/planning/budget/expenses/review?source=VISA&status=ready_for_confirm`

- **Regeln / Regelvorschläge:**
  `http://100.85.29.67:5173/planning/budget/rules`

- **Effektive Ausgaben:**
  `http://100.85.29.67:5173/planning/budget/expenses/actual`

- **Einkünfte:**
  `http://100.85.29.67:5173/planning/budget/income`

- **JS-Fehler:** keine

## Laufende Services

- **Backend:** `http://100.85.29.67:8000`
  PID `629230`

- **Frontend:** `http://100.85.29.67:5173`
  PID `629266`

## Offene Grenzen

- Der Google-Drive-Scan ist als sicherer read-only Adapter/Workflow vorbereitet. Echter produktiver Monatslauf mit Deinen realen Drive-CSV-Dateien wurde nicht gestartet — korrekt so, denn dafür brauche ich später explizit Deinen Befehl.
- Die Monatsimport-UI enthält eine Demo-Dry-Run-Aktion für Browser-/Workflow-Sanity. Produktive Verarbeitung läuft über die Backend-Endpunkte mit Drive-/CSV-Payload.
- Keine echten Beträge wurden im Abschlussbericht genannt.
- Keine echten CSVs wurden committed.
- Keine automatische Massenbuchung wurde gebaut. Sehr unromantisch, aber genau richtig.


[M G] [IMPORTANT: Background process proc_9b8f3db3bfa0 matched watch pattern "Application startup complete".
Command: source ~/jarvis_runtime/finance-system/venv/bin/activate
export JARVIS_FINANCE_RUNTIME_DIR=~/jarvis_runtime/finance-system
unset JARVIS_FINANCE_DB_PATH
PYTHONPATH=src uvicorn jarvis_finance.api.main:app --host 0.0.0.0 --port 8000
Matched output:
INFO:     Application startup complete.]


Ich prüfe kurz, ob das nur ein verspätetes Watcher-Echo ist oder ob tatsächlich ein alter Backend-Prozess noch lebt.

terminal

set -euo pipefail
printf 'listening_8000=\n'; ss -ltnp 2>/dev/null | awk '$4 ~ /:8000/ {print $0}' || true
printf '\nlistening_5173=\n'; ss -ltnp 2>/dev/null | awk '$4 ~ /:5173/ {print $0}' || true
printf '\nhealth_backend='; curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8000/api/health || true
printf '\nhealth_frontend='; curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:5173/ || true
printf '\nbranch='; git branch --show-current
printf '\nhead='; git rev-parse HEAD
printf '\n'


Output:
listening_8000=
LISTEN 0      2048                      0.0.0.0:8000       0.0.0.0:*    users:(("uvicorn",pid=629230,fd=6))

listening_5173=
LISTEN 0      511                       0.0.0.0:5173       0.0.0.0:*    users:(("node",pid=629266,fd=21))

health_backend=200
health_frontend=200
branch=feat/budget-monthly-import-rule-learning-v1

head=7170d47ae99f338af23dbf164656cbcb81b7a032


Das ist eine **verspätete Watcher-Meldung** von einem alten Backend-Start (`proc_9b8f3db3bfa0`). Kann ignoriert werden.

Aktueller Stand:

- **Backend aktiv:** PID `629230`
- **Frontend aktiv:** PID `629266`
- **Backend Health:** `200`
- **Frontend Health:** `200`
- **Branch:** `feat/budget-monthly-import-rule-learning-v1`
- **Commit:** `7170d47ae99f338af23dbf164656cbcb81b7a032`

Kurz: Alles läuft korrekt. Der alte Prozess ist nicht der aktive Listener.