Add end-of-practice summary to the driver's mobile profile
When a driver's personal 5-minute practice timer finishes, a "Pass avslutat" card now appears immediately: how this pass's pace and consistency compared to their recent sessions, any goal or lap-count milestone crossed during the pass with its reward text made prominent, and one suggested next goal (reusing the existing pace recommendation, falling back to the next milestone when there isn't enough data yet). Scoped to the driver's current class, and explicit about insufficient sample sizes instead of showing a misleading number. Adds a per-session consistency calculation to buildPublicTrainingWindowStats server-side; everything else needed (stats, goals, recommendation, milestone) was already sent to the client, so the summary itself is computed there from data already in hand - no raw laps or driver IDs added to what the public endpoint exposes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -165,7 +165,7 @@ test("public profile contains only the selected driver's sanitized aggregates",
|
||||
assert.equal(profile.branding.logoDataUrl, "");
|
||||
assert.equal(profile.stats.totalLaps, 4);
|
||||
assert.equal(profile.stats.bestLapMs, 16000);
|
||||
assert.deepEqual(profile.currentSession, { laps: 2, bestLapMs: 16000, averageLapMs: 16250 });
|
||||
assert.deepEqual(profile.currentSession, { laps: 2, bestLapMs: 16000, averageLapMs: 16250, consistencySpreadPct: 0, consistencySampleReady: false });
|
||||
assert.equal(profile.goals.length, 1);
|
||||
assert.equal(profile.goals[0].title, "Bronze");
|
||||
assert.equal(profile.goals[0].percent, 80);
|
||||
@@ -236,7 +236,30 @@ test("public timer window freezes aggregates after the personal session", () =>
|
||||
const now = Date.now();
|
||||
const profile = buildPublicTrainingProfile(buildState(now), DRIVER_ONE_TOKEN, "", now - 30 * 60000, 20 * 60000);
|
||||
|
||||
assert.deepEqual(profile.currentSession, { laps: 1, bestLapMs: 19000, averageLapMs: 19000 });
|
||||
assert.deepEqual(profile.currentSession, { laps: 1, bestLapMs: 19000, averageLapMs: 19000, consistencySpreadPct: 0, consistencySampleReady: false });
|
||||
});
|
||||
|
||||
test("public timer window reports consistency once it has enough laps, and withholds it otherwise", () => {
|
||||
const now = Date.now();
|
||||
const state = buildState(now);
|
||||
const sessionStart = now - 5 * 60000;
|
||||
state.training.ledger = [
|
||||
{ driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 10000, lapMs: 16000 },
|
||||
{ driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 20000, lapMs: 16100 },
|
||||
{ driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 30000, lapMs: 16200 },
|
||||
{ driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 40000, lapMs: 16300 },
|
||||
{ driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 50000, lapMs: 16400 },
|
||||
];
|
||||
const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, "", sessionStart, 5 * 60000);
|
||||
assert.equal(profile.currentSession.laps, 5);
|
||||
assert.equal(profile.currentSession.consistencySampleReady, true);
|
||||
// mean 16200, p10 (index 0) 16000, p90 (index 4) 16400: (16400-16000)/16200*100
|
||||
assert.ok(Math.abs(profile.currentSession.consistencySpreadPct - ((16400 - 16000) / 16200) * 100) < 0.001);
|
||||
|
||||
const shortProfile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, "", sessionStart + 35000, 5 * 60000);
|
||||
assert.equal(shortProfile.currentSession.laps, 2, "only the last two laps fall inside this shorter window");
|
||||
assert.equal(shortProfile.currentSession.consistencySampleReady, false, "fewer than 5 laps must not report a spread");
|
||||
assert.equal(shortProfile.currentSession.consistencySpreadPct, 0);
|
||||
});
|
||||
|
||||
test("public profile returns null when token or driver is unavailable", () => {
|
||||
|
||||
Reference in New Issue
Block a user