Garmin run classification and progression tracker. Go backend (MCP client to mcp-garmin, SQLite store, deterministic rule engine, REST API) and React/TS frontend (Dashboard, Review Queue, Workout Kinds). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
110 lines
4.4 KiB
SQL
110 lines
4.4 KiB
SQL
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
|
|
);
|