Add class-specific driver training profiles and metadata
This commit is contained in:
@@ -8,55 +8,100 @@ function hasPublicTrainingShare(state, shareToken) {
|
||||
return Boolean(findPublicTrainingShare(state, shareToken));
|
||||
}
|
||||
|
||||
function getPublicDriverProfileId(driver) {
|
||||
const explicitId = String(driver?.profileId || "").trim();
|
||||
if (explicitId) return explicitId;
|
||||
const name = String(driver?.name || "").trim().toLocaleLowerCase().replace(/\s+/g, " ");
|
||||
return name ? `legacy:${name}` : `driver:${String(driver?.id || "unknown")}`;
|
||||
}
|
||||
|
||||
function getPublicDriverProfile(drivers, reference) {
|
||||
const normalizedReference = String(reference || "");
|
||||
const anchor = drivers.find((driver) => (
|
||||
String(driver?.id || "") === normalizedReference
|
||||
|| getPublicDriverProfileId(driver) === normalizedReference
|
||||
));
|
||||
if (!anchor) return null;
|
||||
const profileId = getPublicDriverProfileId(anchor);
|
||||
return {
|
||||
id: profileId,
|
||||
drivers: drivers.filter((driver) => getPublicDriverProfileId(driver) === profileId),
|
||||
};
|
||||
}
|
||||
|
||||
function buildPublicTrainingProfile(state, shareToken, updatedAt, sinceTimestamp = 0, windowMs = 0) {
|
||||
const share = findPublicTrainingShare(state, shareToken);
|
||||
if (!share) return null;
|
||||
const driverId = String(share.driverId || "");
|
||||
const profileReference = String(share.driverId || "");
|
||||
const drivers = Array.isArray(state?.drivers) ? state.drivers : [];
|
||||
const driver = drivers.find((item) => String(item?.id || "") === driverId);
|
||||
if (!driver) return null;
|
||||
const driverProfile = getPublicDriverProfile(drivers, profileReference);
|
||||
if (!driverProfile?.drivers.length) return null;
|
||||
const driver = driverProfile.drivers[0];
|
||||
|
||||
const classes = Array.isArray(state?.classes) ? state.classes : [];
|
||||
const driverClass = classes.find((item) => String(item?.id || "") === String(driver.classId || ""));
|
||||
const ledger = Array.isArray(state?.training?.ledger) ? state.training.ledger : [];
|
||||
const driverLaps = ledger
|
||||
.filter((entry) => String(entry?.driverId || "") === driverId && Number(entry?.lapMs || 0) > 500)
|
||||
.map((entry) => ({
|
||||
timestamp: Math.max(0, Number(entry.timestamp || 0) || 0),
|
||||
lapMs: Math.max(0, Math.round(Number(entry.lapMs || 0) || 0)),
|
||||
sessionId: String(entry.sessionId || ""),
|
||||
}))
|
||||
.sort((left, right) => left.timestamp - right.timestamp);
|
||||
const stats = buildPublicTrainingStats(driverLaps);
|
||||
const goals = (Array.isArray(state?.training?.goals) ? state.training.goals : [])
|
||||
.filter((goal) => String(goal?.driverId || "") === driverId)
|
||||
.map((goal) => buildPublicTrainingGoal(goal, stats.totalLaps));
|
||||
const rankedTotals = getRankedTrainingTotals(ledger, new Set(drivers.map((item) => String(item?.id || "")).filter(Boolean)));
|
||||
const rankIndex = rankedTotals.findIndex(([id]) => id === driverId);
|
||||
const profileReferences = new Set([driverProfile.id, ...driverProfile.drivers.map((item) => String(item.id || ""))]);
|
||||
const profileClassIds = [...new Set(driverProfile.drivers.map((item) => String(item.classId || "")).filter(Boolean))];
|
||||
const classProfiles = profileClassIds.map((classId) => {
|
||||
const classDrivers = driverProfile.drivers.filter((item) => String(item.classId || "") === classId);
|
||||
const classDriverIds = new Set(classDrivers.map((item) => String(item.id || "")));
|
||||
const classLaps = ledger
|
||||
.filter((entry) => classDriverIds.has(String(entry?.driverId || "")) && Number(entry?.lapMs || 0) > 500)
|
||||
.map((entry) => ({
|
||||
timestamp: Math.max(0, Number(entry.timestamp || 0) || 0),
|
||||
lapMs: Math.max(0, Math.round(Number(entry.lapMs || 0) || 0)),
|
||||
sessionId: String(entry.sessionId || ""),
|
||||
}))
|
||||
.sort((left, right) => left.timestamp - right.timestamp);
|
||||
const stats = buildPublicTrainingStats(classLaps);
|
||||
const rankedTotals = getRankedTrainingTotals(ledger, drivers, classId);
|
||||
const rankIndex = rankedTotals.findIndex(([profileId]) => profileId === driverProfile.id);
|
||||
const classGoals = (Array.isArray(state?.training?.goals) ? state.training.goals : [])
|
||||
.filter((goal) => profileReferences.has(String(goal?.driverId || "")) && (!goal?.classId || String(goal.classId) === classId))
|
||||
.map((goal) => buildPublicTrainingGoal(goal, stats.totalLaps));
|
||||
const brands = [...new Set(classDrivers.map((item) => String(item.brand || "").trim()).filter(Boolean))];
|
||||
return {
|
||||
classId,
|
||||
className: String(classes.find((item) => String(item?.id || "") === classId)?.name || "").trim(),
|
||||
brand: brands.join(" / "),
|
||||
clubRank: rankIndex >= 0 ? rankIndex + 1 : 0,
|
||||
rankedDrivers: rankedTotals.length,
|
||||
stats,
|
||||
nextMilestone: getPublicTrainingMilestone(stats.totalLaps),
|
||||
recommendation: getPublicTrainingRecommendation(stats),
|
||||
goals: classGoals,
|
||||
currentSession: sinceTimestamp
|
||||
? buildPublicTrainingWindowStats(classLaps.filter((lap) => lap.timestamp >= sinceTimestamp && (!windowMs || lap.timestamp <= sinceTimestamp + windowMs)))
|
||||
: null,
|
||||
};
|
||||
});
|
||||
const activeClass = classProfiles[0] || {
|
||||
classId: "", className: "", brand: "", clubRank: 0, rankedDrivers: 0,
|
||||
stats: buildPublicTrainingStats([]), nextMilestone: getPublicTrainingMilestone(0),
|
||||
recommendation: getPublicTrainingRecommendation({ totalLaps: 0 }), goals: [], currentSession: null,
|
||||
};
|
||||
const settings = state?.settings && typeof state.settings === "object" ? state.settings : {};
|
||||
const currentSession = sinceTimestamp
|
||||
? buildPublicTrainingWindowStats(driverLaps.filter((lap) => lap.timestamp >= sinceTimestamp && (!windowMs || lap.timestamp <= sinceTimestamp + windowMs)))
|
||||
: null;
|
||||
|
||||
return {
|
||||
updatedAt: String(updatedAt || ""),
|
||||
profile: {
|
||||
name: String(driver.name || "Driver").trim(),
|
||||
brand: String(driver.brand || "").trim(),
|
||||
className: String(driverClass?.name || "").trim(),
|
||||
clubRank: rankIndex >= 0 ? rankIndex + 1 : 0,
|
||||
rankedDrivers: rankedTotals.length,
|
||||
brand: activeClass.brand,
|
||||
className: activeClass.className,
|
||||
clubRank: activeClass.clubRank,
|
||||
rankedDrivers: activeClass.rankedDrivers,
|
||||
},
|
||||
branding: {
|
||||
clubName: String(settings.clubName || "JMK RB RaceController").trim(),
|
||||
clubTagline: String(settings.clubTagline || "RC Timing System").trim(),
|
||||
logoDataUrl: normalizePublicLogoDataUrl(settings.logoDataUrl),
|
||||
},
|
||||
stats,
|
||||
nextMilestone: getPublicTrainingMilestone(stats.totalLaps),
|
||||
recommendation: getPublicTrainingRecommendation(stats),
|
||||
goals,
|
||||
currentSession,
|
||||
classProfiles,
|
||||
stats: activeClass.stats,
|
||||
nextMilestone: activeClass.nextMilestone,
|
||||
recommendation: activeClass.recommendation,
|
||||
goals: activeClass.goals,
|
||||
currentSession: activeClass.currentSession,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -100,12 +145,16 @@ function buildPublicTrainingGoal(goal, currentLaps) {
|
||||
};
|
||||
}
|
||||
|
||||
function getRankedTrainingTotals(ledger, registeredDriverIds) {
|
||||
function getRankedTrainingTotals(ledger, drivers, classId = "") {
|
||||
const profileByDriverId = new Map(drivers
|
||||
.filter((driver) => !classId || String(driver?.classId || "") === String(classId))
|
||||
.map((driver) => [String(driver?.id || ""), getPublicDriverProfileId(driver)]));
|
||||
const totals = new Map();
|
||||
ledger.forEach((entry) => {
|
||||
const driverId = String(entry?.driverId || "");
|
||||
if (!driverId || !registeredDriverIds.has(driverId) || Number(entry?.lapMs || 0) <= 500) return;
|
||||
totals.set(driverId, (totals.get(driverId) || 0) + 1);
|
||||
const profileId = profileByDriverId.get(driverId);
|
||||
if (!profileId || Number(entry?.lapMs || 0) <= 500) return;
|
||||
totals.set(profileId, (totals.get(profileId) || 0) + 1);
|
||||
});
|
||||
return [...totals.entries()].sort((left, right) => right[1] - left[1]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user