lägg till stegträning-övning + v1.3.0
Andra guidade träningsövningen i Open Practice: konfigurera en stegserie (antal varv + lugnt/kontrollerat/snabbt per steg), förvalt till klassiska pyramiden. Training-sidan visar aktuellt steg, kvarvarande varv och en progress-pill per steg. När sekvensen är klar jämförs de två kontrollerade stegens snitt neutralt (t.ex. "+0.90s"), utan att kalla en försämring för trötthet. syncTrainingLedger sprider nu resten av det normaliserade training-objektet istället för att lista fält manuellt, så samma klass av bugg som drabbade Jämna varv inte kan upprepas för framtida fält. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import assert from "node:assert/strict";
|
||||
import {
|
||||
buildDriverActivePracticeStats,
|
||||
buildEvenLapsProgress,
|
||||
buildStepTrainingProgress,
|
||||
buildDriverTrainingStats,
|
||||
buildDriverTimingChannelStats,
|
||||
buildTrainingAchievements,
|
||||
@@ -363,6 +364,103 @@ test("even laps progress without a config or an active session", () => {
|
||||
assert.equal(progress.lastLapMs, null);
|
||||
});
|
||||
|
||||
test("step training progress advances through steps and has nothing to compare until two controlled steps have laps", () => {
|
||||
const startedAt = 10_000;
|
||||
const state = {
|
||||
activeSessionId: "practice_1",
|
||||
sessions: [{ id: "practice_1", type: "open_practice", status: "running", startedAt }],
|
||||
resultsBySession: {
|
||||
practice_1: {
|
||||
passings: [
|
||||
{ driverId: "driver_1", timestamp: 11_000, lapMs: 25_000, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 37_000, lapMs: 25_500, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 59_000, lapMs: 21_000, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 81_200, lapMs: 21_200, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 99_200, lapMs: 18_000, validLap: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const config = { steps: [
|
||||
{ effort: "calm", lapCount: 2 },
|
||||
{ effort: "controlled", lapCount: 2 },
|
||||
{ effort: "fast", lapCount: 2 },
|
||||
{ effort: "controlled", lapCount: 2 },
|
||||
] };
|
||||
const progress = buildStepTrainingProgress(state, "driver_1", "", config, 120_000);
|
||||
assert.equal(progress.active, true);
|
||||
assert.equal(progress.totalSteps, 4);
|
||||
assert.equal(progress.totalLaps, 8);
|
||||
assert.equal(progress.completedLaps, 5);
|
||||
assert.equal(progress.complete, false);
|
||||
assert.equal(progress.currentStepIndex, 2, "the third step (fast) is current after two calm and two controlled laps");
|
||||
assert.deepEqual(progress.currentStep, { index: 2, effort: "fast", lapCount: 2, lapsCompleted: 1, lapsRemaining: 1 });
|
||||
assert.equal(progress.steps[1].averageLapMs, 21_100, "the first controlled step's pace is averaged");
|
||||
assert.equal(progress.controlledComparison, null, "only one controlled step has laps so far, nothing to compare yet");
|
||||
});
|
||||
|
||||
test("step training progress compares the first and last controlled steps as a neutral pace difference", () => {
|
||||
const startedAt = 10_000;
|
||||
const state = {
|
||||
activeSessionId: "practice_1",
|
||||
sessions: [{ id: "practice_1", type: "open_practice", status: "running", startedAt }],
|
||||
resultsBySession: {
|
||||
practice_1: {
|
||||
passings: [
|
||||
{ driverId: "driver_1", timestamp: 11_000, lapMs: 25_000, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 37_000, lapMs: 25_500, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 59_000, lapMs: 21_000, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 81_200, lapMs: 21_200, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 99_200, lapMs: 18_000, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 117_000, lapMs: 17_800, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 138_800, lapMs: 21_800, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 161_000, lapMs: 22_200, validLap: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const config = { steps: [
|
||||
{ effort: "calm", lapCount: 2 },
|
||||
{ effort: "controlled", lapCount: 2 },
|
||||
{ effort: "fast", lapCount: 2 },
|
||||
{ effort: "controlled", lapCount: 2 },
|
||||
] };
|
||||
const progress = buildStepTrainingProgress(state, "driver_1", "", config, 200_000);
|
||||
assert.equal(progress.complete, true);
|
||||
assert.equal(progress.currentStepIndex, 3);
|
||||
assert.deepEqual(progress.controlledComparison, { firstAvgMs: 21_100, lastAvgMs: 22_000, diffMs: 900 });
|
||||
});
|
||||
|
||||
test("step training progress counts an excluded lap toward the step but not its pace average", () => {
|
||||
const startedAt = 10_000;
|
||||
const state = {
|
||||
activeSessionId: "practice_1",
|
||||
sessions: [{ id: "practice_1", type: "open_practice", status: "running", startedAt }],
|
||||
resultsBySession: {
|
||||
practice_1: {
|
||||
passings: [
|
||||
{ driverId: "driver_1", timestamp: 11_000, lapMs: 21_000, validLap: true },
|
||||
{ driverId: "driver_1", timestamp: 200_000, lapMs: 180_000, validLap: true, validationWarning: "above_max_stint" },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const config = { steps: [{ effort: "controlled", lapCount: 2 }] };
|
||||
const progress = buildStepTrainingProgress(state, "driver_1", "", config, 400_000);
|
||||
assert.equal(progress.steps[0].lapsCompleted, 2, "both laps count toward the step's lap count");
|
||||
assert.equal(progress.steps[0].averageLapMs, 21_000, "the excluded lap is left out of the pace average");
|
||||
assert.equal(progress.complete, true);
|
||||
});
|
||||
|
||||
test("step training progress without a config or an active session", () => {
|
||||
assert.equal(buildStepTrainingProgress({}, "driver_1", "", null), null);
|
||||
assert.equal(buildStepTrainingProgress({}, "driver_1", "", { steps: [] }), null);
|
||||
const progress = buildStepTrainingProgress({ activeSessionId: "x", sessions: [], resultsBySession: {} }, "driver_1", "", { steps: [{ effort: "calm", lapCount: 3 }] });
|
||||
assert.equal(progress.active, false);
|
||||
assert.equal(progress.completedLaps, 0);
|
||||
assert.equal(progress.complete, false);
|
||||
});
|
||||
|
||||
test("training stints split after a break over two minutes or a session change", () => {
|
||||
const laps = [
|
||||
{ sessionId: "practice_1", timestamp: 18_000, lapMs: 18_000 },
|
||||
|
||||
Reference in New Issue
Block a user