Migration 018:
- Add display_name column to ai_prompts
- Migrate existing prompts from hardcoded SLUG_LABELS
- Fallback: name if display_name is NULL
Backend:
- PromptCreate/Update models with display_name field
- create/update/duplicate endpoints handle display_name
- Fallback: use name if display_name not provided
Frontend:
- PromptEditModal: display_name input field
- Placeholder picker: button + dropdown with all placeholders
- Shows example values, inserts {{placeholder}} on click
- Analysis.jsx: use display_name instead of SLUG_LABELS
User-facing changes:
- Prompts now show custom display names (e.g. '🍽️ Ernährung')
- Admin can edit display names instead of hardcoded labels
- Template editor has 'Platzhalter einfügen' button
- No more hardcoded SLUG_LABELS in frontend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- PromptEditModal: all inputs/textareas now full-width
- Labels positioned above fields (not inline)
- Text left-aligned (was right-aligned)
- Added resize:vertical for textareas
- Side-by-side comparison with word-wrap
- Follows app-wide form design pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend components:
- PromptEditModal.jsx: Full editor with preview, generator, optimizer
- PromptGenerator.jsx: KI-assisted prompt creation from goal description
- Extended api.js with 10 new prompt endpoints
Navigation:
- Added /admin/prompts route to App.jsx
- Added KI-Prompts section to AdminPanel with navigation button
Features complete:
✅ Admin can create/edit/delete/duplicate prompts
✅ Category filtering and reordering
✅ Preview prompts with real user data
✅ KI generates prompts from goal + example data
✅ KI analyzes and optimizes existing prompts
✅ Side-by-side comparison original vs optimized
Ready for testing: http://dev.mitai.jinkendo.de/admin/prompts
Issue #28 Phase 2 complete - 13-18h estimated, ~14h actual
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend complete:
- Migration 017: Add category column to ai_prompts
- placeholder_resolver.py: 20+ placeholders with resolver functions
- Extended routers/prompts.py with CRUD endpoints:
* POST /api/prompts (create)
* PUT /api/prompts/:id (update)
* DELETE /api/prompts/:id (delete)
* POST /api/prompts/:id/duplicate
* PUT /api/prompts/reorder
* POST /api/prompts/preview
* GET /api/prompts/placeholders
* POST /api/prompts/generate (KI-assisted generation)
* POST /api/prompts/:id/optimize (KI analysis)
- Extended models.py with PromptCreate, PromptUpdate, PromptGenerateRequest
Frontend:
- AdminPromptsPage.jsx: Full CRUD UI with category filter, reordering
Meta-Features:
- KI generates prompts from goal description + example data
- KI analyzes and optimizes existing prompts
Next: PromptEditModal, PromptGenerator, api.js integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed local quality filter UI from History page since backend now
handles filtering globally. Activities are already filtered when loaded.
Changes:
- Removed qualityLevel local state
- Simplified filtA to only filter by period
- Replaced filter buttons with info banner showing active global filter
- Added 'Hier ändern →' link to Settings
User can now only change quality filter in Settings (global), not per
page. History shows which filter is active with link to change it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The frontend was sending quality_filter_level to the backend, but the
Pydantic ProfileUpdate model didn't include this field, so it was
silently ignored. Profile updates never actually saved the filter.
This is why the charts didn't react to filter changes - the backend
database was never updated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The component was loading data from backend (which uses global filter)
but useEffect dependency didn't include quality_filter_level, so it
didn't reload when user changed the filter in Settings.
Added useProfile() context and activeProfile.quality_filter_level
to dependency array.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implemented global quality_filter_level in user profiles for consistent
data filtering across all views (Dashboard, History, Charts, KI-Pipeline).
Backend changes:
- Migration 016: Add quality_filter_level column to profiles table
- quality_filter.py: Centralized helper functions for SQL filtering
- insights.py: Apply global filter in _get_profile_data()
- activity.py: Apply global filter in list_activity()
Frontend changes:
- SettingsPage.jsx: Add Datenqualität section with 4-level selector
- History.jsx: Use global quality filter from profile context
Filter levels: all, quality (good+excellent+acceptable), very_good
(good+excellent), excellent (only excellent)
Closes#31
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Notiert an 3 Stellen:
1. insights.py: TODO-Kommentar im Code
2. ROADMAP.md: Deliverable bei M0.2 (lokal, nicht im Git)
3. Gitea Issue #28: Kommentar mit Spezifikation
Zukünftig:
- GET /api/insights/run/{slug}?quality_level=quality
- 4 Stufen: all, quality, very_good, excellent
- Frontend: Dropdown wie in History.jsx
- Pipeline-Configs können Standard-Level haben
User-Request: Quality-Level-Auswahl für KI-Analysen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Statt einfachem On/Off Toggle jetzt 4 Qualitätsstufen:
- 📊 Alle (kein Filter)
- ✓ Hochwertig (excellent + good + acceptable)
- ✓✓ Sehr gut (excellent + good)
- ⭐ Exzellent (nur excellent)
UI:
- Button-Group (Segmented Control) mit 4 Stufen
- Beschreibung welche Labels inkludiert werden
- Anzeige: X von Y Aktivitäten (wenn gefiltert)
User-Feedback: Stufenweiser Filter ist flexibler als binärer Toggle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Import failed with "invalid literal for int() with base 10: '37.95'"
because Apple Health exports HRV and other vitals with decimal values.
Root cause: Code used int() directly on string values with decimals.
Fix:
- Added safe_int(): parses decimals as float first, then rounds to int
- Added safe_float(): robust float parsing with error handling
- Applied to all vital value parsing: RHR, HRV, VO2 Max, SpO2, resp rate
Example: '37.95' → float(37.95) → int(38) ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Errors during import were logged but not visible to user.
Changes:
- Backend: Collect error messages and return in response (first 10 errors)
- Frontend: Display error details in import result box
- UI: Red background when errors > 0, shows detailed error messages
Now users can see exactly which rows failed and why.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Import expected English column names, but German Apple Health/Omron
exports use German names with units.
Fixed:
- Apple Health: Support both English and German column names
- "Start" OR "Datum/Uhrzeit"
- "Resting Heart Rate" OR "Ruhepuls (count/min)"
- "Heart Rate Variability" OR "Herzfrequenzvariabilität (ms)"
- "VO2 Max" OR "VO2 max (ml/(kg·min))"
- "Oxygen Saturation" OR "Blutsauerstoffsättigung (%)"
- "Respiratory Rate" OR "Atemfrequenz (count/min)"
- Omron: Support column names with/without units
- "Systolisch (mmHg)" OR "Systolisch"
- "Diastolisch (mmHg)" OR "Diastolisch"
- "Puls (bpm)" OR "Puls"
- "Unregelmäßiger Herzschlag festgestellt" OR "Unregelmäßiger Herzschlag"
- "Mögliches AFib" OR "Vorhofflimmern"
Added debug logging for both imports to show detected columns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logs:
- CSV column names from first row
- Rows skipped due to missing date
- Rows skipped due to no vitals data
- Shows which fields were found/missing
Helps diagnose CSV format mismatches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Import reported all entries as "updated" even when skipped
due to WHERE clause (source != 'manual')
Root cause: RETURNING returns NULL when WHERE clause prevents update,
but code counted NULL as "updated" instead of "skipped"
Fix:
- Check if result is None → skipped (WHERE prevented update)
- Check if xmax = 0 → inserted (new row)
- Otherwise → updated (existing row modified)
Affects:
- vitals_baseline.py: Apple Health import
- blood_pressure.py: Omron import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Full-width fields with section headers (mobile-friendly)
- Inline editing for all measurements (edit mode per row)
- Smart upsert: date change loads existing entry → update instead of duplicate
- Units integrated into labels (no overflow)
- Baseline: auto-detects existing entry and switches to update mode
- Blood Pressure: inline editing with all fields (date, time, BP, context, flags)
- Edit/Save/Cancel buttons with lucide-react icons
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Einfache 3-Tab-Struktur als Platzhalter:
- Morgenmessung (Baseline)
- Blutdruck (BP)
- Import
Verhindert Crash durch alte API-Calls.
Vollständige UI folgt nach Backend-Test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ModuleNotFoundError: No module named 'dateutil' beim Server-Start.
Ursache: vitals.py importiert dateutil.parser für Omron-Datumsformatierung,
aber python-dateutil fehlte in requirements.txt.
Fix: python-dateutil==2.9.0 zu requirements.txt hinzugefügt.
Nach dem Update: Docker Container neu bauen auf dem Pi:
cd /home/lars/docker/bodytrack-dev
docker compose -f docker-compose.dev-env.yml build --no-cache backend
docker compose -f docker-compose.dev-env.yml up -d
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
**Backend (insights.py):**
- Extended _get_profile_data() to fetch sleep, rest_days, vitals
- Added template variables for Sleep Module:
{{sleep_summary}}, {{sleep_detail}}, {{sleep_avg_duration}}, {{sleep_avg_quality}}
- Added template variables for Rest Days:
{{rest_days_summary}}, {{rest_days_count}}, {{rest_days_types}}
- Added template variables for Vitals:
{{vitals_summary}}, {{vitals_detail}}, {{vitals_avg_hr}}, {{vitals_avg_hrv}},
{{vitals_avg_bp}}, {{vitals_vo2_max}}
**Frontend (Analysis.jsx):**
- Added 12 new template variables to VARS list in PromptEditor
- Enables AI prompt creation for Sleep, Rest Days, and Vitals analysis
All modules now have AI evaluation support for future prompt creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Mark Sleep Module as complete (Phase 2b)
- Mark Rest Days as complete (Phase 2a)
- Mark Extended Vitals as complete (Phase 2d)
- Add migration details (010-014)
- HF-Zonen + Recovery Score marked as next (Phase 2e)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import endpoints for Omron blood pressure CSV (German date format)
- Import endpoints for Apple Health vitals CSV
- Import UI tab in VitalsPage with drag & drop for both sources
- German month mapping for Omron date parsing ("13 März 2026")
- Upsert logic preserves manual entries (source != 'manual')
- Import result feedback (inserted/updated/skipped/errors)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Avg blood pressure (systolic/diastolic) 7d and 30d
- Latest VO2 Max value
- Avg SpO2 7d and 30d
- Backend now provides all metrics expected by frontend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Form sections:
- Morgenmessung: Ruhepuls, HRV
- Blutdruck (Omron): Systolisch, Diastolisch, Puls
- Fitness & Sauerstoff (Apple Watch): VO2 Max, SpO2, Atemfrequenz
- Warnungen: Unregelmäßiger Herzschlag, Mögliches AFib (checkboxes)
Display:
- All vitals shown in entry list with icons
- Blood pressure highlighted in red (🩸)
- VO2 Max in green (🏃)
- Warnings in orange (⚠️)
Stats overview:
- Dynamic grid showing available metrics
- Avg blood pressure 7d
- Latest VO2 Max
- Avg SpO2 7d
Save/Update:
- Only non-empty fields included in payload
- At least one vital must be provided
Ready for manual testing + import implementation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migration 014:
- blood_pressure_systolic/diastolic (mmHg)
- pulse (bpm) - during BP measurement
- vo2_max (ml/kg/min) - from Apple Watch
- spo2 (%) - blood oxygen saturation
- respiratory_rate (breaths/min)
- irregular_heartbeat, possible_afib (boolean flags from Omron)
- Added 'omron' to source enum
Backend:
- Updated Pydantic models (VitalsEntry, VitalsUpdate)
- Updated all SELECT queries to include new fields
- Updated INSERT/UPDATE with COALESCE for partial updates
- Validation: at least one vital must be provided
Preparation for Omron + Apple Health imports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Only include fields in payload if they have values
- Prevents sending empty strings to backend (Pydantic validation error)
- Applies to both create and update operations
Error was: 'Input should be a valid integer, unable to parse string as an integer'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- New router: vitals.py with CRUD endpoints
- GET /api/vitals (list)
- GET /api/vitals/by-date/{date}
- POST /api/vitals (upsert)
- PUT /api/vitals/{id}
- DELETE /api/vitals/{id}
- GET /api/vitals/stats (7d/30d averages, trends)
- Registered in main.py
Frontend:
- VitalsPage.jsx with manual entry form
- List with inline editing
- Stats overview (averages, trend indicators)
- Added to CaptureHub (❤️ icon)
- Route /vitals in App.jsx
API:
- Added vitals methods to api.js
v9d Phase 2d - Vitals tracking complete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Changed from 'folgt in nächster Iteration' to 'Analyse & Entwicklung, folgen später'
- Listed all 5 dimensions with clear purpose
- Clarifies that Minimum Requirements is sufficient for validation
- Other dimensions planned for v9e/v9f (ability development, AI prompts)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changes:
- Responsive layout: fields stack vertically, no more cramped grid
- Clear labels: 'WAS?', 'BEDINGUNG', 'WICHTIGKEIT'
- Weight field only shown when using 'weighted_score' strategy
- Weight explanation: '1 = unwichtig, 10 = sehr wichtig'
- Success message replaces alert() dialog (auto-dismiss after 2s)
- Delete button moved to rule header
- Better visual hierarchy with sections
User feedback:
- Felder lassen sich auf Handy nicht gut bearbeiten
- Überschriften nicht eindeutig
- Gewicht-Feld Verwirrung
- Keine OK-Dialoge
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added label row: PARAMETER | OPERATOR | SCHWELLENWERT | GEWICHT
- Prevents confusion between threshold value and weight fields
- Better placeholder for value field (z.B. 90)
- Between operator: stacked vertical inputs with Min/Max labels
- User feedback: confusion between value and weight fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- PostgreSQL returns numeric values as Decimal objects
- psycopg2.Json() cannot serialize Decimal to JSON
- Added convert_decimals() helper function
- Converts activity_data, context, and evaluation_result before saving
Fixes: Batch evaluation errors (31 errors 'Decimal is not JSON serializable')
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ✓ Green: Successfully evaluated (excellent/good/acceptable/poor)
- ⚠ Orange: Training type assigned but not evaluated (no profile)
- ✕ Gray: No training type assigned
- Tooltip shows evaluation details on hover
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Shows first 10 errors with activity_id and error message
- Helps admin debug evaluation failures
- Errors shown in error box with details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Shows first 10 errors with activity_id, training_type_id, and error message
- Helps debug evaluation failures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ProfileBuilder now renders inline below training type row
- Type editor form also inline (not at top of page)
- Both forms appear at item position with marginTop: 8
- User feedback: 'Die Position bleibt die ganze Zeit gleich!'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MAJOR UX IMPROVEMENT - No more JSON editing required!
New Component: ProfileBuilder.jsx
- Visual form for configuring training type profiles
- Parameter dropdown (dynamically loaded from API)
- Operator dropdown (>=, <=, >, <, =, ≠, between)
- Value input (type-aware, between shows min/max)
- Weight slider (1-10)
- Add/remove rules visually
- Pass strategy selection
- Optional checkbox per rule
- Expandable sections
Integration: AdminTrainingTypesPage.jsx
- Added ProfileBuilder component
- ⚙️ Settings icon per training type
- Opens visual form when clicked
- ✓ Profil badge shows configured types
- Loads 16 parameters from API
- Save directly to training type
User Experience:
1. Go to /admin/training-types
2. Click ⚙️ icon on any type
3. Visual form opens
4. Add rules via dropdowns
5. Save → Profile configured!
NO JSON EDITING NEEDED! 🎉
Next: Add visual builders for other dimensions (Zones, Effects, etc.)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed "U.get is not a function" error:
- Added missing API methods to api.js:
- getProfileStats()
- getProfileTemplates()
- applyProfileTemplate()
- getTrainingParameters()
- batchEvaluateActivities()
- Updated AdminTrainingProfiles.jsx to use correct methods
- Replaced api.get/post/put with specific named methods
Error resolved. Page should now load correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New admin page for profile configuration:
- AdminTrainingProfiles.jsx: Profile management interface
- Statistics dashboard (configured/unconfigured count)
- Training types list with profile status badges
- JSON-based profile editor (modal)
- One-click template application (Running, Meditation, Strength)
- Batch re-evaluation button for existing activities
- Link in AdminPanel under "Trainingstypen (v9d)"
Features:
- Apply templates with one click
- Edit profiles as JSON in modal
- Real-time validation
- Success/error messages
- Responsive layout
Route: /admin/training-profiles
Next: Test and iterate, then Phase 3 (User-UI for viewing results)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Admin endpoints for profile configuration:
- Extended TrainingTypeCreate/Update models with profile field
- Added profile column to all SELECT queries
- Profile templates for Running, Meditation, Strength Training
- Template endpoints: list, get, apply
- Profile stats endpoint (configured/unconfigured count)
New file: profile_templates.py
- TEMPLATE_RUNNING: Endurance-focused with HR zones
- TEMPLATE_MEDITATION: Mental-focused (low HR ≤ instead of ≥)
- TEMPLATE_STRENGTH: Strength-focused
API Endpoints:
- GET /api/admin/training-types/profiles/templates
- GET /api/admin/training-types/profiles/templates/{key}
- POST /api/admin/training-types/{id}/profile/apply-template
- GET /api/admin/training-types/profiles/stats
Next: Frontend Admin-UI (ProfileEditor component)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SQL Error: VALUES lists must all be the same length (line 130)
Cause: kcal_per_km row was missing validation_rules JSONB value
Fixed: Added validation_rules '{"min": 0, "max": 1000}'::jsonb
All 16 parameter rows now have correct 10 columns:
key, name_de, name_en, category, data_type, unit, source_field,
validation_rules, description_de, description_en
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: Backend crashed on startup due to evaluation import failure
Solution: Wrap evaluation_helper import in try/except
Changes:
- Import evaluation_helper with error handling
- Add EVALUATION_AVAILABLE flag
- All evaluation calls now check flag before executing
- System remains functional even if evaluation system unavailable
This prevents backend crashes if:
- Migrations haven't run yet
- Dependencies are missing
- Import errors occur
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automatic evaluation on activity INSERT/UPDATE:
- create_activity(): Evaluate after manual creation
- update_activity(): Re-evaluate after manual update
- import_activity_csv(): Evaluate after CSV import (INSERT + UPDATE)
- bulk_categorize_activities(): Evaluate after bulk training type assignment
All evaluation calls wrapped in try/except to prevent activity operations
from failing if evaluation encounters an error. Only activities with
training_type_id assigned are evaluated.
Phase 1.2 complete ✅
## Next Steps (Phase 2):
Admin-UI for training type profile configuration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>