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:
@@ -7,19 +7,18 @@ import (
|
||||
|
||||
// Lap is one lap/split of an activity, from get_activity_splits, plus
|
||||
// derived HR drift/recovery metrics computed from activity_samples.
|
||||
//
|
||||
// Deliberately NOT modeled here: StartTimeUTC, DistanceMeters, MaxHR,
|
||||
// MaxSpeedMps, ElevationGainM, ElevationLossM (removed in the 2026-07
|
||||
// dedup pass -- zero consumers anywhere, pure duplicates of RawJSON), plus
|
||||
// DurationSeconds and AvgHR, which DO have a real frontend chart need but no
|
||||
// backend one, so the API layer decodes them from RawJSON at response time
|
||||
// instead of storing a redundant copy -- see decodeLapDisplayFields.
|
||||
type Lap struct {
|
||||
ID int64
|
||||
ActivityID int64
|
||||
LapIndex int
|
||||
StartTimeUTC string
|
||||
DurationSeconds float64
|
||||
DistanceMeters float64
|
||||
AvgHR *float64
|
||||
MaxHR *float64
|
||||
AvgSpeedMps *float64
|
||||
MaxSpeedMps *float64
|
||||
ElevationGainM *float64
|
||||
ElevationLossM *float64
|
||||
IntensityType string
|
||||
HRDriftBpmPerMin *float64
|
||||
HRRecoveryBpmPerMin *float64
|
||||
@@ -52,13 +51,11 @@ func (db *DB) ReplaceLaps(ctx context.Context, activityID int64, laps []Lap) err
|
||||
for _, l := range laps {
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO laps (
|
||||
activity_id, lap_index, start_time_utc, duration_seconds, distance_meters,
|
||||
avg_hr, max_hr, avg_speed_mps, max_speed_mps, elevation_gain_m, elevation_loss_m,
|
||||
activity_id, lap_index, avg_speed_mps,
|
||||
intensity_type, hr_drift_bpm_per_min, hr_recovery_bpm_per_min,
|
||||
target_pace_low_mps, target_pace_high_mps, target_hr_low_bpm, target_hr_high_bpm, raw_json
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
activityID, l.LapIndex, l.StartTimeUTC, l.DurationSeconds, l.DistanceMeters,
|
||||
l.AvgHR, l.MaxHR, l.AvgSpeedMps, l.MaxSpeedMps, l.ElevationGainM, l.ElevationLossM,
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
activityID, l.LapIndex, l.AvgSpeedMps,
|
||||
l.IntensityType, l.HRDriftBpmPerMin, l.HRRecoveryBpmPerMin,
|
||||
l.TargetPaceLowMps, l.TargetPaceHighMps, l.TargetHRLowBpm, l.TargetHRHighBpm, l.RawJSON,
|
||||
)
|
||||
@@ -72,8 +69,7 @@ func (db *DB) ReplaceLaps(ctx context.Context, activityID int64, laps []Lap) err
|
||||
// LapsForActivity returns all laps for an activity, ordered by lap_index.
|
||||
func (db *DB) LapsForActivity(ctx context.Context, activityID int64) ([]Lap, error) {
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT id, activity_id, lap_index, start_time_utc, duration_seconds, distance_meters,
|
||||
avg_hr, max_hr, avg_speed_mps, max_speed_mps, elevation_gain_m, elevation_loss_m,
|
||||
SELECT id, activity_id, lap_index, avg_speed_mps,
|
||||
intensity_type, hr_drift_bpm_per_min, hr_recovery_bpm_per_min,
|
||||
target_pace_low_mps, target_pace_high_mps, target_hr_low_bpm, target_hr_high_bpm, raw_json
|
||||
FROM laps WHERE activity_id = ? ORDER BY lap_index`, activityID)
|
||||
@@ -86,8 +82,7 @@ func (db *DB) LapsForActivity(ctx context.Context, activityID int64) ([]Lap, err
|
||||
for rows.Next() {
|
||||
var l Lap
|
||||
if err := rows.Scan(
|
||||
&l.ID, &l.ActivityID, &l.LapIndex, &l.StartTimeUTC, &l.DurationSeconds, &l.DistanceMeters,
|
||||
&l.AvgHR, &l.MaxHR, &l.AvgSpeedMps, &l.MaxSpeedMps, &l.ElevationGainM, &l.ElevationLossM,
|
||||
&l.ID, &l.ActivityID, &l.LapIndex, &l.AvgSpeedMps,
|
||||
&l.IntensityType, &l.HRDriftBpmPerMin, &l.HRRecoveryBpmPerMin,
|
||||
&l.TargetPaceLowMps, &l.TargetPaceHighMps, &l.TargetHRLowBpm, &l.TargetHRHighBpm, &l.RawJSON,
|
||||
); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user