cmd/seedsample: provision a sample user before seeding
Every store call now needs a userID -- seedsample provisions one fixed "seedsample-user" account up front and threads it through the rest of the seeding logic, unchanged in what it actually seeds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user