fix: add all keys for configmap garmin

This commit is contained in:
2026-05-10 18:21:36 +02:00
parent c5c2a5b5bd
commit b44af21cef
+363 -16
View File
@@ -6,13 +6,14 @@ metadata:
data: data:
sync.py: | sync.py: |
import os import os
import json
import time import time
from datetime import date, timedelta from datetime import date, timedelta
import psycopg2
from garminconnect import Garmin from garminconnect import Garmin
email = os.environ["GARMIN_EMAIL"] email = os.environ["GARMIN_EMAIL"]
password = os.environ["GARMIN_PASSWORD"] password = os.environ["GARMIN_PASSWORD"]
db_url = os.environ["DATABASE_URL"]
client = Garmin(email, password) client = Garmin(email, password)
for attempt in range(5): for attempt in range(5):
@@ -30,21 +31,367 @@ data:
else: else:
raise Exception("Login nach 5 Versuchen fehlgeschlagen") raise Exception("Login nach 5 Versuchen fehlgeschlagen")
d = (date.today() - timedelta(days=1)).isoformat() today = date.today()
print(f"=== Debug fuer {d} ===") start = today - timedelta(days=365 * 3)
for name, fn, args in [ conn = psycopg2.connect(db_url)
("stats", client.get_stats, [d]), cur = conn.cursor()
("stress", client.get_stress_data, [d]),
("bb", client.get_body_battery, [d, d]), # ── Schema ────────────────────────────────────────────────────────────────
("spo2", client.get_spo2_data, [d]),
("resp", client.get_respiration_data, [d]), cur.execute("""
("hrv", client.get_hrv_data, [d]), CREATE TABLE IF NOT EXISTS activities (
("sleep", client.get_sleep_data, [d]), id BIGINT PRIMARY KEY,
]: name TEXT,
type TEXT,
start_time TIMESTAMP,
duration_seconds FLOAT,
distance_meters FLOAT,
avg_hr INTEGER,
max_hr INTEGER,
avg_pace FLOAT,
vo2max FLOAT,
calories FLOAT,
elevation_gain FLOAT,
steps INTEGER,
cadence FLOAT,
hr_zone_1 FLOAT,
hr_zone_2 FLOAT,
hr_zone_3 FLOAT,
hr_zone_4 FLOAT,
hr_zone_5 FLOAT
)
""")
# Keys verified: (D) = aus deinem Debug-Output direkt bestaetigt
# (S) = aus Quellcode/Pydantic-Models
cur.execute("""
CREATE TABLE IF NOT EXISTS daily_health (
date DATE PRIMARY KEY,
-- get_stats (D)
resting_hr INTEGER, -- restingHeartRate
min_hr INTEGER, -- minHeartRate
max_hr INTEGER, -- maxHeartRate
total_steps INTEGER, -- totalSteps
daily_step_goal INTEGER, -- dailyStepGoal
total_distance_meters FLOAT, -- totalDistanceMeters
active_calories FLOAT, -- activeKilocalories
total_calories FLOAT, -- totalKilocalories
bmr_calories FLOAT, -- bmrKilocalories
highly_active_seconds INTEGER, -- highlyActiveSeconds
active_seconds INTEGER, -- activeSeconds
sedentary_seconds INTEGER, -- sedentarySeconds
sleeping_seconds INTEGER, -- sleepingSeconds
moderate_intensity_minutes INTEGER, -- moderateIntensityMinutes
vigorous_intensity_minutes INTEGER, -- vigorousIntensityMinutes
floors_ascended FLOAT, -- floorsAscended
floors_descended FLOAT, -- floorsDescended
average_stress INTEGER, -- averageStressLevel
max_stress INTEGER, -- maxStressLevel
rest_stress_duration INTEGER, -- restStressDuration
low_stress_duration INTEGER, -- lowStressDuration
medium_stress_duration INTEGER, -- mediumStressDuration
high_stress_duration INTEGER, -- highStressDuration
stress_qualifier TEXT, -- stressQualifier
body_battery_highest INTEGER, -- bodyBatteryHighestValue
body_battery_lowest INTEGER, -- bodyBatteryLowestValue
body_battery_most_recent INTEGER, -- bodyBatteryMostRecentValue
body_battery_at_wake INTEGER, -- bodyBatteryAtWakeTime
body_battery_during_sleep INTEGER, -- bodyBatteryDuringSleep
average_spo2 FLOAT, -- averageSpo2
lowest_spo2 FLOAT, -- lowestSpo2
latest_spo2 FLOAT, -- latestSpo2
-- get_hrv_data -> hrvSummary (S - wird null wenn Uhr nicht getragen)
hrv_weekly_avg FLOAT, -- weeklyAvg
hrv_last_night FLOAT, -- lastNight
hrv_last_night_5min_high FLOAT, -- lastNight5MinHigh
hrv_baseline_low FLOAT, -- baselineLowUpper
hrv_baseline_high FLOAT, -- baselineBalancedLower
hrv_status TEXT, -- status
hrv_feedback TEXT, -- feedbackPhrase
-- get_sleep_data -> dailySleepDTO (D)
sleep_start_local TIMESTAMP, -- sleepStartTimestampLocal
sleep_end_local TIMESTAMP, -- sleepEndTimestampLocal
sleep_duration_seconds INTEGER, -- sleepTimeSeconds
sleep_nap_seconds INTEGER, -- napTimeSeconds
sleep_deep_seconds INTEGER, -- deepSleepSeconds
sleep_light_seconds INTEGER, -- lightSleepSeconds
sleep_rem_seconds INTEGER, -- remSleepSeconds
sleep_awake_seconds INTEGER, -- awakeSleepSeconds
sleep_rem_capable BOOLEAN, -- deviceRemCapable
sleep_score INTEGER, -- sleepScores.overall.value
sleep_feedback TEXT, -- sleepScores.overall.qualifierKey
-- get_respiration_data (D)
respiration_avg_waking FLOAT, -- avgWakingRespirationValue
respiration_avg_sleep FLOAT, -- avgSleepRespirationValue
respiration_lowest FLOAT, -- lowestRespirationValue
respiration_highest FLOAT -- highestRespirationValue
)
""")
conn.commit()
# ── Activities ────────────────────────────────────────────────────────────
activities = client.get_activities_by_date(
start.isoformat(), today.isoformat()
)
for a in activities:
cur.execute("""
INSERT INTO activities
(id, name, type, start_time, duration_seconds, distance_meters,
avg_hr, max_hr, avg_pace, vo2max, calories, elevation_gain,
steps, cadence, hr_zone_1, hr_zone_2, hr_zone_3, hr_zone_4, hr_zone_5)
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
duration_seconds = EXCLUDED.duration_seconds,
distance_meters = EXCLUDED.distance_meters,
avg_hr = EXCLUDED.avg_hr,
max_hr = EXCLUDED.max_hr,
avg_pace = EXCLUDED.avg_pace,
vo2max = EXCLUDED.vo2max,
calories = EXCLUDED.calories,
elevation_gain = EXCLUDED.elevation_gain,
steps = EXCLUDED.steps,
cadence = EXCLUDED.cadence,
hr_zone_1 = EXCLUDED.hr_zone_1,
hr_zone_2 = EXCLUDED.hr_zone_2,
hr_zone_3 = EXCLUDED.hr_zone_3,
hr_zone_4 = EXCLUDED.hr_zone_4,
hr_zone_5 = EXCLUDED.hr_zone_5
""", (
a.get("activityId"),
a.get("activityName"),
a.get("activityType", {}).get("typeKey"),
a.get("startTimeLocal"),
a.get("duration"),
a.get("distance"),
a.get("averageHR"),
a.get("maxHR"),
a.get("averageSpeed"),
a.get("vO2MaxValue"),
a.get("calories"),
a.get("elevationGain"),
a.get("steps"),
a.get("averageRunningCadenceInStepsPerMinute"),
a.get("hrTimeInZone_1"),
a.get("hrTimeInZone_2"),
a.get("hrTimeInZone_3"),
a.get("hrTimeInZone_4"),
a.get("hrTimeInZone_5"),
))
conn.commit()
print(f"Synced {len(activities)} activities")
# ── Daily Health (letzte 7 Tage) ──────────────────────────────────────────
def safe_get(fn, *args):
try: try:
result = fn(*args) return fn(*args)
print(f"\n=== {name} ===")
print(json.dumps(result, indent=2, default=str)[:3000])
except Exception as e: except Exception as e:
print(f"\n=== {name} FEHLER: {e} ===") print(f" Warnung {fn.__name__}: {e}")
return None
synced = 0
check_date = today - timedelta(days=7)
while check_date <= today:
d = check_date.isoformat()
stats = safe_get(client.get_stats, d) or {}
hrv_raw = safe_get(client.get_hrv_data, d) or {}
sleep_r = safe_get(client.get_sleep_data, d) or {}
resp = safe_get(client.get_respiration_data, d) or {}
hrv = hrv_raw.get("hrvSummary") or {}
sleep = sleep_r.get("dailySleepDTO") or {}
scores = ((sleep.get("sleepScores") or {}).get("overall") or {})
cur.execute("""
INSERT INTO daily_health (
date,
resting_hr, min_hr, max_hr,
total_steps, daily_step_goal, total_distance_meters,
active_calories, total_calories, bmr_calories,
highly_active_seconds, active_seconds,
sedentary_seconds, sleeping_seconds,
moderate_intensity_minutes, vigorous_intensity_minutes,
floors_ascended, floors_descended,
average_stress, max_stress,
rest_stress_duration, low_stress_duration,
medium_stress_duration, high_stress_duration,
stress_qualifier,
body_battery_highest, body_battery_lowest,
body_battery_most_recent, body_battery_at_wake,
body_battery_during_sleep,
average_spo2, lowest_spo2, latest_spo2,
hrv_weekly_avg, hrv_last_night, hrv_last_night_5min_high,
hrv_baseline_low, hrv_baseline_high,
hrv_status, hrv_feedback,
sleep_start_local, sleep_end_local,
sleep_duration_seconds, sleep_nap_seconds,
sleep_deep_seconds, sleep_light_seconds,
sleep_rem_seconds, sleep_awake_seconds,
sleep_rem_capable, sleep_score, sleep_feedback,
respiration_avg_waking, respiration_avg_sleep,
respiration_lowest, respiration_highest
) VALUES (
%s,
%s,%s,%s,
%s,%s,%s,
%s,%s,%s,
%s,%s,
%s,%s,
%s,%s,
%s,%s,
%s,%s,
%s,%s,
%s,%s,
%s,
%s,%s,
%s,%s,
%s,
%s,%s,%s,
%s,%s,%s,
%s,%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,
min_hr = EXCLUDED.min_hr,
max_hr = EXCLUDED.max_hr,
total_steps = EXCLUDED.total_steps,
daily_step_goal = EXCLUDED.daily_step_goal,
total_distance_meters = EXCLUDED.total_distance_meters,
active_calories = EXCLUDED.active_calories,
total_calories = EXCLUDED.total_calories,
bmr_calories = EXCLUDED.bmr_calories,
highly_active_seconds = EXCLUDED.highly_active_seconds,
active_seconds = EXCLUDED.active_seconds,
sedentary_seconds = EXCLUDED.sedentary_seconds,
sleeping_seconds = EXCLUDED.sleeping_seconds,
moderate_intensity_minutes = EXCLUDED.moderate_intensity_minutes,
vigorous_intensity_minutes = EXCLUDED.vigorous_intensity_minutes,
floors_ascended = EXCLUDED.floors_ascended,
floors_descended = EXCLUDED.floors_descended,
average_stress = EXCLUDED.average_stress,
max_stress = EXCLUDED.max_stress,
rest_stress_duration = EXCLUDED.rest_stress_duration,
low_stress_duration = EXCLUDED.low_stress_duration,
medium_stress_duration = EXCLUDED.medium_stress_duration,
high_stress_duration = EXCLUDED.high_stress_duration,
stress_qualifier = EXCLUDED.stress_qualifier,
body_battery_highest = EXCLUDED.body_battery_highest,
body_battery_lowest = EXCLUDED.body_battery_lowest,
body_battery_most_recent = EXCLUDED.body_battery_most_recent,
body_battery_at_wake = EXCLUDED.body_battery_at_wake,
body_battery_during_sleep = EXCLUDED.body_battery_during_sleep,
average_spo2 = EXCLUDED.average_spo2,
lowest_spo2 = EXCLUDED.lowest_spo2,
latest_spo2 = EXCLUDED.latest_spo2,
hrv_weekly_avg = EXCLUDED.hrv_weekly_avg,
hrv_last_night = EXCLUDED.hrv_last_night,
hrv_last_night_5min_high = EXCLUDED.hrv_last_night_5min_high,
hrv_baseline_low = EXCLUDED.hrv_baseline_low,
hrv_baseline_high = EXCLUDED.hrv_baseline_high,
hrv_status = EXCLUDED.hrv_status,
hrv_feedback = EXCLUDED.hrv_feedback,
sleep_start_local = EXCLUDED.sleep_start_local,
sleep_end_local = EXCLUDED.sleep_end_local,
sleep_duration_seconds = EXCLUDED.sleep_duration_seconds,
sleep_nap_seconds = EXCLUDED.sleep_nap_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_rem_capable = EXCLUDED.sleep_rem_capable,
sleep_score = EXCLUDED.sleep_score,
sleep_feedback = EXCLUDED.sleep_feedback,
respiration_avg_waking = EXCLUDED.respiration_avg_waking,
respiration_avg_sleep = EXCLUDED.respiration_avg_sleep,
respiration_lowest = EXCLUDED.respiration_lowest,
respiration_highest = EXCLUDED.respiration_highest
""", (
check_date,
# get_stats
stats.get("restingHeartRate"),
stats.get("minHeartRate"),
stats.get("maxHeartRate"),
stats.get("totalSteps"),
stats.get("dailyStepGoal"),
stats.get("totalDistanceMeters"),
stats.get("activeKilocalories"),
stats.get("totalKilocalories"),
stats.get("bmrKilocalories"),
stats.get("highlyActiveSeconds"),
stats.get("activeSeconds"),
stats.get("sedentarySeconds"),
stats.get("sleepingSeconds"),
stats.get("moderateIntensityMinutes"),
stats.get("vigorousIntensityMinutes"),
stats.get("floorsAscended"),
stats.get("floorsDescended"),
stats.get("averageStressLevel"),
stats.get("maxStressLevel"),
stats.get("restStressDuration"),
stats.get("lowStressDuration"),
stats.get("mediumStressDuration"),
stats.get("highStressDuration"),
stats.get("stressQualifier"),
stats.get("bodyBatteryHighestValue"),
stats.get("bodyBatteryLowestValue"),
stats.get("bodyBatteryMostRecentValue"),
stats.get("bodyBatteryAtWakeTime"),
stats.get("bodyBatteryDuringSleep"),
stats.get("averageSpo2"),
stats.get("lowestSpo2"),
stats.get("latestSpo2"),
# get_hrv_data
hrv.get("weeklyAvg"),
hrv.get("lastNight"),
hrv.get("lastNight5MinHigh"),
hrv.get("baselineLowUpper"),
hrv.get("baselineBalancedLower"),
hrv.get("status"),
hrv.get("feedbackPhrase"),
# get_sleep_data
sleep.get("sleepStartTimestampLocal"),
sleep.get("sleepEndTimestampLocal"),
sleep.get("sleepTimeSeconds"),
sleep.get("napTimeSeconds"),
sleep.get("deepSleepSeconds"),
sleep.get("lightSleepSeconds"),
sleep.get("remSleepSeconds"),
sleep.get("awakeSleepSeconds"),
sleep.get("deviceRemCapable"),
scores.get("value"),
scores.get("qualifierKey"),
# get_respiration_data
resp.get("avgWakingRespirationValue"),
resp.get("avgSleepRespirationValue"),
resp.get("lowestRespirationValue"),
resp.get("highestRespirationValue"),
))
conn.commit()
synced += 1
check_date += timedelta(days=1)
time.sleep(0.5)
cur.close()
conn.close()
print(f"Synced {synced} daily health records")