Add persistent driver training history and progression

This commit is contained in:
larssand
2026-08-24 16:06:36 +02:00
parent 79a8cb24b8
commit c2ed00861a
8 changed files with 424 additions and 36 deletions

View File

@@ -399,16 +399,21 @@ Type:
Owns:
- training-state normalization
- valid driver-lap aggregation across `resultsBySession`
- lifetime/session/best-lap/consistency calculations
- durable ledger synchronization from `resultsBySession`
- valid driver-lap aggregation across the training ledger
- lifetime/session/day/best-lap/average/consistency calculations
- daily summaries, controlled-lap streak, and recent personal-best gain
- rule-based practice recommendation selection
- milestone progress calculation
- automatic and custom milestone progress calculation
Rules:
- only passings with a matching `driverId` are included
- 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
- every save replaces ledger rows only for session IDs that still exist in `resultsBySession`
- ledger rows for a deleted session are retained and become historical records
- `sourceKey` deduplicates repeated saves of the same session passing
### `src/training_views.js`
Type:
@@ -417,6 +422,7 @@ Type:
Owns:
- driver selector
- training stat cards and recommendation display
- automatic next-level card and daily practice history
- milestone/reward create, claim, and delete actions
- mobile-responsive training markup
@@ -1435,7 +1441,8 @@ This bucket is the most dynamic part of state and is mutated heavily during live
```js
{
goals: TrainingGoal[]
goals: TrainingGoal[],
ledger: TrainingLap[]
}
```
@@ -1453,20 +1460,40 @@ This bucket is the most dynamic part of state and is mutated heavily during live
}
```
`TrainingLap`:
```js
{
id: string,
sourceKey: string, // sessionId + passing timestamp + competitor + passing index
sessionId: string,
sessionName: string,
eventId: string,
eventName: string,
driverId: string,
timestamp: number,
lapMs: number,
recordedAt: string
}
```
Normalized by:
- `normalizeTrainingState()` wrapper in `src/app.js`
- `normalizeTrainingState()` implementation in `src/training_logic.js`
- `syncTrainingLedger()` wrapper in `src/app.js`
- `syncTrainingLedger()` implementation in `src/training_logic.js`
Persisted through:
- `buildPersistableState()` in `src/app.js`
- `applyPersistedStateHelper()` in `src/runtime_services.js`
- existing full-state `POST /api/state` backend path
Derived statistics are intentionally not stored. They are recalculated from `resultsBySession` so corrections and invalidated laps are reflected immediately.
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.
Known lifecycle constraint:
- deleting historical sessions removes their source passings and therefore lowers calculated lifetime totals
- a future immutable training ledger should be introduced before session archival/deletion is used routinely
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
- invalid laps, anonymous transponders, and laps at or below `500 ms` are excluded
## 15.9 `Settings`