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:
larssand
2026-09-09 15:05:49 +02:00
parent 3373f8dddd
commit 643902ff15
8 changed files with 242 additions and 25 deletions

View File

@@ -79,13 +79,15 @@ Verified 2026-09-09: a browser test (`tests/browser_decoder_reconnect.js`) drops
## 3. Clearer practice follow-up
Acceptance: a driver receives a short, encouraging summary based on their own class-specific data, with one useful next goal.
Acceptance: a driver receives a short, encouraging summary based on their own class-specific data, with one useful next goal. Implemented 2026-09-09.
- [ ] Add an end-of-practice summary: pace, consistency, progress and achieved goals.
- [ ] Suggest one next training goal using existing calculations.
- [ ] Keep summaries separate by class and explain insufficient sample sizes.
- [ ] Include the summary in the personal mobile profile.
- [ ] Add coach/parent reward follow-up without turning practice into a public ranking.
- [x] Add an end-of-practice summary: pace, consistency, progress and achieved goals. Triggered the moment the personal 5-minute timer on the mobile profile finishes ("Passet är klart") - a "Pass avslutat" card appears immediately alongside the existing finish cue.
- [x] Suggest one next training goal using existing calculations. Reuses the existing pace-based recommendation (`buildTrainingRecommendation`/`getPublicTrainingRecommendation`); falls back to the next lap-count milestone when there isn't yet enough data for a pace recommendation.
- [x] Keep summaries separate by class and explain insufficient sample sizes. Scoped to the driver's currently selected class, same as the rest of the page; explicitly explains when there aren't enough laps (this pass, or overall) to judge pace or consistency, rather than showing a misleading number.
- [x] Include the summary in the personal mobile profile. It only exists there - the admin Training page remains the always-current dashboard it already was.
- [x] Add coach/parent reward follow-up without turning practice into a public ranking. Decided 2026-09-09 - rather than a new notification mechanism, when a goal is reached during the pass its existing reward text is surfaced prominently in the summary ("Du nådde X - be din coach/förälder om: [belöning]"), still private to the driver's own share link, never a cross-driver comparison.
Verified 2026-09-09: unit tests (`tests/public_training_profile.test.js`) cover the new per-session consistency calculation added to `buildPublicTrainingWindowStats` (enough-laps vs. too-few-laps cases, with a hand-verified spread value). A real headless-browser check (`tests/browser_*` scratch pattern, isolated temp SQLite) seeded a driver mid-pass with a goal and a lap-count milestone both crossing exactly during the session, confirmed the summary reports the faster pace, the tight consistency, the goal, its reward text, and the milestone all correctly, confirmed a "Stäng" click dismisses it and the dismissal survives a reload of the same session, and confirmed a later, different session shows its own summary again. Found and fixed a real bug in the process: triggering the summary immediately (rather than waiting for the next background poll) called `renderProfile()` from inside `updateTimerDisplay()`, which itself calls back into `updateTimerDisplay()` - the reentrancy guard was updated *after* that call instead of before, so the stale guard let the finished-phase branch fire again on every reentry, recursing until the browser's call stack overflowed. A second headless check with a near-empty driver history confirmed the "not enough data" messaging renders cleanly instead.
### Guided practice exercises