CREATE TABLE activities ( id INTEGER PRIMARY KEY AUTOINCREMENT, garmin_activity_id INTEGER NOT NULL UNIQUE, activity_name TEXT NOT NULL DEFAULT '', activity_type TEXT NOT NULL, start_time_utc TEXT NOT NULL, begin_timestamp_ms INTEGER NOT NULL, duration_seconds REAL NOT NULL, distance_meters REAL NOT NULL, avg_hr REAL, max_hr REAL, avg_speed_mps REAL, max_speed_mps REAL, elevation_gain_m REAL, elevation_loss_m REAL, calories REAL, lap_count INTEGER NOT NULL DEFAULT 0, aerobic_training_effect REAL, anaerobic_training_effect REAL, training_effect_label TEXT NOT NULL DEFAULT '', vo2max_value REAL, hr_time_in_zone_1 REAL, hr_time_in_zone_2 REAL, hr_time_in_zone_3 REAL, hr_time_in_zone_4 REAL, hr_time_in_zone_5 REAL, raw_json TEXT NOT NULL, details_fetched_at TEXT, details_raw_json TEXT, splits_fetched_at TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX idx_activities_start_time ON activities(start_time_utc); CREATE INDEX idx_activities_activity_type ON activities(activity_type); CREATE TABLE laps ( id INTEGER PRIMARY KEY AUTOINCREMENT, activity_id INTEGER NOT NULL REFERENCES activities(id) ON DELETE CASCADE, lap_index INTEGER NOT NULL, start_time_utc TEXT NOT NULL, duration_seconds REAL NOT NULL, distance_meters REAL NOT NULL, avg_hr REAL, max_hr REAL, avg_speed_mps REAL, max_speed_mps REAL, elevation_gain_m REAL, elevation_loss_m REAL, intensity_type TEXT NOT NULL DEFAULT '', hr_drift_bpm_per_min REAL, hr_recovery_bpm_per_min REAL, raw_json TEXT NOT NULL, UNIQUE(activity_id, lap_index) ); CREATE TABLE activity_samples ( id INTEGER PRIMARY KEY AUTOINCREMENT, activity_id INTEGER NOT NULL REFERENCES activities(id) ON DELETE CASCADE, elapsed_seconds REAL NOT NULL, timestamp_ms INTEGER NOT NULL, heart_rate REAL, speed_mps REAL, distance_m REAL, elevation_m REAL ); CREATE INDEX idx_activity_samples_activity ON activity_samples(activity_id, elapsed_seconds); CREATE TABLE workout_kinds ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, description TEXT NOT NULL DEFAULT '', color TEXT NOT NULL DEFAULT '', rule_json TEXT NOT NULL, priority INTEGER NOT NULL DEFAULT 0, is_active INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE kind_assignments ( id INTEGER PRIMARY KEY AUTOINCREMENT, activity_id INTEGER NOT NULL REFERENCES activities(id) ON DELETE CASCADE, workout_kind_id INTEGER REFERENCES workout_kinds(id), assignment_source TEXT NOT NULL CHECK(assignment_source IN ('rule_engine','manual')), status TEXT NOT NULL CHECK(status IN ('assigned','needs_review')), confidence REAL, candidate_kinds_json TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX idx_kind_assignments_activity ON kind_assignments(activity_id); CREATE INDEX idx_kind_assignments_status ON kind_assignments(status); CREATE VIEW current_kind_assignment AS SELECT a.* FROM kind_assignments a JOIN ( SELECT activity_id, MAX(id) AS max_id FROM kind_assignments GROUP BY activity_id ) latest ON a.activity_id = latest.activity_id AND a.id = latest.max_id; CREATE TABLE sync_runs ( id INTEGER PRIMARY KEY AUTOINCREMENT, kind TEXT NOT NULL CHECK(kind IN ('backfill','incremental')), started_at TEXT NOT NULL, finished_at TEXT, activities_fetched INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL CHECK(status IN ('running','success','error')), error_message TEXT );