From bc4d68da07381887693ef63dd31cc06bcb09bb3e Mon Sep 17 00:00:00 2001 From: owain Date: Mon, 8 Jun 2026 12:25:11 +0100 Subject: [PATCH] Fix avg_hr_day: remove dead averageHeartRate lookup; add max_hr_day from UDS export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit garmin.get_stats() never returns averageHeartRate — avg_hr_day is only computable from intraday HR which Garmin's API only serves for recent dates (~90-120 days). The dead lookup gave false confidence that historical backfill would work. Also populate max_hr_day from the Garmin export's UDS daily summaries (maxHeartRate field is present for the full history), so historical max HR is available after re-importing the export. Co-Authored-By: Claude Sonnet 4.6 --- backend/app/services/garmin_connect_sync.py | 2 +- backend/app/workers/tasks.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/app/services/garmin_connect_sync.py b/backend/app/services/garmin_connect_sync.py index 3b49958..8a2ea8f 100644 --- a/backend/app/services/garmin_connect_sync.py +++ b/backend/app/services/garmin_connect_sync.py @@ -524,7 +524,7 @@ def _parse_day(stats, sleep_data, hrv_data) -> dict: if stats: _set(row, "resting_hr", stats.get("restingHeartRate")) - _set(row, "avg_hr_day", stats.get("averageHeartRate")) + # averageHeartRate is absent from get_stats; avg_hr_day is computed below from intraday HR _set(row, "max_hr_day", stats.get("maxHeartRate")) _set(row, "steps", stats.get("totalSteps")) _set(row, "floors_climbed", stats.get("floorsAscended")) diff --git a/backend/app/workers/tasks.py b/backend/app/workers/tasks.py index 1228553..cec5b37 100644 --- a/backend/app/workers/tasks.py +++ b/backend/app/workers/tasks.py @@ -422,12 +422,13 @@ def process_garmin_health_zip(zip_path: str, user_id: int): from sqlalchemy import text INSERT_SQL = text(""" - INSERT INTO health_metrics (user_id, date, resting_hr, steps, + INSERT INTO health_metrics (user_id, date, resting_hr, max_hr_day, steps, floors_climbed, active_calories, total_calories, avg_stress, spo2_avg) - VALUES (:user_id, :date, :resting_hr, :steps, + VALUES (:user_id, :date, :resting_hr, :max_hr_day, :steps, :floors, :active_cal, :total_cal, :stress, :spo2) ON CONFLICT (user_id, date) DO UPDATE SET resting_hr = COALESCE(EXCLUDED.resting_hr, health_metrics.resting_hr), + max_hr_day = COALESCE(EXCLUDED.max_hr_day, health_metrics.max_hr_day), steps = COALESCE(EXCLUDED.steps, health_metrics.steps), floors_climbed = COALESCE(EXCLUDED.floors_climbed, health_metrics.floors_climbed), active_calories = COALESCE(EXCLUDED.active_calories, health_metrics.active_calories), @@ -463,6 +464,7 @@ def process_garmin_health_zip(zip_path: str, user_id: int): db.execute(INSERT_SQL, { "user_id": user_id, "date": date_dt, "resting_hr": item.get("restingHeartRate"), + "max_hr_day": item.get("maxHeartRate"), "steps": item.get("totalSteps"), "floors": _floors_from_item(item), "active_cal": item.get("activeKilocalories"),