feat(training): base training stats on practice, keep race laps separate

Training stats count Open/Free practice by default; laps from
qualifying, heats and finals are kept and shown under Based on: Races
or All, marked with race, session and series. Races are listed per race
session and never split into stints. Same picker in the mobile profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-10-10 20:08:17 +02:00
parent 9a9379aa83
commit af79b610db
15 changed files with 377 additions and 42 deletions

View File

@@ -87,7 +87,14 @@ function normalizePublicTrainingLayout(value, state) {
return layouts.some((item) => String(item?.id || "") === layout) ? layout : "current";
}
function buildPublicTrainingProfile(state, shareToken, updatedAt, sinceTimestamp = 0, windowMs = 0, evenLapsSince = 0, stepTrainingSince = 0, layout = "current") {
// Mirrors getTrainingLapSource in src/training_logic.js: race laps (qualifying, heats,
// finals, team races) only count when "race" or "all" is chosen; unknown = practice.
const PUBLIC_RACE_SESSION_TYPES = new Set(["qualification", "heat", "final", "team_race"]);
function normalizePublicTrainingSource(value) {
return ["practice", "race", "all"].includes(String(value)) ? String(value) : "practice";
}
function buildPublicTrainingProfile(state, shareToken, updatedAt, sinceTimestamp = 0, windowMs = 0, evenLapsSince = 0, stepTrainingSince = 0, layout = "current", source = "practice") {
const share = findPublicTrainingShare(state, shareToken);
if (!share) return null;
const visibility = normalizePublicTrainingVisibility(share.visibility);
@@ -98,7 +105,13 @@ function buildPublicTrainingProfile(state, shareToken, updatedAt, sinceTimestamp
const driver = driverProfile.drivers[0];
const classes = Array.isArray(state?.classes) ? state.classes : [];
const ledger = Array.isArray(state?.training?.ledger) ? state.training.ledger : [];
const selectedSource = normalizePublicTrainingSource(source);
const sessionTypes = new Map((Array.isArray(state?.sessions) ? state.sessions : []).map((session) => [String(session?.id || ""), String(session?.type || "")]));
const ledger = (Array.isArray(state?.training?.ledger) ? state.training.ledger : []).filter((entry) => {
if (selectedSource === "all") return true;
const isRace = PUBLIC_RACE_SESSION_TYPES.has(String(entry?.sessionType || sessionTypes.get(String(entry?.sessionId || "")) || ""));
return selectedSource === "race" ? isRace : !isRace;
});
const currentLayoutId = getCurrentTrackLayoutId(state);
const selectedLayout = normalizePublicTrainingLayout(layout, state);
const layoutFilterId = selectedLayout === "current" ? currentLayoutId : selectedLayout === "none" ? "" : selectedLayout;
@@ -188,6 +201,7 @@ function buildPublicTrainingProfile(state, shareToken, updatedAt, sinceTimestamp
logoDataUrl: normalizePublicLogoDataUrl(settings.logoDataUrl),
},
classProfiles,
lapSource: selectedSource,
trackLayouts: {
selected: selectedLayout,
currentId: currentLayoutId,