docs: Plan-Haekchen Ausbaustufe 9
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Ausbaustufe 9 Implementation Plan — Vorschlags-Algorithmus v2 (v0.9.0)
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
||||
|
||||
**Goal:** `suggest_recurring` erkennt monatliche/vierteljährliche/jährliche Serien mit letztem Betrag, Aktiv-Check, Betrags-Clustern, Umfirmierungs-Merge und robustem Bestandsabgleich; GUI zeigt Rhythmus/Start/Hinweis; Release v0.9.0 mit Live-Gate gegen die echten Daten.
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
**Interfaces:**
|
||||
- Produces: `suggest_recurring(session, today: date | None = None) -> list[dict]` mit Keys `name, amount (Decimal), rhythm, due_day, start_date (date|None), category_id, hinweis (str)`; `SuggestionOut` mit denselben Feldern (`start_date: date | None = None`, `hinweis: str = ""`). Task 2 rendert genau diese Felder.
|
||||
|
||||
- [ ] **Step 1: Failing Tests** — in `tests/test_planning_api.py` die drei bestehenden Suggestion-Tests ERSETZEN/ERWEITERN durch die Spec-Fälle (Helper zum Anlegen synthetischer Buchungen schreiben; `dedup_hash` eindeutig, `status="confirmed"`; ein Account genügt; `today=date(2026, 7, 20)` in alle Aufrufe injizieren):
|
||||
- [x] **Step 1: Failing Tests** — in `tests/test_planning_api.py` die drei bestehenden Suggestion-Tests ERSETZEN/ERWEITERN durch die Spec-Fälle (Helper zum Anlegen synthetischer Buchungen schreiben; `dedup_hash` eindeutig, `status="confirmed"`; ein Account genügt; `today=date(2026, 7, 20)` in alle Aufrufe injizieren):
|
||||
|
||||
```python
|
||||
from datetime import date
|
||||
@@ -122,9 +122,9 @@ def test_suggest_zwei_vertraege_getrennt(db):
|
||||
|
||||
Run: `.venv/bin/python -m pytest tests/test_planning_api.py -q` → neue Tests FAIL
|
||||
|
||||
- [ ] **Step 2: Rewrite `app/services/suggestions.py`** gemäß Spec-Abschnitt „Algorithmus" (8 Schritte, Konstanten `WINDOW_DAYS=460`, Rhythmus-Tabelle monthly 25-36/≥3, quarterly 80-105/≥3, yearly 330-400/≥2, `STEP_DAYS={"monthly":30,"quarterly":91,"yearly":365}`, `ACTIVITY_FACTOR` 7/4 als `Fraction` oder Tage-Vergleich ganzzahlig, Cluster 35 %, Merge 25 %, Bestand 10 % — alle Toleranzvergleiche als `Decimal`). Struktur: `_norm`, `_rel_diff`, `_amount_clusters` (greedy gegen letztes Mitglied, gleiches Vorzeichen), `_classify` (Median der Abstände), Merge-Pass je Konto über alle Serien, `_covered_by_existing`, Hauptfunktion `suggest_recurring(session, today=None)`. Deutsche Docstrings/Kommentare zur Begründung der Toleranzen.
|
||||
- [x] **Step 2: Rewrite `app/services/suggestions.py`** gemäß Spec-Abschnitt „Algorithmus" (8 Schritte, Konstanten `WINDOW_DAYS=460`, Rhythmus-Tabelle monthly 25-36/≥3, quarterly 80-105/≥3, yearly 330-400/≥2, `STEP_DAYS={"monthly":30,"quarterly":91,"yearly":365}`, `ACTIVITY_FACTOR` 7/4 als `Fraction` oder Tage-Vergleich ganzzahlig, Cluster 35 %, Merge 25 %, Bestand 10 % — alle Toleranzvergleiche als `Decimal`). Struktur: `_norm`, `_rel_diff`, `_amount_clusters` (greedy gegen letztes Mitglied, gleiches Vorzeichen), `_classify` (Median der Abstände), Merge-Pass je Konto über alle Serien, `_covered_by_existing`, Hauptfunktion `suggest_recurring(session, today=None)`. Deutsche Docstrings/Kommentare zur Begründung der Toleranzen.
|
||||
|
||||
- [ ] **Step 3: `SuggestionOut` erweitern** — `routers/planning.py`:
|
||||
- [x] **Step 3: `SuggestionOut` erweitern** — `routers/planning.py`:
|
||||
|
||||
```python
|
||||
class SuggestionOut(BaseModel):
|
||||
@@ -137,11 +137,11 @@ class SuggestionOut(BaseModel):
|
||||
hinweis: str = ""
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Tests + Suite grün** — `.venv/bin/python -m pytest -q` → PASS (Alt-Test-Anpassungen im Report begründen).
|
||||
- [x] **Step 4: Tests + Suite grün** — `.venv/bin/python -m pytest -q` → PASS (Alt-Test-Anpassungen im Report begründen).
|
||||
|
||||
- [ ] **Step 5: Fable-Testagent-Abnahme** (Faktencheck: Toleranz-Arithmetik Decimal-rein; Aktiv-Check-Grenzen; Merge-Bedingungen; keine `date.today()`-Streuung; Alt-Test-Anpassungen berechtigt). Erst nach VERIFIED weiter.
|
||||
- [x] **Step 5: Fable-Testagent-Abnahme** (Faktencheck: Toleranz-Arithmetik Decimal-rein; Aktiv-Check-Grenzen; Merge-Bedingungen; keine `date.today()`-Streuung; Alt-Test-Anpassungen berechtigt). Erst nach VERIFIED weiter.
|
||||
|
||||
- [ ] **Step 6: Commit** — `git add finance/app/services/suggestions.py finance/app/routers/planning.py finance/tests/test_planning_api.py && git commit -m "feat: Vorschlags-Algorithmus v2 (Rhythmen, letzter Betrag, Aktiv-Check, Merge)"`
|
||||
- [x] **Step 6: Commit** — `git add finance/app/services/suggestions.py finance/app/routers/planning.py finance/tests/test_planning_api.py && git commit -m "feat: Vorschlags-Algorithmus v2 (Rhythmen, letzter Betrag, Aktiv-Check, Merge)"`
|
||||
|
||||
---
|
||||
|
||||
@@ -154,7 +154,7 @@ class SuggestionOut(BaseModel):
|
||||
**Interfaces:**
|
||||
- Consumes: `SuggestionOut`-Felder aus Task 1; Filter `|eur`/`|de_label`.
|
||||
|
||||
- [ ] **Step 1: Failing GUI-Test** — in `tests/test_gui.py` (synthetische Serie seeden, `/planung` laden):
|
||||
- [x] **Step 1: Failing GUI-Test** — in `tests/test_gui.py` (synthetische Serie seeden, `/planung` laden):
|
||||
|
||||
```python
|
||||
def test_vorschlaege_zeigen_rhythmus_und_start(client, db):
|
||||
@@ -175,17 +175,17 @@ def test_vorschlaege_zeigen_rhythmus_und_start(client, db):
|
||||
|
||||
WICHTIG: Der Test hängt von `date.today()` der App ab (Aktiv-Check!) — Serie so legen, dass sie um den echten Testlauf-Zeitpunkt herum aktiv ist, oder (besser) `suggest_recurring` in `planung_page` unverändert lassen und den Test mit relativen Daten um `date.today()` konstruieren (letzte Buchung ≤ 45 Tage vor heute, Quartalsschritte rückwärts). Die Variante mit relativen Daten umsetzen; die obigen Fixdaten sind als Muster zu verstehen und auf `date.today()`-relative Werte umzustellen (inkl. erwartetem Start-String via `.strftime('%d.%m.%Y')`).
|
||||
|
||||
- [ ] **Step 2: Template** — Vorschlags-Tabelle: Kopf `Name | Betrag | Rhythmus | Fälligkeitstag | Start | (Aktion)`; Zellen `{{ s.rhythm|de_label }}`, `{{ s.start_date.strftime('%d.%m.%Y') if s.start_date else '–' }}`; Betrag-Zelle ergänzt `{% if s.hinweis %}<span class="muted">{{ s.hinweis }}</span>{% endif %}`; Übernahme-Formular: hidden inputs unverändert plus `<input type="hidden" name="start_date" value="{{ s.start_date.isoformat() if s.start_date else '' }}">` (json-form macht leer → null). Hinweistext unter dem Fieldset: „Erkannt werden monatliche, vierteljährliche und jährliche Serien; Betrag = jeweils letzte Buchung."
|
||||
- [x] **Step 2: Template** — Vorschlags-Tabelle: Kopf `Name | Betrag | Rhythmus | Fälligkeitstag | Start | (Aktion)`; Zellen `{{ s.rhythm|de_label }}`, `{{ s.start_date.strftime('%d.%m.%Y') if s.start_date else '–' }}`; Betrag-Zelle ergänzt `{% if s.hinweis %}<span class="muted">{{ s.hinweis }}</span>{% endif %}`; Übernahme-Formular: hidden inputs unverändert plus `<input type="hidden" name="start_date" value="{{ s.start_date.isoformat() if s.start_date else '' }}">` (json-form macht leer → null). Hinweistext unter dem Fieldset: „Erkannt werden monatliche, vierteljährliche und jährliche Serien; Betrag = jeweils letzte Buchung."
|
||||
|
||||
- [ ] **Step 3: Suite grün**; **Step 4: Fable-Abnahme** (Live-Approximation: Rendering + Übernahme-Roundtrip eines Quartals-Vorschlags inkl. start_date); **Step 5: Commit** `feat: Vorschlaege mit Rhythmus, Start und Hinweis`.
|
||||
- [x] **Step 3: Suite grün**; **Step 4: Fable-Abnahme** (Live-Approximation: Rendering + Übernahme-Roundtrip eines Quartals-Vorschlags inkl. start_date); **Step 5: Commit** `feat: Vorschlaege mit Rhythmus, Start und Hinweis`.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Release v0.9.0 + Live-Gate gegen echte Daten
|
||||
|
||||
- [ ] **Step 1:** Suite final; `VERSION` → 0.9.0; Commit; `./create_pod_finance.sh`; `/api/version` == 0.9.0.
|
||||
- [ ] **Step 2: Fable-Release-Gate (LIVE, lesend):** `GET /api/recurring/suggestions` gegen die echte DB. Prüfen: (a) KEINER der bestehenden ~41 Fixposten wird erneut vorgeschlagen (Bestandsabgleich wirkt, auch bei gedrifteten Beträgen); (b) keine bekannten toten Serien (gelöschte PayPal-−4,99-Serie, ausgelaufene WWK-−35-Police) im Ergebnis; (c) verbleibende Vorschläge einzeln gegen die Buchungen plausibilisieren (echte aktive Serie? korrekte Werte?). Ergebnisliste NUR im Chat/Bericht, nie committen. Bei Fehlklassifikationen: Befund zurück an Task 1 (Toleranzen), Fix + Re-Gate.
|
||||
- [ ] **Step 3:** Ledger (generisch) + Plan-Häkchen + Push; Kandidatenliste dem Nutzer berichten.
|
||||
- [x] **Step 1:** Suite final; `VERSION` → 0.9.0; Commit; `./create_pod_finance.sh`; `/api/version` == 0.9.0.
|
||||
- [x] **Step 2: Fable-Release-Gate (LIVE, lesend):** `GET /api/recurring/suggestions` gegen die echte DB. Prüfen: (a) KEINER der bestehenden ~41 Fixposten wird erneut vorgeschlagen (Bestandsabgleich wirkt, auch bei gedrifteten Beträgen); (b) keine bekannten toten Serien (gelöschte PayPal-−4,99-Serie, ausgelaufene WWK-−35-Police) im Ergebnis; (c) verbleibende Vorschläge einzeln gegen die Buchungen plausibilisieren (echte aktive Serie? korrekte Werte?). Ergebnisliste NUR im Chat/Bericht, nie committen. Bei Fehlklassifikationen: Befund zurück an Task 1 (Toleranzen), Fix + Re-Gate.
|
||||
- [x] **Step 3:** Ledger (generisch) + Plan-Häkchen + Push; Kandidatenliste dem Nutzer berichten.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user