Switch VO2 max source to get_max_metrics (maxmet/daily endpoint)
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:
@@ -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)
|
logger.warning("Failed to upsert health_metrics for %s: %s", day_str, exc)
|
||||||
db.rollback()
|
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()
|
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)
|
fa_data = _safe(garmin.get_fitnessage_data, today_str)
|
||||||
logger.info("fitnessage raw response: %s", fa_data)
|
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:
|
if fa_data:
|
||||||
vo2 = (fa_data.get("vo2Max")
|
fa_age = fa_data.get("fitnessAge") or fa_data.get("achievableFitnessAge")
|
||||||
or fa_data.get("vo2MaxPreciseValue")
|
|
||||||
or fa_data.get("biometricProfile", {}).get("vo2Max"))
|
logger.info("parsed vo2=%s fitness_age=%s", vo2, fa_age)
|
||||||
fa = fa_data.get("chronologicalAge") or fa_data.get("fitnessAge")
|
|
||||||
logger.info("fitnessage parsed: vo2=%s fa=%s", vo2, fa)
|
if vo2:
|
||||||
if vo2 and float(vo2) > 0:
|
try:
|
||||||
try:
|
fa_row = {"vo2max": vo2}
|
||||||
fa_row = {"vo2max": float(vo2)}
|
if fa_age:
|
||||||
if fa:
|
fa_row["fitness_age"] = int(fa_age)
|
||||||
fa_row["fitness_age"] = int(fa)
|
fa_cols = list(fa_row.keys())
|
||||||
fa_cols = list(fa_row.keys())
|
db.execute(text(f"""
|
||||||
db.execute(text(f"""
|
INSERT INTO health_metrics (user_id, date, {", ".join(fa_cols)})
|
||||||
INSERT INTO health_metrics (user_id, date, {", ".join(fa_cols)})
|
VALUES (:user_id, :day, {", ".join(f":{c}" for c in fa_cols)})
|
||||||
VALUES (:user_id, :day, {", ".join(f":{c}" for c in fa_cols)})
|
ON CONFLICT (user_id, date) DO UPDATE SET
|
||||||
ON CONFLICT (user_id, date) DO UPDATE SET
|
{", ".join(f"{c} = EXCLUDED.{c}" for c in fa_cols)}
|
||||||
{", ".join(f"{c} = EXCLUDED.{c}" for c in fa_cols)}
|
"""), {"user_id": user_id, "day": today_str, **fa_row})
|
||||||
"""), {"user_id": user_id, "day": today_str, **fa_row})
|
db.commit()
|
||||||
db.commit()
|
except Exception as exc:
|
||||||
except Exception as exc:
|
logger.warning("Failed to upsert VO2 max: %s", exc)
|
||||||
logger.warning("Failed to upsert VO2 max: %s", exc)
|
db.rollback()
|
||||||
db.rollback()
|
|
||||||
|
|
||||||
return processed
|
return processed
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user