Charts router had no prefix, causing 404 errors.
Fixed:
- Added prefix="/api/charts" to APIRouter()
- Changed all endpoint paths from "/charts/..." to "/..."
(prefix already includes /api/charts)
Now endpoints resolve correctly:
/api/charts/energy-balance
/api/charts/recovery-score
etc.
All 23 chart endpoints now accessible.
Bug: 30 days with 29 data points returned 'insufficient' because
it fell into the 90+ day branch which requires >= 30 data points.
Fix: Changed condition from 'days_requested <= 28' to 'days_requested < 90'
so that 8-89 day ranges use the medium-term thresholds:
- high >= 18 data points
- medium >= 12
- low >= 8
This means 30 days with 29 entries now returns 'high' confidence.
Affects: nutrition_avg, and all other medium-term metrics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two critical fixes for placeholder resolution:
1. Missing import in activity_metrics.py:
- Added 'import statistics' at module level
- Fixes calculate_monotony_score() and calculate_strain_score()
- Error: NameError: name 'statistics' is not defined
2. Outdated focus_weights function in body_metrics.py:
- Changed from goal_utils.get_focus_weights (uses old focus_areas table)
- To data_layer.scores.get_user_focus_weights (uses new v2.0 system)
- Fixes calculate_body_progress_score()
- Error: UndefinedTable: relation "focus_areas" does not exist
These were causing many placeholders to fail silently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical bug fix: In-function imports were still referencing calculations/ module.
This caused all calculated placeholders to fail silently.
Fixed imports in:
- activity_metrics.py: calculate_activity_score (scores import)
- recovery_metrics.py: calculate_recent_load_balance_3d (activity_metrics import)
- scores.py: 12 function imports (body/nutrition/activity/recovery metrics)
- correlations.py: 11 function imports (scores, body, nutrition, activity, recovery metrics)
All data_layer modules now reference each other correctly.
Placeholders should resolve properly now.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Migrated all 16 calculation functions from calculations/nutrition_metrics.py to data_layer/nutrition_metrics.py
- Functions: Energy balance (7d calculation, deficit/surplus classification)
- Functions: Protein adequacy (g/kg, days in target, 28d score)
- Functions: Macro consistency (score, intake volatility)
- Functions: Nutrition scoring (main score with focus weights, calorie/macro adherence helpers)
- Functions: Energy availability warning (with severity levels and recommendations)
- Functions: Data quality assessment
- Functions: Fiber/sugar averages (TODO stubs)
- Updated data_layer/__init__.py with 12 new exports
- Refactored placeholder_resolver.py to import nutrition_metrics from data_layer
Module 2/6 complete. Single Source of Truth for nutrition metrics established.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Migrated all 20 calculation functions from calculations/body_metrics.py to data_layer/body_metrics.py
- Functions: weight trends (7d median, 28d/90d slopes, goal projection, progress)
- Functions: body composition (FM/LBM changes)
- Functions: circumferences (waist/hip/chest/arm/thigh deltas, WHR)
- Functions: recomposition quadrant
- Functions: scoring (body progress, data quality)
- Updated data_layer/__init__.py with 20 new exports
- Refactored placeholder_resolver.py to import body_metrics from data_layer
Module 1/6 complete. Single Source of Truth for body metrics established.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Data Layer:
- get_resting_heart_rate_data() - avg RHR with min/max trend
- get_heart_rate_variability_data() - avg HRV with min/max trend
- get_vo2_max_data() - latest VO2 Max with date
Placeholder Layer:
- get_vitals_avg_hr() - refactored to use data layer
- get_vitals_avg_hrv() - refactored to use data layer
- get_vitals_vo2_max() - refactored to use data layer
All 3 health data functions + 3 placeholder refactors complete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Data Layer:
- get_sleep_duration_data() - avg duration with hours/minutes breakdown
- get_sleep_quality_data() - Deep+REM percentage with phase breakdown
- get_rest_days_data() - total count + breakdown by rest type
Placeholder Layer:
- get_sleep_avg_duration() - refactored to use data layer
- get_sleep_avg_quality() - refactored to use data layer
- get_rest_days_count() - refactored to use data layer
All 3 recovery data functions + 3 placeholder refactors complete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Data Layer:
- get_activity_summary_data() - count, duration, calories, frequency
- get_activity_detail_data() - detailed activity log with all fields
- get_training_type_distribution_data() - category distribution with percentages
Placeholder Layer:
- get_activity_summary() - refactored to use data layer
- get_activity_detail() - refactored to use data layer
- get_trainingstyp_verteilung() - refactored to use data layer
All 3 activity data functions + 3 placeholder refactors complete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Data Layer:
- get_nutrition_average_data() - all macros in one call
- get_nutrition_days_data() - coverage tracking
- get_protein_targets_data() - 1.6g/kg and 2.2g/kg targets
- get_energy_balance_data() - deficit/surplus/maintenance
- get_protein_adequacy_data() - 0-100 score
- get_macro_consistency_data() - 0-100 score
Placeholder Layer:
- get_nutrition_avg() - refactored to use data layer
- get_nutrition_days() - refactored to use data layer
- get_protein_ziel_low() - refactored to use data layer
- get_protein_ziel_high() - refactored to use data layer
All 6 nutrition data functions + 4 placeholder refactors complete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ROOT CAUSE: get_active_goals() SELECT was missing start_date and created_at
IMPACT: Time-based deviation calculation failed silently for all goals
Now returns:
- start_date: Required for accurate time-based progress calculation
- created_at: Fallback when start_date is not set
This fixes:
- Zielgewicht (weight) should now show +7% ahead
- Körperfett should show time deviation
- All goals with target_date now have time-based tracking
- Log when using created_at as fallback for start_date
- Log when skipping due to missing created_at
- Log when skipping due to invalid date range (total_days <= 0)
This will reveal exactly why Körperfett and Zielgewicht are not added.
- Log each goal processing (name, values, dates)
- Log skip reasons (missing values, no target_date)
- Log exceptions during calculation
- Log successful additions with calculated values
This will reveal why Weight goal (+7% ahead) is not showing up.
OLD: Showed 3 goals with lowest progress %
NEW: Calculates expected progress based on elapsed time vs. total time
Shows goals with largest negative deviation (behind schedule)
Example Weight Goal:
- Total time: 98 days (22.02 - 31.05)
- Elapsed: 34 days (35%)
- Actual progress: 41%
- Deviation: +7% (AHEAD, not behind)
Also updated on_track to show goals with positive deviation (ahead of schedule).
Note: Linear progress is a simplification. Real-world progress curves vary
by goal type (weight loss, muscle gain, VO2max, etc). Future: AI-based
projection models for more realistic expectations.
- Start value already showed start_date in parentheses
- Now target value also shows target_date in parentheses
- Consistent UX: both dates visible at their respective values
Root cause: listGoalsGrouped() SELECT was missing g.start_date and g.reached_date
Result: Frontend used grouped goals for editing, so start_date was undefined
This is why target_date worked (it was in SELECT) but start_date didn't.
- Added serialize_dates() helper to convert date objects to strings
- Applied to list_goals and get_goals_grouped endpoints
- Fixes issue where start_date was saved but not visible in frontend
- Python datetime.date objects need explicit .isoformat() conversion
Root cause: FastAPI doesn't auto-serialize all date types consistently
- Log UPDATE SQL and parameters
- Verify saved values after UPDATE
- Show date types in list_goals response
- Track down why start_date not visible in UI
- Rewrote update logic to determine final_start_date/start_value first
- Then append to updates/params arrays (ensures alignment)
- Fixes bug where only start_value was saved but not start_date
User feedback: start_value correctly calculated but start_date not persisted
**Problem 1:** Edit form showed today's date instead of stored start_date
- Cause: Fallback logic `goal.start_date || today` always defaulted to today
- Fix: Load actual date or empty string (no fallback)
- Input field: Remove fallback from value binding
**Problem 2:** Timeline only showed target_date, not start_date
- Added dedicated timeline display below values
- Shows: "📅 15.01.26 → 31.05.26"
- Only appears if at least one date exists
- Start date with calendar icon, target date bold
**Result:**
- Editing goals now preserves the start_date ✓
- Timeline clearly shows start → target dates ✓
- No more accidental overwrites with today's date ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
**User Feedback:** "Macht es nicht Sinn, den nächsten verfügbaren Wert
am oder nach dem Startdatum automatisch zu ermitteln und auch das
Startdatum dann automatisch auf den Wert zu setzen?"
**New Logic:**
1. User sets start_date: 2026-01-01
2. System finds FIRST measurement >= 2026-01-01 (e.g., 2026-01-15: 88 kg)
3. System auto-adjusts:
- start_date → 2026-01-15
- start_value → 88 kg
4. User sees: "Start: 88 kg (15.01.26)" ✓
**Benefits:**
- User doesn't need to know exact date of first measurement
- More user-friendly UX
- Automatically finds closest available data
**Implementation:**
- Changed query from "BETWEEN date ±7 days" to "WHERE date >= target_date"
- Returns dict with {'value': float, 'date': date}
- Both create_goal() and update_goal() now adjust start_date automatically
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
**Error:**
function pg_catalog.extract(unknown, integer) does not exist
HINT: No function matches the given name and argument types.
**Problem:**
In PostgreSQL, date - date returns INTEGER (days), not INTERVAL.
EXTRACT(EPOCH FROM integer) fails because EPOCH expects timestamp/interval.
**Solution:**
Changed from:
ORDER BY ABS(EXTRACT(EPOCH FROM (date - '2026-01-01')))
To:
ORDER BY ABS(date - '2026-01-01'::date)
This directly uses the day difference (integer) for sorting,
which is exactly what we need to find the closest date.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>