This commit is contained in:
larssand
2026-08-24 21:28:38 +02:00
parent 355a1ad3b0
commit 3aeb77a042
13 changed files with 642 additions and 8 deletions

View File

@@ -147,6 +147,108 @@ function buildDailySummaries(laps) {
.sort((left, right) => right.latestTimestamp - left.latestTimestamp);
}
export function splitTrainingStints(laps, breakThresholdMs = 2 * 60 * 1000) {
const sorted = (Array.isArray(laps) ? laps : [])
.filter((lap) => Number(lap?.lapMs || 0) > 500 && Number(lap?.timestamp || 0) > 0)
.map((lap) => ({ ...lap, timestamp: Number(lap.timestamp), lapMs: Number(lap.lapMs) }))
.sort((left, right) => left.timestamp - right.timestamp);
const stints = [];
sorted.forEach((lap) => {
const previousStint = stints.at(-1);
const previousLap = previousStint?.laps.at(-1);
const lapStartedAt = Math.max(0, lap.timestamp - lap.lapMs);
const breakMs = previousLap ? Math.max(0, lap.timestamp - previousLap.timestamp) : 0;
const startsNewStint = !previousStint
|| String(previousLap.sessionId || "") !== String(lap.sessionId || "")
|| breakMs > breakThresholdMs;
const normalizedLap = {
...lap,
excludedFromPace: Boolean(previousLap && breakMs > breakThresholdMs && lap.lapMs > breakThresholdMs),
};
if (startsNewStint) {
stints.push({
sessionId: String(lap.sessionId || ""),
startedAt: lapStartedAt,
endedAt: lap.timestamp,
durationMs: lap.lapMs,
laps: [normalizedLap],
});
return;
}
previousStint.laps.push(normalizedLap);
previousStint.endedAt = lap.timestamp;
previousStint.durationMs = Math.max(0, previousStint.endedAt - previousStint.startedAt);
});
return stints;
}
export function getBestConsecutiveTrainingLaps(stints, lapCount = 3) {
const count = Math.max(1, Math.round(Number(lapCount || 1) || 1));
let best = null;
(Array.isArray(stints) ? stints : []).forEach((stint) => {
const paceLaps = stint.laps.filter((lap) => !lap.excludedFromPace);
for (let index = 0; index <= paceLaps.length - count; index += 1) {
const laps = paceLaps.slice(index, index + count);
const totalMs = laps.reduce((sum, lap) => sum + lap.lapMs, 0);
const candidate = {
lapCount: count,
totalMs,
averageLapMs: Math.round(totalMs / count),
startedAt: Math.max(0, laps[0].timestamp - laps[0].lapMs),
endedAt: laps.at(-1).timestamp,
sessionId: String(stint.sessionId || ""),
};
if (!best || candidate.totalMs < best.totalMs) best = candidate;
}
});
return best;
}
export function getBestTrainingDurationResult(stints, targetMs = 5 * 60 * 1000) {
const target = Math.max(1, Math.round(Number(targetMs || 0) || 0));
let best = null;
(Array.isArray(stints) ? stints : []).forEach((stint) => {
const paceLaps = stint.laps.filter((lap) => !lap.excludedFromPace);
for (let startIndex = 0; startIndex < paceLaps.length; startIndex += 1) {
const firstLap = paceLaps[startIndex];
const startedAt = Math.max(0, firstLap.timestamp - firstLap.lapMs);
const cutoffAt = startedAt + target;
const counted = [];
for (let index = startIndex; index < paceLaps.length; index += 1) {
const lap = paceLaps[index];
const lapStartedAt = Math.max(0, lap.timestamp - lap.lapMs);
if (lapStartedAt >= cutoffAt) break;
counted.push(lap);
}
const lastLap = counted.at(-1);
if (!lastLap || lastLap.timestamp < cutoffAt) continue;
const totalMs = Math.max(0, lastLap.timestamp - startedAt);
const lapCount = counted.length;
const candidate = {
targetMs: target,
lapCount,
totalMs,
averageLapMs: lapCount ? Math.round(counted.reduce((sum, lap) => sum + lap.lapMs, 0) / lapCount) : 0,
startedAt,
endedAt: lastLap.timestamp,
sessionId: String(stint.sessionId || ""),
};
if (!best || candidate.lapCount > best.lapCount || (candidate.lapCount === best.lapCount && candidate.totalMs < best.totalMs)) {
best = candidate;
}
}
});
return best;
}
export function buildDriverTrainingStats(state, driverId) {
const laps = getDriverTrainingLaps(state, driverId);
const lapTimes = laps.map((lap) => lap.lapMs);
@@ -173,6 +275,9 @@ export function buildDriverTrainingStats(state, driverId) {
controlledLapStreak += 1;
}
const dailySummaries = buildDailySummaries(laps);
const stints = splitTrainingStints(laps);
const bestThreeConsecutive = getBestConsecutiveTrainingLaps(stints, 3);
const bestFiveMinuteResult = getBestTrainingDurationResult(stints, 5 * 60 * 1000);
return {
totalLaps,
@@ -189,6 +294,43 @@ export function buildDriverTrainingStats(state, driverId) {
recentLapCount: recentLapTimes.length,
lastLapAt: laps.at(-1)?.timestamp || 0,
dailySummaries,
stintCount: stints.length,
bestThreeConsecutive,
bestFiveMinuteResult,
};
}
export function buildDriverActivePracticeStats(state, driverId, nowTs = Date.now()) {
const session = (Array.isArray(state?.sessions) ? state.sessions : [])
.find((item) => String(item?.id || "") === String(state?.activeSessionId || ""));
const practiceTypes = new Set(["open_practice", "free_practice", "practice"]);
if (!session || session.status !== "running" || !practiceTypes.has(String(session.type || "").toLowerCase())) return null;
const result = state?.resultsBySession?.[session.id];
const startedAt = Math.max(0, Number(session.startedAt || 0) || 0);
const laps = (Array.isArray(result?.passings) ? result.passings : [])
.filter((passing) => (
String(passing?.driverId || "") === String(driverId || "")
&& passing?.validLap !== false
&& Number(passing?.lapMs || 0) > 500
&& (!startedAt || Number(passing?.timestamp || 0) >= startedAt)
))
.map((passing) => ({
timestamp: Math.max(0, Number(passing.timestamp || 0) || 0),
lapMs: Math.round(Number(passing.lapMs || 0) || 0),
}))
.sort((left, right) => left.timestamp - right.timestamp);
const lapTimes = laps.map((lap) => lap.lapMs);
return {
sessionId: String(session.id || ""),
sessionName: String(session.name || "").trim(),
startedAt,
elapsedMs: startedAt ? Math.max(0, Number(nowTs || 0) - startedAt) : 0,
laps: lapTimes.length,
bestLapMs: lapTimes.length ? Math.min(...lapTimes) : 0,
averageLapMs: lapTimes.length ? Math.round(lapTimes.reduce((sum, lapMs) => sum + lapMs, 0) / lapTimes.length) : 0,
lastLapMs: lapTimes.at(-1) || 0,
};
}