Improve training statistics, achievements and stable pace audio
This commit is contained in:
@@ -107,12 +107,12 @@ function buildPublicTrainingProfile(state, shareToken, updatedAt, sinceTimestamp
|
||||
}
|
||||
|
||||
function buildPublicTimingChannelStats(state, classDriverIds) {
|
||||
const splitTimes = [];
|
||||
const splits = [];
|
||||
const pitTimes = [];
|
||||
Object.values(state?.resultsBySession || {}).forEach((result) => {
|
||||
(Array.isArray(result?.splits) ? result.splits : []).forEach((split) => {
|
||||
const splitMs = Math.round(Number(split?.splitMs || 0) || 0);
|
||||
if (classDriverIds.has(String(split?.driverId || "")) && splitMs > 0) splitTimes.push(splitMs);
|
||||
if (classDriverIds.has(String(split?.driverId || "")) && splitMs > 0) splits.push({ splitMs, timestamp: Number(split?.timestamp || 0) });
|
||||
});
|
||||
(Array.isArray(result?.pitStops) ? result.pitStops : []).forEach((stop) => {
|
||||
const pitStopMs = Math.round(Number(stop?.pitStopMs || 0) || 0);
|
||||
@@ -122,10 +122,19 @@ function buildPublicTimingChannelStats(state, classDriverIds) {
|
||||
const average = (values) => values.length
|
||||
? Math.round(values.reduce((sum, value) => sum + value, 0) / values.length)
|
||||
: 0;
|
||||
splits.sort((left, right) => left.timestamp - right.timestamp);
|
||||
const splitTimes = splits.map((split) => split.splitMs);
|
||||
let runningBestSplitMs = 0;
|
||||
let latestSplitGainMs = 0;
|
||||
splits.forEach((split) => {
|
||||
if (runningBestSplitMs && split.splitMs < runningBestSplitMs) latestSplitGainMs = runningBestSplitMs - split.splitMs;
|
||||
if (!runningBestSplitMs || split.splitMs < runningBestSplitMs) runningBestSplitMs = split.splitMs;
|
||||
});
|
||||
return {
|
||||
splitCount: splitTimes.length,
|
||||
bestSplitMs: splitTimes.length ? Math.min(...splitTimes) : 0,
|
||||
averageSplitMs: average(splitTimes),
|
||||
latestSplitGainMs,
|
||||
pitStopCount: pitTimes.length,
|
||||
totalPitMs: pitTimes.reduce((sum, value) => sum + value, 0),
|
||||
bestPitStopMs: pitTimes.length ? Math.min(...pitTimes) : 0,
|
||||
@@ -217,12 +226,41 @@ function buildPublicTrainingStats(laps) {
|
||||
.slice(0, 30);
|
||||
const bestLapMs = lapTimes.length ? Math.min(...lapTimes) : 0;
|
||||
const controlTargetMs = bestLapMs ? Math.round(bestLapMs * 1.08) : 0;
|
||||
let controlledLapStreak = 0;
|
||||
for (let index = lapTimes.length - 1; index >= 0; index -= 1) {
|
||||
if (!controlTargetMs || lapTimes[index] > controlTargetMs) break;
|
||||
controlledLapStreak += 1;
|
||||
}
|
||||
const stints = splitPublicTrainingStints(laps);
|
||||
const summarizeStints = (items) => {
|
||||
let controlledLapStreak = 0;
|
||||
let outlierLaps = 0;
|
||||
let strongFinishStints = 0;
|
||||
let cleanFiveMinuteStints = 0;
|
||||
let completedFiveMinuteStints = 0;
|
||||
items.forEach((stint) => {
|
||||
let currentStreak = 0;
|
||||
const values = stint.laps.filter((lap) => !lap.excludedFromPace).map((lap) => lap.lapMs);
|
||||
values.forEach((lapMs) => {
|
||||
currentStreak = controlTargetMs && lapMs <= controlTargetMs ? currentStreak + 1 : 0;
|
||||
controlledLapStreak = Math.max(controlledLapStreak, currentStreak);
|
||||
});
|
||||
const medianMs = percentile(values, 0.5);
|
||||
const stintOutliers = values.filter((lapMs) => medianMs && lapMs > medianMs * 1.15).length;
|
||||
outlierLaps += stintOutliers;
|
||||
const comparisonSize = values.length >= 6 ? Math.max(3, Math.floor(values.length / 3)) : 0;
|
||||
const startPaceMs = comparisonSize ? average(values.slice(0, comparisonSize)) : 0;
|
||||
const endPaceMs = comparisonSize ? average(values.slice(-comparisonSize)) : 0;
|
||||
if (comparisonSize && endPaceMs <= startPaceMs * 1.01) strongFinishStints += 1;
|
||||
const fiveMinuteResult = getPublicBestDurationResult([stint], 5 * 60 * 1000);
|
||||
if (fiveMinuteResult) completedFiveMinuteStints += 1;
|
||||
if (!stintOutliers && fiveMinuteResult) cleanFiveMinuteStints += 1;
|
||||
});
|
||||
return { controlledLapStreak, outlierLaps, strongFinishStints, cleanFiveMinuteStints, completedFiveMinuteStints };
|
||||
};
|
||||
const stintSummary = summarizeStints(stints);
|
||||
const today = new Date();
|
||||
const todayKey = [today.getFullYear(), String(today.getMonth() + 1).padStart(2, "0"), String(today.getDate()).padStart(2, "0")].join("-");
|
||||
const todayStintSummary = summarizeStints(splitPublicTrainingStints(laps.filter((lap) => {
|
||||
const date = new Date(lap.timestamp);
|
||||
const dateKey = [date.getFullYear(), String(date.getMonth() + 1).padStart(2, "0"), String(date.getDate()).padStart(2, "0")].join("-");
|
||||
return !Number.isNaN(date.getTime()) && dateKey === todayKey;
|
||||
})));
|
||||
const bestThreeConsecutive = getPublicBestConsecutiveLaps(stints, 3);
|
||||
const bestFiveMinuteResult = getPublicBestDurationResult(stints, 5 * 60 * 1000);
|
||||
|
||||
@@ -240,7 +278,16 @@ function buildPublicTrainingStats(laps) {
|
||||
previousConsistencySpreadPct,
|
||||
consistencyTrendPct: trendSampleReady ? previousConsistencySpreadPct - consistencySpreadPct : 0,
|
||||
trendSampleReady,
|
||||
controlledLapStreak,
|
||||
controlZoneMinMs: bestLapMs,
|
||||
controlZoneMaxMs: controlTargetMs,
|
||||
controlledLapStreak: stintSummary.controlledLapStreak,
|
||||
outlierLaps: stintSummary.outlierLaps,
|
||||
strongFinishStints: stintSummary.strongFinishStints,
|
||||
cleanFiveMinuteStints: stintSummary.cleanFiveMinuteStints,
|
||||
todayControlledLapStreak: todayStintSummary.controlledLapStreak,
|
||||
todayStrongFinishStints: todayStintSummary.strongFinishStints,
|
||||
todayCleanFiveMinuteStints: todayStintSummary.cleanFiveMinuteStints,
|
||||
todayCompletedFiveMinuteStints: todayStintSummary.completedFiveMinuteStints,
|
||||
dailySummaries,
|
||||
stintCount: stints.length,
|
||||
bestThreeConsecutive,
|
||||
@@ -259,6 +306,8 @@ function buildPublicTrainingAchievements(stats) {
|
||||
["days_3", Number(stats.trainingDays || 0), 3],
|
||||
["days_10", Number(stats.trainingDays || 0), 10],
|
||||
["controlled_5", Number(stats.controlledLapStreak || 0), 5],
|
||||
["strong_finish", Number(stats.strongFinishStints || 0), 1],
|
||||
["clean_five_minute", Number(stats.cleanFiveMinuteStints || 0), 1],
|
||||
["five_minute", stats.bestFiveMinuteResult ? 1 : 0, 1],
|
||||
];
|
||||
return definitions.map(([id, current, target]) => ({
|
||||
|
||||
Reference in New Issue
Block a user