Switch VO2 max source to get_max_metrics (maxmet/daily endpoint)
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 5s

fitnessage endpoint contains fitness age only, not VO2 max. The maxmet
endpoint (/metrics-service/metrics/maxmet/daily) is the correct source.
Keep debug logging temporarily to confirm key names from live API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 21:38:41 +01:00
parent e440fb35dd
commit 367ae4e8f7
+47 -22
View File
@@ -325,32 +325,57 @@ def sync_wellness(garmin, user_id: int, since: Optional[datetime], db,
logger.warning("Failed to upsert health_metrics for %s: %s", day_str, exc)
db.rollback()
# Fetch current VO2 max and fitness age once (slow-changing — only update today's row)
# Fetch current VO2 max once via maxmet endpoint (slow-changing — only update today's row)
today_str = date.today().isoformat()
mm_data = _safe(garmin.get_max_metrics, today_str)
logger.info("maxmet raw response: %s", mm_data)
fa_data = _safe(garmin.get_fitnessage_data, today_str)
logger.info("fitnessage raw response: %s", fa_data)
vo2 = None
if mm_data:
# maxmet returns a list of metric dicts or a wrapper
items = mm_data if isinstance(mm_data, list) else mm_data.get("allMetrics", {}).get("metricsMap", {})
if isinstance(items, list):
for item in items:
for key in ("vo2MaxPreciseValue", "vo2Max", "generic"):
v = item.get(key)
if v and isinstance(v, (int, float)) and float(v) > 0:
vo2 = float(v)
break
if vo2:
break
elif isinstance(items, dict):
for key in ("VO2_MAX", "vo2Max", "vo2MaxPreciseValue"):
entry = items.get(key)
if entry:
v = entry[0].get("value") if isinstance(entry, list) else entry.get("value")
if v and float(v) > 0:
vo2 = float(v)
break
fa_age = None
if fa_data:
vo2 = (fa_data.get("vo2Max")
or fa_data.get("vo2MaxPreciseValue")
or fa_data.get("biometricProfile", {}).get("vo2Max"))
fa = fa_data.get("chronologicalAge") or fa_data.get("fitnessAge")
logger.info("fitnessage parsed: vo2=%s fa=%s", vo2, fa)
if vo2 and float(vo2) > 0:
try:
fa_row = {"vo2max": float(vo2)}
if fa:
fa_row["fitness_age"] = int(fa)
fa_cols = list(fa_row.keys())
db.execute(text(f"""
INSERT INTO health_metrics (user_id, date, {", ".join(fa_cols)})
VALUES (:user_id, :day, {", ".join(f":{c}" for c in fa_cols)})
ON CONFLICT (user_id, date) DO UPDATE SET
{", ".join(f"{c} = EXCLUDED.{c}" for c in fa_cols)}
"""), {"user_id": user_id, "day": today_str, **fa_row})
db.commit()
except Exception as exc:
logger.warning("Failed to upsert VO2 max: %s", exc)
db.rollback()
fa_age = fa_data.get("fitnessAge") or fa_data.get("achievableFitnessAge")
logger.info("parsed vo2=%s fitness_age=%s", vo2, fa_age)
if vo2:
try:
fa_row = {"vo2max": vo2}
if fa_age:
fa_row["fitness_age"] = int(fa_age)
fa_cols = list(fa_row.keys())
db.execute(text(f"""
INSERT INTO health_metrics (user_id, date, {", ".join(fa_cols)})
VALUES (:user_id, :day, {", ".join(f":{c}" for c in fa_cols)})
ON CONFLICT (user_id, date) DO UPDATE SET
{", ".join(f"{c} = EXCLUDED.{c}" for c in fa_cols)}
"""), {"user_id": user_id, "day": today_str, **fa_row})
db.commit()
except Exception as exc:
logger.warning("Failed to upsert VO2 max: %s", exc)
db.rollback()
return processed