feat: add health to garmincraper

This commit is contained in:
2026-05-10 13:20:37 +02:00
parent 435d08767c
commit dbe75abacc
+162 -31
View File
@@ -34,13 +34,11 @@ data:
today = date.today() today = date.today()
start = today - timedelta(days=365 * 3) start = today - timedelta(days=365 * 3)
activities = client.get_activities_by_date(
start.isoformat(), today.isoformat()
)
conn = psycopg2.connect(db_url) conn = psycopg2.connect(db_url)
cur = conn.cursor() cur = conn.cursor()
# ── Schema ────────────────────────────────────────────────────────────────
cur.execute(""" cur.execute("""
CREATE TABLE IF NOT EXISTS activities ( CREATE TABLE IF NOT EXISTS activities (
id BIGINT PRIMARY KEY, id BIGINT PRIMARY KEY,
@@ -66,20 +64,41 @@ data:
""") """)
cur.execute(""" cur.execute("""
ALTER TABLE activities CREATE TABLE IF NOT EXISTS daily_health (
ADD COLUMN IF NOT EXISTS max_hr INTEGER, date DATE PRIMARY KEY,
ADD COLUMN IF NOT EXISTS vo2max FLOAT, resting_hr INTEGER,
ADD COLUMN IF NOT EXISTS calories FLOAT, total_steps INTEGER,
ADD COLUMN IF NOT EXISTS elevation_gain FLOAT, total_distance_meters FLOAT,
ADD COLUMN IF NOT EXISTS steps INTEGER, active_calories FLOAT,
ADD COLUMN IF NOT EXISTS cadence FLOAT, total_calories FLOAT,
ADD COLUMN IF NOT EXISTS hr_zone_1 FLOAT, stress_avg INTEGER,
ADD COLUMN IF NOT EXISTS hr_zone_2 FLOAT, stress_max INTEGER,
ADD COLUMN IF NOT EXISTS hr_zone_3 FLOAT, body_battery_high INTEGER,
ADD COLUMN IF NOT EXISTS hr_zone_4 FLOAT, body_battery_low INTEGER,
ADD COLUMN IF NOT EXISTS hr_zone_5 FLOAT spo2_avg FLOAT,
spo2_min FLOAT,
respiration_avg FLOAT,
hrv_weekly_avg FLOAT,
hrv_last_night FLOAT,
hrv_status TEXT,
sleep_duration_seconds INTEGER,
sleep_deep_seconds INTEGER,
sleep_light_seconds INTEGER,
sleep_rem_seconds INTEGER,
sleep_awake_seconds INTEGER,
sleep_score INTEGER,
sleep_feedback TEXT
)
""") """)
conn.commit()
# ── Activities ────────────────────────────────────────────────────────────
activities = client.get_activities_by_date(
start.isoformat(), today.isoformat()
)
for a in activities: for a in activities:
cur.execute(""" cur.execute("""
INSERT INTO activities INSERT INTO activities
@@ -89,20 +108,20 @@ data:
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (id) DO UPDATE SET ON CONFLICT (id) DO UPDATE SET
duration_seconds = EXCLUDED.duration_seconds, duration_seconds = EXCLUDED.duration_seconds,
distance_meters = EXCLUDED.distance_meters, distance_meters = EXCLUDED.distance_meters,
avg_hr = EXCLUDED.avg_hr, avg_hr = EXCLUDED.avg_hr,
max_hr = EXCLUDED.max_hr, max_hr = EXCLUDED.max_hr,
avg_pace = EXCLUDED.avg_pace, avg_pace = EXCLUDED.avg_pace,
vo2max = EXCLUDED.vo2max, vo2max = EXCLUDED.vo2max,
calories = EXCLUDED.calories, calories = EXCLUDED.calories,
elevation_gain = EXCLUDED.elevation_gain, elevation_gain = EXCLUDED.elevation_gain,
steps = EXCLUDED.steps, steps = EXCLUDED.steps,
cadence = EXCLUDED.cadence, cadence = EXCLUDED.cadence,
hr_zone_1 = EXCLUDED.hr_zone_1, hr_zone_1 = EXCLUDED.hr_zone_1,
hr_zone_2 = EXCLUDED.hr_zone_2, hr_zone_2 = EXCLUDED.hr_zone_2,
hr_zone_3 = EXCLUDED.hr_zone_3, hr_zone_3 = EXCLUDED.hr_zone_3,
hr_zone_4 = EXCLUDED.hr_zone_4, hr_zone_4 = EXCLUDED.hr_zone_4,
hr_zone_5 = EXCLUDED.hr_zone_5 hr_zone_5 = EXCLUDED.hr_zone_5
""", ( """, (
a.get("activityId"), a.get("activityId"),
a.get("activityName"), a.get("activityName"),
@@ -126,6 +145,118 @@ data:
)) ))
conn.commit() conn.commit()
print(f"Synced {len(activities)} activities")
# ── Daily Health (letzte 7 Tage, inkrementell) ────────────────────────────
def safe_get(fn, *args):
try:
return fn(*args)
except Exception as e:
print(f" Warnung {fn.__name__}: {e}")
return None
health_start = today - timedelta(days=7)
check_date = health_start
synced = 0
while check_date <= today:
d = check_date.isoformat()
stats = safe_get(client.get_stats, d) or {}
stress = safe_get(client.get_stress_data, d) or {}
bb_list = safe_get(client.get_body_battery, d, d) or []
spo2 = safe_get(client.get_spo2_data, d) or {}
resp = safe_get(client.get_respiration_data, d) or {}
hrv_raw = safe_get(client.get_hrv_data, d) or {}
sleep_raw = safe_get(client.get_sleep_data, d) or {}
hrv = hrv_raw.get("hrvSummary", {})
sleep = sleep_raw.get("dailySleepDTO", {})
bb_high = max((e.get("charged", 0) for e in bb_list), default=None)
bb_low = min((e.get("drained", 0) for e in bb_list), default=None)
cur.execute("""
INSERT INTO daily_health (
date,
resting_hr, total_steps, total_distance_meters,
active_calories, total_calories,
stress_avg, stress_max,
body_battery_high, body_battery_low,
spo2_avg, spo2_min,
respiration_avg,
hrv_weekly_avg, hrv_last_night, hrv_status,
sleep_duration_seconds, sleep_deep_seconds,
sleep_light_seconds, sleep_rem_seconds, sleep_awake_seconds,
sleep_score, sleep_feedback
) VALUES (
%s,
%s,%s,%s,
%s,%s,
%s,%s,
%s,%s,
%s,%s,
%s,
%s,%s,%s,
%s,%s,
%s,%s,%s,
%s,%s
)
ON CONFLICT (date) DO UPDATE SET
resting_hr = EXCLUDED.resting_hr,
total_steps = EXCLUDED.total_steps,
total_distance_meters = EXCLUDED.total_distance_meters,
active_calories = EXCLUDED.active_calories,
total_calories = EXCLUDED.total_calories,
stress_avg = EXCLUDED.stress_avg,
stress_max = EXCLUDED.stress_max,
body_battery_high = EXCLUDED.body_battery_high,
body_battery_low = EXCLUDED.body_battery_low,
spo2_avg = EXCLUDED.spo2_avg,
spo2_min = EXCLUDED.spo2_min,
respiration_avg = EXCLUDED.respiration_avg,
hrv_weekly_avg = EXCLUDED.hrv_weekly_avg,
hrv_last_night = EXCLUDED.hrv_last_night,
hrv_status = EXCLUDED.hrv_status,
sleep_duration_seconds = EXCLUDED.sleep_duration_seconds,
sleep_deep_seconds = EXCLUDED.sleep_deep_seconds,
sleep_light_seconds = EXCLUDED.sleep_light_seconds,
sleep_rem_seconds = EXCLUDED.sleep_rem_seconds,
sleep_awake_seconds = EXCLUDED.sleep_awake_seconds,
sleep_score = EXCLUDED.sleep_score,
sleep_feedback = EXCLUDED.sleep_feedback
""", (
check_date,
stats.get("restingHeartRate"),
stats.get("totalSteps"),
stats.get("totalDistanceMeters"),
stats.get("activeKilocalories"),
stats.get("totalKilocalories"),
stress.get("avgStressLevel"),
stress.get("maxStressLevel"),
bb_high,
bb_low,
spo2.get("avgSpo2"),
spo2.get("lowestSpo2"),
resp.get("avgWakingRespirationValue"),
hrv.get("weeklyAvg"),
hrv.get("lastNight"),
hrv.get("status"),
sleep.get("sleepTimeSeconds"),
sleep.get("deepSleepSeconds"),
sleep.get("lightSleepSeconds"),
sleep.get("remSleepSeconds"),
sleep.get("awakeSleepSeconds"),
(sleep.get("sleepScores") or {}).get("overall", {}).get("value"),
(sleep.get("sleepScores") or {}).get("overall", {}).get("qualifierKey"),
))
conn.commit()
synced += 1
check_date += timedelta(days=1)
time.sleep(0.5)
cur.close() cur.close()
conn.close() conn.close()
print(f"Synced {len(activities)} activities") print(f"Synced {synced} daily health records")