Fix VO2 max sync: robust fallback when maxmet range returns non-list or valueless entries
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

The previous code used `if not mm_history:` to decide whether to fall back to
get_training_status(). If the maxmet API returned a non-empty list with no valid
vo2max values (or a non-list type), the fallback was skipped and nothing stored.

Changes:
- Normalise mm_raw: only use it if it's a list (handles dict/None responses)
- Check valid_from_range: fall back to training_status whenever no usable value
  was found in the range query, regardless of whether it returned entries
- Upgrade all related log lines to INFO so the result is visible without debug mode
- Guard the entry loop against non-dict items

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 23:01:18 +01:00
parent 0bb1f9bc1e
commit 546fdd96b5
2 changed files with 605 additions and 10 deletions
+24 -10
View File
@@ -332,25 +332,40 @@ def sync_wellness(garmin, user_id: int, since: Optional[datetime], db,
if fa_data:
fa_age = fa_data.get("fitnessAge") or fa_data.get("achievableFitnessAge")
mm_entries = []
try:
mm_history = garmin.connectapi(
mm_raw = garmin.connectapi(
f"/metrics-service/metrics/maxmet/daily/{start_date.isoformat()}/{today_str}"
)
logger.info("maxmet range query returned type=%s len=%s",
type(mm_raw).__name__,
len(mm_raw) if isinstance(mm_raw, (list, dict)) else "n/a")
if isinstance(mm_raw, list):
mm_entries = mm_raw
except Exception as exc:
logger.debug("maxmet history fetch failed: %s", exc)
mm_history = []
logger.info("maxmet history fetch failed: %s", exc)
# Fall back to most-recent from training status if history is empty
if not mm_history:
# Check whether the range query yielded any usable vo2max values
valid_from_range = any(
(entry.get("vo2MaxPreciseValue") or entry.get("vo2MaxValue") or 0)
for entry in mm_entries
if isinstance(entry, dict)
)
# Always fall back to training_status when the range query had no valid data
if not valid_from_range:
ts_data = _safe(garmin.get_training_status, today_str)
generic = (ts_data or {}).get("mostRecentVO2Max", {}).get("generic") or {}
generic = ((ts_data or {}).get("mostRecentVO2Max") or {}).get("generic") or {}
v = generic.get("vo2MaxPreciseValue") or generic.get("vo2MaxValue")
logger.info("training_status vo2max=%s at %s", v, generic.get("calendarDate"))
if v and float(v) > 0:
mm_history = [{"calendarDate": generic.get("calendarDate") or today_str,
mm_entries = [{"calendarDate": generic.get("calendarDate") or today_str,
"vo2MaxPreciseValue": float(v)}]
stored = 0
for entry in (mm_history or []):
for entry in mm_entries:
if not isinstance(entry, dict):
continue
v = entry.get("vo2MaxPreciseValue") or entry.get("vo2MaxValue")
if not v or float(v) <= 0:
continue
@@ -372,8 +387,7 @@ def sync_wellness(garmin, user_id: int, since: Optional[datetime], db,
logger.warning("Failed to upsert VO2 max for %s: %s", entry_date, exc)
db.rollback()
if stored:
logger.info("Stored %d VO2 max data points", stored)
logger.info("VO2 max: stored=%d from range_valid=%s", stored, valid_from_range)
return processed