Files

72 lines
3.0 KiB
Python

"""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.")}