add stat
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user