Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields (activity name/type, lap duration/HR, structured workout raw JSON) from RawJSON at API-response time instead of storing redundant columns - Add a fully configurable chart color system (pace/HR main-line colors, 4 effort-kind colors, tint/darken/brighten intensity knobs) under Profile > Chart colors - Rename training types and fix their display order (Easy, Long, 60'/30' Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed - Add an Efficiency Factor progression metric; fix Progression chart axes to use tight non-zero-based domains, m:ss/km pace formatting, and rounded ticks instead of raw floating-point labels - Expose the raw get_workout_by_id() payload in the raw-data viewer alongside activity/lap/detail JSON; enlarge the modal and shrink array indentation for readability - Fix "last sync" reporting a meaningless activity count: record one combined sync run per manual "Sync now" and count genuinely new activities instead of re-listing whatever Garmin returned for the queried window - Let a Review Queue activity be manually cleared back to Unclassified, and make "Reset all" available even while disconnected from Garmin Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
3
backend/internal/store/migrations/0011_profile_name.sql
Normal file
3
backend/internal/store/migrations/0011_profile_name.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- Names the profile so a future multi-profile setup can show which one is
|
||||
-- active. Only one profile row exists today (id=1), so this is just a label.
|
||||
ALTER TABLE profile ADD COLUMN name TEXT NOT NULL DEFAULT 'Default';
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Replaces the named-zone-only expected_hr_zone with a custom %HRR range
|
||||
-- per training type (e.g. "easy runs at 70-80% heart rate reserve"),
|
||||
-- matching how pace range is already modeled. expected_hr_zone is left in
|
||||
-- place but unused going forward -- nothing reads or writes it anymore.
|
||||
ALTER TABLE workout_type_paces ADD COLUMN hr_min_pct_hrr REAL;
|
||||
ALTER TABLE workout_type_paces ADD COLUMN hr_max_pct_hrr REAL;
|
||||
@@ -0,0 +1,35 @@
|
||||
-- Phase 1 of the raw-JSON duplication cleanup: drops every Activity/Lap
|
||||
-- column that was a pure untransformed copy of a value already present in
|
||||
-- that row's own raw_json, with zero SQL/classify/functional consumer
|
||||
-- anywhere in the app (see the 2026-07 field-by-field duplication audit).
|
||||
-- Unlike this project's usual additive-only migrations, dropping these
|
||||
-- columns outright is the whole point of this one -- leaving them inert
|
||||
-- would keep the exact duplication being removed. activity_name/
|
||||
-- activity_type (Activity) and duration_seconds/avg_hr (laps) also go here
|
||||
-- even though the frontend still displays them: they're now decoded from
|
||||
-- raw_json at API-response time instead of stored separately (see
|
||||
-- internal/api's decodeActivityDisplayFields/decodeLapDisplayFields).
|
||||
DROP INDEX idx_activities_activity_type;
|
||||
|
||||
ALTER TABLE activities DROP COLUMN activity_name;
|
||||
ALTER TABLE activities DROP COLUMN activity_type;
|
||||
ALTER TABLE activities DROP COLUMN begin_timestamp_ms;
|
||||
ALTER TABLE activities DROP COLUMN max_speed_mps;
|
||||
ALTER TABLE activities DROP COLUMN elevation_loss_m;
|
||||
ALTER TABLE activities DROP COLUMN calories;
|
||||
ALTER TABLE activities DROP COLUMN lap_count;
|
||||
ALTER TABLE activities DROP COLUMN training_effect_label;
|
||||
ALTER TABLE activities DROP COLUMN hr_time_in_zone_1;
|
||||
ALTER TABLE activities DROP COLUMN hr_time_in_zone_2;
|
||||
ALTER TABLE activities DROP COLUMN hr_time_in_zone_3;
|
||||
ALTER TABLE activities DROP COLUMN hr_time_in_zone_4;
|
||||
ALTER TABLE activities DROP COLUMN hr_time_in_zone_5;
|
||||
|
||||
ALTER TABLE laps DROP COLUMN start_time_utc;
|
||||
ALTER TABLE laps DROP COLUMN duration_seconds;
|
||||
ALTER TABLE laps DROP COLUMN distance_meters;
|
||||
ALTER TABLE laps DROP COLUMN avg_hr;
|
||||
ALTER TABLE laps DROP COLUMN max_hr;
|
||||
ALTER TABLE laps DROP COLUMN max_speed_mps;
|
||||
ALTER TABLE laps DROP COLUMN elevation_gain_m;
|
||||
ALTER TABLE laps DROP COLUMN elevation_loss_m;
|
||||
10
backend/internal/store/migrations/0014_chart_colors.sql
Normal file
10
backend/internal/store/migrations/0014_chart_colors.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
-- User-configurable chart colors: 2 "main line" colors (one per metric) and
|
||||
-- 4 "effort kind" colors (one per workout phase), used together to derive
|
||||
-- the pace/HR chart's line, under-the-line fill, and phase-background fill
|
||||
-- colors (see frontend's ExpectedVsActualChart).
|
||||
ALTER TABLE profile ADD COLUMN pace_color TEXT NOT NULL DEFAULT '#3b82f6';
|
||||
ALTER TABLE profile ADD COLUMN heart_rate_color TEXT NOT NULL DEFAULT '#ef4444';
|
||||
ALTER TABLE profile ADD COLUMN warmup_color TEXT NOT NULL DEFAULT '#c2410c';
|
||||
ALTER TABLE profile ADD COLUMN effort_color TEXT NOT NULL DEFAULT '#7c3aed';
|
||||
ALTER TABLE profile ADD COLUMN recovery_color TEXT NOT NULL DEFAULT '#15803d';
|
||||
ALTER TABLE profile ADD COLUMN cooldown_color TEXT NOT NULL DEFAULT '#fb923c';
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Renames the default training-type taxonomy to a fixed display convention
|
||||
-- (drop the redundant "Run" suffix, numeral before "Threshold", "Intervals"
|
||||
-- not "Interval") and assigns explicit priorities so kinds always list in
|
||||
-- this exact order wherever they're shown (Activities filters, Progression's
|
||||
-- kind picker, Profile's Training types card) -- existing ORDER BY priority
|
||||
-- DESC, name already does the sorting, no query changes needed:
|
||||
-- Easy, Long, 60' Threshold, 30' Threshold, Tempo, Intervals, MAS Test, Race.
|
||||
--
|
||||
-- Each UPDATE matches on the original seeded name, so a kind the user has
|
||||
-- already renamed themselves (no longer matching) is left untouched.
|
||||
UPDATE workout_kinds SET name = 'Easy', priority = 80 WHERE name = 'Easy Run';
|
||||
UPDATE workout_kinds SET name = 'Long', priority = 70 WHERE name = 'Long Run';
|
||||
UPDATE workout_kinds SET name = '60'' Threshold', priority = 60 WHERE name = 'Threshold 60''';
|
||||
UPDATE workout_kinds SET name = '30'' Threshold', priority = 50 WHERE name = 'Threshold 30''';
|
||||
UPDATE workout_kinds SET priority = 40 WHERE name = 'Tempo';
|
||||
UPDATE workout_kinds SET name = 'Intervals', priority = 30 WHERE name = 'Interval';
|
||||
UPDATE workout_kinds SET priority = 20 WHERE name = 'MAS Test';
|
||||
UPDATE workout_kinds SET priority = 10 WHERE name = 'Race';
|
||||
@@ -0,0 +1,5 @@
|
||||
-- How strongly the main line color (blue/red) tints the effort-kind fill
|
||||
-- under the line, as a percentage (0-100) mixed in -- see frontend's
|
||||
-- ExpectedVsActualChart mixColor(). The phase background above the line is
|
||||
-- never tinted by the main line color regardless of this setting.
|
||||
ALTER TABLE profile ADD COLUMN main_line_tint_pct REAL NOT NULL DEFAULT 20;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- How strongly (0-100) the effort-kind color is darkened for the phase
|
||||
-- background above the line -- see frontend's ExpectedVsActualChart
|
||||
-- darken(). Never mixed with the main line color, unlike the fill below the
|
||||
-- line (see main_line_tint_pct).
|
||||
ALTER TABLE profile ADD COLUMN background_darken_pct REAL NOT NULL DEFAULT 35;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- How strongly (0-100) a chart's main line color (and everything tinted
|
||||
-- from it) is brightened when that chart actually has a structured-workout
|
||||
-- target range to show -- a color-based cue that a target is present,
|
||||
-- replacing a text label -- see frontend's ExpectedVsActualChart brighten().
|
||||
ALTER TABLE profile ADD COLUMN target_brighten_pct REAL NOT NULL DEFAULT 20;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Genuine raw JSON of the activity's structured Garmin workout (get_workout_by_id),
|
||||
-- the source used to compute each lap's TargetPaceLowMps/HighMps and
|
||||
-- TargetHRLowBpm/HighBpm (see internal/sync/mapping.go's alignWorkoutTargets).
|
||||
-- Null for activities with no WorkoutID, or synced before this column existed.
|
||||
ALTER TABLE activities ADD COLUMN workout_raw_json TEXT;
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Widens sync_runs.kind's CHECK constraint to also allow 'full' (a manual
|
||||
-- "Sync now" pass recorded as one combined run instead of separate
|
||||
-- backfill/incremental rows -- see internal/sync.Service.FullSync). SQLite
|
||||
-- has no ALTER TABLE for CHECK constraints, so the table is rebuilt.
|
||||
CREATE TABLE sync_runs_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
kind TEXT NOT NULL CHECK(kind IN ('backfill','incremental','full')),
|
||||
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
|
||||
);
|
||||
|
||||
INSERT INTO sync_runs_new (id, kind, started_at, finished_at, activities_fetched, status, error_message)
|
||||
SELECT id, kind, started_at, finished_at, activities_fetched, status, error_message FROM sync_runs;
|
||||
|
||||
DROP TABLE sync_runs;
|
||||
ALTER TABLE sync_runs_new RENAME TO sync_runs;
|
||||
Reference in New Issue
Block a user