"""Bounded optional daily Garmin imports; provider failures never disclose sessions.""" from datetime import UTC, datetime, timedelta from zoneinfo import ZoneInfo from .analytics import numeric from .extensions import db from .models import DailyHealth def normalize(stats, hrv, sleep): summary = (hrv or {}).get("hrvSummary") or {} dto = (sleep or {}).get("dailySleepDTO") or {} candidates = {"resting_hr": (stats or {}).get("restingHeartRate"), "hrv": summary.get("lastNightAvg"), "sleep_hours": dto.get("sleepTimeSeconds"), "stress": (stats or {}).get("averageStressLevel")} values = {} for key, number in candidates.items(): if not numeric(number): continue if key == "sleep_hours": number /= 3600 low, high = {"resting_hr": (25, 220), "hrv": (1, 500), "sleep_hours": (0, 24), "stress": (0, 100)}[key] if low <= number <= high: values[key] = number return values def sync_health(api, user, days=90, limit=5): today = datetime.now(UTC).astimezone(ZoneInfo(user.timezone)).date() first = today - timedelta(days=days - 1) known = {row.day: row for row in db.session.scalars(db.select(DailyHealth).where( DailyHealth.owner_id == user.id, DailyHealth.day >= first))} # Refresh today/yesterday; then walk backwards through missing history. dates = [today, today - timedelta(days=1)] dates += [today - timedelta(days=i) for i in range(2, days) if today - timedelta(days=i) not in known or not known[today - timedelta(days=i)].values.get("_complete")] dates = [day for day in dates if day >= first][:limit] imported = failures = 0 for day in dates: documents = [] day_failures = 0 for method in ("get_stats", "get_hrv_data", "get_sleep_data"): try: document = getattr(api, method)(day.isoformat()) documents.append(document if isinstance(document, dict) else {}) except Exception: # noqa: BLE001 -- never log provider responses or credentials documents.append({}) failures += 1 day_failures += 1 values = normalize(*documents) if day_failures and not values: # Retry later; stop the batch instead of hammering a failing provider. break row = known.get(day) if not row: row = DailyHealth(owner_id=user.id, day=day, values={}) db.session.add(row) row.values = {**row.values, **values, "_complete": day_failures == 0} row.fetched_at = datetime.now(UTC) db.session.commit() imported += 1 if day_failures: break return {"days": imported, "failures": failures, "message": f"Daily recovery: {imported} dates checked." + (" Some observations unavailable; will retry." if failures else " Missing watch observations remain missing.")}