docs: A13 Plan-Haken gesetzt
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,7 +46,7 @@
|
|||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Produces: `app.engine.scenario.modified_amount(amount: Decimal, category_id: int | None, recurring_id: int | None, modifiers: list[PlainModifier]) -> Decimal | None`; `app.engine.scenario.effective_end(r: PlainRecurring, modifiers: list[PlainModifier]) -> date | None`; `app.services.projection_service.ScenarioInputs` (frozen dataclass: `recurring: list[PlainRecurring]`, `planned: list[PlainPlanned]`, `loans: list[Loan]` (ORM), `modifiers: list[PlainModifier]`); `app.services.projection_service.load_scenario_inputs(session: Session, scenario: Scenario | None) -> ScenarioInputs` — `None` = System-Plan (alle Fixposten, alle globalen Einmalposten, alle Kredite, keine Modifikatoren, keine Szenario-Einmalposten).
|
- Produces: `app.engine.scenario.modified_amount(amount: Decimal, category_id: int | None, recurring_id: int | None, modifiers: list[PlainModifier]) -> Decimal | None`; `app.engine.scenario.effective_end(r: PlainRecurring, modifiers: list[PlainModifier]) -> date | None`; `app.services.projection_service.ScenarioInputs` (frozen dataclass: `recurring: list[PlainRecurring]`, `planned: list[PlainPlanned]`, `loans: list[Loan]` (ORM), `modifiers: list[PlainModifier]`); `app.services.projection_service.load_scenario_inputs(session: Session, scenario: Scenario | None) -> ScenarioInputs` — `None` = System-Plan (alle Fixposten, alle globalen Einmalposten, alle Kredite, keine Modifikatoren, keine Szenario-Einmalposten).
|
||||||
|
|
||||||
- [ ] **Step 1: Failing Tests** — `finance/tests/test_scenario_inputs.py`:
|
- [x] **Step 1: Failing Tests** — `finance/tests/test_scenario_inputs.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from datetime import date
|
from datetime import date
|
||||||
@@ -119,9 +119,9 @@ def test_load_scenario_inputs_system_plan(db):
|
|||||||
assert inp.modifiers == []
|
assert inp.modifiers == []
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Rot prüfen** — `cd finance && .venv/bin/python -m pytest tests/test_scenario_inputs.py -q` → FAIL (`ImportError: cannot import name 'effective_end'`).
|
- [x] **Step 2: Rot prüfen** — `cd finance && .venv/bin/python -m pytest tests/test_scenario_inputs.py -q` → FAIL (`ImportError: cannot import name 'effective_end'`).
|
||||||
|
|
||||||
- [ ] **Step 3: Engine umbenennen** — in `finance/app/engine/scenario.py` `def _modified(` → `def modified_amount(` und `def _effective_end(` → `def effective_end(`; in `build_cashflows` die drei Aufrufe anpassen:
|
- [x] **Step 3: Engine umbenennen** — in `finance/app/engine/scenario.py` `def _modified(` → `def modified_amount(` und `def _effective_end(` → `def effective_end(`; in `build_cashflows` die drei Aufrufe anpassen:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
for r in recurring:
|
for r in recurring:
|
||||||
@@ -137,7 +137,7 @@ def test_load_scenario_inputs_system_plan(db):
|
|||||||
|
|
||||||
Prüfen, dass keine alten Namen übrig sind: `grep -rn "_modified\|_effective_end" app tests` → keine Treffer.
|
Prüfen, dass keine alten Namen übrig sind: `grep -rn "_modified\|_effective_end" app tests` → keine Treffer.
|
||||||
|
|
||||||
- [ ] **Step 4: `load_scenario_inputs`** — in `finance/app/services/projection_service.py` Imports ergänzen (`from dataclasses import dataclass`) und vor `run_projection` einfügen; `run_projection` bis einschließlich `flows = …` ersetzen:
|
- [x] **Step 4: `load_scenario_inputs`** — in `finance/app/services/projection_service.py` Imports ergänzen (`from dataclasses import dataclass`) und vor `run_projection` einfügen; `run_projection` bis einschließlich `flows = …` ersetzen:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -199,16 +199,16 @@ def run_projection(session: Session, scenario: Scenario, horizon_days: int,
|
|||||||
|
|
||||||
Der Rest von `run_projection` (ab `proj = project(...)`) bleibt unverändert.
|
Der Rest von `run_projection` (ab `proj = project(...)`) bleibt unverändert.
|
||||||
|
|
||||||
- [ ] **Step 5: Grün prüfen** — `.venv/bin/python -m pytest -q` → alle grün, 314 passed (310 + 4), 3 skipped; insbesondere `tests/test_projection.py`, `tests/test_projection_refresh.py`, `tests/test_scenario_engine.py` unverändert grün.
|
- [x] **Step 5: Grün prüfen** — `.venv/bin/python -m pytest -q` → alle grün, 314 passed (310 + 4), 3 skipped; insbesondere `tests/test_projection.py`, `tests/test_projection_refresh.py`, `tests/test_scenario_engine.py` unverändert grün.
|
||||||
|
|
||||||
- [ ] **Step 6: Commit**
|
- [x] **Step 6: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add finance/app/engine/scenario.py finance/app/services/projection_service.py finance/tests/test_scenario_inputs.py
|
git add finance/app/engine/scenario.py finance/app/services/projection_service.py finance/tests/test_scenario_inputs.py
|
||||||
git commit -m "refactor: Modifikator-Helfer öffentlich, load_scenario_inputs aus run_projection (A13-T1)"
|
git commit -m "refactor: Modifikator-Helfer öffentlich, load_scenario_inputs aus run_projection (A13-T1)"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 7: Fable-Gate** — Faktencheck Verhaltensgleichheit: `git diff HEAD~1 -- finance/app/services/projection_service.py` zeigt nur Umzug + `order_by` (Reihenfolge ist für `build_cashflows` irrelevant, da `sorted(flows)`); `include_recurring`/`include_planned`/Szenario-Einmalposten/Szenario-Kredite exakt wie vorher; eine Projektion eines Test-Szenarios vor/nach dem Commit (z. B. per `git stash`-freiem Vergleich zweier Checkouts in einem Wegwerf-Verzeichnis) liefert identische Punkte.
|
- [x] **Step 7: Fable-Gate** — Faktencheck Verhaltensgleichheit: `git diff HEAD~1 -- finance/app/services/projection_service.py` zeigt nur Umzug + `order_by` (Reihenfolge ist für `build_cashflows` irrelevant, da `sorted(flows)`); `include_recurring`/`include_planned`/Szenario-Einmalposten/Szenario-Kredite exakt wie vorher; eine Projektion eines Test-Szenarios vor/nach dem Commit (z. B. per `git stash`-freiem Vergleich zweier Checkouts in einem Wegwerf-Verzeichnis) liefert identische Punkte.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ git commit -m "refactor: Modifikator-Helfer öffentlich, load_scenario_inputs au
|
|||||||
- `share(amount: Decimal, basis: Decimal) -> Decimal | None` (|amount| ÷ basis × 100, eine Nachkommastelle; `basis == 0` ⇒ `None`)
|
- `share(amount: Decimal, basis: Decimal) -> Decimal | None` (|amount| ÷ basis × 100, eine Nachkommastelle; `basis == 0` ⇒ `None`)
|
||||||
- `category_rows(rows: list[BreakdownRow], categories: set[str]) -> list[BreakdownRow]` (nur Kostenzeilen der Kategorien, Reihenfolge wie `rows`)
|
- `category_rows(rows: list[BreakdownRow], categories: set[str]) -> list[BreakdownRow]` (nur Kostenzeilen der Kategorien, Reihenfolge wie `rows`)
|
||||||
|
|
||||||
- [ ] **Step 1: Failing Tests** — `finance/tests/test_breakdown.py`:
|
- [x] **Step 1: Failing Tests** — `finance/tests/test_breakdown.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from datetime import date
|
from datetime import date
|
||||||
@@ -410,9 +410,9 @@ def test_category_rows_nur_kosten_der_kategorien():
|
|||||||
assert category_rows(rs, {FREI}) == []
|
assert category_rows(rs, {FREI}) == []
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_breakdown.py -q` → FAIL (`ModuleNotFoundError: app.engine.breakdown`).
|
- [x] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_breakdown.py -q` → FAIL (`ModuleNotFoundError: app.engine.breakdown`).
|
||||||
|
|
||||||
- [ ] **Step 3: Implementierung** — `finance/app/engine/breakdown.py`:
|
- [x] **Step 3: Implementierung** — `finance/app/engine/breakdown.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Aufteilung (Ausbaustufe 13): Monatsbeträge je Posten für einen Monat.
|
"""Aufteilung (Ausbaustufe 13): Monatsbeträge je Posten für einen Monat.
|
||||||
@@ -545,16 +545,16 @@ def category_rows(rows: list[BreakdownRow], categories: set[str]) -> list[Breakd
|
|||||||
return [r for r in rows if r.amount < 0 and r.category in categories]
|
return [r for r in rows if r.amount < 0 and r.category in categories]
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Grün prüfen** — `.venv/bin/python -m pytest tests/test_breakdown.py -q` → PASS; Gesamtsuite grün (Anzahl steigt um die neuen Tests).
|
- [x] **Step 4: Grün prüfen** — `.venv/bin/python -m pytest tests/test_breakdown.py -q` → PASS; Gesamtsuite grün (Anzahl steigt um die neuen Tests).
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add finance/app/engine/breakdown.py finance/tests/test_breakdown.py
|
git add finance/app/engine/breakdown.py finance/tests/test_breakdown.py
|
||||||
git commit -m "feat: Engine breakdown – Monatsbeträge je Posten, Summen, Kategorie-Filter (A13-T2)"
|
git commit -m "feat: Engine breakdown – Monatsbeträge je Posten, Summen, Kategorie-Filter (A13-T2)"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: Fable-Gate** — unabhängige Handrechnung mit eigenen Werten: jährlicher/quartalsweiser Posten inkl. Rundung auf den Cent, Annuität mit Zins (Rate aus `annuity_payment` nachrechnen), endfällig (Zinsrate und Schlussmonat), Laufzeitgrenzen und `ende`-Modifikator, Modifikator-Kombination wie in `build_cashflows`; Reinheit: `grep -n "^from\|^import" finance/app/engine/breakdown.py` zeigt nur Stdlib und `app.engine.*`; kein `float`.
|
- [x] **Step 6: Fable-Gate** — unabhängige Handrechnung mit eigenen Werten: jährlicher/quartalsweiser Posten inkl. Rundung auf den Cent, Annuität mit Zins (Rate aus `annuity_payment` nachrechnen), endfällig (Zinsrate und Schlussmonat), Laufzeitgrenzen und `ende`-Modifikator, Modifikator-Kombination wie in `build_cashflows`; Reinheit: `grep -n "^from\|^import" finance/app/engine/breakdown.py` zeigt nur Stdlib und `app.engine.*`; kein `float`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -572,7 +572,7 @@ git commit -m "feat: Engine breakdown – Monatsbeträge je Posten, Summen, Kate
|
|||||||
|
|
||||||
Regeln: Werte ≤ 0 entfallen; Basis = Summe der Werte + `rest` (falls > 0); sortiert absteigend nach Wert, bei Gleichstand nach Label; benannt werden höchstens `max_named` Werte mit Anteil ≥ `min_share` (Farben `s1`…); die übrigen: genau einer ⇒ bleibt mit eigenem Label, Farbe `"uebrige"`, `merged=False`; zwei oder mehr ⇒ ein Segment `UEBRIGE` (Farbe `"uebrige"`, `merged=True`, `members` in Größenreihenfolge); `rest` (> 0) wird nie zusammengefasst, kommt als letztes Segment mit Label `rest_label` und Farbe `"frei"`; genau ein Segment ⇒ `path=None`.
|
Regeln: Werte ≤ 0 entfallen; Basis = Summe der Werte + `rest` (falls > 0); sortiert absteigend nach Wert, bei Gleichstand nach Label; benannt werden höchstens `max_named` Werte mit Anteil ≥ `min_share` (Farben `s1`…); die übrigen: genau einer ⇒ bleibt mit eigenem Label, Farbe `"uebrige"`, `merged=False`; zwei oder mehr ⇒ ein Segment `UEBRIGE` (Farbe `"uebrige"`, `merged=True`, `members` in Größenreihenfolge); `rest` (> 0) wird nie zusammengefasst, kommt als letztes Segment mit Label `rest_label` und Farbe `"frei"`; genau ein Segment ⇒ `path=None`.
|
||||||
|
|
||||||
- [ ] **Step 1: Failing Tests** — `finance/tests/test_pie.py`:
|
- [x] **Step 1: Failing Tests** — `finance/tests/test_pie.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@@ -653,9 +653,9 @@ def test_rest_none_oder_null_ignoriert():
|
|||||||
assert len(pie_slices([("A", D("1"))], rest=D("0"), rest_label="X")) == 1
|
assert len(pie_slices([("A", D("1"))], rest=D("0"), rest_label="X")) == 1
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_pie.py -q` → FAIL (`ModuleNotFoundError: app.engine.pie`).
|
- [x] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_pie.py -q` → FAIL (`ModuleNotFoundError: app.engine.pie`).
|
||||||
|
|
||||||
- [ ] **Step 3: Implementierung** — `finance/app/engine/pie.py`:
|
- [x] **Step 3: Implementierung** — `finance/app/engine/pie.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Tortendiagramm-Segmente (Ausbaustufe 13), rein.
|
"""Tortendiagramm-Segmente (Ausbaustufe 13), rein.
|
||||||
@@ -735,16 +735,16 @@ def pie_slices(values: list[tuple[str, Decimal]], rest: Decimal | None = None,
|
|||||||
return slices
|
return slices
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Grün prüfen** — `.venv/bin/python -m pytest tests/test_pie.py -q` → PASS; Gesamtsuite grün.
|
- [x] **Step 4: Grün prüfen** — `.venv/bin/python -m pytest tests/test_pie.py -q` → PASS; Gesamtsuite grün.
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add finance/app/engine/pie.py finance/tests/test_pie.py
|
git add finance/app/engine/pie.py finance/tests/test_pie.py
|
||||||
git commit -m "feat: Engine pie – Tortensegmente mit Übrige-Regel und SVG-Pfaden (A13-T3)"
|
git commit -m "feat: Engine pie – Tortensegmente mit Übrige-Regel und SVG-Pfaden (A13-T3)"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: Fable-Gate** — Geometrie-Faktencheck: für 3–4 eigene Wertelisten Endpunkte der Pfade nachrechnen (12 Uhr Start, Uhrzeigersinn, `large-arc` ab > 50 %), Anteilssumme ≈ 100 % (Rundung), Übrige-Regel (3 %, max. 8 benannte, Einzelner behält Namen, max. 10 Segmente), `rest` nie zusammengefasst; ein erzeugtes SVG (Wegwerfdatei im Scratchpad, `viewBox="-1.05 -1.05 2.1 2.1"`) rendern und ansehen (z. B. per `rsvg-convert` oder Browser-Container), keine Lücken/Überlappungen.
|
- [x] **Step 6: Fable-Gate** — Geometrie-Faktencheck: für 3–4 eigene Wertelisten Endpunkte der Pfade nachrechnen (12 Uhr Start, Uhrzeigersinn, `large-arc` ab > 50 %), Anteilssumme ≈ 100 % (Rundung), Übrige-Regel (3 %, max. 8 benannte, Einzelner behält Namen, max. 10 Segmente), `rest` nie zusammengefasst; ein erzeugtes SVG (Wegwerfdatei im Scratchpad, `viewBox="-1.05 -1.05 2.1 2.1"`) rendern und ansehen (z. B. per `rsvg-convert` oder Browser-Container), keine Lücken/Überlappungen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -758,7 +758,7 @@ git commit -m "feat: Engine pie – Tortensegmente mit Übrige-Regel und SVG-Pfa
|
|||||||
- Consumes: `load_scenario_inputs`, `ScenarioInputs` (Task 1); `Filters`, `PlainLoan`, `BreakdownRow`, `Summary`, `breakdown_rows`, `summarize` (Task 2); `KREDITRATEN`, `UNKATEGORISIERT` (`services/planning_chart.py`); `all_conflicts`, `Konflikt` (`services/double_count.py`); `SYSTEM_PREFIX` (`app/reserved.py`).
|
- Consumes: `load_scenario_inputs`, `ScenarioInputs` (Task 1); `Filters`, `PlainLoan`, `BreakdownRow`, `Summary`, `breakdown_rows`, `summarize` (Task 2); `KREDITRATEN`, `UNKATEGORISIERT` (`services/planning_chart.py`); `all_conflicts`, `Konflikt` (`services/double_count.py`); `SYSTEM_PREFIX` (`app/reserved.py`).
|
||||||
- Produces: `app.services.breakdown.PLAN_NAME: str` (= „System-Plan"); `app.services.breakdown.Breakdown` (frozen dataclass: `month: date` (Monatserster), `rows: list[BreakdownRow]`, `summary: Summary`, `conflicts: list[Konflikt]`); `app.services.breakdown.compute_breakdown(session: Session, month: date, scenario: Scenario | None, filters: Filters) -> Breakdown`.
|
- Produces: `app.services.breakdown.PLAN_NAME: str` (= „System-Plan"); `app.services.breakdown.Breakdown` (frozen dataclass: `month: date` (Monatserster), `rows: list[BreakdownRow]`, `summary: Summary`, `conflicts: list[Konflikt]`); `app.services.breakdown.compute_breakdown(session: Session, month: date, scenario: Scenario | None, filters: Filters) -> Breakdown`.
|
||||||
|
|
||||||
- [ ] **Step 1: Failing Tests** — `finance/tests/test_breakdown_service.py`:
|
- [x] **Step 1: Failing Tests** — `finance/tests/test_breakdown_service.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from datetime import date
|
from datetime import date
|
||||||
@@ -851,9 +851,9 @@ def test_konflikt_nur_wenn_beide_zeilen_enthalten(db):
|
|||||||
assert compute_breakdown(db, M, sc, Filters()).conflicts == []
|
assert compute_breakdown(db, M, sc, Filters()).conflicts == []
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_breakdown_service.py -q` → FAIL (`ModuleNotFoundError: app.services.breakdown`).
|
- [x] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_breakdown_service.py -q` → FAIL (`ModuleNotFoundError: app.services.breakdown`).
|
||||||
|
|
||||||
- [ ] **Step 3: Implementierung** — `finance/app/services/breakdown.py`:
|
- [x] **Step 3: Implementierung** — `finance/app/services/breakdown.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""Aufteilung (Ausbaustufe 13): Brücke DB -> engine/breakdown.py.
|
"""Aufteilung (Ausbaustufe 13): Brücke DB -> engine/breakdown.py.
|
||||||
@@ -907,16 +907,16 @@ def compute_breakdown(session: Session, month: date, scenario: Scenario | None,
|
|||||||
return Breakdown(month.replace(day=1), rows, summarize(rows), conflicts)
|
return Breakdown(month.replace(day=1), rows, summarize(rows), conflicts)
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Grün prüfen** — `.venv/bin/python -m pytest tests/test_breakdown_service.py -q` → PASS; Gesamtsuite grün.
|
- [x] **Step 4: Grün prüfen** — `.venv/bin/python -m pytest tests/test_breakdown_service.py -q` → PASS; Gesamtsuite grün.
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add finance/app/services/breakdown.py finance/tests/test_breakdown_service.py
|
git add finance/app/services/breakdown.py finance/tests/test_breakdown_service.py
|
||||||
git commit -m "feat: Service compute_breakdown – System-Plan/Szenario, Doppelzählungs-Hinweis (A13-T4)"
|
git commit -m "feat: Service compute_breakdown – System-Plan/Szenario, Doppelzählungs-Hinweis (A13-T4)"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: Fable-Gate** — Faktencheck gegen `run_projection`: gleiche Datenbasis je Szenario (Flags, Szenario-Kredite, Szenario-Einmalposten, Modifikatoren); System-Plan entspricht `planning_chart.planning_monthly_totals` für einen Monat bis auf die Normalisierung der Fixposten (eigenes Beispiel mit nur monatlichen Posten + Einmalposten + Kredit: Kategoriesummen identisch); Konflikt-Filter prüft beide IDs.
|
- [x] **Step 6: Fable-Gate** — Faktencheck gegen `run_projection`: gleiche Datenbasis je Szenario (Flags, Szenario-Kredite, Szenario-Einmalposten, Modifikatoren); System-Plan entspricht `planning_chart.planning_monthly_totals` für einen Monat bis auf die Normalisierung der Fixposten (eigenes Beispiel mit nur monatlichen Posten + Einmalposten + Kredit: Kategoriesummen identisch); Konflikt-Filter prüft beide IDs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -938,7 +938,7 @@ git commit -m "feat: Service compute_breakdown – System-Plan/Szenario, Doppelz
|
|||||||
- Consumes: `compute_breakdown`, `PLAN_NAME` (Task 4); `Filters`, `FREI`, `share`, `BreakdownRow` (Task 2); `pie_slices` (Task 3).
|
- Consumes: `compute_breakdown`, `PLAN_NAME` (Task 4); `Filters`, `FREI`, `share`, `BreakdownRow` (Task 2); `pie_slices` (Task 3).
|
||||||
- Produces: `app.formats.pct(value: Decimal | None) -> str` („12,5 %", `None` ⇒ „–"); in `routers/gui.py`: `_aufteilung_context(session, today, monat, szenario, fixposten, einmal, kredite, gesendet) -> dict` mit den Schlüsseln `scenarios`, `filters`, `plan_name`, `monat_wert`, `monat_anzeige`, `szenario_id`, `szenario_name`, `error`, `b` (Breakdown oder None), `s` (Summary), `zeilen` (Liste von `{"r": BreakdownRow, "art": str, "anteil": Decimal | None, "einnahme": bool}`), `frei_anteil`, `haupt` (Liste von Segment-Dicts `{"s": Slice, "q": str | None, "gewaehlt": bool, "zusatz": str | None}`), `haupt_titel`; `_segmente(slices) -> list[dict]`; Jinja-Makro `torte(segmente, titel)` in `_torte_macros.html`; Container-IDs `#aufteilung-torte`.
|
- Produces: `app.formats.pct(value: Decimal | None) -> str` („12,5 %", `None` ⇒ „–"); in `routers/gui.py`: `_aufteilung_context(session, today, monat, szenario, fixposten, einmal, kredite, gesendet) -> dict` mit den Schlüsseln `scenarios`, `filters`, `plan_name`, `monat_wert`, `monat_anzeige`, `szenario_id`, `szenario_name`, `error`, `b` (Breakdown oder None), `s` (Summary), `zeilen` (Liste von `{"r": BreakdownRow, "art": str, "anteil": Decimal | None, "einnahme": bool}`), `frei_anteil`, `haupt` (Liste von Segment-Dicts `{"s": Slice, "q": str | None, "gewaehlt": bool, "zusatz": str | None}`), `haupt_titel`; `_segmente(slices) -> list[dict]`; Jinja-Makro `torte(segmente, titel)` in `_torte_macros.html`; Container-IDs `#aufteilung-torte`.
|
||||||
|
|
||||||
- [ ] **Step 1: Failing Tests** — `finance/tests/test_aufteilung_gui.py`:
|
- [x] **Step 1: Failing Tests** — `finance/tests/test_aufteilung_gui.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import re
|
import re
|
||||||
@@ -1113,9 +1113,9 @@ def test_aufteilung_tabelle_sortierbar_tfoot_fest(client, db):
|
|||||||
assert re.search(r"</tbody>\s*<tfoot>", table)
|
assert re.search(r"</tbody>\s*<tfoot>", table)
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_aufteilung_gui.py tests/test_gui.py tests/test_sort_templates.py -q` → FAIL (404 / fehlender Menüpunkt).
|
- [x] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_aufteilung_gui.py tests/test_gui.py tests/test_sort_templates.py -q` → FAIL (404 / fehlender Menüpunkt).
|
||||||
|
|
||||||
- [ ] **Step 3: `pct`-Filter** — in `finance/app/formats.py` anhängen:
|
- [x] **Step 3: `pct`-Filter** — in `finance/app/formats.py` anhängen:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def pct(value) -> str:
|
def pct(value) -> str:
|
||||||
@@ -1126,7 +1126,7 @@ def pct(value) -> str:
|
|||||||
return f"{d}".replace(".", ",") + " %"
|
return f"{d}".replace(".", ",") + " %"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Router** — in `finance/app/routers/gui.py`:
|
- [x] **Step 4: Router** — in `finance/app/routers/gui.py`:
|
||||||
|
|
||||||
Imports ergänzen (`import re` oben; `from app.formats import de_label, eur, pct`; `Scenario` in den `app.models.tables`-Import; neu):
|
Imports ergänzen (`import re` oben; `from app.formats import de_label, eur, pct`; `Scenario` in den `app.models.tables`-Import; neu):
|
||||||
|
|
||||||
@@ -1224,7 +1224,7 @@ def aufteilung_page(request: Request, monat: str | None = None,
|
|||||||
return templates.TemplateResponse(request, "aufteilung.html", ctx)
|
return templates.TemplateResponse(request, "aufteilung.html", ctx)
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: Templates** —
|
- [x] **Step 5: Templates** —
|
||||||
|
|
||||||
`finance/app/templates/_torte_macros.html`:
|
`finance/app/templates/_torte_macros.html`:
|
||||||
|
|
||||||
@@ -1347,7 +1347,7 @@ def aufteilung_page(request: Request, monat: str | None = None,
|
|||||||
<a href="/admin">Admin</a>
|
<a href="/admin">Admin</a>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: CSS** — an `finance/app/static/style.css` anhängen:
|
- [x] **Step 6: CSS** — an `finance/app/static/style.css` anhängen:
|
||||||
|
|
||||||
```css
|
```css
|
||||||
/* Aufteilung (Ausbaustufe 13). Tortenfarben: dataviz-Referenzpalette,
|
/* Aufteilung (Ausbaustufe 13). Tortenfarben: dataviz-Referenzpalette,
|
||||||
@@ -1387,16 +1387,16 @@ def aufteilung_page(request: Request, monat: str | None = None,
|
|||||||
.hinweis-klein { font-size: 0.85rem; color: #555; }
|
.hinweis-klein { font-size: 0.85rem; color: #555; }
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 7: Grün prüfen** — `.venv/bin/python -m pytest -q` → alle grün.
|
- [x] **Step 7: Grün prüfen** — `.venv/bin/python -m pytest -q` → alle grün.
|
||||||
|
|
||||||
- [ ] **Step 8: Commit**
|
- [x] **Step 8: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add finance/app/formats.py finance/app/routers/gui.py finance/app/templates/aufteilung.html finance/app/templates/_aufteilung_torte.html finance/app/templates/_torte_macros.html finance/app/templates/base.html finance/app/static/style.css finance/tests/test_aufteilung_gui.py finance/tests/test_gui.py finance/tests/test_sort_templates.py
|
git add finance/app/formats.py finance/app/routers/gui.py finance/app/templates/aufteilung.html finance/app/templates/_aufteilung_torte.html finance/app/templates/_torte_macros.html finance/app/templates/base.html finance/app/static/style.css finance/tests/test_aufteilung_gui.py finance/tests/test_gui.py finance/tests/test_sort_templates.py
|
||||||
git commit -m "feat: Seite Aufteilung – Auswahl, Kennzahlen, Tabelle, Tortendiagramm (A13-T5)"
|
git commit -m "feat: Seite Aufteilung – Auswahl, Kennzahlen, Tabelle, Tortendiagramm (A13-T5)"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 9: Fable-Gate** — Live-Approximation: lokaler uvicorn gegen Wegwerf-SQLite (`FB_DATABASE_URL=sqlite:////<scratchpad>/a13.sqlite`, `FB_GUI_PASSWORD_HASH` per `app.auth.hash_password`, `alembic upgrade head`, `uvicorn app.main:app --port 8199`), synthetische Daten per API; prüfen: Menüpunkt, Erstaufruf alle Filter an, alle Häkchen aus ⇒ „keine Posten", Szenario + Vormonat ⇒ Hinweis und Formular bleibt, Unterdeckung, Tabellensortierung per Klick (`sort.js`, `tfoot` bleibt unten), Torte sichtbar mit Legende; Screenshot per Playwright-Container (`podman run --rm --network host mcr.microsoft.com/playwright/python:v1.47.0-jammy …`) bei 1200 px und 390 px Breite ansehen (kein horizontales Scrollen, Torte unter der Tabelle auf schmal); danach aufräumen.
|
- [x] **Step 9: Fable-Gate** — Live-Approximation: lokaler uvicorn gegen Wegwerf-SQLite (`FB_DATABASE_URL=sqlite:////<scratchpad>/a13.sqlite`, `FB_GUI_PASSWORD_HASH` per `app.auth.hash_password`, `alembic upgrade head`, `uvicorn app.main:app --port 8199`), synthetische Daten per API; prüfen: Menüpunkt, Erstaufruf alle Filter an, alle Häkchen aus ⇒ „keine Posten", Szenario + Vormonat ⇒ Hinweis und Formular bleibt, Unterdeckung, Tabellensortierung per Klick (`sort.js`, `tfoot` bleibt unten), Torte sichtbar mit Legende; Screenshot per Playwright-Container (`podman run --rm --network host mcr.microsoft.com/playwright/python:v1.47.0-jammy …`) bei 1200 px und 390 px Breite ansehen (kein horizontales Scrollen, Torte unter der Tabelle auf schmal); danach aufräumen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1415,7 +1415,7 @@ git commit -m "feat: Seite Aufteilung – Auswahl, Kennzahlen, Tabelle, Tortendi
|
|||||||
- Consumes: `_aufteilung_context`, `_segmente` (Task 5); `category_rows`, `share` (Task 2); `pie_slices`, `UEBRIGE` (Task 3); `pct` (Task 5).
|
- Consumes: `_aufteilung_context`, `_segmente` (Task 5); `category_rows`, `share` (Task 2); `pie_slices`, `UEBRIGE` (Task 3); `pct` (Task 5).
|
||||||
- Produces: `GET /aufteilung/kategorie` (Parameter wie `/aufteilung` plus `kategorie` bzw. `sammel=1`) → Fragment `_aufteilung_kategorie.html` (Status 200) mit `#aufteilung-torte` als `hx-swap-oob="true"`; `aufteilung_page` versteht `kategorie`/`sammel`; ctx-Schlüssel `detail` (`{"titel", "summe", "enthaelt", "segmente"}` oder None), `detail_hinweis` (str oder None), `oob` (bool, nur Fragment).
|
- Produces: `GET /aufteilung/kategorie` (Parameter wie `/aufteilung` plus `kategorie` bzw. `sammel=1`) → Fragment `_aufteilung_kategorie.html` (Status 200) mit `#aufteilung-torte` als `hx-swap-oob="true"`; `aufteilung_page` versteht `kategorie`/`sammel`; ctx-Schlüssel `detail` (`{"titel", "summe", "enthaelt", "segmente"}` oder None), `detail_hinweis` (str oder None), `oob` (bool, nur Fragment).
|
||||||
|
|
||||||
- [ ] **Step 1: Failing Tests** — an `finance/tests/test_aufteilung_gui.py` anhängen:
|
- [x] **Step 1: Failing Tests** — an `finance/tests/test_aufteilung_gui.py` anhängen:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def _drill(client, pfad="/aufteilung/kategorie", **params):
|
def _drill(client, pfad="/aufteilung/kategorie", **params):
|
||||||
@@ -1536,9 +1536,9 @@ def test_drilldown_sonderzeichen(client, db):
|
|||||||
assert "Haus & Garten: 300,00 € / Monat" in r.text and "Rasen" in r.text
|
assert "Haus & Garten: 300,00 € / Monat" in r.text and "Rasen" in r.text
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_aufteilung_gui.py -q` → die neuen Tests FAIL (404 bzw. fehlende Links).
|
- [x] **Step 2: Rot prüfen** — `.venv/bin/python -m pytest tests/test_aufteilung_gui.py -q` → die neuen Tests FAIL (404 bzw. fehlende Links).
|
||||||
|
|
||||||
- [ ] **Step 3: Router** — in `finance/app/routers/gui.py`: Imports ergänzen (`from urllib.parse import urlencode`; `category_rows` in den `app.engine.breakdown`-Import; `UEBRIGE` zum `app.engine.pie`-Import). Nach `_aufteilung_context` einfügen:
|
- [x] **Step 3: Router** — in `finance/app/routers/gui.py`: Imports ergänzen (`from urllib.parse import urlencode`; `category_rows` in den `app.engine.breakdown`-Import; `UEBRIGE` zum `app.engine.pie`-Import). Nach `_aufteilung_context` einfügen:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def _basis_query(ctx: dict) -> dict:
|
def _basis_query(ctx: dict) -> dict:
|
||||||
@@ -1623,7 +1623,7 @@ def aufteilung_kategorie_fragment(request: Request, monat: str | None = None,
|
|||||||
return templates.TemplateResponse(request, "_aufteilung_kategorie.html", ctx)
|
return templates.TemplateResponse(request, "_aufteilung_kategorie.html", ctx)
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Templates** —
|
- [x] **Step 4: Templates** —
|
||||||
|
|
||||||
`finance/app/templates/_torte_macros.html` komplett ersetzen:
|
`finance/app/templates/_torte_macros.html` komplett ersetzen:
|
||||||
|
|
||||||
@@ -1701,7 +1701,7 @@ ersetzen durch:
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: CSS** — an `finance/app/static/style.css` anhängen:
|
- [x] **Step 5: CSS** — an `finance/app/static/style.css` anhängen:
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.torte-svg .seg.klickbar { cursor: pointer; }
|
.torte-svg .seg.klickbar { cursor: pointer; }
|
||||||
@@ -1711,16 +1711,16 @@ ersetzen durch:
|
|||||||
#aufteilung-kategorie h2 { font-size: 1.1rem; margin-top: 1.5rem; }
|
#aufteilung-kategorie h2 { font-size: 1.1rem; margin-top: 1.5rem; }
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: Grün prüfen** — `.venv/bin/python -m pytest -q` → alle grün.
|
- [x] **Step 6: Grün prüfen** — `.venv/bin/python -m pytest -q` → alle grün.
|
||||||
|
|
||||||
- [ ] **Step 7: Commit**
|
- [x] **Step 7: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add finance/app/routers/gui.py finance/app/templates/_torte_macros.html finance/app/templates/_aufteilung_torte.html finance/app/templates/_aufteilung_kategorie.html finance/app/templates/aufteilung.html finance/app/static/style.css finance/tests/test_aufteilung_gui.py
|
git add finance/app/routers/gui.py finance/app/templates/_torte_macros.html finance/app/templates/_aufteilung_torte.html finance/app/templates/_aufteilung_kategorie.html finance/app/templates/aufteilung.html finance/app/static/style.css finance/tests/test_aufteilung_gui.py
|
||||||
git commit -m "feat: Aufteilung – Drill-down in Kategorie per Klick auf Tortensegment (A13-T6)"
|
git commit -m "feat: Aufteilung – Drill-down in Kategorie per Klick auf Tortensegment (A13-T6)"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 8: Fable-Gate** — Browser-Test gegen lokalen uvicorn (Wegwerf-SQLite wie Task 5, synthetische Daten mit ≥ 3 Kategorien, davon 2 unter 3 %) per Playwright-Container (`--network host`): Login; Klick auf ein SVG-Segment ⇒ `#aufteilung-kategorie` enthält eine Torte, URL enthält `kategorie=`, Seite navigiert NICHT komplett (kein voller Reload: Formularfeld-Wert bleibt, Netzwerk-Request geht an `/aufteilung/kategorie`); Segment in Haupttorte markiert (`.gewaehlt`); Klick auf Legenden-Link ebenso; „Übrige" ⇒ Sammeldetail; Zurück-Button ⇒ vorherige URL; Neuladen der URL mit `kategorie=` zeigt dieselbe zweite Torte; „Frei verfügbar" nicht klickbar; Screenshots ansehen; aufräumen.
|
- [x] **Step 8: Fable-Gate** — Browser-Test gegen lokalen uvicorn (Wegwerf-SQLite wie Task 5, synthetische Daten mit ≥ 3 Kategorien, davon 2 unter 3 %) per Playwright-Container (`--network host`): Login; Klick auf ein SVG-Segment ⇒ `#aufteilung-kategorie` enthält eine Torte, URL enthält `kategorie=`, Seite navigiert NICHT komplett (kein voller Reload: Formularfeld-Wert bleibt, Netzwerk-Request geht an `/aufteilung/kategorie`); Segment in Haupttorte markiert (`.gewaehlt`); Klick auf Legenden-Link ebenso; „Übrige" ⇒ Sammeldetail; Zurück-Button ⇒ vorherige URL; Neuladen der URL mit `kategorie=` zeigt dieselbe zweite Torte; „Frei verfügbar" nicht klickbar; Screenshots ansehen; aufräumen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1732,7 +1732,7 @@ git commit -m "feat: Aufteilung – Drill-down in Kategorie per Klick auf Torten
|
|||||||
- Modify: `finance/VERSION`
|
- Modify: `finance/VERSION`
|
||||||
- Modify (anderes Repo): `/home/wlfb/fb/.claude/skills/finanzberatung/SKILL.md`
|
- Modify (anderes Repo): `/home/wlfb/fb/.claude/skills/finanzberatung/SKILL.md`
|
||||||
|
|
||||||
- [ ] **Step 1: `hilfe.html`** — im Abschnitt „Konzepte" (Liste `<ul>`) einen neuen `<li>` im Stil der vorhandenen:
|
- [x] **Step 1: `hilfe.html`** — im Abschnitt „Konzepte" (Liste `<ul>`) einen neuen `<li>` im Stil der vorhandenen:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<li>
|
<li>
|
||||||
@@ -1755,36 +1755,36 @@ git commit -m "feat: Aufteilung – Drill-down in Kategorie per Klick auf Torten
|
|||||||
</li>
|
</li>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: `docs/ARCHITEKTUR.md`** —
|
- [x] **Step 2: `docs/ARCHITEKTUR.md`** —
|
||||||
- Komponenten-Diagramm: `T_pages` um `aufteilung.html` ergänzen; `R_gui`-Label um `aufteilung_page, aufteilung_kategorie_fragment`; `S_projection`-Label um `load_scenario_inputs`; neue Knoten `S_breakdown["breakdown.py<br/>compute_breakdown"]` (SERVICES), `E_breakdown["breakdown.py<br/>breakdown_rows, summarize,<br/>category_rows, share"]` und `E_pie["pie.py<br/>pie_slices"]` (ENGINE); Kanten `R_gui --> S_breakdown`, `R_gui --> E_breakdown`, `R_gui --> E_pie`, `S_breakdown --> S_projection`, `S_breakdown --> E_breakdown`, `S_breakdown --> E_loans`, `S_breakdown --> S_doublecount`, `E_breakdown --> E_scenario`, `E_breakdown --> E_recurrence`.
|
- Komponenten-Diagramm: `T_pages` um `aufteilung.html` ergänzen; `R_gui`-Label um `aufteilung_page, aufteilung_kategorie_fragment`; `S_projection`-Label um `load_scenario_inputs`; neue Knoten `S_breakdown["breakdown.py<br/>compute_breakdown"]` (SERVICES), `E_breakdown["breakdown.py<br/>breakdown_rows, summarize,<br/>category_rows, share"]` und `E_pie["pie.py<br/>pie_slices"]` (ENGINE); Kanten `R_gui --> S_breakdown`, `R_gui --> E_breakdown`, `R_gui --> E_pie`, `S_breakdown --> S_projection`, `S_breakdown --> E_breakdown`, `S_breakdown --> E_loans`, `S_breakdown --> S_doublecount`, `E_breakdown --> E_scenario`, `E_breakdown --> E_recurrence`.
|
||||||
- Abschnitt 4 „Szenario-Engine ist rein": Satz „nur `scenario.py` importiert innerhalb der Engine …" korrigieren zu „`scenario.py` und `breakdown.py` importieren innerhalb der Engine (`loans.py`/`recurrence.py`/`scenario.py`)"; `services/projection_service.py::load_scenario_inputs` als gemeinsame Ladefunktion nennen (`None` = System-Plan).
|
- Abschnitt 4 „Szenario-Engine ist rein": Satz „nur `scenario.py` importiert innerhalb der Engine …" korrigieren zu „`scenario.py` und `breakdown.py` importieren innerhalb der Engine (`loans.py`/`recurrence.py`/`scenario.py`)"; `services/projection_service.py::load_scenario_inputs` als gemeinsame Ladefunktion nennen (`None` = System-Plan).
|
||||||
- Neuer Konzeptabsatz **„Aufteilung (Ausbaustufe 13)."**: `/aufteilung` (GET-Formular, Auswahl in der URL), `services/breakdown.py::compute_breakdown` (`PLAN_NAME` „System-Plan" = `load_scenario_inputs(session, None)`), Rechenregeln aus `engine/breakdown.py` (Normalisierung per `recurrence.STEP`, Einmalposten/Kreditraten voll im Fälligkeitsmonat, Auszahlung nie), Torte serverseitig aus `engine/pie.py::pie_slices` (`MIN_SHARE` 3 %, `MAX_NAMED` 8, `UEBRIGE`), Drill-down `GET /aufteilung/kategorie` mit `hx-swap-oob` der Haupttorte und `sammel=1` für die Sammelgruppe; keine Tabelle/View, Grafana unberührt.
|
- Neuer Konzeptabsatz **„Aufteilung (Ausbaustufe 13)."**: `/aufteilung` (GET-Formular, Auswahl in der URL), `services/breakdown.py::compute_breakdown` (`PLAN_NAME` „System-Plan" = `load_scenario_inputs(session, None)`), Rechenregeln aus `engine/breakdown.py` (Normalisierung per `recurrence.STEP`, Einmalposten/Kreditraten voll im Fälligkeitsmonat, Auszahlung nie), Torte serverseitig aus `engine/pie.py::pie_slices` (`MIN_SHARE` 3 %, `MAX_NAMED` 8, `UEBRIGE`), Drill-down `GET /aufteilung/kategorie` mit `hx-swap-oob` der Haupttorte und `sammel=1` für die Sammelgruppe; keine Tabelle/View, Grafana unberührt.
|
||||||
- Nur Namen verwenden, die im Code existieren.
|
- Nur Namen verwenden, die im Code existieren.
|
||||||
|
|
||||||
- [ ] **Step 3: Version** — `finance/VERSION` → `0.13.0`; `.venv/bin/python -m pytest -q` grün.
|
- [x] **Step 3: Version** — `finance/VERSION` → `0.13.0`; `.venv/bin/python -m pytest -q` grün.
|
||||||
|
|
||||||
- [ ] **Step 4: Commit (Repo `~/bin`)**
|
- [x] **Step 4: Commit (Repo `~/bin`)**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add finance/app/templates/hilfe.html docs/ARCHITEKTUR.md finance/VERSION
|
git add finance/app/templates/hilfe.html docs/ARCHITEKTUR.md finance/VERSION
|
||||||
git commit -m "chore: Release v0.13.0 (Seite Aufteilung)"
|
git commit -m "chore: Release v0.13.0 (Seite Aufteilung)"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: Beratungsumgebung** — in `/home/wlfb/fb/.claude/skills/finanzberatung/SKILL.md`, Abschnitt „5. Vergleichen und Empfehlung formulieren", nach dem Grafana-Hinweis einen Punkt ergänzen: „Für die Monatsstruktur einer Variante auf die GUI-Seite **Aufteilung** (`/aufteilung`) verweisen: Monat + Szenario wählen, zeigt Kosten je Kategorie in % der Einkünfte und „Frei verfügbar"; Klick auf ein Tortensegment zeigt die Posten der Kategorie. Rechnet mit Planungsdaten (Fixposten auf den Monat umgerechnet), nicht mit Ist-Buchungen." Commit im Repo `/home/wlfb/fb`: `git -C /home/wlfb/fb add .claude/skills/finanzberatung/SKILL.md && git -C /home/wlfb/fb commit -m "docs: finanzberatung – Hinweis auf Seite Aufteilung (Tool v0.13.0)"` (mit Co-Authored-By-Zeile). Nicht pushen.
|
- [x] **Step 5: Beratungsumgebung** — in `/home/wlfb/fb/.claude/skills/finanzberatung/SKILL.md`, Abschnitt „5. Vergleichen und Empfehlung formulieren", nach dem Grafana-Hinweis einen Punkt ergänzen: „Für die Monatsstruktur einer Variante auf die GUI-Seite **Aufteilung** (`/aufteilung`) verweisen: Monat + Szenario wählen, zeigt Kosten je Kategorie in % der Einkünfte und „Frei verfügbar"; Klick auf ein Tortensegment zeigt die Posten der Kategorie. Rechnet mit Planungsdaten (Fixposten auf den Monat umgerechnet), nicht mit Ist-Buchungen." Commit im Repo `/home/wlfb/fb`: `git -C /home/wlfb/fb add .claude/skills/finanzberatung/SKILL.md && git -C /home/wlfb/fb commit -m "docs: finanzberatung – Hinweis auf Seite Aufteilung (Tool v0.13.0)"` (mit Co-Authored-By-Zeile). Nicht pushen.
|
||||||
|
|
||||||
- [ ] **Step 6: Fable-Gate** — Faktencheck aller Doku-Aussagen gegen Code (Konstanten, Routen, Rechenregeln, Funktionsnamen), Mermaid valide (z. B. per `mermaid-cli`-Container rendern), Hilfe-Text stimmt mit dem Verhalten überein.
|
- [x] **Step 6: Fable-Gate** — Faktencheck aller Doku-Aussagen gegen Code (Konstanten, Routen, Rechenregeln, Funktionsnamen), Mermaid valide (z. B. per `mermaid-cli`-Container rendern), Hilfe-Text stimmt mit dem Verhalten überein.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Task 8: Backup, Redeploy, Live-Smoke (nach Whole-Branch-Review)
|
### Task 8: Backup, Redeploy, Live-Smoke (nach Whole-Branch-Review)
|
||||||
|
|
||||||
- [ ] **Step 1: Backup** — `./backup_finance_pod.sh` (vor Redeploy).
|
- [x] **Step 1: Backup** — `./backup_finance_pod.sh` (vor Redeploy).
|
||||||
- [ ] **Step 2: Redeploy** — `./create_pod_finance.sh`; `GET /api/version` (mit Bearer) → `0.13.0`; `alembic current` unverändert gegenüber vor dem Deploy (keine Migration).
|
- [x] **Step 2: Redeploy** — `./create_pod_finance.sh`; `GET /api/version` (mit Bearer) → `0.13.0`; `alembic current` unverändert gegenüber vor dem Deploy (keine Migration).
|
||||||
- [ ] **Step 3: Live-Smoke** (curl mit Session-Cookie gegen `127.0.0.1:8096`; nur Statuscodes, Zählwerte, Booleans — DATENSCHUTZ):
|
- [x] **Step 3: Live-Smoke** (curl mit Session-Cookie gegen `127.0.0.1:8096`; nur Statuscodes, Zählwerte, Booleans — DATENSCHUTZ):
|
||||||
- Seiten `/`, `/buchungen`, `/salden`, `/import`, `/planung`, `/szenarien`, `/aufteilung`, `/admin`, `/hilfe` → 200; Menüpunkt „Aufteilung" vorhanden.
|
- Seiten `/`, `/buchungen`, `/salden`, `/import`, `/planung`, `/szenarien`, `/aufteilung`, `/admin`, `/hilfe` → 200; Menüpunkt „Aufteilung" vorhanden.
|
||||||
- `/aufteilung` (laufender Monat, System-Plan): Anzahl `<tbody>`-Zeilen > 0, `<tfoot>` vorhanden, `<svg class="torte-svg"` vorhanden.
|
- `/aufteilung` (laufender Monat, System-Plan): Anzahl `<tbody>`-Zeilen > 0, `<tfoot>` vorhanden, `<svg class="torte-svg"` vorhanden.
|
||||||
- `/aufteilung?monat=<Vormonat>&szenario=<irgendeine ID>` → 200 mit Hinweis „Szenarien sind erst ab dem laufenden Monat wählbar" (Boolean).
|
- `/aufteilung?monat=<Vormonat>&szenario=<irgendeine ID>` → 200 mit Hinweis „Szenarien sind erst ab dem laufenden Monat wählbar" (Boolean).
|
||||||
- Ein `hx-get`-Link der Haupttorte (ersten per grep, ohne ihn auszugeben) als `/aufteilung/kategorie?…` abrufen → 200, enthält `hx-swap-oob` und `<svg` (Booleans).
|
- Ein `hx-get`-Link der Haupttorte (ersten per grep, ohne ihn auszugeben) als `/aufteilung/kategorie?…` abrufen → 200, enthält `hx-swap-oob` und `<svg` (Booleans).
|
||||||
- Bestandszahlen (Buchungen, Konten, Kategorien, Szenarien, Kredite, wiederkehrende/Einmalposten) vor = nach; `planning_monthly`/`projection_points` unberührt von Seitenaufrufen der Aufteilung (Zeilenzahl vor/nach Aufruf gleich).
|
- Bestandszahlen (Buchungen, Konten, Kategorien, Szenarien, Kredite, wiederkehrende/Einmalposten) vor = nach; `planning_monthly`/`projection_points` unberührt von Seitenaufrufen der Aufteilung (Zeilenzahl vor/nach Aufruf gleich).
|
||||||
- [ ] **Step 4: Live-Klicktest** — Playwright-Container gegen `https://fbwl.creature-go.com/` (Secure-Cookie aktiv, Login nur dort): Passwort innerhalb des Skripts aus `~/.local/share/finance_pod/.env` lesen (nie ausgeben); `/aufteilung` öffnen, erstes `.seg.klickbar` klicken ⇒ Boolean „zweite Torte geladen", „URL enthält kategorie= oder sammel=", „Segment markiert". Keine Screenshots/Inhalte weitergeben.
|
- [x] **Step 4: Live-Klicktest** — Playwright-Container gegen `https://fbwl.creature-go.com/` (Secure-Cookie aktiv, Login nur dort): Passwort innerhalb des Skripts aus `~/.local/share/finance_pod/.env` lesen (nie ausgeben); `/aufteilung` öffnen, erstes `.seg.klickbar` klicken ⇒ Boolean „zweite Torte geladen", „URL enthält kategorie= oder sammel=", „Segment markiert". Keine Screenshots/Inhalte weitergeben.
|
||||||
- [ ] **Step 5: Fable-Release-Gate** (read-only Live-Verifikation) + Ledger-Eintrag „AUSBAUSTUFE 13 ABGESCHLOSSEN" in `.superpowers/sdd/progress.md`; Plan-Haken; Commit `docs: A13 Plan-Haken`.
|
- [x] **Step 5: Fable-Release-Gate** (read-only Live-Verifikation) + Ledger-Eintrag „AUSBAUSTUFE 13 ABGESCHLOSSEN" in `.superpowers/sdd/progress.md`; Plan-Haken; Commit `docs: A13 Plan-Haken`.
|
||||||
|
|||||||
Reference in New Issue
Block a user