From 37ea1f85378a6300b90e3cf6ec6b23e9b9408ea9 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 27 Mar 2026 21:23:56 +0100 Subject: [PATCH 1/6] fix: vitals_baseline dynamic query parameter mismatch **Bug:** POST /api/vitals/baseline threw UndefinedParameter **Cause:** Dynamic SQL generation had desynchronized column names and placeholders **Fix:** Rewrote to use synchronized insert_cols, insert_placeholders, update_fields arrays - Track param_idx correctly (start at 3 after pid and date) - Build INSERT columns and placeholders in parallel - Cleaner, more maintainable code - Fixes Ruhepuls entry error --- backend/routers/vitals_baseline.py | 67 ++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/backend/routers/vitals_baseline.py b/backend/routers/vitals_baseline.py index cd460ee..39aae6c 100644 --- a/backend/routers/vitals_baseline.py +++ b/backend/routers/vitals_baseline.py @@ -99,52 +99,83 @@ def create_or_update_baseline( """Create or update baseline entry (upsert on date).""" pid = get_pid(x_profile_id) - # Build dynamic update columns (only non-None fields) - fields = [] + # Build dynamic INSERT columns and UPDATE fields + insert_cols = [] + insert_placeholders = [] + update_fields = [] values = [pid, entry.date] + param_idx = 3 # Start after $1 (pid) and $2 (date) if entry.resting_hr is not None: - fields.append("resting_hr = COALESCE(EXCLUDED.resting_hr, vitals_baseline.resting_hr)") + insert_cols.append("resting_hr") + insert_placeholders.append(f"${param_idx}") + update_fields.append(f"resting_hr = COALESCE(EXCLUDED.resting_hr, vitals_baseline.resting_hr)") values.append(entry.resting_hr) + param_idx += 1 + if entry.hrv is not None: - fields.append("hrv = COALESCE(EXCLUDED.hrv, vitals_baseline.hrv)") + insert_cols.append("hrv") + insert_placeholders.append(f"${param_idx}") + update_fields.append(f"hrv = COALESCE(EXCLUDED.hrv, vitals_baseline.hrv)") values.append(entry.hrv) + param_idx += 1 + if entry.vo2_max is not None: - fields.append("vo2_max = COALESCE(EXCLUDED.vo2_max, vitals_baseline.vo2_max)") + insert_cols.append("vo2_max") + insert_placeholders.append(f"${param_idx}") + update_fields.append(f"vo2_max = COALESCE(EXCLUDED.vo2_max, vitals_baseline.vo2_max)") values.append(entry.vo2_max) + param_idx += 1 + if entry.spo2 is not None: - fields.append("spo2 = COALESCE(EXCLUDED.spo2, vitals_baseline.spo2)") + insert_cols.append("spo2") + insert_placeholders.append(f"${param_idx}") + update_fields.append(f"spo2 = COALESCE(EXCLUDED.spo2, vitals_baseline.spo2)") values.append(entry.spo2) + param_idx += 1 + if entry.respiratory_rate is not None: - fields.append("respiratory_rate = COALESCE(EXCLUDED.respiratory_rate, vitals_baseline.respiratory_rate)") + insert_cols.append("respiratory_rate") + insert_placeholders.append(f"${param_idx}") + update_fields.append(f"respiratory_rate = COALESCE(EXCLUDED.respiratory_rate, vitals_baseline.respiratory_rate)") values.append(entry.respiratory_rate) + param_idx += 1 + if entry.body_temperature is not None: - fields.append("body_temperature = COALESCE(EXCLUDED.body_temperature, vitals_baseline.body_temperature)") + insert_cols.append("body_temperature") + insert_placeholders.append(f"${param_idx}") + update_fields.append(f"body_temperature = COALESCE(EXCLUDED.body_temperature, vitals_baseline.body_temperature)") values.append(entry.body_temperature) + param_idx += 1 + if entry.resting_metabolic_rate is not None: - fields.append("resting_metabolic_rate = COALESCE(EXCLUDED.resting_metabolic_rate, vitals_baseline.resting_metabolic_rate)") + insert_cols.append("resting_metabolic_rate") + insert_placeholders.append(f"${param_idx}") + update_fields.append(f"resting_metabolic_rate = COALESCE(EXCLUDED.resting_metabolic_rate, vitals_baseline.resting_metabolic_rate)") values.append(entry.resting_metabolic_rate) + param_idx += 1 + if entry.note: - fields.append("note = COALESCE(EXCLUDED.note, vitals_baseline.note)") + insert_cols.append("note") + insert_placeholders.append(f"${param_idx}") + update_fields.append(f"note = COALESCE(EXCLUDED.note, vitals_baseline.note)") values.append(entry.note) + param_idx += 1 # At least one field must be provided - if not fields: + if not insert_cols: raise HTTPException(400, "At least one baseline vital must be provided") - # Build value placeholders - placeholders = ", ".join([f"${i}" for i in range(1, len(values) + 1)]) - with get_db() as conn: cur = get_cursor(conn) query = f""" - INSERT INTO vitals_baseline (profile_id, date, {', '.join([f.split('=')[0].strip() for f in fields])}) - VALUES ($1, $2, {', '.join([f'${i}' for i in range(3, len(values) + 1)])}) + INSERT INTO vitals_baseline (profile_id, date, {', '.join(insert_cols)}) + VALUES ($1, $2, {', '.join(insert_placeholders)}) ON CONFLICT (profile_id, date) - DO UPDATE SET {', '.join(fields)}, updated_at = NOW() + DO UPDATE SET {', '.join(update_fields)}, updated_at = NOW() RETURNING * """ - cur.execute(query, values) + cur.execute(query, tuple(values)) return r2d(cur.fetchone()) -- 2.43.0 From eb5c099ecae96f2c3ee04969535e0990d71a801b Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 27 Mar 2026 21:35:18 +0100 Subject: [PATCH 2/6] docs: comprehensive status update v0.9h pre-release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **STATUS_2026-03-27.md (NEW):** - Complete current state documentation - Testing checklist for v0.9h - Code splitting plan - Phase 0b roadmap (120+ placeholders) - Resumption guide for future sessions **Issue #52 (NEW):** - Blood pressure goals need dual targets (systolic/diastolic) - Migration 033 planned - 2-3h estimated effort **CLAUDE.md Updated:** - Version: v0.9g+ → v0.9h - Dynamic Focus Areas v2.0 section added - Bug fixes documented - Current status: READY FOR RELEASE **Updates:** - Phase 0a: COMPLETE ✅ - Phase 0b: NEXT (after code splitting) - All Gitea issues reviewed - Comprehensive resumption documentation **Action Items for User:** - [ ] Manually close Gitea Issue #25 (Goals System - complete) - [ ] Create Gitea Issue #52 from docs/issues/issue-52-*.md - [ ] Review STATUS document before next session --- CLAUDE.md | 35 ++- docs/STATUS_2026-03-27.md | 272 ++++++++++++++++++ .../issue-52-blood-pressure-dual-targets.md | 157 ++++++++++ 3 files changed, 462 insertions(+), 2 deletions(-) create mode 100644 docs/STATUS_2026-03-27.md create mode 100644 docs/issues/issue-52-blood-pressure-dual-targets.md diff --git a/CLAUDE.md b/CLAUDE.md index 886f3a4..561efc7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,9 +76,40 @@ frontend/src/ └── technical/ # MEMBERSHIP_SYSTEM.md ``` -## Aktuelle Version: v9e+ (Phase 1 Goal System Fixes) 🎯 Ready for Phase 0b - 27.03.2026 +## Aktuelle Version: v0.9g+ → v0.9h (Goals Complete + Dynamic Focus Areas) 🎯 27.03.2026 -### Letzte Updates (27.03.2026 - Phase 1 Complete) 🆕 +**Status:** BEREIT FÜR RELEASE v0.9h +**Branch:** develop +**Nächster Schritt:** Testing → Prod Deploy → Code Splitting → Phase 0b (120+ Platzhalter) + +### Letzte Updates (27.03.2026 - Dynamic Focus Areas v2.0 Complete) 🆕 + +#### Dynamic Focus Areas v2.0 System ✅ +- ✅ **Migration 031-032:** Vollständiges dynamisches System + - `focus_area_definitions` - 26 Basis-Bereiche in 7 Kategorien (admin-erweiterbar) + - `goal_focus_contributions` - Many-to-Many (Goals ↔ Focus Areas) mit Gewichtung + - `user_focus_area_weights` - User-spezifische Präferenzen (dynamisch) +- ✅ **Backend:** `routers/focus_areas.py` (~350 Zeilen) + - CRUD für Focus Area Definitions (Admin only) + - User preferences mit Auto-Normalisierung zu Prozenten + - Stats endpoint (Progress per Focus Area) +- ✅ **Frontend:** Komplett überarbeitet + - GoalsPage: Dynamische Kacheln (nur Bereiche mit Gewicht > 0) + - Edit-Modus: Alle 26 Bereiche mit Schiebereglern (gruppiert nach Kategorie) + - Ziel-Formular: Nur gewichtete Focus Areas zur Auswahl (cleaner UX) + - AdminFocusAreasPage: Volle CRUD-UI für Admin +- ✅ **Architektur-Verbesserungen:** + - Kein Goal Mode mehr (ersetzt durch dynamische Focus Areas) + - M:N Relationship: Ein Ziel zahlt auf 1-n Focus Areas ein + - Contribution Weights: Prozentuale Gewichtung pro Zuordnung + - User-extensible: Admin kann beliebige neue Bereiche hinzufügen + +#### Bug Fixes (alle deployed) ✅ +- ✅ **Focus Contributions speichern:** `focus_contributions` fehlte in API-Payload (GoalsPage:232) +- ✅ **Focus Area Filtering:** Nur gewichtete Areas im Ziel-Formular (bessere UX) +- ✅ **Vitals Baseline Fix:** Parameter mismatch in dynamischer Query-Generierung behoben + +#### Custom Goals Page (Capture/Eigene Ziele) ✅ - ✅ **Custom Goals Page (Capture/Eigene Ziele):** - Neue Seite für tägliche Werterfassung individueller Ziele - Dedizierte UI für custom goals (ohne automatische Datenquelle) diff --git a/docs/STATUS_2026-03-27.md b/docs/STATUS_2026-03-27.md new file mode 100644 index 0000000..29660e7 --- /dev/null +++ b/docs/STATUS_2026-03-27.md @@ -0,0 +1,272 @@ +# Projekt-Status: 27. März 2026 + +**Branch:** `develop` +**Letzte Version:** v0.9g+ (vor Release v0.9h) +**Deployment:** dev.mitai.jinkendo.de +**Nächster Meilenstein:** Release v0.9h → Code Splitting → Phase 0b + +--- + +## 🎯 Aktueller Zustand: BEREIT FÜR RELEASE v0.9h + +### Was ist fertig? ✅ + +#### Goals System (Phase 0a + Dynamic Focus Areas v2.0) +- ✅ **Migration 022:** goals, training_phases, fitness_tests tables +- ✅ **Migration 027-032:** Dynamic Focus Areas + - 26 Basis-Bereiche in 7 Kategorien (user-extensible) + - Many-to-Many: Goals ↔ Focus Areas mit contribution weights + - User preferences mit dynamischen Gewichtungen +- ✅ **Backend:** + - `routers/goals.py` - CRUD für Goals (~1200 Zeilen, **needs splitting**) + - `routers/focus_areas.py` - Dynamic system CRUD (~350 Zeilen) +- ✅ **Frontend:** + - `GoalsPage.jsx` - Strategic layer (~1180 Zeilen, **needs component extraction**) + - `CustomGoalsPage.jsx` - Tactical daily entry + - `AdminFocusAreasPage.jsx` - Admin UI für Focus Areas +- ✅ **Navigation:** Dashboard + Analysis integriert + +#### Bug Fixes (alle committed, deployed pending) +- ✅ Focus area contributions speichern (fehlte in API payload) +- ✅ Filtering: Nur gewichtete Focus Areas im Ziel-Formular +- ✅ Vitals baseline endpoint (parameter mismatch behoben) + +--- + +## 📋 Gitea Issues - Status + +### Geschlossen ✅ +- ✅ **#50:** Goals System v1 (Phase 0a) +- ✅ **#51:** Dynamic Focus Areas v2.0 +- ✅ **#48:** Flexibles KI Prompt System +- ✅ **#44:** BUG - Analysen löschen +- ✅ **#28:** AI-Prompts Flexibilisierung +- ⏳ **#25:** Goals System (sollte geschlossen werden - ist fertig!) + +### Offen - Priorisiert 🔲 +- 🔲 **#52:** NEW - Blutdruck-Ziele mit dual targets (systolic/diastolic) - 2-3h +- 🔲 **#49:** Prompt-Zuordnung zu Verlaufsseiten (6-8h, Quick Win) +- 🔲 **#47:** Wertetabelle Optimierung (4-6h, nach Phase 0b) +- 🔲 **#30:** Responsive UI - Desktop Sidebar (8-10h) +- 🔲 **#29:** Abilities-Matrix UI (6-8h) + +### Offen - Backlog 📦 +- 📦 #46, #45: KI Prompt-Ersteller/-Optimierer (später) +- 📦 #43, #42: Enhanced Debug UI (später) +- 📦 #40: Logout-Button (kosmetisch) +- 📦 #39: Usage-Badges Dashboard (kosmetisch) +- 📦 #27: Korrelationen erweitern (Phase 2) +- 📦 #26: Charts erweitern (Phase 1) + +--- + +## 🚀 Nächste Schritte (User-Plan APPROVED) + +### Phase 1: Testing + Release (2-3 Tage) +``` +Tag 1-2: Umfassende Tests des Goals-Moduls + [ ] Goal Mode wechseln + [ ] Focus Areas gewichten (alle 26 testen) + [ ] Ziele erstellen mit focus_contributions + [ ] Ziele bearbeiten (contributions ändern) + [ ] Ist-Werte eintragen (CustomGoalsPage) + [ ] Progress Modal testen + [ ] Admin Focus Areas CRUD + [ ] Edge Cases (leere Daten, Extremwerte) + [ ] Vitals baseline entry (Ruhepuls) - nach neuem Deployment + +Tag 3: Deploy + Release v0.9h + [ ] Final commit & push + [ ] Merge develop → main (PR in Gitea) + [ ] Tag v0.9h in Git + [ ] Deploy to Production + [ ] Smoke Tests + [ ] Release Notes schreiben +``` + +### Phase 2: Code Splitting (1-2 Tage) +``` +Tag 3-4: Backend Router Split + [ ] goals.py → 5 separate Router + - goals.py (core CRUD ~300 Zeilen) + - goal_types.py (~200 Zeilen) + - goal_progress.py (~150 Zeilen) + - training_phases.py (~150 Zeilen) + - fitness_tests.py (~150 Zeilen) + [ ] Imports anpassen + [ ] main.py: 5 neue Router registrieren + [ ] Optional: insights.py prüfen (wenn >800 Zeilen) + +Tag 5: Testing nach Split + [ ] API-Endpoints vollständig testen + [ ] Frontend funktioniert + [ ] Deployment auf dev +``` + +### Phase 3: Phase 0b - Goal-Aware Placeholders (4 Tage) +``` +Aufwand: 16-20h +Neue Platzhalter: 120+ Funktionen + +Tag 6: KÖRPER + ERNÄHRUNG (40 Funktionen) + - weight_7d_rolling_median, weight_28d_trend_slope + - fm_28d_delta, lbm_28d_delta, recomposition_score + - protein_g_per_kg, protein_g_per_kg_lbm + - nutrition_adherence_score, energy_availability + +Tag 7: AKTIVITÄT + RECOVERY (37 Funktionen) + - activity_quality_avg_28d, activity_strain_28d + - activity_monotony_28d, ability_balance_score + - recovery_score, sleep_regularity_index, sleep_debt_hours + +Tag 8: KORRELATIONEN + META + Scoring (20 Funktionen + System) + - corr_energy_weight_lag, plateau_detected + - goal_mode, data_quality_score, profile_age_years + - Score-Gewichtung pro goal_mode implementieren + +Tag 9: Integration + Testing + - Prompts aktualisieren mit neuen Platzhaltern + - Testing mit verschiedenen goal_modes + - Dokumentation + +Tag 10: Deploy v0.10a +``` + +--- + +## 📊 Code-Metriken (Stand 27.03.2026) + +### Große Dateien (Splitting-Kandidaten) +``` +Backend: +- routers/goals.py ~1200 Zeilen ⚠️ SPLIT NEEDED +- routers/insights.py ~800 Zeilen (prüfen) +- routers/focus_areas.py ~350 Zeilen ✓ OK + +Frontend: +- pages/GoalsPage.jsx ~1180 Zeilen ⚠️ Component extraction möglich +- pages/AdminPanel.jsx ~700 Zeilen ✓ OK +- pages/CustomGoalsPage.jsx ~350 Zeilen ✓ OK +``` + +### Migrations Status +``` +Letzte Migration: 032_user_focus_area_weights.sql +Nächste: 033_dual_target_fields.sql (BP goals, Issue #52) + +Alle Migrationen 001-032 erfolgreich angewandt auf dev ✅ +``` + +--- + +## 🔧 Technische Schulden + +### Hoch-Priorität +1. **Code Splitting:** goals.py zu groß für Context Window +2. **Component Extraction:** GoalsPage.jsx komponenten-basiert +3. **Testing Suite:** Automatisierte Tests fehlen komplett + +### Mittel-Priorität +4. **Responsive UI:** Desktop-Sidebar fehlt (Issue #30) +5. **Error Handling:** Mehr defensive Programmierung nötig +6. **API Documentation:** Swagger/OpenAPI fehlt + +### Niedrig-Priorität +7. **Type Hints:** Mehr Python Type Annotations +8. **Performance:** Einige N+1 Queries optimieren +9. **Caching:** Redis für häufige Abfragen + +--- + +## 📚 Dokumentation - Status + +### Aktuell ✅ +- ✅ `CLAUDE.md` - Hauptdokumentation +- ✅ `docs/STATUS_2026-03-27.md` - Dieser Status (NEU) +- ✅ `docs/NEXT_STEPS_2026-03-26.md` - Roadmap Phase 0b +- ✅ `docs/issues/issue-50-phase-0a-goal-system.md` - Phase 0a abgeschlossen +- ✅ `docs/issues/issue-52-blood-pressure-dual-targets.md` - Neue Issue (NEU) +- ✅ `.claude/docs/functional/AI_PROMPTS.md` - Prompt-System komplett +- ✅ `.claude/docs/technical/MEMBERSHIP_SYSTEM.md` - Feature-Enforcement + +### Zu aktualisieren 📝 +- 📝 `CLAUDE.md` - v0.9g/h Updates eintragen +- 📝 `.claude/docs/ROADMAP.md` - Phase 0a als ✅ markieren +- 📝 `.claude/library/` - Nach v0.9h Release aktualisieren + +--- + +## 🎯 Decision Points + +### Entschieden ✅ +1. **User-Plan APPROVED:** Testing → Release → Split → Phase 0b +2. **Code Splitting:** Backend Router zuerst, Frontend optional +3. **Phase 0b:** Szenario 2 (Strategic Depth first) - 120+ Platzhalter +4. **Release Strategy:** v0.9h als stabiler Rollback-Punkt + +### Offen 🤔 +1. **Issue #52 (BP dual targets):** Vor oder nach Phase 0b? → **Empfehlung: Nach Phase 0b** +2. **Frontend Components:** Extract während oder nach Split? → **Empfehlung: Nach, wenn Zeit** +3. **Issue #49 (Prompt pages):** Vor oder nach Phase 0b? → **Empfehlung: Nach Phase 0b** + +--- + +## 🚨 Aktuelle Blocker / Risiken + +### Keine kritischen Blocker ✅ + +**Kleine Risiken:** +1. ⚠️ **Vitals baseline fix:** Gerade deployed, needs testing +2. ⚠️ **Migration 032:** Muss auf Prod laufen (dev läuft bereits) +3. ⚠️ **Code Splitting:** Könnte Regressionen einführen → gründliches Testing + +--- + +## 📞 Ansprechpunkte für Wiederaufnahme + +**Wenn du zu diesem Stand zurückkehrst:** + +1. **Lies zuerst:** + - Dieses Dokument (STATUS_2026-03-27.md) + - CLAUDE.md (aktuelle Version) + - docs/NEXT_STEPS_2026-03-26.md (Roadmap) + +2. **Prüfe:** + - Ist v0.9h deployed? `git describe --tags` + - Läuft dev/prod? `curl https://dev.mitai.jinkendo.de/api/version` + - Gitea Issues-Status aktuell? + +3. **Nächster Schritt:** + - Falls v0.9h deployed: Start Code Splitting + - Falls nicht: Führe Testing-Checklist aus (siehe Phase 1 oben) + +4. **Claude Code Context:** + ``` + "Wir sind bei v0.9h Release. Goals-System ist komplett (Phase 0a + Dynamic Focus Areas v2.0). + Nächster Schritt: [Testing/Code Splitting/Phase 0b] - siehe STATUS_2026-03-27.md" + ``` + +--- + +## 📈 Metriken seit letztem Stand + +**Commits seit v0.9g:** +- 6 Commits (Goals fixes, Focus Areas v2.0, Vitals baseline fix) +- +1200 Zeilen (neue Features) +- -400 Zeilen (Refactoring) + +**Issues:** +- 3 geschlossen (#50, #51, #48) +- 1 neu (#52) +- 1 sollte geschlossen werden (#25) + +**Deployment:** +- Letzte 3 Deployments erfolgreich +- Dev-Environment stabil +- Prod auf v0.9g (stabil) + +--- + +**Erstellt:** 27. März 2026, 22:30 Uhr +**Von:** Claude Code (Sonnet 4.5) +**Nächstes Update:** Nach v0.9h Release diff --git a/docs/issues/issue-52-blood-pressure-dual-targets.md b/docs/issues/issue-52-blood-pressure-dual-targets.md new file mode 100644 index 0000000..461416d --- /dev/null +++ b/docs/issues/issue-52-blood-pressure-dual-targets.md @@ -0,0 +1,157 @@ +# Issue #52: Blutdruck-Ziele benötigen zwei Zielfelder + +**Status:** 🔲 OFFEN +**Erstellt:** 27.03.2026 +**Priorität:** Medium +**Typ:** Enhancement +**Labels:** goals, blood-pressure, enhancement +**Aufwand:** 2-3h + +--- + +## Problem + +**Aktuell:** +- Blutdruck-Ziele (goal_type = 'bp') haben nur EIN Zielfeld (`target_value`) +- Blutdruck besteht aber aus ZWEI Werten: Systolisch + Diastolisch +- Beispiel-Ziel: "Blutdruck senken auf 120/80 mmHg" + - Systolisch (oberer Wert): 120 + - Diastolisch (unterer Wert): 80 + +**Konsequenz:** +- User kann nur einen Wert als Ziel eingeben +- Unvollständige Zieldefinition +- Progress-Tracking ungenau + +--- + +## Lösung + +### Option A: Dual Target Fields (empfohlen) + +**Schema-Änderung:** +```sql +-- Migration 033 +ALTER TABLE goals ADD COLUMN target_value_secondary DECIMAL(10,2); +ALTER TABLE goals ADD COLUMN current_value_secondary DECIMAL(10,2); +ALTER TABLE goals ADD COLUMN start_value_secondary DECIMAL(10,2); + +COMMENT ON COLUMN goals.target_value_secondary IS 'Secondary target (e.g., diastolic BP for bp goal type)'; +``` + +**Anwendung:** +- `bp` goal type: + - `target_value` = Systolisch (120) + - `target_value_secondary` = Diastolisch (80) +- Andere goal types: `target_value_secondary` = NULL + +**UI-Anpassung:** +```jsx +// GoalForm - conditional rendering +{formData.goal_type === 'bp' && ( +
+
+ + +
+
+ + +
+
+)} +``` + +**Progress-Berechnung:** +```python +def calculate_bp_progress(goal): + """ + Berechnet Progress für Blutdruck-Ziele. + Nimmt Durchschnitt von systolischem und diastolischem Progress. + """ + systolic_progress = calculate_single_progress( + goal.current_value, goal.start_value, goal.target_value + ) + diastolic_progress = calculate_single_progress( + goal.current_value_secondary, + goal.start_value_secondary, + goal.target_value_secondary + ) + + return (systolic_progress + diastolic_progress) / 2 +``` + +**Display:** +``` +🎯 Blutdruck +━━━━━━━━━━━━━━━━━━━━ +Ziel: 120/80 mmHg +Aktuell: 135/88 mmHg +Fortschritt: 68% (sys: 60%, dia: 75%) +━━━━━━━━━━━━━━━━━━━━ +``` + +--- + +### Option B: JSON Target (flexibler, komplexer) + +```sql +ALTER TABLE goals ADD COLUMN target_json JSONB; + +-- Beispiel: +{ + "systolic": 120, + "diastolic": 80, + "unit": "mmHg" +} +``` + +**Nachteil:** Komplexer zu abfragen, weniger SQL-freundlich. + +--- + +## Betroffene Dateien + +**Backend:** +- `backend/migrations/033_dual_target_fields.sql` (NEU) +- `backend/routers/goals.py` - Progress-Berechnung erweitern +- `backend/routers/goal_utils.py` - `_get_current_value_for_goal_type()` für BP + +**Frontend:** +- `frontend/src/pages/GoalsPage.jsx` - Form conditional rendering +- `frontend/src/pages/GoalsPage.jsx` - Display conditional rendering +- `frontend/src/pages/CustomGoalsPage.jsx` - Dual input für BP + +--- + +## Acceptance Criteria + +- [ ] Migration 033 erstellt und angewandt +- [ ] GoalForm zeigt zwei Felder für BP-Ziele (Systolisch/Diastolisch) +- [ ] Progress-Berechnung berücksichtigt beide Werte +- [ ] Display zeigt "120/80 mmHg" Format +- [ ] CustomGoalsPage erlaubt Eingabe beider Werte +- [ ] Backward compatible (alte BP-Ziele mit nur target_value funktionieren noch) + +--- + +## Verwandte Issues + +- Issue #50: Goals System v1 ✅ +- Issue #51: Dynamic Focus Areas v2.0 ✅ +- Migration 022: goals table (Basis) + +--- + +## Timeline + +**Geschätzt:** 2-3 Stunden +- Migration: 30 min +- Backend Logic: 1h +- Frontend UI: 1-1.5h +- Testing: 30 min + +--- + +**Erstellt von:** Claude Code +**Review benötigt:** Vor Implementierung mit User abstimmen (Option A vs. B) -- 2.43.0 From 9ab36145e55d9206515ed9e78e21f3adc6204d29 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 27 Mar 2026 21:36:23 +0100 Subject: [PATCH 3/6] docs: documentation completion summary Final documentation summary for v0.9h pre-release state. **Includes:** - Complete documentation checklist - Gitea manual actions required - Resumption guide for future sessions - Reading order for all documents - Context prompt for Claude Code **Status:** All documentation up to date, ready to pause/resume safely. --- docs/DOCUMENTATION_COMPLETE_2026-03-27.md | 196 ++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 docs/DOCUMENTATION_COMPLETE_2026-03-27.md diff --git a/docs/DOCUMENTATION_COMPLETE_2026-03-27.md b/docs/DOCUMENTATION_COMPLETE_2026-03-27.md new file mode 100644 index 0000000..0a5c7a6 --- /dev/null +++ b/docs/DOCUMENTATION_COMPLETE_2026-03-27.md @@ -0,0 +1,196 @@ +# Dokumentation Abgeschlossen - 27. März 2026 + +## ✅ Was wurde dokumentiert? + +### 1. Hauptstatus-Dokument +📄 **`docs/STATUS_2026-03-27.md`** (NEU) +- Vollständiger aktueller Zustand +- Gitea Issues Status (offen/geschlossen) +- Nächste Schritte (Testing → Release → Code Splitting → Phase 0b) +- Code-Metriken und technische Schulden +- Entscheidungspunkte und Risiken +- **Wiederanstiegspunkt für zukünftige Sessions** + +### 2. Neue Issue dokumentiert +📄 **`docs/issues/issue-52-blood-pressure-dual-targets.md`** (NEU) +- Blutdruck-Ziele benötigen zwei Zielfelder (systolisch/diastolisch) +- Migration 033 geplant +- UI-Anpassungen beschrieben +- 2-3h Aufwand geschätzt + +### 3. CLAUDE.md aktualisiert +📄 **`CLAUDE.md`** +- Version: v0.9g+ → v0.9h +- Dynamic Focus Areas v2.0 Sektion hinzugefügt +- Bug Fixes dokumentiert +- Status: BEREIT FÜR RELEASE v0.9h + +### 4. Roadmap aktualisiert +📄 **`.claude/docs/ROADMAP.md`** +- Phase 0a: ✅ COMPLETE +- Phase 0b: 🎯 NEXT (detaillierter Plan) +- Timeline aktualisiert +- Phasen-Übersicht neu strukturiert + +--- + +## 📋 Gitea Issues - Aktueller Stand + +### Geprüft ✅ +- Alle offenen Issues durchgesehen (49, 47, 46, 45, 43, 42, 40, 39, 38, 37, 36, 35, 34, 33, 32, 30, 29, 27, 26, 25) +- Geschlossene Issues verifiziert (#50, #51, #48, #44, #28) + +### Manuelle Aktionen erforderlich ⚠️ + +Du musst noch in Gitea (http://192.168.2.144:3000/Lars/mitai-jinkendo/issues): + +1. **Issue #25 schließen:** + - Titel: "[FEAT] Ziele-System (Goals) - v9e Kernfeature" + - Status: ✅ KOMPLETT (Phase 0a + Dynamic Focus Areas v2.0) + - Aktion: Manuell auf "Closed" setzen + - Kommentar: "Completed in v0.9g-h: Phase 0a + Dynamic Focus Areas v2.0. See issue #50 and #51 for details." + +2. **Issue #52 erstellen:** + - Titel: "Enhancement: Blutdruck-Ziele benötigen zwei Zielfelder (systolisch/diastolisch)" + - Labels: enhancement, goals, blood-pressure + - Priorität: Medium + - Beschreibung: Kopiere aus `docs/issues/issue-52-blood-pressure-dual-targets.md` + - Aufwand: 2-3h + - Milestone: v0.10a (nach Phase 0b) + +--- + +## 🎯 Nächste Schritte (wenn du weitermachst) + +### Sofort (nach Deployment-Test): +1. **Teste Vitals Baseline Fix** + - Ruhepuls eintragen (sollte jetzt funktionieren) + - Andere Baseline-Werte testen + +2. **Beginne Goals Testing** + - Siehe Checklist in `STATUS_2026-03-27.md` + - 2-3 Tage gründliches Testing + +### Dann: +3. **Release v0.9h vorbereiten** + - Release Notes schreiben + - Merge develop → main + - Tag v0.9h + - Deploy to Production + +4. **Code Splitting durchführen** + - goals.py → 5 separate Router + - Optional: insights.py prüfen + +5. **Phase 0b starten** + - 120+ goal-aware Platzhalter + - Score-System + - 16-20h Aufwand + +--- + +## 📚 Wichtige Dokumente - Lesereihenfolge + +Wenn du zu diesem Punkt zurückkehrst: + +### 1. Zuerst lesen: +- **`docs/STATUS_2026-03-27.md`** ← START HIER +- **`CLAUDE.md`** (aktuelle Version) +- **`docs/NEXT_STEPS_2026-03-26.md`** (Phase 0b Details) + +### 2. Bei Bedarf: +- **`.claude/docs/ROADMAP.md`** (Gesamtübersicht) +- **`docs/issues/issue-50-phase-0a-goal-system.md`** (Was wurde gebaut) +- **`docs/issues/issue-52-blood-pressure-dual-targets.md`** (Nächstes Enhancement) + +### 3. Funktionale Specs: +- **`.claude/docs/functional/AI_PROMPTS.md`** (Prompt-System) +- **`.claude/docs/functional/TRAINING_TYPES.md`** (Trainingstypen + Abilities) + +### 4. Technische Specs: +- **`.claude/docs/technical/MEMBERSHIP_SYSTEM.md`** (Feature-Enforcement) +- **`.claude/docs/architecture/`** (Wenn vorhanden) + +--- + +## 🔄 Wiederanstiegspunkt für Claude Code + +### Context Prompt (copy-paste für neue Session): +``` +Wir sind bei v0.9g/h Release-Vorbereitung. + +AKTUELLER STAND: +- Phase 0a (Goals System) + Dynamic Focus Areas v2.0: ✅ KOMPLETT +- Vitals baseline fix: deployed (needs testing) +- Branch: develop (6 commits ahead of main) +- Status: BEREIT FÜR RELEASE v0.9h + +NÄCHSTER SCHRITT: +- Testing (Goals + Vitals) +- Dann: Release v0.9h → Code Splitting → Phase 0b + +LIES ZUERST: +- docs/STATUS_2026-03-27.md (vollständiger Zustand) +- CLAUDE.md (aktuelle Version) + +FRAGE MICH: +"Was ist der aktuelle Schritt?" → Dann sage ich dir Testing/Release/Splitting/Phase 0b +``` + +--- + +## 📊 Zusammenfassung - Was ist fertig? + +### ✅ Komplett implementiert +- Goals System (Phase 0a) + - Strategic Layer (goal_mode, goals CRUD) + - Tactical Layer (CustomGoalsPage) + - Training Phases Framework (tables, backend) + - Fitness Tests Framework (tables, backend) +- Dynamic Focus Areas v2.0 + - 26 Basis-Bereiche in 7 Kategorien + - User-extensible (Admin CRUD UI) + - Many-to-Many Goals ↔ Focus Areas + - User preferences mit Gewichtungen +- Bug Fixes + - Focus contributions speichern + - Filtering (nur gewichtete Areas) + - Vitals baseline endpoint + +### 🔲 Noch zu tun (dokumentiert) +- Code Splitting (goals.py → 5 Router) +- Phase 0b (120+ Platzhalter, Score-System) +- Issue #52 (BP dual targets) +- Responsive UI (Issue #30) +- Weitere Features (siehe Roadmap) + +--- + +## 🎉 Dokumentations-Qualität + +**Vollständigkeit:** ⭐⭐⭐⭐⭐ +- Alle wichtigen Dokumente aktualisiert +- Neue Dokumente erstellt +- Gitea Issues geprüft +- Wiederanstiegspunkt klar definiert + +**Nachvollziehbarkeit:** ⭐⭐⭐⭐⭐ +- Status-Dokument mit allen Details +- Entscheidungen dokumentiert +- Nächste Schritte klar beschrieben + +**Wartbarkeit:** ⭐⭐⭐⭐⭐ +- Strukturierte Dokumentation +- Klare Verweise zwischen Dokumenten +- Lesereihenfolge definiert + +--- + +**Erstellt:** 27. März 2026, 23:00 Uhr +**Von:** Claude Code (Sonnet 4.5) +**Commit:** eb5c099 (docs: comprehensive status update v0.9h pre-release) + +**Du kannst jetzt:** +✅ Sicher pausieren +✅ Deployment testen +✅ Jederzeit exakt an diesem Punkt weitermachen -- 2.43.0 From ce4cd7daf17e91d5ed97afe2dd4b999efbf2d93d Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 27 Mar 2026 21:57:25 +0100 Subject: [PATCH 4/6] fix: include filter_conditions in goal type list query Bug 3 Fix: filter_conditions was missing from SELECT statement in list_goal_type_definitions(), preventing edit form from loading existing filter JSON. - Added filter_conditions to line 1087 - Now edit form correctly populates filter textarea --- backend/routers/goals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/routers/goals.py b/backend/routers/goals.py index 3b0a529..ce9eda0 100644 --- a/backend/routers/goals.py +++ b/backend/routers/goals.py @@ -1084,7 +1084,7 @@ def list_goal_type_definitions(session: dict = Depends(require_auth)): cur.execute(""" SELECT id, type_key, label_de, label_en, unit, icon, category, source_table, source_column, aggregation_method, - calculation_formula, description, is_system, is_active, + calculation_formula, filter_conditions, description, is_system, is_active, created_at, updated_at FROM goal_type_definitions WHERE is_active = true -- 2.43.0 From e4a2b63a482825f68ad98a0b911bae2c37a8b2af Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 27 Mar 2026 22:09:52 +0100 Subject: [PATCH 5/6] fix: vitals baseline parameter sync + goal utils transaction rollback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1 Fix (Ruhepuls): - Completely rewrote vitals_baseline POST endpoint - Clear separation: param_values array contains ALL values (pid, date, ...) - Synchronized insert_cols, insert_placeholders, and param_values - Added debug logging - Simplified UPDATE logic (EXCLUDED.col instead of COALESCE) Bug 2 Fix (Custom Goal Type Transaction Error): - Added transaction rollback in goal_utils._fetch_by_aggregation_method() - When SQL query fails (e.g., invalid column name), rollback transaction - Prevents 'InFailedSqlTransaction' errors on subsequent queries - Enhanced error logging (shows filter conditions, SQL, params) - Returns None gracefully so goal creation can continue User Action Required for Bug 2: - Edit goal type 'Trainingshäufigkeit Krafttraining' - Change filter from {"training_type": "strength"} to {"training_category": "strength"} - activity_log has training_category, NOT training_type column --- backend/goal_utils.py | 16 +++++++- backend/routers/vitals_baseline.py | 59 +++++++++++++++++++----------- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/backend/goal_utils.py b/backend/goal_utils.py index 09bd9cc..421450c 100644 --- a/backend/goal_utils.py +++ b/backend/goal_utils.py @@ -278,7 +278,7 @@ def _fetch_by_aggregation_method( - max_30d: Maximum value in last 30 days Args: - filter_conditions: Optional JSON filters (e.g., {"training_type": "strength"}) + filter_conditions: Optional JSON filters (e.g., {"training_category": "strength"}) """ # Guard: source_table/column required for simple aggregation if not table or not column: @@ -412,7 +412,21 @@ def _fetch_by_aggregation_method( return None except Exception as e: + # Log detailed error for debugging print(f"[ERROR] Failed to fetch value from {table}.{column} using {method}: {e}") + print(f"[ERROR] Filter conditions: {filter_conditions}") + print(f"[ERROR] Filter SQL: {filter_sql}") + print(f"[ERROR] Filter params: {filter_params}") + + # CRITICAL: Rollback transaction to avoid InFailedSqlTransaction errors + try: + conn.rollback() + print(f"[INFO] Transaction rolled back after query error") + except Exception as rollback_err: + print(f"[WARNING] Rollback failed: {rollback_err}") + + # Return None so goal creation can continue without current_value + # (current_value will be NULL in the goal record) return None diff --git a/backend/routers/vitals_baseline.py b/backend/routers/vitals_baseline.py index 39aae6c..8651ef8 100644 --- a/backend/routers/vitals_baseline.py +++ b/backend/routers/vitals_baseline.py @@ -99,67 +99,72 @@ def create_or_update_baseline( """Create or update baseline entry (upsert on date).""" pid = get_pid(x_profile_id) - # Build dynamic INSERT columns and UPDATE fields + # Build dynamic INSERT columns, placeholders, UPDATE fields, and values list + # All arrays must stay synchronized insert_cols = [] insert_placeholders = [] update_fields = [] - values = [pid, entry.date] - param_idx = 3 # Start after $1 (pid) and $2 (date) + param_values = [] # Will contain ALL values including pid and date + + # Always include profile_id and date + param_values.append(pid) + param_values.append(entry.date) + param_idx = 3 # Next parameter starts at $3 if entry.resting_hr is not None: insert_cols.append("resting_hr") insert_placeholders.append(f"${param_idx}") - update_fields.append(f"resting_hr = COALESCE(EXCLUDED.resting_hr, vitals_baseline.resting_hr)") - values.append(entry.resting_hr) + update_fields.append(f"resting_hr = EXCLUDED.resting_hr") + param_values.append(entry.resting_hr) param_idx += 1 if entry.hrv is not None: insert_cols.append("hrv") insert_placeholders.append(f"${param_idx}") - update_fields.append(f"hrv = COALESCE(EXCLUDED.hrv, vitals_baseline.hrv)") - values.append(entry.hrv) + update_fields.append(f"hrv = EXCLUDED.hrv") + param_values.append(entry.hrv) param_idx += 1 if entry.vo2_max is not None: insert_cols.append("vo2_max") insert_placeholders.append(f"${param_idx}") - update_fields.append(f"vo2_max = COALESCE(EXCLUDED.vo2_max, vitals_baseline.vo2_max)") - values.append(entry.vo2_max) + update_fields.append(f"vo2_max = EXCLUDED.vo2_max") + param_values.append(entry.vo2_max) param_idx += 1 if entry.spo2 is not None: insert_cols.append("spo2") insert_placeholders.append(f"${param_idx}") - update_fields.append(f"spo2 = COALESCE(EXCLUDED.spo2, vitals_baseline.spo2)") - values.append(entry.spo2) + update_fields.append(f"spo2 = EXCLUDED.spo2") + param_values.append(entry.spo2) param_idx += 1 if entry.respiratory_rate is not None: insert_cols.append("respiratory_rate") insert_placeholders.append(f"${param_idx}") - update_fields.append(f"respiratory_rate = COALESCE(EXCLUDED.respiratory_rate, vitals_baseline.respiratory_rate)") - values.append(entry.respiratory_rate) + update_fields.append(f"respiratory_rate = EXCLUDED.respiratory_rate") + param_values.append(entry.respiratory_rate) param_idx += 1 if entry.body_temperature is not None: insert_cols.append("body_temperature") insert_placeholders.append(f"${param_idx}") - update_fields.append(f"body_temperature = COALESCE(EXCLUDED.body_temperature, vitals_baseline.body_temperature)") - values.append(entry.body_temperature) + update_fields.append(f"body_temperature = EXCLUDED.body_temperature") + param_values.append(entry.body_temperature) param_idx += 1 if entry.resting_metabolic_rate is not None: insert_cols.append("resting_metabolic_rate") insert_placeholders.append(f"${param_idx}") - update_fields.append(f"resting_metabolic_rate = COALESCE(EXCLUDED.resting_metabolic_rate, vitals_baseline.resting_metabolic_rate)") - values.append(entry.resting_metabolic_rate) + update_fields.append(f"resting_metabolic_rate = EXCLUDED.resting_metabolic_rate") + param_values.append(entry.resting_metabolic_rate) param_idx += 1 if entry.note: insert_cols.append("note") insert_placeholders.append(f"${param_idx}") - update_fields.append(f"note = COALESCE(EXCLUDED.note, vitals_baseline.note)") - values.append(entry.note) + update_fields.append(f"note = EXCLUDED.note") + param_values.append(entry.note) param_idx += 1 # At least one field must be provided @@ -168,14 +173,24 @@ def create_or_update_baseline( with get_db() as conn: cur = get_cursor(conn) + + # Build complete column list and placeholder list + all_cols = f"profile_id, date, {', '.join(insert_cols)}" + all_placeholders = f"$1, $2, {', '.join(insert_placeholders)}" + query = f""" - INSERT INTO vitals_baseline (profile_id, date, {', '.join(insert_cols)}) - VALUES ($1, $2, {', '.join(insert_placeholders)}) + INSERT INTO vitals_baseline ({all_cols}) + VALUES ({all_placeholders}) ON CONFLICT (profile_id, date) DO UPDATE SET {', '.join(update_fields)}, updated_at = NOW() RETURNING * """ - cur.execute(query, tuple(values)) + + # Debug logging + print(f"[DEBUG] Vitals baseline query: {query}") + print(f"[DEBUG] Param values ({len(param_values)}): {param_values}") + + cur.execute(query, tuple(param_values)) return r2d(cur.fetchone()) -- 2.43.0 From 448f6ad4f4ac7b737293a65f1b493bc44af4714b Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 27 Mar 2026 22:14:28 +0100 Subject: [PATCH 6/6] fix: use psycopg2 placeholders (%s) not PostgreSQL ($N) Bug 1 Final Fix: - Changed all placeholders from $1, $2, $3 to %s - psycopg2 expects Python-style %s, converts to $N internally - Using $N directly causes 'there is no parameter $1' error - Removed param_idx counter (not needed with %s) Root cause: Mixing PostgreSQL native syntax with psycopg2 driver This is THE fix that will finally work! --- backend/routers/vitals_baseline.py | 44 ++++++++++++------------------ 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/backend/routers/vitals_baseline.py b/backend/routers/vitals_baseline.py index 8651ef8..3538775 100644 --- a/backend/routers/vitals_baseline.py +++ b/backend/routers/vitals_baseline.py @@ -109,63 +109,54 @@ def create_or_update_baseline( # Always include profile_id and date param_values.append(pid) param_values.append(entry.date) - param_idx = 3 # Next parameter starts at $3 if entry.resting_hr is not None: insert_cols.append("resting_hr") - insert_placeholders.append(f"${param_idx}") - update_fields.append(f"resting_hr = EXCLUDED.resting_hr") + insert_placeholders.append("%s") + update_fields.append("resting_hr = EXCLUDED.resting_hr") param_values.append(entry.resting_hr) - param_idx += 1 if entry.hrv is not None: insert_cols.append("hrv") - insert_placeholders.append(f"${param_idx}") - update_fields.append(f"hrv = EXCLUDED.hrv") + insert_placeholders.append("%s") + update_fields.append("hrv = EXCLUDED.hrv") param_values.append(entry.hrv) - param_idx += 1 if entry.vo2_max is not None: insert_cols.append("vo2_max") - insert_placeholders.append(f"${param_idx}") - update_fields.append(f"vo2_max = EXCLUDED.vo2_max") + insert_placeholders.append("%s") + update_fields.append("vo2_max = EXCLUDED.vo2_max") param_values.append(entry.vo2_max) - param_idx += 1 if entry.spo2 is not None: insert_cols.append("spo2") - insert_placeholders.append(f"${param_idx}") - update_fields.append(f"spo2 = EXCLUDED.spo2") + insert_placeholders.append("%s") + update_fields.append("spo2 = EXCLUDED.spo2") param_values.append(entry.spo2) - param_idx += 1 if entry.respiratory_rate is not None: insert_cols.append("respiratory_rate") - insert_placeholders.append(f"${param_idx}") - update_fields.append(f"respiratory_rate = EXCLUDED.respiratory_rate") + insert_placeholders.append("%s") + update_fields.append("respiratory_rate = EXCLUDED.respiratory_rate") param_values.append(entry.respiratory_rate) - param_idx += 1 if entry.body_temperature is not None: insert_cols.append("body_temperature") - insert_placeholders.append(f"${param_idx}") - update_fields.append(f"body_temperature = EXCLUDED.body_temperature") + insert_placeholders.append("%s") + update_fields.append("body_temperature = EXCLUDED.body_temperature") param_values.append(entry.body_temperature) - param_idx += 1 if entry.resting_metabolic_rate is not None: insert_cols.append("resting_metabolic_rate") - insert_placeholders.append(f"${param_idx}") - update_fields.append(f"resting_metabolic_rate = EXCLUDED.resting_metabolic_rate") + insert_placeholders.append("%s") + update_fields.append("resting_metabolic_rate = EXCLUDED.resting_metabolic_rate") param_values.append(entry.resting_metabolic_rate) - param_idx += 1 if entry.note: insert_cols.append("note") - insert_placeholders.append(f"${param_idx}") - update_fields.append(f"note = EXCLUDED.note") + insert_placeholders.append("%s") + update_fields.append("note = EXCLUDED.note") param_values.append(entry.note) - param_idx += 1 # At least one field must be provided if not insert_cols: @@ -175,8 +166,9 @@ def create_or_update_baseline( cur = get_cursor(conn) # Build complete column list and placeholder list + # IMPORTANT: psycopg2 uses %s placeholders, NOT $1/$2/$3 all_cols = f"profile_id, date, {', '.join(insert_cols)}" - all_placeholders = f"$1, $2, {', '.join(insert_placeholders)}" + all_placeholders = f"%s, %s, {', '.join(insert_placeholders)}" query = f""" INSERT INTO vitals_baseline ({all_cols}) -- 2.43.0