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>
**Bug:** start_date was not being loaded into edit form or sent in update request
**Fixed:**
1. handleEditGoal() - Added start_date to formData when editing
2. handleSaveGoal() - Added start_date to API payload for both create and update
Now when editing a goal:
- start_date field is populated with existing value
- Changing start_date triggers backend to recalculate start_value
- Update request includes start_date
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added start_date field to goal creation/editing form:
1. New "Startdatum" input field before "Zieldatum"
- Defaults to today
- Hint: "Startwert wird automatisch aus historischen Daten ermittelt"
2. Display start_date in goals list
- Shows next to start_value: "85 kg (01.01.26)"
- Compact format for better readability
3. Updated formData state
- Added start_date with today as default
- API calls automatically include it
User can now:
- Set historical start date (e.g., 3 months ago)
- Backend auto-populates start_value from that date
- See exact start date and value for each goal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
**Problem:** Goals created today had start_value = current_value,
showing 0% progress even after months of tracking.
**Solution:**
1. Added start_date and start_value to GoalCreate/GoalUpdate models
2. New function _get_historical_value_for_goal_type():
- Queries source table for value on specific date
- ±7 day window for closest match
- Works with all goal types via goal_type_definitions
3. create_goal() logic:
- If start_date < today → auto-populate from historical data
- If start_date = today → use current value
- User can override start_value manually
4. update_goal() logic:
- Changing start_date recalculates start_value
- Can manually override start_value
**Example:**
- Goal created today with start_date = 3 months ago
- System finds weight on that date (88 kg)
- Current weight: 85.2 kg, Target: 82 kg
- Progress: (85.2 - 88) / (82 - 88) = 47% ✓
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Goal progress placeholders were filtering out all goals because
start_value was missing from the SELECT statement.
Added start_value to both:
- get_active_goals() - for placeholder formatters
- get_goal_by_id() - for consistency
This will fix:
- active_goals_md progress column (was all "-")
- top_3_goals_behind_schedule (was "keine Ziele")
- top_3_goals_on_track (was "keine Ziele")
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>