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:
2026-07-20 06:33:47 +02:00
parent 9818d35910
commit 518bccf5ab
56 changed files with 2334 additions and 890 deletions

View File

@@ -5,33 +5,33 @@
export interface Activity {
ID: number;
GarminActivityID: number;
// ActivityName/ActivityType aren't stored columns -- the backend decodes
// them from RawJSON at response time (see internal/api/display_fields.go).
ActivityName: string;
ActivityType: string;
EventTypeKey: string;
WorkoutID: number | null;
StartTimeUTC: string;
BeginTimestampMs: number;
DurationSeconds: number;
DistanceMeters: number;
AvgHR: number | null;
MaxHR: number | null;
AvgSpeedMps: number | null;
MaxSpeedMps: number | null;
ElevationGainM: number | null;
ElevationLossM: number | null;
Calories: number | null;
LapCount: number;
AerobicTrainingEffect: number | null;
AnaerobicTrainingEffect: number | null;
TrainingEffectLabel: string;
VO2MaxValue: number | null;
// Raw JSON strings from Garmin, kept for fields not modeled above. Only
// needed by the Review Queue's raw-data viewer, so left as strings here
// needed by the Activities page's raw-data viewer, so left as strings here
// rather than typed -- the viewer parses them for display.
RawJSON: string;
DetailsFetchedAt: string | null;
DetailsRawJSON: string | null;
SplitsFetchedAt: string | null;
// Genuine raw get_workout_by_id() response for this activity's structured
// workout. Null when the activity has no WorkoutID, or was synced before
// this column existed.
WorkoutRawJSON: string | null;
CreatedAt: string;
UpdatedAt: string;
}
@@ -40,15 +40,11 @@ export interface Lap {
ID: number;
ActivityID: number;
LapIndex: number;
StartTimeUTC: string;
// DurationSeconds/AvgHR aren't stored columns -- the backend decodes them
// from RawJSON at response time (see internal/api/display_fields.go).
DurationSeconds: number;
DistanceMeters: number;
AvgHR: number | null;
MaxHR: number | null;
AvgSpeedMps: number | null;
MaxSpeedMps: number | null;
ElevationGainM: number | null;
ElevationLossM: number | null;
IntensityType: string;
HRDriftBpmPerMin: number | null;
HRRecoveryBpmPerMin: number | null;
@@ -91,10 +87,12 @@ export interface WorkoutKind {
UpdatedAt: string;
pace_min_sec_per_km: number | null;
pace_max_sec_per_km: number | null;
expected_hr_zone: number | null;
hr_min_pct_hrr: number | null;
hr_max_pct_hrr: number | null;
}
export interface Profile {
Name: string;
GarminEmail: string;
GarminPassword: string;
RollingWindowDays: number;
@@ -115,6 +113,25 @@ export interface Profile {
CooldownMinutes: number;
MinRepresentativePaceSecPerKm: number;
MinRepresentativeTimeSeconds: number;
// Chart colors: PaceColor/HeartRateColor are each chart's "main line"
// color; Warmup/Effort/Recovery/CooldownColor are the "effort kind"
// colors used to derive the under-the-line fill and phase background
// (see ExpectedVsActualChart).
PaceColor: string;
HeartRateColor: string;
WarmupColor: string;
EffortColor: string;
RecoveryColor: string;
CooldownColor: string;
// How strongly (0-100) the main line color mixes into the effort-kind
// fill under the line. Never affects the phase background above the line.
MainLineTintPct: number;
// How strongly (0-100) the effort-kind color is darkened for the phase
// background above the line. Never mixed with the main line color.
BackgroundDarkenPct: number;
// How strongly (0-100) a chart's main line color is brightened when that
// chart has a structured-workout target range to show.
TargetBrightenPct: number;
CreatedAt: string;
UpdatedAt: string;
}
@@ -181,4 +198,4 @@ export interface SyncStatus {
last_run?: SyncRun;
}
export type ProgressionMetric = "pace" | "hr" | "vo2max" | "aerobic_te" | "anaerobic_te";
export type ProgressionMetric = "pace" | "hr" | "vo2max" | "aerobic_te" | "anaerobic_te" | "efficiency_factor";