fix: add all keys for configmap garmin
This commit is contained in:
@@ -6,13 +6,14 @@ metadata:
|
||||
data:
|
||||
sync.py: |
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from datetime import date, timedelta
|
||||
import psycopg2
|
||||
from garminconnect import Garmin
|
||||
|
||||
email = os.environ["GARMIN_EMAIL"]
|
||||
password = os.environ["GARMIN_PASSWORD"]
|
||||
db_url = os.environ["DATABASE_URL"]
|
||||
|
||||
client = Garmin(email, password)
|
||||
for attempt in range(5):
|
||||
@@ -30,21 +31,367 @@ data:
|
||||
else:
|
||||
raise Exception("Login nach 5 Versuchen fehlgeschlagen")
|
||||
|
||||
d = (date.today() - timedelta(days=1)).isoformat()
|
||||
print(f"=== Debug fuer {d} ===")
|
||||
today = date.today()
|
||||
start = today - timedelta(days=365 * 3)
|
||||
|
||||
for name, fn, args in [
|
||||
("stats", client.get_stats, [d]),
|
||||
("stress", client.get_stress_data, [d]),
|
||||
("bb", client.get_body_battery, [d, d]),
|
||||
("spo2", client.get_spo2_data, [d]),
|
||||
("resp", client.get_respiration_data, [d]),
|
||||
("hrv", client.get_hrv_data, [d]),
|
||||
("sleep", client.get_sleep_data, [d]),
|
||||
]:
|
||||
conn = psycopg2.connect(db_url)
|
||||
cur = conn.cursor()
|
||||
|
||||
# ── Schema ────────────────────────────────────────────────────────────────
|
||||
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS activities (
|
||||
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:
|
||||
result = fn(*args)
|
||||
print(f"\n=== {name} ===")
|
||||
print(json.dumps(result, indent=2, default=str)[:3000])
|
||||
return fn(*args)
|
||||
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")
|
||||
Reference in New Issue
Block a user