diff --git a/backend/cmd/seedsample/main.go b/backend/cmd/seedsample/main.go index da9d41d..7110ef2 100644 --- a/backend/cmd/seedsample/main.go +++ b/backend/cmd/seedsample/main.go @@ -29,13 +29,16 @@ func main() { } defer db.Close() + userID, err := db.ProvisionUser(ctx, "seedsample-user", "Sample") + must(err) + // Set a max heart rate on the profile so avg_hr_pct_max is computed // during classification below (it's nil/unset by default). - profile, err := db.GetProfile(ctx) + profile, err := db.GetProfile(ctx, userID) must(err) maxHR := 190.0 profile.MaxHeartRate = &maxHR - must(db.UpdateProfile(ctx, profile)) + must(db.UpdateProfile(ctx, userID, profile)) // Easy Run and Tempo's pace/HR ranges deliberately overlap a little // (330-340 sec/km, 0.70-0.85 HR%max) so a run that lands in that overlap @@ -43,8 +46,8 @@ func main() { // gap between disjoint ranges. The taxonomy migration already seeded // these rows (by fixed name) -- update their rules in place rather than // creating new ones, since names are unique. - easyID := mustFindKindID(ctx, db, "Easy") - must(db.UpdateWorkoutKind(ctx, store.WorkoutKind{ + easyID := mustFindKindID(ctx, db, userID, "Easy") + must(db.UpdateWorkoutKind(ctx, userID, store.WorkoutKind{ ID: easyID, Name: "Easy", Description: "Easy conversational runs", Color: "#22c55e", RuleJSON: `{"match":"all","conditions":[ {"metric":"avg_pace_sec_per_km","op":"between","value":[330,420]}, @@ -53,8 +56,8 @@ func main() { IsActive: true, })) - tempoID := mustFindKindID(ctx, db, "Tempo") - must(db.UpdateWorkoutKind(ctx, store.WorkoutKind{ + tempoID := mustFindKindID(ctx, db, userID, "Tempo") + must(db.UpdateWorkoutKind(ctx, userID, store.WorkoutKind{ ID: tempoID, Name: "Tempo", Description: "Comfortably hard sustained effort", Color: "#f59e0b", RuleJSON: `{"match":"all","conditions":[ {"metric":"avg_pace_sec_per_km","op":"between","value":[300,340]}, @@ -63,15 +66,15 @@ func main() { IsActive: true, })) - intervalID := mustFindKindID(ctx, db, "Intervals") - must(db.UpdateWorkoutKind(ctx, store.WorkoutKind{ + intervalID := mustFindKindID(ctx, db, userID, "Intervals") + must(db.UpdateWorkoutKind(ctx, userID, store.WorkoutKind{ ID: intervalID, Name: "Intervals", Description: "Structured work/rest intervals", Color: "#ef4444", RuleJSON: `{"match":"all","conditions":[{"metric":"lap_interval_pattern","op":"==","value":true}]}`, IsActive: true, })) m := &mock.Client{} - svc := appsync.NewService(m, db, appsync.Config{MinConfidence: 0.6}, nil) + svc := appsync.NewService(m, db, userID, appsync.Config{MinConfidence: 0.6}, nil) today := time.Now() activityIDs := []int64{} @@ -81,7 +84,7 @@ func main() { start := today.AddDate(0, 0, -60+i*10) speed := 1000.0 / (390 - float64(i)*5) // pace improving from 390 -> 370 sec/km hr := 125.0 + float64(i) // pct-of-max stays comfortably under the 0.75 ceiling - id := seedActivity(ctx, db, seedParams{ + id := seedActivity(ctx, db, userID, seedParams{ garminID: 1000 + int64(i), name: "Easy morning run", start: start, distance: 8000, duration: 8000 / (speed) * 1, speedMps: speed, avgHR: hr, aerobicTE: 2.5, anaerobicTE: 0.3, @@ -93,7 +96,7 @@ func main() { for i := 0; i < 3; i++ { start := today.AddDate(0, 0, -45+i*15) speed := 1000.0 / 320.0 // centered in Tempo's [300,340] range - id := seedActivity(ctx, db, seedParams{ + id := seedActivity(ctx, db, userID, seedParams{ garminID: 2000 + int64(i), name: "Tempo run", start: start, distance: 6000, duration: 1800, speedMps: speed, avgHR: 168, aerobicTE: 3.8, anaerobicTE: 1.2, @@ -104,12 +107,12 @@ func main() { // 1 Interval workout, with alternating ACTIVE/REST laps + HR samples // showing drift on the work intervals and recovery on the rest ones. { - id := seedActivity(ctx, db, seedParams{ + id := seedActivity(ctx, db, userID, seedParams{ garminID: 3000, name: "Track intervals", start: today.AddDate(0, 0, -5), distance: 8000, duration: 2400, speedMps: 1000.0 / 240.0, avgHR: 165, aerobicTE: 3.0, anaerobicTE: 3.5, }) - seedIntervalLapsAndSamples(ctx, db, id) + seedIntervalLapsAndSamples(ctx, db, userID, id) activityIDs = append(activityIDs, id) } @@ -118,7 +121,7 @@ func main() { // and Tempo's >=0.70 -- so it should land in the review queue with two // candidates, not a clean single match. { - id := seedActivity(ctx, db, seedParams{ + id := seedActivity(ctx, db, userID, seedParams{ garminID: 4000, name: "Ambiguous run", start: today.AddDate(0, 0, -2), distance: 7000, duration: 2200, speedMps: 1000.0 / 335.0, avgHR: 148, aerobicTE: 3.0, anaerobicTE: 0.8, @@ -145,12 +148,12 @@ type seedParams struct { anaerobicTE float64 } -func seedActivity(ctx context.Context, db *store.DB, p seedParams) int64 { +func seedActivity(ctx context.Context, db *store.DB, userID int64, p seedParams) int64 { speed := p.speedMps hr := p.avgHR aerobic := p.aerobicTE anaerobic := p.anaerobicTE - id, err := db.UpsertActivity(ctx, store.Activity{ + id, err := db.UpsertActivity(ctx, userID, store.Activity{ GarminActivityID: p.garminID, StartTimeUTC: p.start.Format("2006-01-02 15:04:05"), DurationSeconds: p.duration, @@ -171,7 +174,7 @@ func seedActivity(ctx context.Context, db *store.DB, p seedParams) int64 { // seedIntervalLapsAndSamples gives one activity 6 alternating ACTIVE/REST // laps plus per-second HR samples: rising HR within each ACTIVE lap (drift) // and falling HR within each REST lap (recovery). -func seedIntervalLapsAndSamples(ctx context.Context, db *store.DB, activityID int64) { +func seedIntervalLapsAndSamples(ctx context.Context, db *store.DB, userID, activityID int64) { var laps []store.Lap var samples []store.Sample elapsed := 0.0 @@ -225,8 +228,8 @@ func seedIntervalLapsAndSamples(ctx context.Context, db *store.DB, activityID in elapsed += lapDuration } - must(db.ReplaceActivitySamples(ctx, activityID, samples)) - must(db.ReplaceLaps(ctx, activityID, laps)) + must(db.ReplaceActivitySamples(ctx, userID, activityID, samples)) + must(db.ReplaceLaps(ctx, userID, activityID, laps)) } func must(err error) { @@ -235,14 +238,14 @@ func must(err error) { } } -func mustFindKindID(ctx context.Context, db *store.DB, name string) int64 { - kinds, err := db.ListWorkoutKinds(ctx, false) +func mustFindKindID(ctx context.Context, db *store.DB, userID int64, name string) int64 { + kinds, err := db.ListWorkoutKinds(ctx, userID, false) must(err) for _, k := range kinds { if k.Name == name { return k.ID } } - log.Fatalf("seedsample: no workout kind named %q found (did migration 0004 run?)", name) + log.Fatalf("seedsample: no workout kind named %q found for the seeded user (did ProvisionUser seed the taxonomy?)", name) return 0 }