From ffd7647c5fa25f00eacec4d242aba2d5205b2654b471f4340a85837d494a8f28 Mon Sep 17 00:00:00 2001 From: wlfb Date: Mon, 28 Sep 2026 19:44:35 +0200 Subject: [PATCH] docs: A11 Plan-Haken gesetzt Co-Authored-By: Claude Opus 5.5 (1M context) --- .../plans/2026-09-28-ausbaustufe-11.md | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/docs/superpowers/plans/2026-09-28-ausbaustufe-11.md b/docs/superpowers/plans/2026-09-28-ausbaustufe-11.md index 842a769..49237c2 100644 --- a/docs/superpowers/plans/2026-09-28-ausbaustufe-11.md +++ b/docs/superpowers/plans/2026-09-28-ausbaustufe-11.md @@ -56,7 +56,7 @@ geschrieben aus RecurringItems, PlannedItems und Tilgungsplänen aller Kredite. **Interfaces:** - Produces: `app.models.views.RESERVED_SCENARIO_NAME: str = "System-Ist"` (Task 2 verwendet sie in der View-SQL). -- [ ] **Step 1: Failing Tests schreiben** +- [x] **Step 1: Failing Tests schreiben** An `finance/tests/test_crud_api.py` anhängen (dort existiert bereits `H = {"Authorization": "Bearer test-key"}`): @@ -112,12 +112,12 @@ def test_szenario_loeschen_roundtrip_gui(client, db): assert "Wegwerf-Roundtrip" not in client.get("/szenarien").text ``` -- [ ] **Step 2: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 2: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_crud_api.py tests/test_gui.py -k "reserviert or loeschen or normale_namen"` Expected: FAIL (POST liefert 201 statt 422; Button fehlt). `test_szenario_loeschen_roundtrip_gui` und `test_szenario_normale_namen_weiter_erlaubt` dürfen schon grün sein. -- [ ] **Step 3: Konstante in `views.py`** +- [x] **Step 3: Konstante in `views.py`** In `finance/app/models/views.py` direkt nach den Imports: @@ -128,7 +128,7 @@ In `finance/app/models/views.py` direkt nach den Imports: RESERVED_SCENARIO_NAME = "System-Ist" ``` -- [ ] **Step 4: Namensprüfung in `routers/scenarios.py`** +- [x] **Step 4: Namensprüfung in `routers/scenarios.py`** Import ergänzen: `from app.models.views import RESERVED_SCENARIO_NAME` @@ -147,7 +147,7 @@ In `create_scenario` als erste Zeile: `_check_reserved_name(data.name)`. In `patch_scenario` direkt nach `fields = data.model_dump(exclude_unset=True)`: `_check_reserved_name(fields.get("name"))`. -- [ ] **Step 5: Löschen-Button in `szenarien.html`** +- [x] **Step 5: Löschen-Button in `szenarien.html`** Im `

` direkt nach dem „Bearbeiten"-Button einfügen. Bewusst ein ` ``` -- [ ] **Step 6: Tests grün + Gesamtsuite** +- [x] **Step 6: Tests grün + Gesamtsuite** Run: `cd finance && .venv/bin/python -m pytest -q` Expected: alle grün, 216 + 8 = 224 passed. -- [ ] **Step 7: Fable-Gate** — Faktencheck: Button im gerenderten HTML korrekt verschachtelt (nicht in ``/außerhalb `

`), API-422 für alle Schreibvarianten, bestehende Löschlogik unverändert (Abräumung abhängiger Zeilen), Name-Konstante nur an einer Stelle definiert. +- [x] **Step 7: Fable-Gate** — Faktencheck: Button im gerenderten HTML korrekt verschachtelt (nicht in ``/außerhalb `

`), API-422 für alle Schreibvarianten, bestehende Löschlogik unverändert (Abräumung abhängiger Zeilen), Name-Konstante nur an einer Stelle definiert. -- [ ] **Step 8: Commit** +- [x] **Step 8: Commit** ```bash git add finance/app/models/views.py finance/app/routers/scenarios.py \ @@ -192,7 +192,7 @@ git commit -m "feat: Szenario per GUI löschen, Name System-Ist reserviert (A11- - `app.services.projection_service.refresh_stale_projections(session: Session, today: date) -> int`. - `app.routers.gui._safe_refresh(session: Session, refresh: Callable[[Session, date], object], today: date) -> None` (Task 3 nutzt es). -- [ ] **Step 1: Failing Service-Tests** — `finance/tests/test_projection_refresh.py`: +- [x] **Step 1: Failing Service-Tests** — `finance/tests/test_projection_refresh.py`: ```python from datetime import date, timedelta @@ -263,7 +263,7 @@ def test_refresh_ist_idempotent(db): assert _count(db, sc.id) == get_settings().horizon_days ``` -- [ ] **Step 2: Failing GUI-Tests + View-Strukturtest** — an `finance/tests/test_gui.py` anhängen: +- [x] **Step 2: Failing GUI-Tests + View-Strukturtest** — an `finance/tests/test_gui.py` anhängen: ```python def test_szenarien_seite_rechnet_veraltete_projektion_neu(client, db): @@ -300,7 +300,7 @@ def test_v_projection_zeigt_ist_serie_und_keine_alten_punkte(): assert names.index("v_balance_total") < names.index("v_projection") ``` -- [ ] **Step 3: Postgres-View-Test anlegen** — `finance/tests/test_views_pg.py` (läuft nur, wenn `FB_TEST_PG_URL` auf eine Wegwerf-DB mit Namen `*_test` zeigt; sonst skip): +- [x] **Step 3: Postgres-View-Test anlegen** — `finance/tests/test_views_pg.py` (läuft nur, wenn `FB_TEST_PG_URL` auf eine Wegwerf-DB mit Namen `*_test` zeigt; sonst skip): ```python """Postgres-only-Prüfung der Grafana-Views (Ausbaustufe 11). @@ -387,12 +387,12 @@ def test_v_projection_ist_serie_und_szenario_ab_heute(pg): assert not any(r.scenario == "Ohne Punkte" for r in rows) ``` -- [ ] **Step 4: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 4: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_projection_refresh.py tests/test_gui.py tests/test_views_pg.py` Expected: FAIL (ImportError `refresh_stale_projections`; View-Test: `System-Ist` fehlt); `test_views_pg.py` skipped. -- [ ] **Step 5: `advisory_xact_lock` in `finance/app/db.py`** +- [x] **Step 5: `advisory_xact_lock` in `finance/app/db.py`** Import `from sqlalchemy import create_engine, text` und anhängen: @@ -405,7 +405,7 @@ def advisory_xact_lock(session: Session, key: int) -> None: session.execute(text("SELECT pg_advisory_xact_lock(:k)"), {"k": key}) ``` -- [ ] **Step 6: `refresh_stale_projections` in `projection_service.py`** +- [x] **Step 6: `refresh_stale_projections` in `projection_service.py`** Imports ergänzen: `from sqlalchemy import delete, func, select` und `from app.db import advisory_xact_lock`. Anhängen: @@ -439,7 +439,7 @@ def refresh_stale_projections(session: Session, today: date) -> int: return refreshed ``` -- [ ] **Step 7: `v_projection` in `views.py` ersetzen** +- [x] **Step 7: `v_projection` in `views.py` ersetzen** ```python # Szenario-Vergleich (Ausbaustufe 11): Ist-Gesamtsaldo bis heute als eigene @@ -465,7 +465,7 @@ def refresh_stale_projections(session: Session, today: date) -> int: Hinweis: die Spalte `scenario` wechselt von `varchar(200)` auf `text` → `CREATE OR REPLACE` schlägt fehl, `create_views` fällt automatisch auf DROP+CREATE zurück (vorhandenes Verhalten, kein Code nötig). -- [ ] **Step 8: GUI-Hooks in `routers/gui.py`** +- [x] **Step 8: GUI-Hooks in `routers/gui.py`** Imports: `import logging`, `from typing import Callable`, `from app.services.projection_service import refresh_stale_projections`. Nach `router = APIRouter()`: @@ -494,14 +494,14 @@ In `index` als erste Zeile und in `szenarien_page` als erste Zeile: (Der Name `refresh_stale_projections` wird zur Laufzeit im Modul nachgeschlagen — deshalb greift `monkeypatch.setattr("app.routers.gui.refresh_stale_projections", ...)`.) -- [ ] **Step 9: Tests grün + Gesamtsuite** +- [x] **Step 9: Tests grün + Gesamtsuite** Run: `cd finance && .venv/bin/python -m pytest -q` Expected: 224 + 8 = 232 passed, 1 skipped (`test_views_pg.py`). -- [ ] **Step 10: Fable-Gate** — (a) Wegwerf-Postgres starten (Kommando im Docstring von `test_views_pg.py`), `FB_TEST_PG_URL=... pytest tests/test_views_pg.py` → passed; Container danach entfernen. (b) Race-Probe auf derselben Wegwerf-DB: zwei Threads rufen `refresh_stale_projections` gleichzeitig für ein veraltetes Szenario → danach genau `horizon_days` Punkte (keine Duplikate). (c) Code-Review Lock-Semantik (Lock endet mit commit in `run_projection`). +- [x] **Step 10: Fable-Gate** — (a) Wegwerf-Postgres starten (Kommando im Docstring von `test_views_pg.py`), `FB_TEST_PG_URL=... pytest tests/test_views_pg.py` → passed; Container danach entfernen. (b) Race-Probe auf derselben Wegwerf-DB: zwei Threads rufen `refresh_stale_projections` gleichzeitig für ein veraltetes Szenario → danach genau `horizon_days` Punkte (keine Duplikate). (c) Code-Review Lock-Semantik (Lock endet mit commit in `run_projection`). -- [ ] **Step 11: Commit** +- [x] **Step 11: Commit** ```bash git add finance/app/db.py finance/app/models/views.py finance/app/services/projection_service.py \ @@ -530,7 +530,7 @@ git commit -m "fix: Szenario-Vergleich zeigt vor heute Ist-Saldo, veraltete Szen - `app.services.planning_chart.planning_monthly_totals(session, today: date, horizon_days: int) -> dict[tuple[date, str], Decimal]` - `app.services.planning_chart.refresh_planning_chart(session, today: date) -> int` (Anzahl geschriebener Zeilen). -- [ ] **Step 1: Failing Tests** — `finance/tests/test_planning_chart.py` (Handrechnung; `today=28.09.2026`, `horizon_days=100` → Fenster 01.09.2026 … 06.01.2027): +- [x] **Step 1: Failing Tests** — `finance/tests/test_planning_chart.py` (Handrechnung; `today=28.09.2026`, `horizon_days=100` → Fenster 01.09.2026 … 06.01.2027): ```python from datetime import date @@ -687,12 +687,12 @@ def test_planning_chart_refresh_auf_postgres(pg): assert pg.query(PlanningMonthly).count() == 1 ``` -- [ ] **Step 2: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 2: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_planning_chart.py tests/test_gui.py tests/test_grafana_dashboard.py` Expected: FAIL (ImportError `PlanningMonthly`/`planning_chart`, Panel fehlt). -- [ ] **Step 3: Modell** — an `finance/app/models/tables.py` anhängen: +- [x] **Step 3: Modell** — an `finance/app/models/tables.py` anhängen: ```python class PlanningMonthly(Base): @@ -706,7 +706,7 @@ class PlanningMonthly(Base): total: Mapped[Decimal] = mapped_column(MONEY) ``` -- [ ] **Step 4: Migration** — `finance/alembic/versions/d5e8b3c7a410_planning_monthly.py`: +- [x] **Step 4: Migration** — `finance/alembic/versions/d5e8b3c7a410_planning_monthly.py`: ```python """planning_monthly (Grafana-Panel Planung nach Kategorie) @@ -741,7 +741,7 @@ def downgrade() -> None: op.drop_table('planning_monthly') ``` -- [ ] **Step 5: Service** — `finance/app/services/planning_chart.py`: +- [x] **Step 5: Service** — `finance/app/services/planning_chart.py`: ```python """Planungs-Diagramm (Grafana-Panel „Planung nach Kategorie", Ausbaustufe 11). @@ -812,13 +812,13 @@ def refresh_planning_chart(session: Session, today: date) -> int: return len(totals) ``` -- [ ] **Step 6: GUI-Hooks** — in `routers/gui.py` Import `from app.services.planning_chart import refresh_planning_chart`. In `index` (nach dem Projektions-Refresh aus Task 2) und als erste Zeile in `planung_page`: +- [x] **Step 6: GUI-Hooks** — in `routers/gui.py` Import `from app.services.planning_chart import refresh_planning_chart`. In `index` (nach dem Projektions-Refresh aus Task 2) und als erste Zeile in `planung_page`: ```python _safe_refresh(session, refresh_planning_chart, date.today()) ``` -- [ ] **Step 7: Grafana-Panel** — in `finance/grafana/dashboards/finanzen.json` nach Panel 4 als fünftes Element von `panels` einfügen (2 Leerzeichen Einrückung wie bestehend): +- [x] **Step 7: Grafana-Panel** — in `finance/grafana/dashboards/finanzen.json` nach Panel 4 als fünftes Element von `panels` einfügen (2 Leerzeichen Einrückung wie bestehend): ```json { @@ -847,14 +847,14 @@ def refresh_planning_chart(session: Session, today: date) -> int: Danach `python3 -m json.tool finance/grafana/dashboards/finanzen.json >/dev/null` (Syntax-Check). -- [ ] **Step 8: Tests grün + Gesamtsuite** +- [x] **Step 8: Tests grün + Gesamtsuite** Run: `cd finance && .venv/bin/python -m pytest -q` Expected: 232 + 8 = 240 passed, 2 skipped (`test_views_pg.py`). -- [ ] **Step 9: Fable-Gate** — (a) Wegwerf-Postgres: `alembic upgrade head` → `alembic downgrade -1` → `alembic upgrade head` gegen `FB_DATABASE_URL` der Wegwerf-DB; Tabelle/PK per `\d planning_monthly` prüfen. (b) `FB_TEST_PG_URL=... pytest tests/test_views_pg.py` → 2 passed. (c) Unabhängige Handrechnung eines zweiten Szenarios (z.B. endfälliger Kredit `bullet` mit Zins) gegen `planning_monthly_totals`. (d) Datenschutz-Grep: keine Echtdaten in neuen Tests. +- [x] **Step 9: Fable-Gate** — (a) Wegwerf-Postgres: `alembic upgrade head` → `alembic downgrade -1` → `alembic upgrade head` gegen `FB_DATABASE_URL` der Wegwerf-DB; Tabelle/PK per `\d planning_monthly` prüfen. (b) `FB_TEST_PG_URL=... pytest tests/test_views_pg.py` → 2 passed. (c) Unabhängige Handrechnung eines zweiten Szenarios (z.B. endfälliger Kredit `bullet` mit Zins) gegen `planning_monthly_totals`. (d) Datenschutz-Grep: keine Echtdaten in neuen Tests. -- [ ] **Step 10: Commit** +- [x] **Step 10: Commit** ```bash git add finance/app/models/tables.py finance/alembic/versions/d5e8b3c7a410_planning_monthly.py \ @@ -882,7 +882,7 @@ git commit -m "feat: Grafana-Panel Planung nach Kategorie inkl. Kreditraten (A11 - Produces (JS, `window.FBSort` bzw. `module.exports`): `sortKey(raw: string, fromDataSort: bool) -> {kind:'date'|'number'|'text', value, text} | null`, `columnKind(keys) -> 'date'|'number'|'text'`, `sortItems(items: [{key, index, ...}], dir: 'asc'|'desc') -> items`, `groupRows(rows, isFollow) -> rows[][]`, `sortTable(table, colIndex, dir)`, `initTable(table)`, `init(root)`. - Template-Konventionen (Task 5 nutzt `data-sort-mode="server"`): `tr[data-sort-follow]` hängt an der Vorzeile; `td[data-sort]` überschreibt den Sortierwert (Zahlen Punkt-dezimal, Datum ISO); `th[data-nosort]` bekommt keinen Button; `table[data-sort-mode="server"]` wird übersprungen. -- [ ] **Step 1: Failing JS-Unit-Tests** — `finance/tests/js/sort.test.js`: +- [x] **Step 1: Failing JS-Unit-Tests** — `finance/tests/js/sort.test.js`: ```js 'use strict'; @@ -959,7 +959,7 @@ test('groupRows: Folgezeilen hängen an der Vorzeile', () => { }); ``` -- [ ] **Step 2: DOM-Test (jsdom, optional)** — `finance/tests/js/sort.dom.test.js`: +- [x] **Step 2: DOM-Test (jsdom, optional)** — `finance/tests/js/sort.dom.test.js`: ```js 'use strict'; @@ -1024,7 +1024,7 @@ test('init zweimal erzeugt keine doppelten Buttons', { skip: !JSDOM }, () => { }); ``` -- [ ] **Step 3: pytest-Wrapper** — `finance/tests/test_sort_js.py`: +- [x] **Step 3: pytest-Wrapper** — `finance/tests/test_sort_js.py`: ```python """Führt die JS-Tests von static/sort.js aus, falls `node` installiert ist. @@ -1047,7 +1047,7 @@ def test_sort_js(): assert r.returncode == 0, r.stdout + r.stderr ``` -- [ ] **Step 4: Failing Template-Tests** — `finance/tests/test_sort_templates.py`: +- [x] **Step 4: Failing Template-Tests** — `finance/tests/test_sort_templates.py`: ```python import re @@ -1124,12 +1124,12 @@ def test_buchungen_tabelle_ist_serverseitig(client): assert 'data-sort-mode="server"' in client.get("/buchungen").text ``` -- [ ] **Step 5: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 5: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_sort_templates.py tests/test_sort_js.py` Expected: FAIL in `test_sort_templates.py`; `test_sort_js.py` skipped (kein node). -- [ ] **Step 6: `finance/app/static/sort.js` implementieren** +- [x] **Step 6: `finance/app/static/sort.js` implementieren** ```js /* Tabellensortierung per Spaltenkopf (Ausbaustufe 11). @@ -1268,7 +1268,7 @@ Expected: FAIL in `test_sort_templates.py`; `test_sort_js.py` skipped (kein node })(); ``` -- [ ] **Step 7: Einbinden + CSS** +- [x] **Step 7: Einbinden + CSS** `base.html`: direkt nach ``: ```html @@ -1295,7 +1295,7 @@ Expected: FAIL in `test_sort_templates.py`; `test_sort_js.py` skipped (kein node } ``` -- [ ] **Step 8: Template-Attribute setzen** (jede Stelle einzeln): +- [x] **Step 8: Template-Attribute setzen** (jede Stelle einzeln): `planning.html`: - `` → `` @@ -1319,19 +1319,19 @@ Expected: FAIL in `test_sort_templates.py`; `test_sort_js.py` skipped (kein node Tilgungsplan (per JS erzeugt) bleibt bewusst unsortiert (kein htmx-Swap → kein `onLoad`). -- [ ] **Step 9: Tests grün + Gesamtsuite** +- [x] **Step 9: Tests grün + Gesamtsuite** Run: `cd finance && .venv/bin/python -m pytest -q` Expected: 240 + 6 = 246 passed, 3 skipped. -- [ ] **Step 10: Fable-Gate** — JS-Tests per Podman (Host hat kein node): +- [x] **Step 10: Fable-Gate** — JS-Tests per Podman (Host hat kein node): ```bash cd finance && podman run --rm -v "$PWD":/w:ro -w /w docker.io/library/node:22-alpine \ sh -c 'npm i --silent --no-save --prefix /tmp/j jsdom >/dev/null 2>&1; NODE_PATH=/tmp/j/node_modules node --test tests/js/' ``` Erwartet: alle Unit- und DOM-Tests grün (DOM-Tests NICHT geskippt). Zusätzlich jede Tabelle der GUI (lokaler uvicorn gegen Test-/SQLite-DB oder gerenderter HTML-Dump) auf korrekte `data-sort`/`data-nosort`/`data-sort-follow` durchsehen; Salden-Gesamtzeile im `tfoot`. -- [ ] **Step 11: Commit** +- [x] **Step 11: Commit** ```bash git add finance/app/static/sort.js finance/app/static/style.css finance/app/templates/ \ @@ -1353,7 +1353,7 @@ git commit -m "feat: Tabellen per Spaltenkopf sortierbar (A11-T4)" - Consumes: `data-sort-mode="server"` an der Buchungstabelle (Task 4); CSS `.sort-btn` (Task 4). - Produces: `app.routers.transactions.normalize_sort(sort: str | None, dir: str | None) -> tuple[str, str]`; `list_transactions(..., sort: str = "datum", dir: str = "asc", ...)`. Erlaubte Schlüssel: `datum, konto, zweck, empfaenger, betrag, kategorie`. -- [ ] **Step 1: Failing Tests** — `finance/tests/test_transactions_sort.py`: +- [x] **Step 1: Failing Tests** — `finance/tests/test_transactions_sort.py`: ```python from datetime import date, timedelta @@ -1449,12 +1449,12 @@ def test_links_erhalten_filter_und_sortierung(client, many): Hinweis zur Link-Reihenfolge: die Parameter-Reihenfolge ergibt sich aus dem `filter_params`-Dict (account_id, date_from, date_to, category_id, q, status); leere Werte entfallen — wie beim bestehenden Pager. -- [ ] **Step 2: Tests laufen lassen — müssen fehlschlagen** +- [x] **Step 2: Tests laufen lassen — müssen fehlschlagen** Run: `cd finance && .venv/bin/python -m pytest -q tests/test_transactions_sort.py` Expected: FAIL (Sortierung wird ignoriert, Links fehlen). `test_standard_bleibt_datum_aufsteigend` darf grün sein. -- [ ] **Step 3: Sortierlogik in `routers/transactions.py`** (vor `list_transactions` einfügen): +- [x] **Step 3: Sortierlogik in `routers/transactions.py`** (vor `list_transactions` einfügen): ```python # Serverseitige Sortierung der Buchungsliste (Ausbaustufe 11): Whitelist @@ -1497,9 +1497,9 @@ In `list_transactions` Parameter `sort: str = "datum", dir: str = "asc",` vor `l (Standard `datum/asc` ergibt `booking_date ASC NULLS LAST, id ASC` = bisherige Reihenfolge.) -- [ ] **Step 4: `gui.buchungen`** — Import `normalize_sort` ergänzen (`from app.routers.transactions import count_transactions, list_transactions, normalize_sort`). Parameter `sort: str = "datum", dir: str = "asc",` nach `page` ergänzen; nach `page = max(1, page)`: `sort, dir = normalize_sort(sort, dir)`; an `list_transactions(...)` zusätzlich `sort=sort, dir=dir,` übergeben; im Template-Kontext `"sort": sort, "dir": dir,` ergänzen. +- [x] **Step 4: `gui.buchungen`** — Import `normalize_sort` ergänzen (`from app.routers.transactions import count_transactions, list_transactions, normalize_sort`). Parameter `sort: str = "datum", dir: str = "asc",` nach `page` ergänzen; nach `page = max(1, page)`: `sort, dir = normalize_sort(sort, dir)`; an `list_transactions(...)` zusätzlich `sort=sort, dir=dir,` übergeben; im Template-Kontext `"sort": sort, "dir": dir,` ergänzen. -- [ ] **Step 5: Template `transactions.html`** +- [x] **Step 5: Template `transactions.html`** Vor `` einfügen (ersetzt das bisherige `pager_params` im Pager): @@ -1537,14 +1537,14 @@ Pager: den `{% set pager_params = … %}`-Block entfernen; beide Pager-Links auf (`Weiter` analog mit `page + 1`.) -- [ ] **Step 6: Tests grün + Gesamtsuite** +- [x] **Step 6: Tests grün + Gesamtsuite** Run: `cd finance && .venv/bin/python -m pytest -q` Expected: 246 + 12 = 258 passed, 3 skipped. Bestehende Pager-Tests (`test_gui.py` „Weiter", `?page=2`) weiterhin grün. -- [ ] **Step 7: Fable-Gate** — Live-Approximation gegen Wegwerf-Postgres (Collation `en_US.utf8` wie Prod): `FB_DATABASE_URL` auf Wegwerf-DB, `alembic upgrade head`, künstliche Buchungen, `uvicorn` lokal, jede Spalte auf/ab per curl inkl. Seitengrenze und Filter; manipulierte Parameter (`sort=%27;--`, `dir=`) → 200. +- [x] **Step 7: Fable-Gate** — Live-Approximation gegen Wegwerf-Postgres (Collation `en_US.utf8` wie Prod): `FB_DATABASE_URL` auf Wegwerf-DB, `alembic upgrade head`, künstliche Buchungen, `uvicorn` lokal, jede Spalte auf/ab per curl inkl. Seitengrenze und Filter; manipulierte Parameter (`sort=%27;--`, `dir=`) → 200. -- [ ] **Step 8: Commit** +- [x] **Step 8: Commit** ```bash git add finance/app/routers/transactions.py finance/app/routers/gui.py \ @@ -1566,22 +1566,22 @@ git commit -m "feat: Buchungen serverseitig nach Spalte sortierbar (A11-T5)" **Interfaces:** - Consumes: alles aus Task 1–5. -- [ ] **Step 1: `docs/ARCHITEKTUR.md`** — Komponenten-Diagramm: `S_projection` um `refresh_stale_projections` ergänzen; neuen Knoten `S_planning_chart["planning_chart.py
refresh_planning_chart"]` mit Kanten von `gui.py` und zur DB; `M_views` unverändert benannt, im Abschnitt 4 (Zentrale Konzepte) kurz: `v_projection` = System-Ist + Szenarien ab heute, reservierter Name; Tabelle `planning_monthly` (abgeleitet, bei GUI-Aufruf neu geschrieben, Advisory-Locks 110001/110002); `static/sort.js` + serverseitige Buchungs-Sortierung. Jeder im Diagramm genannte Name muss real im Code existieren (CLAUDE.md-Regel). +- [x] **Step 1: `docs/ARCHITEKTUR.md`** — Komponenten-Diagramm: `S_projection` um `refresh_stale_projections` ergänzen; neuen Knoten `S_planning_chart["planning_chart.py
refresh_planning_chart"]` mit Kanten von `gui.py` und zur DB; `M_views` unverändert benannt, im Abschnitt 4 (Zentrale Konzepte) kurz: `v_projection` = System-Ist + Szenarien ab heute, reservierter Name; Tabelle `planning_monthly` (abgeleitet, bei GUI-Aufruf neu geschrieben, Advisory-Locks 110001/110002); `static/sort.js` + serverseitige Buchungs-Sortierung. Jeder im Diagramm genannte Name muss real im Code existieren (CLAUDE.md-Regel). -- [ ] **Step 2: `hilfe.html`** — im Konzepte-Abschnitt: Szenarien-Absatz um „Löschen" und automatisches Neurechnen veralteter Szenarien ergänzen; Grafana-Absatz um „System-Ist"-Kurve und Panel „Planung nach Kategorie"; ein Satz zur Sortierung per Spaltenkopf (⇅/▲/▼; Buchungen über alle Seiten). +- [x] **Step 2: `hilfe.html`** — im Konzepte-Abschnitt: Szenarien-Absatz um „Löschen" und automatisches Neurechnen veralteter Szenarien ergänzen; Grafana-Absatz um „System-Ist"-Kurve und Panel „Planung nach Kategorie"; ein Satz zur Sortierung per Spaltenkopf (⇅/▲/▼; Buchungen über alle Seiten). -- [ ] **Step 3: Version** — `finance/VERSION`: `0.10.0` → `0.11.0`. Suite: `cd finance && .venv/bin/python -m pytest -q` → 258 passed, 3 skipped (bzw. aktueller Stand, alle grün). +- [x] **Step 3: Version** — `finance/VERSION`: `0.10.0` → `0.11.0`. Suite: `cd finance && .venv/bin/python -m pytest -q` → 258 passed, 3 skipped (bzw. aktueller Stand, alle grün). -- [ ] **Step 4: Commit Release** +- [x] **Step 4: Commit Release** ```bash git add docs/ARCHITEKTUR.md finance/app/templates/hilfe.html finance/VERSION git commit -m "chore: Release v0.11.0 (Szenario-Löschen, Grafana-Korrekturen, Tabellensortierung)" ``` -- [ ] **Step 5: Backup + Redeploy** — `./backup_finance_pod.sh` (vor Migration), dann `./create_pod_finance.sh`. Prüfen: `curl -s 127.0.0.1:8096/api/version` → `0.11.0`; `alembic current` im API-Container → `d5e8b3c7a410`. +- [x] **Step 5: Backup + Redeploy** — `./backup_finance_pod.sh` (vor Migration), dann `./create_pod_finance.sh`. Prüfen: `curl -s 127.0.0.1:8096/api/version` → `0.11.0`; `alembic current` im API-Container → `d5e8b3c7a410`. -- [ ] **Step 6: Live-Smoke** (Details nur Chat/Scratchpad, DATENSCHUTZ): +- [x] **Step 6: Live-Smoke** (Details nur Chat/Scratchpad, DATENSCHUTZ): - Seiten `/`, `/buchungen`, `/salden`, `/import`, `/planung`, `/szenarien`, `/admin`, `/hilfe` → 200. - Nach Aufruf von `/`: alle bereits gerechneten Live-Szenarien haben ersten Projektionspunkt = morgen (SQL `min(day)` je Szenario, ohne Beträge ausgeben). - `v_projection`: Serie `System-Ist` endet heute; keine Szenario-Zeile < heute. @@ -1592,4 +1592,4 @@ git commit -m "chore: Release v0.11.0 (Szenario-Löschen, Grafana-Korrekturen, T - `/buchungen?sort=betrag&dir=desc` → 200, erste Zeile = betragsgrößte Buchung (nur prüfen, nicht zitieren). - Bestandsdaten unverändert: Anzahl confirmed Buchungen, Konten, Kategorien, Szenarien wie vor dem Deploy. -- [ ] **Step 7: Fable-Release-Gate** (Whole-Branch `dd6110b..HEAD`) + Ledger-Eintrag „AUSBAUSTUFE 11 ABGESCHLOSSEN" in `.superpowers/sdd/progress.md`; Plan-Haken setzen; commit `docs: A11 Ledger + Plan-Haken`. +- [x] **Step 7: Fable-Release-Gate** (Whole-Branch `dd6110b..HEAD`) + Ledger-Eintrag „AUSBAUSTUFE 11 ABGESCHLOSSEN" in `.superpowers/sdd/progress.md`; Plan-Haken setzen; commit `docs: A11 Ledger + Plan-Haken`.