docs: A12 Plan-Haken gesetzt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 07:52:17 +02:00
co-authored by Claude Opus 5.5
parent f79e1ce26b
commit b904709d0b
@@ -47,7 +47,7 @@
**Interfaces:**
- Produces: `app.reserved.SYSTEM_PREFIX: str`, `app.reserved.RESERVED_MESSAGE: str`, `app.reserved.is_reserved_name(name: str) -> bool`, `app.reserved.check_reserved_name(name: str | None) -> None` (wirft `HTTPException(422, RESERVED_MESSAGE)`).
- [ ] **Step 1: Failing Tests** — `finance/tests/test_reserved.py`:
- [x] **Step 1: Failing Tests** — `finance/tests/test_reserved.py`:
```python
import pytest
@@ -102,12 +102,12 @@ def test_szenario_praefix_reserviert(client, name):
assert "reserviert" in r.json()["detail"]
```
- [ ] **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_reserved.py`
Expected: FAIL (`ModuleNotFoundError: app.reserved`).
- [ ] **Step 3: `finance/app/reserved.py`**
- [x] **Step 3: `finance/app/reserved.py`**
```python
"""Reservierte Namen (Ausbaustufe 12).
@@ -136,7 +136,7 @@ def check_reserved_name(name: str | None) -> None:
raise HTTPException(422, RESERVED_MESSAGE)
```
- [ ] **Step 4: Konstanten ableiten**
- [x] **Step 4: Konstanten ableiten**
`finance/app/models/views.py`: Import `from app.reserved import SYSTEM_PREFIX` und
```python
@@ -146,20 +146,20 @@ RESERVED_SCENARIO_NAME = f"{SYSTEM_PREFIX}Ist"
`finance/app/services/planning_chart.py`: Import `from app.reserved import SYSTEM_PREFIX`, `KREDITRATEN = f"{SYSTEM_PREFIX}Kreditraten"`; im Modul-Docstring „Kreditraten" → „System-Kreditraten".
- [ ] **Step 5: Router**
- [x] **Step 5: Router**
`finance/app/routers/scenarios.py`: Import `RESERVED_SCENARIO_NAME` und die Funktion `_check_reserved_name` entfernen; `from app.reserved import check_reserved_name`; die beiden Aufrufe `_check_reserved_name(...)` → `check_reserved_name(...)` (gleiche Stellen: erste Zeile `create_scenario`, nach `model_dump` in `patch_scenario`).
`finance/app/routers/categories.py`: `from app.reserved import check_reserved_name`; als erste Zeile in `create_category` und `patch_category`: `check_reserved_name(data.name)`.
- [ ] **Step 6: Tests grün + Gesamtsuite**
- [x] **Step 6: Tests grün + Gesamtsuite**
Run: `cd finance && .venv/bin/python -m pytest -q`
Expected: 260 + 17 = **277 passed, 3 skipped** (bestehende System-Ist-Tests in `test_crud_api.py` bleiben grün, ihre Meldung enthält „reserviert").
- [ ] **Step 7: Fable-Gate** — Faktencheck: Präfix nur in `reserved.py` als Literal (`grep -rn '"System-' app/` → nur `reserved.py`), View-SQL rendert weiterhin `'System-Ist'`, alle Namens-Schreibwege für Kategorien/Szenarien geprüft, keine Bestandsdaten betroffen.
- [x] **Step 7: Fable-Gate** — Faktencheck: Präfix nur in `reserved.py` als Literal (`grep -rn '"System-' app/` → nur `reserved.py`), View-SQL rendert weiterhin `'System-Ist'`, alle Namens-Schreibwege für Kategorien/Szenarien geprüft, keine Bestandsdaten betroffen.
- [ ] **Step 8: Commit**
- [x] **Step 8: Commit**
```bash
git add finance/app/reserved.py finance/app/models/views.py finance/app/services/planning_chart.py \
@@ -190,7 +190,7 @@ git commit -m "feat: Präfix System- für Tool-Serien reserviert, Kreditraten-Se
- `all_conflicts(session) -> list[Konflikt]` (sortiert nach Posten-, dann Kreditname)
- Abweichung ggü. Spec: `exclude_id` entfällt — die Prüfung ist typübergreifend (Posten gegen Kredite), ein Datensatz kann nicht mit sich selbst kollidieren.
- [ ] **Step 1: Failing Tests** — `finance/tests/test_double_count.py`:
- [x] **Step 1: Failing Tests** — `finance/tests/test_double_count.py`:
```python
from datetime import date
@@ -294,12 +294,12 @@ def test_conflicts_for_recurring_und_loan(db):
assert [k.posten_name for k in conflicts_for_loan(db, kredit)] == ["Miete"]
```
- [ ] **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_double_count.py`
Expected: FAIL (`ModuleNotFoundError`).
- [ ] **Step 3: `finance/app/services/double_count.py`**
- [x] **Step 3: `finance/app/services/double_count.py`**
```python
"""Doppelzählungs-Erkennung Kredit ↔ wiederkehrender Posten (Ausbaustufe 12).
@@ -431,14 +431,14 @@ def all_conflicts(session: Session) -> list[Konflikt]:
return out
```
- [ ] **Step 4: Tests grün + Gesamtsuite**
- [x] **Step 4: Tests grün + Gesamtsuite**
Run: `cd finance && .venv/bin/python -m pytest -q`
Expected: 277 + 20 = **297 passed, 3 skipped** (1 + 5 + 3 + 1 + 1 + 6 + 1 + 1 + 1).
- [ ] **Step 5: Fable-Gate** — unabhängige Handrechnung: je ein Annuitätenkredit mit Zins (eigene Werte), endfällig mit Zins, Grenzfälle der Toleranz auf den Cent, Zeitraum-Grenzen an erster/letzter Rate (inkl. Monatsende-Clamp, z.B. Auszahlung 31.01.) gegen `find_conflict`; Decimal-Durchgängigkeit (kein float).
- [x] **Step 5: Fable-Gate** — unabhängige Handrechnung: je ein Annuitätenkredit mit Zins (eigene Werte), endfällig mit Zins, Grenzfälle der Toleranz auf den Cent, Zeitraum-Grenzen an erster/letzter Rate (inkl. Monatsende-Clamp, z.B. Auszahlung 31.01.) gegen `find_conflict`; Decimal-Durchgängigkeit (kein float).
- [ ] **Step 6: Commit**
- [x] **Step 6: Commit**
```bash
git add finance/app/services/double_count.py finance/tests/test_double_count.py
@@ -457,7 +457,7 @@ git commit -m "feat: Erkennung möglicher Doppelzählung Kredit/Fixposten (A12-T
- Consumes (Task 2): `CODE`, `PostenDaten`, `KreditDaten`, `Konflikt`, `posten_aus`, `kredit_aus`, `conflicts_for_recurring`, `conflicts_for_loan`.
- Produces: 409-Body `{"detail": {"code": "doppelzaehlung_kredit", "meldung": str, "konflikte": [{"posten", "posten_betrag", "kredit", "kredit_rate"}]}}` (Beträge als Strings, Punkt-Dezimal, 2 Nachkommastellen). Request-Feld `doppelzaehlung_bestaetigt` (Task 4 sendet es).
- [ ] **Step 1: Failing Tests** — `finance/tests/test_double_count_api.py`:
- [x] **Step 1: Failing Tests** — `finance/tests/test_double_count_api.py`:
```python
from decimal import Decimal
@@ -553,12 +553,12 @@ def test_vorschlag_uebernehmen_nutzt_denselben_endpunkt(client):
assert r.status_code == 409
```
- [ ] **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_double_count_api.py`
Expected: FAIL (201 statt 409).
- [ ] **Step 3: Implementierung in `routers/planning.py`**
- [x] **Step 3: Implementierung in `routers/planning.py`**
Imports:
```python
@@ -648,14 +648,14 @@ nach der bestehenden Ende-vor-Start-Prüfung, vor `session.commit()`:
Hinweis: Bei neu angelegten Datensätzen ist `posten_id`/`kredit_id` in der Konflikt-Antwort `None` — die Antwort enthält bewusst nur Namen und Beträge.
- [ ] **Step 4: Tests grün + Gesamtsuite**
- [x] **Step 4: Tests grün + Gesamtsuite**
Run: `cd finance && .venv/bin/python -m pytest -q`
Expected: 297 + 8 = **305 passed, 3 skipped**; alle bestehenden Planning-/Szenario-Tests grün (z.B. `test_loan_in_scenario_keeps_balance_positive`: Miete −600 vs. Rate 117,43 → kein Konflikt).
- [ ] **Step 5: Fable-Gate** — Live-Approximation: lokaler uvicorn gegen Wegwerf-SQLite oder Wegwerf-Postgres (`FB_DATABASE_URL`, `alembic upgrade head`), curl-Roundtrips aller vier Schreibwege mit/ohne Flag, reine Umbenennung, Konflikt-Paar-Logik bei PATCH, Antwortschemata ohne Flag-Feld, 409-Body exakt; danach Aufräumen.
- [x] **Step 5: Fable-Gate** — Live-Approximation: lokaler uvicorn gegen Wegwerf-SQLite oder Wegwerf-Postgres (`FB_DATABASE_URL`, `alembic upgrade head`), curl-Roundtrips aller vier Schreibwege mit/ohne Flag, reine Umbenennung, Konflikt-Paar-Logik bei PATCH, Antwortschemata ohne Flag-Feld, 409-Body exakt; danach Aufräumen.
- [ ] **Step 6: Commit**
- [x] **Step 6: Commit**
```bash
git add finance/app/routers/planning.py finance/tests/test_double_count_api.py
@@ -679,7 +679,7 @@ git commit -m "feat: API meldet mögliche Doppelzählung mit 409, Speichern nur
- Consumes: 409-Body aus Task 3; `all_conflicts` aus Task 2; `tests/test_sort_js.py` sammelt `tests/js/*.test.js` automatisch (keine Änderung nötig).
- Produces (JS, `window.FBDoppel` bzw. `module.exports`): `parseDetail(xhr) -> detail|null`, `konfliktText(k) -> string`, `handle(evt) -> bool`, `bestaetigen(dlg)`, `abbrechen(dlg)`, `init(doc)`.
- [ ] **Step 1: Failing JS-Unit-Tests** — `finance/tests/js/doppelzaehlung.test.js`:
- [x] **Step 1: Failing JS-Unit-Tests** — `finance/tests/js/doppelzaehlung.test.js`:
```js
'use strict';
@@ -704,7 +704,7 @@ test('konfliktText deutsch formatiert', () => {
});
```
- [ ] **Step 2: Failing DOM-Tests** — `finance/tests/js/doppelzaehlung.dom.test.js`:
- [x] **Step 2: Failing DOM-Tests** — `finance/tests/js/doppelzaehlung.dom.test.js`:
```js
'use strict';
@@ -784,7 +784,7 @@ test('Flag nach Request entfernt', { skip: !JSDOM }, () => {
});
```
- [ ] **Step 3: Failing Python-Tests** — an `finance/tests/test_gui.py` anhängen:
- [x] **Step 3: Failing Python-Tests** — an `finance/tests/test_gui.py` anhängen:
```python
def test_base_hat_doppelzaehlungs_dialog(client):
@@ -832,12 +832,12 @@ def test_vorschlag_formular_nutzt_json_form_post(client):
(`Loan`, `RecurringItem`, `Decimal`, `date` sind in `test_gui.py` bereits importiert — sonst ergänzen.)
- [ ] **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_gui.py -k "doppelzaehlung or vorschlag_formular"`
Expected: FAIL (Dialog/Hinweis fehlen; `test_vorschlag_formular_nutzt_json_form_post` darf bereits grün sein).
- [ ] **Step 5: `finance/app/static/doppelzaehlung.js`**
- [x] **Step 5: `finance/app/static/doppelzaehlung.js`**
```js
/* Warnfenster bei möglicher Doppelzählung Kredit ↔ Fixposten (Ausbaustufe 12).
@@ -931,7 +931,7 @@ Expected: FAIL (Dialog/Hinweis fehlen; `test_vorschlag_formular_nutzt_json_form_
})();
```
- [ ] **Step 6: `base.html`**
- [x] **Step 6: `base.html`**
Nach `<script src="/static/sort.js"></script>`:
```html
@@ -968,7 +968,7 @@ Am Ende des Inline-Scripts (nach `toggleEdit`):
if (window.FBDoppel) { window.FBDoppel.init(document); }
```
- [ ] **Step 7: Dauerhafter Hinweis**
- [x] **Step 7: Dauerhafter Hinweis**
`routers/gui.py`: Import `from app.services.double_count import all_conflicts`; in `planung_page` vor dem `TemplateResponse`:
```python
@@ -1002,19 +1002,19 @@ Kredit-Namenszelle (`<td>{{ l.name }}</td>` in `loan-row`):
}
```
- [ ] **Step 8: Tests grün + Gesamtsuite**
- [x] **Step 8: Tests grün + Gesamtsuite**
Run: `cd finance && .venv/bin/python -m pytest -q`
Expected: 305 + 4 = **309 passed, 3 skipped** (JS-Wrapper skippt auf dem Host).
- [ ] **Step 9: Fable-Gate** — JS per Podman, Dateien explizit:
- [x] **Step 9: Fable-Gate** — JS per Podman, Dateien explizit:
```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/doppelzaehlung.test.js tests/js/doppelzaehlung.dom.test.js tests/js/sort.test.js tests/js/sort.dom.test.js'
```
Erwartet: alle grün, DOM-Tests nicht geskippt. Zusätzlich: gerenderte `/planung` + echtes `htmx.min.js` + `doppelzaehlung.js` in jsdom mit gemocktem XHR (409-Antwort, danach 201) — Formular-Submit → Dialog → „Trotzdem speichern" → zweiter Request trägt `doppelzaehlung_bestaetigt: true`, dritter Submit ohne Flag. Bestehende Fehler (z.B. 422) weiter per `alert`.
- [ ] **Step 10: Commit**
- [x] **Step 10: Commit**
```bash
git add finance/app/static/doppelzaehlung.js finance/app/static/style.css finance/app/templates/base.html \
@@ -1033,36 +1033,36 @@ git commit -m "feat: Warnfenster und Planungs-Hinweis bei möglicher Doppelzähl
- Modify: `finance/VERSION`
- Modify (anderes Repo): `/home/wlfb/fb/.claude/skills/finanz-api/SKILL.md`
- [ ] **Step 1: `hilfe.html`** — im Konzepte-Abschnitt zwei kurze Absätze: (a) **Reservierte Namen:** Kategorien/Szenarien dürfen nicht mit „System-" beginnen; Tool-Serien heißen „System-Ist" und „System-Kreditraten". (b) **Doppelzählungs-Warnung:** was geprüft wird (monatlicher Ausgabe-Posten ≈ reguläre Kreditrate, ±3 %, mind. 1 €, überlappender Zeitraum), Warnfenster mit „Trotzdem speichern", dauerhafter ⚠-Hinweis in der Planung; Kreditraten am besten nur über „Kredite" pflegen. Die bestehende Nennung „Kreditraten" im Grafana-Absatz auf „System-Kreditraten" ändern.
- [x] **Step 1: `hilfe.html`** — im Konzepte-Abschnitt zwei kurze Absätze: (a) **Reservierte Namen:** Kategorien/Szenarien dürfen nicht mit „System-" beginnen; Tool-Serien heißen „System-Ist" und „System-Kreditraten". (b) **Doppelzählungs-Warnung:** was geprüft wird (monatlicher Ausgabe-Posten ≈ reguläre Kreditrate, ±3 %, mind. 1 €, überlappender Zeitraum), Warnfenster mit „Trotzdem speichern", dauerhafter ⚠-Hinweis in der Planung; Kreditraten am besten nur über „Kredite" pflegen. Die bestehende Nennung „Kreditraten" im Grafana-Absatz auf „System-Kreditraten" ändern.
- [ ] **Step 2: `docs/ARCHITEKTUR.md`** — Komponenten-Diagramm: Knoten für `reserved.py` (`check_reserved_name`) und `services/double_count.py` (`all_conflicts`) mit Kanten von `planning.py`/`categories.py`/`scenarios.py` bzw. `gui.py`; Abschnitt 4 kurz: Präfix-Regel, Doppelzählungs-Check (409 `doppelzaehlung_kredit`, Feld `doppelzaehlung_bestaetigt`, Warnfenster `static/doppelzaehlung.js`). Nur Namen, die im Code existieren.
- [x] **Step 2: `docs/ARCHITEKTUR.md`** — Komponenten-Diagramm: Knoten für `reserved.py` (`check_reserved_name`) und `services/double_count.py` (`all_conflicts`) mit Kanten von `planning.py`/`categories.py`/`scenarios.py` bzw. `gui.py`; Abschnitt 4 kurz: Präfix-Regel, Doppelzählungs-Check (409 `doppelzaehlung_kredit`, Feld `doppelzaehlung_bestaetigt`, Warnfenster `static/doppelzaehlung.js`). Nur Namen, die im Code existieren.
- [ ] **Step 3: Version** — `finance/VERSION` → `0.12.0`; Suite grün.
- [x] **Step 3: Version** — `finance/VERSION` → `0.12.0`; Suite grün.
- [ ] **Step 4: Commit (Repo `~/bin`)**
- [x] **Step 4: Commit (Repo `~/bin`)**
```bash
git add finance/app/templates/hilfe.html docs/ARCHITEKTUR.md finance/VERSION
git commit -m "chore: Release v0.12.0 (System-Präfix, Doppelzählungs-Warnung)"
```
- [ ] **Step 5: Beratungsumgebung** — in `/home/wlfb/fb/.claude/skills/finanz-api/SKILL.md`:
- [x] **Step 5: Beratungsumgebung** — in `/home/wlfb/fb/.claude/skills/finanz-api/SKILL.md`:
- Abschnitt „Wiederkehrende Posten" und „Kredite": Absatz „**Doppelzählungs-Schutz:** `POST`/`PATCH` antworten mit `409` und `detail.code == "doppelzaehlung_kredit"` (plus `meldung`, `konflikte`), wenn ein monatlicher Ausgabe-Posten vermutlich einer Kreditrate entspricht. Dann **den Nutzer fragen**; nur nach ausdrücklicher Zustimmung erneut mit `"doppelzaehlung_bestaetigt": true` senden. Besser: Kreditraten nur über `/api/loans` pflegen."
- Abschnitte „Szenarien" (und Kategorien, falls beschrieben): „Namen mit Präfix `System-` sind reserviert (422)."
- Commit im Repo `/home/wlfb/fb`: `git -C /home/wlfb/fb add .claude/skills/finanz-api/SKILL.md && git -C /home/wlfb/fb commit -m "docs: finanz-api – Doppelzählungs-Schutz (409) und reservierter Präfix System- (Tool v0.12.0)"` (mit Co-Authored-By-Zeile). Nicht pushen.
- [ ] **Step 6: Fable-Gate** — Faktencheck aller Doku-Aussagen gegen Code (Namen, Toleranz, Code, Feldname, Meldung), Mermaid valide, fb-Skill-Text korrekt und mit Rückfrage-Regel.
- [x] **Step 6: Fable-Gate** — Faktencheck aller Doku-Aussagen gegen Code (Namen, Toleranz, Code, Feldname, Meldung), Mermaid valide, fb-Skill-Text korrekt und mit Rückfrage-Regel.
---
### Task 6: Backup, Redeploy, Live-Smoke (nach Whole-Branch-Review)
- [ ] **Step 1: Backup** — `./backup_finance_pod.sh` (vor Redeploy).
- [ ] **Step 2: Redeploy** — `./create_pod_finance.sh`; `/api/version` (mit Bearer) → `0.12.0`; `alembic current` unverändert `d5e8b3c7a410` (keine Migration).
- [ ] **Step 3: Live-Smoke** (nur Zählwerte/Booleans, DATENSCHUTZ):
- [x] **Step 1: Backup** — `./backup_finance_pod.sh` (vor Redeploy).
- [x] **Step 2: Redeploy** — `./create_pod_finance.sh`; `/api/version` (mit Bearer) → `0.12.0`; `alembic current` unverändert `d5e8b3c7a410` (keine Migration).
- [x] **Step 3: Live-Smoke** (nur Zählwerte/Booleans, DATENSCHUTZ):
- Seiten `/`, `/buchungen`, `/salden`, `/import`, `/planung`, `/szenarien`, `/admin`, `/hilfe` → 200; `/static/doppelzaehlung.js` → 200.
- `POST /api/categories {"name":"System-Test"}` → 422; `POST /api/scenarios {"name":"System-Test"}` → 422.
- Wegwerf-Kredit `SMOKE-A12-K` (1.200 / 0 % / 12 Monate) anlegen → 201; Wegwerf-Posten `SMOKE-A12-P` −100 monatlich → 409 mit `code`; mit Flag → 201; `/planung` zeigt genau für diese beiden den ⚠-Hinweis (grep-Zählung der SMOKE-Namen im Hinweis); beide wieder löschen (204); danach keine SMOKE-Reste.
- `planning_monthly`: keine Kategorie `Kreditraten` (alt) mehr; `System-Kreditraten` genau dann, wenn Kredite existieren.
- Bestandszahlen (Buchungen, Konten, Kategorien, Szenarien, Kredite, wiederkehrende/Einmalposten) vor = nach.
- [ ] **Step 4: Fable-Release-Gate** (read-only Live-Verifikation) + Ledger-Eintrag „AUSBAUSTUFE 12 ABGESCHLOSSEN" in `.superpowers/sdd/progress.md`; Plan-Haken; Commit `docs: A12 Plan-Haken`.
- [x] **Step 4: Fable-Release-Gate** (read-only Live-Verifikation) + Ledger-Eintrag „AUSBAUSTUFE 12 ABGESCHLOSSEN" in `.superpowers/sdd/progress.md`; Plan-Haken; Commit `docs: A12 Plan-Haken`.