Add class-specific driver training profiles and metadata
This commit is contained in:
@@ -160,18 +160,19 @@ Runtime environment variables:
|
||||
|
||||
### `server/public_training_profile.js`
|
||||
Role:
|
||||
- pure CommonJS backend helper for tokenized driver profiles
|
||||
- pure CommonJS backend helper for tokenized personal training profiles
|
||||
|
||||
Owns:
|
||||
- enabled share-token lookup
|
||||
- selected-driver lap filtering and aggregate statistics
|
||||
- sanitized branding, class, goal, rank, and recommendation response
|
||||
- profile resolution across several race-specific driver records
|
||||
- strict per-class lap filtering and aggregate statistics
|
||||
- sanitized branding, class-specific goal, rank, and recommendation response
|
||||
- clock-skew-safe personal timer start/window normalization
|
||||
- bounded current-session lap, best-lap, and average calculations
|
||||
|
||||
Security boundary:
|
||||
- returns no driver IDs, transponders, raw passings, decoder settings, or other driver names
|
||||
- disabled, short, unknown, or driverless tokens return no profile
|
||||
- disabled, short, unknown, or profileless tokens return no profile
|
||||
- logo data is restricted to bounded PNG/JPEG/WebP data URLs
|
||||
|
||||
### `scripts/serverctl.js`
|
||||
@@ -406,6 +407,20 @@ Owns:
|
||||
- Drivers
|
||||
- Cars
|
||||
|
||||
### `src/driver_profile_logic.js`
|
||||
Type:
|
||||
- pure personal-profile identity helpers
|
||||
|
||||
Owns:
|
||||
- resolving an explicit `profileId` or a legacy normalized-name profile key
|
||||
- grouping race-specific driver records that belong to one person
|
||||
- listing all driver IDs and class IDs attached to one personal profile
|
||||
|
||||
Imported by:
|
||||
- `src/core_views.js` for profile assignment in the Drivers UI
|
||||
- `src/training_logic.js` for class-scoped lap aggregation
|
||||
- `src/training_views.js` for person and class selectors
|
||||
|
||||
### `src/misc_views.js`
|
||||
Type:
|
||||
- misc page renderers
|
||||
@@ -431,9 +446,9 @@ Type:
|
||||
Owns:
|
||||
- training-state normalization
|
||||
- durable ledger synchronization from `resultsBySession`
|
||||
- valid driver-lap aggregation across the training ledger
|
||||
- valid driver-lap aggregation across the training ledger, scoped by personal profile and class
|
||||
- lifetime/session/day/best-lap/average/consistency calculations
|
||||
- active Open/Free/regular Practice statistics scoped to one driver and the current session start
|
||||
- active Open/Free/regular Practice statistics scoped to one personal profile, one class, and the current session start
|
||||
- stint splitting after passing gaps greater than `120000 ms`
|
||||
- best N consecutive laps and fixed-duration result calculation
|
||||
- five-minute (`300000 ms`) results include the final lap when its start is before the cutoff and expose average pace for the counted laps
|
||||
@@ -443,6 +458,10 @@ Owns:
|
||||
|
||||
Rules:
|
||||
- only passings with a matching `driverId` are included
|
||||
- `profileId` links multiple race-specific driver records to one person without changing decoder or race identity
|
||||
- statistics combine linked driver records only when their `classId` matches the selected class
|
||||
- 2WD and 4WD lap times, stints, best-three, five-minute results, and recommendations are never combined
|
||||
- legacy driver records without `profileId` are grouped by normalized driver name until explicitly linked or separated in the Drivers UI
|
||||
- invalid laps and lap times at or below `500 ms` are excluded
|
||||
- anonymous Open Practice transponders are excluded until registered as drivers
|
||||
- consistency is the P10-to-P90 spread of the latest 30 valid laps divided by their mean
|
||||
@@ -474,8 +493,8 @@ Mutations:
|
||||
|
||||
Route behavior:
|
||||
- `/?view=training` opens the training view directly
|
||||
- `driver=<driverId>` selects a driver and supports mobile bookmarks
|
||||
- an explicitly enabled driver receives `/public-training/<shareToken>` as a separate read-only profile
|
||||
- `driver=<profileId>` selects a personal profile and `class=<classId>` selects its performance class for bookmarks
|
||||
- an explicitly enabled personal profile receives `/public-training/<shareToken>` as a separate read-only profile with a class selector
|
||||
- replacing or disabling the share invalidates its old route immediately after state sync
|
||||
|
||||
### `public-training.html`
|
||||
@@ -494,7 +513,8 @@ Type:
|
||||
|
||||
Owns:
|
||||
- fetching only `/public-training/api/:shareToken`
|
||||
- rendering one driver's sanitized statistics, goals, rewards, and daily history
|
||||
- rendering one person's sanitized statistics, goals, rewards, and daily history with a class selector
|
||||
- switching between class-specific statistics without combining lap performance across classes
|
||||
- local 20-minute practice timer with 10-minute push/control phases
|
||||
- token-scoped start-age and bounded-window polling for aggregate lap count, best lap, and average during the personal timer; using an age avoids mobile/server clock-skew errors and the bounded window freezes results after the timer finishes
|
||||
- Swedish/English copy selected by `lang` query or browser locale
|
||||
@@ -503,7 +523,7 @@ Important isolation:
|
||||
- does not import `app.js`
|
||||
- does not call `/api/state`
|
||||
- does not mutate backend or race state
|
||||
- timer state is stored in the mobile browser under a token-scoped localStorage key
|
||||
- timer state is stored in the mobile browser under a token-and-class-scoped localStorage key
|
||||
- session-window responses contain aggregates only; individual lap rows and timestamps are not exposed
|
||||
|
||||
### `src/public_training.css`
|
||||
@@ -538,6 +558,11 @@ Owns:
|
||||
- `src/training_logic.js`
|
||||
- `src/training_views.js`
|
||||
|
||||
### Training/profile imports
|
||||
- `src/core_views.js` imports `src/driver_profile_logic.js`
|
||||
- `src/training_logic.js` imports `src/driver_profile_logic.js`
|
||||
- `src/training_views.js` imports `src/driver_profile_logic.js`
|
||||
|
||||
### `src/event_workspace_controller.js` imports
|
||||
- `src/event_views.js`
|
||||
|
||||
@@ -1339,7 +1364,10 @@ Produced/normalized by:
|
||||
classId: string,
|
||||
transponder: string,
|
||||
brand: string,
|
||||
speechName: string // optional phonetic form used only by browser speech
|
||||
speechName: string, // optional phonetic form used only by browser speech
|
||||
profileId: string, // shared personal practice-profile identity; race identity remains `id`
|
||||
club: string, // personal profile metadata, not class performance data
|
||||
country: string // personal profile metadata, not class performance data
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1353,6 +1381,14 @@ Used by:
|
||||
- decoder passing resolution
|
||||
- exports/imports
|
||||
|
||||
Profile rules:
|
||||
- `src/driver_profile_logic.js` resolves `profileId`, groups linked driver records, and returns all references belonging to one person
|
||||
- a driver without an explicit `profileId` receives a legacy grouping key based on its normalized name
|
||||
- creating another same-name driver links to that profile by default; the Drivers editor can link to another profile or create a separate one
|
||||
- `driverId`, `classId`, and transponder remain independent per race entry
|
||||
- `club` and `country` are inherited and synchronized across explicitly linked entries by the Drivers UI
|
||||
- practice goals and public share tokens belong to the personal profile, while measured performance belongs to a selected class
|
||||
|
||||
## 15.4 `Car`
|
||||
|
||||
```js
|
||||
@@ -1548,6 +1584,7 @@ This bucket is the most dynamic part of state and is mutated heavily during live
|
||||
{
|
||||
id: string,
|
||||
driverId: string,
|
||||
classId: string, // class whose lap total advances the goal
|
||||
title: string,
|
||||
reward: string,
|
||||
targetLaps: number,
|
||||
@@ -1568,8 +1605,8 @@ This bucket is the most dynamic part of state and is mutated heavily during live
|
||||
```
|
||||
|
||||
Share rules:
|
||||
- no share exists until an admin explicitly enables the selected driver profile
|
||||
- only one normalized share entry is retained per driver
|
||||
- no share exists until an admin explicitly enables the selected personal profile
|
||||
- only one normalized share entry is retained per personal profile
|
||||
- disabled shares fail public route lookup
|
||||
- rotating a share replaces its token; the previous URL no longer resolves after persistence
|
||||
- tokens are generated in the browser with `crypto.getRandomValues()`, never from the shorter UI `uid()` helper
|
||||
@@ -1604,6 +1641,8 @@ Persisted through:
|
||||
|
||||
Derived statistics are intentionally not stored. They are recalculated from `state.training.ledger`. Before persistence, `saveState()` synchronizes every session currently present in `resultsBySession`: existing rows for those session IDs are replaced from current valid passings, so judging corrections are reflected. Session IDs absent from `resultsBySession` are not removed from the ledger, preserving lifetime totals after archival or deletion.
|
||||
|
||||
`TrainingLap.driverId` always records the exact race/decoder driver entry that produced the lap. At read time, `src/driver_profile_logic.js` resolves all driver IDs belonging to the selected personal profile, then `src/training_logic.js` narrows them to the selected `classId`. This permits several transponders in one class while preventing incomparable classes from being mixed.
|
||||
|
||||
Migration behavior:
|
||||
- older state without `training.ledger` normalizes to an empty ledger
|
||||
- the next `saveState()` imports valid registered-driver laps from all current session results
|
||||
@@ -1950,17 +1989,17 @@ Authorization:
|
||||
- missing, disabled, replaced, or unknown tokens return `404` rather than revealing whether a driver exists
|
||||
|
||||
Sanitized response fields:
|
||||
- selected driver display name, class, and brand
|
||||
- numeric club rank without other driver names
|
||||
- aggregate totals, best/average time, consistency, controlled streak, and daily summaries
|
||||
- optional `currentSession` aggregates when a valid start age (or backward-compatible recent `since` timestamp) is supplied
|
||||
- next automatic milestone
|
||||
- recommendation level and target lap
|
||||
- selected driver's goal titles, reward text, progress, and claimed state
|
||||
- selected person's display name plus linked class names and brands
|
||||
- one `classProfiles` entry per linked class; no cross-class performance aggregate is emitted
|
||||
- class-specific numeric club rank without other driver names
|
||||
- class-specific totals, best/average time, consistency, controlled streak, stints, best-three, five-minute result, and daily summaries
|
||||
- optional class-specific `currentSession` aggregates when a valid start age (or backward-compatible recent `since` timestamp) is supplied
|
||||
- class-specific next automatic milestone, recommendation level, target lap, goals, and reward progress
|
||||
- public club branding and safe PNG/JPEG/WebP logo data
|
||||
|
||||
Explicitly excluded:
|
||||
- transponder IDs
|
||||
- club and country profile metadata
|
||||
- driver IDs and names of other drivers
|
||||
- cars, events, sessions, assignments, and raw passings
|
||||
- race settings and decoder/AMMC configuration
|
||||
@@ -2088,16 +2127,16 @@ npm test
|
||||
- invalid, anonymous, and sub-500 ms lap exclusion
|
||||
- replacement of current-session ledger rows after corrections
|
||||
- retention of archived/deleted-session history
|
||||
- per-driver isolation and day/session aggregation
|
||||
- personal-profile isolation, same-class multi-transponder aggregation, and 2WD/4WD separation
|
||||
- recommendation branch selection
|
||||
- custom goal and automatic milestone progress
|
||||
- selected-driver active-practice isolation, current-start filtering, and non-practice rejection
|
||||
- selected-profile/class active-practice isolation, current-start filtering, and non-practice rejection
|
||||
- two-minute stint boundaries, cross-session isolation, and boundary-lap exclusion
|
||||
- best-three consecutive calculation and five-minute final-lap/average-pace behavior
|
||||
|
||||
`tests/public_training_profile.test.js` covers:
|
||||
- enabled/disabled/unknown share-token handling
|
||||
- selected-driver isolation and response sanitization
|
||||
- personal-profile isolation, class-specific statistics, and response sanitization
|
||||
- personal timer aggregate and frozen-window calculations
|
||||
- clock-skew-safe query normalization and bounds
|
||||
|
||||
|
||||
Reference in New Issue
Block a user