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:
larssand
2026-09-10 14:37:32 +02:00
parent fd6efe2c7e
commit a7272d3844
8 changed files with 169 additions and 18 deletions

View File

@@ -177,10 +177,12 @@ Implemented 2026-09-10 (`src/track_layouts_logic.js`, new):
- [x] The "track_record" audio cue (`getTrackRecordMs` in `src/timing_logic.js`) now only compares a live lap against other sessions tagged with the *same* layout. With no layouts configured, every session's `trackLayoutId` is `""`, so the comparison is unscoped exactly as before - the feature is fully inert until a club actually adds a layout.
- [x] The Results Archive shows which layout each session ran on, both in the session list and the detail view's header badge.
Deliberately not touched in this pass - a real remaining gap, not an oversight: the mobile/driver training profile's own cross-session personal-best trend and pace-baseline statistics (`buildDriverTrainingStats` and related functions in `src/training_logic.js`) still compare across a driver's entire lap ledger regardless of layout. Only the live-race "personal_best" cue is already session-scoped by design (it resets every session) and needed no change; only the cross-session "track_record" cue needed - and got - the fix above. Revisit the training-ledger side as a separate, later pass if it turns out to matter in practice.
Verified 2026-09-10: unit tests (`tests/track_layouts_logic.test.js`) cover normalization/sorting, the contiguous "layout active at time X" lookup at its boundaries, and `getTrackRecordMs`'s layout scoping (including that a layout-tagged session is never conflated with an untagged legacy one). A full headless-browser run against an isolated temp SQLite instance added two layouts through the real Settings UI (confirming the default "Layout 1" label, the custom-label case, and that a backdated second layout is rejected with a clear message), started a session and confirmed it was stamped with whichever layout was active at that moment, confirmed the Results Archive shows the correct layout in both the list and the detail header, and confirmed deleting a layout still in use is blocked while deleting an unused one succeeds.
Extended 2026-09-10, per the user pointing out this matters most for Open Practice/training use, not races: the mobile driver training profile's own cross-session personal-best, pace-baseline and consistency statistics (`buildDriverTrainingStats`/`buildTrainingStintAnalysis` in `src/training_logic.js`, and the duplicate server-side implementation in `server/public_training_profile.js` that actually powers the public mobile link) were still comparing across a driver's entire lap ledger regardless of layout. Both now split every stat into two groups: lifetime/commitment numbers (total laps, total sessions, training days, calendar history, milestone badges) still read the full history - a track rebuild doesn't erase past practice - while every pace/record/consistency number (personal best, recent-average trend, consistency spread, the "stable pace" control zone, stint pace/streak achievements, "best stint") now reads only laps tagged with whichever layout is active right now. Each ledger entry is tagged with `trackLayoutId` at the moment it's synced from a session's passings (`syncTrainingLedger`), so it survives the source session later being deleted, the same way `sessionName`/`eventName` already do. Left deliberately unscoped, as lower-risk trend visualizations rather than single "your record is X" claims: the pace-bucket and weekday charts, marker before/after comparisons (narrow time windows, virtually always same-layout in practice), and the split/pit-stop timing-channel stats (track-geometry-independent for pit stops; splits are a smaller, less-used feature left for a future pass if needed).
Verified 2026-09-10: new unit tests in `tests/training_logic.test.js` and `tests/public_training_profile.test.js` cover a driver with laps on two different layouts, confirming lifetime totals count every lap while personal-best/consistency numbers only reflect the current layout, and that `buildTrainingStintAnalysis`'s "best stint" pick never surfaces a faster stint from an old, incomparable layout. A full headless-browser run seeded a driver with a fast old-layout lap and a slower current-layout lap, then confirmed **both** the admin Training page and the real public mobile profile page show the slower current-layout lap as "Best lap" (never the faster but incomparable old one) while still showing the correct full lifetime lap count in both places.
## PDF export character encoding
Fixed 2026-09-09, reported while exporting an old race from the new Results archive - correctly suspected as a general PDF export bug, not specific to that one feature. `buildSimplePdf` in `server.js` (the shared renderer behind every PDF export: race results, start lists, heat sheets, team reports, and the archive) declared its font with no `/Encoding` at all, so viewers fell back to the standard font's built-in encoding, which has no å/ä/ö glyphs - and separately, the content stream was written as UTF-8, so even a correctly-encoded font would have received the wrong bytes (a multi-byte UTF-8 sequence where a single WinAnsi byte was expected), the classic "åäö" mojibake pattern. Fixed by declaring `/Encoding /WinAnsiEncoding` and writing the content stream through a proper WinAnsi (~CP1252) byte mapper instead of UTF-8 - Swedish å/ä/ö/Å/Ä/Ö map directly to their Latin-1 byte values, with the handful of CP1252 "smart punctuation" code points (curly quotes, en/em dash, €, etc.) mapped explicitly; anything else falls back to `?` rather than corrupting the byte stream.