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:
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user