Scope driver training-profile stats by track layout too
The user pointed out that track layout changes matter most for Open Practice/training, not races - so the admin Training page and the driver's own mobile profile (server/public_training_profile.js, the actual thing most viewed after practice) needed the same layout scoping already applied to the live "track record" audio cue. Every stat now splits into two groups: lifetime/commitment numbers (total laps, sessions, training days, milestone badges) still read a driver's whole history, since a track rebuild doesn't erase past practice. Pace/record/consistency numbers (personal best, recent-pace trend, consistency spread, the "stable pace" control zone, stint achievements, "best stint") now read only laps tagged with whichever layout is currently active. Each ledger entry is tagged with trackLayoutId at the moment it's synced from a session, so it survives the source session later being deleted. Verified with new unit tests covering both implementations and a real headless-browser run confirming both the admin page and the actual public mobile profile show the current-layout best lap (never a faster but incomparable old-layout one) while keeping the correct full lifetime lap count in both places. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -197,6 +197,34 @@ test("public profile contains only the selected driver's sanitized aggregates",
|
||||
assert.equal(serialized.includes("secret-decoder"), false);
|
||||
});
|
||||
|
||||
test("public profile's personal-best and lifetime totals are scoped correctly across a track layout change", () => {
|
||||
const now = Date.now();
|
||||
const oldLayoutDay = now - 60 * 24 * 60 * 60 * 1000;
|
||||
const state = buildState(now);
|
||||
state.trackLayouts = [
|
||||
{ id: "layout_1", label: "Layout 1", startedAt: oldLayoutDay - 1000 },
|
||||
{ id: "layout_2", label: "Layout 2", startedAt: now - 24 * 60 * 60 * 1000 },
|
||||
];
|
||||
// Old layout: a much faster time from a track that no longer exists.
|
||||
state.training.ledger.push(
|
||||
{ driverId: "driver_1", sessionId: "old_track", timestamp: oldLayoutDay, lapMs: 9000, trackLayoutId: "layout_1" },
|
||||
{ driverId: "driver_1", sessionId: "old_track", timestamp: oldLayoutDay + 10000, lapMs: 9200, trackLayoutId: "layout_1" },
|
||||
);
|
||||
// Every lap already in buildState's fixture predates any layout tracking (no
|
||||
// trackLayoutId at all) - it must not be conflated with "layout_2" either.
|
||||
const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, "");
|
||||
const classOne = profile.classProfiles.find((item) => item.classId === "class_1");
|
||||
assert.equal(classOne.stats.totalLaps, 6, "lifetime total counts the old-layout laps too");
|
||||
assert.equal(classOne.stats.bestLapMs, 0, "no lap is tagged with the currently active layout yet, so there is no current-layout best");
|
||||
|
||||
// Tag one of the existing (previously untagged) laps as run on the current layout.
|
||||
state.training.ledger.find((lap) => lap.lapMs === 16000).trackLayoutId = "layout_2";
|
||||
const updatedProfile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, "");
|
||||
const updatedClassOne = updatedProfile.classProfiles.find((item) => item.classId === "class_1");
|
||||
assert.equal(updatedClassOne.stats.bestLapMs, 16000, "the current-layout lap is now the reported best, not the faster but incomparable old-layout lap");
|
||||
assert.equal(updatedClassOne.stats.totalLaps, 6, "lifetime total is unaffected by which layout is current");
|
||||
});
|
||||
|
||||
test("public profile visibility removes disabled sections from the API payload", () => {
|
||||
const state = buildState();
|
||||
state.training.shares[0].visibility = { progress: false, achievements: false, performance: false, goals: false, history: false };
|
||||
|
||||
Reference in New Issue
Block a user