"""SQLite schema + connection helpers.""" from __future__ import annotations import sqlite3 from pathlib import Path DB_PATH = Path(__file__).parent / "data" / "garmin.db" SCHEMA = """ CREATE TABLE IF NOT EXISTS activities ( activity_id INTEGER PRIMARY KEY, start_time_local TEXT, start_time_gmt TEXT, activity_type TEXT, activity_name TEXT, distance_m REAL, duration_s REAL, moving_duration_s REAL, avg_speed_mps REAL, max_speed_mps REAL, avg_hr REAL, max_hr REAL, calories REAL, elevation_gain_m REAL, elevation_loss_m REAL, training_load REAL, aerobic_te REAL, anaerobic_te REAL, vo2_max REAL, raw TEXT NOT NULL, fetched_at TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_activities_start ON activities(start_time_local); CREATE INDEX IF NOT EXISTS idx_activities_type ON activities(activity_type); CREATE TABLE IF NOT EXISTS activity_splits ( activity_id INTEGER NOT NULL, split_index INTEGER NOT NULL, distance_m REAL, duration_s REAL, avg_hr REAL, avg_speed_mps REAL, elevation_gain_m REAL, raw TEXT NOT NULL, PRIMARY KEY (activity_id, split_index), FOREIGN KEY (activity_id) REFERENCES activities(activity_id) ); CREATE TABLE IF NOT EXISTS daily_steps ( calendar_date TEXT PRIMARY KEY, total_steps INTEGER, step_goal INTEGER, distance_m REAL, raw TEXT NOT NULL, fetched_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS daily_sleep ( calendar_date TEXT PRIMARY KEY, sleep_start_gmt TEXT, sleep_end_gmt TEXT, deep_s REAL, light_s REAL, rem_s REAL, awake_s REAL, sleep_score REAL, raw TEXT NOT NULL, fetched_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS daily_stress ( calendar_date TEXT PRIMARY KEY, avg_stress REAL, max_stress REAL, raw TEXT NOT NULL, fetched_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS daily_hrv ( calendar_date TEXT PRIMARY KEY, weekly_avg REAL, last_night_avg REAL, last_night_5min REAL, status TEXT, raw TEXT NOT NULL, fetched_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS daily_body_battery ( calendar_date TEXT PRIMARY KEY, charged INTEGER, drained INTEGER, highest INTEGER, lowest INTEGER, raw TEXT NOT NULL, fetched_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS daily_intensity_minutes ( calendar_date TEXT PRIMARY KEY, moderate_minutes INTEGER, vigorous_minutes INTEGER, raw TEXT NOT NULL, fetched_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS daily_resting_hr ( calendar_date TEXT PRIMARY KEY, resting_hr INTEGER, raw TEXT NOT NULL, fetched_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS activity_fit_files ( activity_id INTEGER PRIMARY KEY, fit_path TEXT NOT NULL, indexed_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS activity_time_in_zone ( activity_id INTEGER PRIMARY KEY, z1_s REAL, z2_s REAL, z3_s REAL, z4_s REAL, z5_s REAL, total_s REAL, source TEXT, computed_at TEXT NOT NULL, FOREIGN KEY (activity_id) REFERENCES activities(activity_id) ); CREATE TABLE IF NOT EXISTS sync_state ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL ); """ def connect() -> sqlite3.Connection: DB_PATH.parent.mkdir(exist_ok=True) conn = sqlite3.connect(DB_PATH) conn.execute("PRAGMA journal_mode = WAL") conn.execute("PRAGMA foreign_keys = ON") conn.row_factory = sqlite3.Row conn.executescript(SCHEMA) return conn def set_state(conn: sqlite3.Connection, key: str, value: str) -> None: conn.execute( "INSERT INTO sync_state(key, value, updated_at) VALUES (?, ?, datetime('now')) " "ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at", (key, value), ) def get_state(conn: sqlite3.Connection, key: str) -> str | None: row = conn.execute("SELECT value FROM sync_state WHERE key = ?", (key,)).fetchone() return row["value"] if row else None