From b3b3e3832c139436a87509eec4f44ced4873c5c9 Mon Sep 17 00:00:00 2001 From: ruy-bot <278561507+ruy-bot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:02:36 +0000 Subject: [PATCH 1/3] fix: rank leaderboard cards by the same averaged data as the charts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header leaderboard computed its own aggregate — an unweighted mean over every variation x fixture cell with DNF results silently imputed as the slowest successful time — and sorted the default (average) view by wins first. Card order could contradict the times printed on the cards and the averaged chart data. Reuse calculateAverageVariationData (the same calculation behind the charts' average data, which already filters out DNF results) to build the per-fixture values, and sort by average time in every view so rank always matches the displayed value. Co-Authored-By: Claude Fable 5 --- app/src/lib/utils.ts | 99 +++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 60 deletions(-) diff --git a/app/src/lib/utils.ts b/app/src/lib/utils.ts index 56623acc4b..ad019cf5ee 100644 --- a/app/src/lib/utils.ts +++ b/app/src/lib/utils.ts @@ -298,64 +298,51 @@ export const calculateLeaderboard = ( ?.variations.filter((v) => v !== "average") || []; } - // Calculate performance - variationsToUse.forEach((variation) => { - const dataSource = usePerPackageData - ? chartData.perPackageCountChartData.data - : chartData.chartData.data; - const variationData = dataSource[variation]; - if (!variationData) return; + // Calculate performance from the same per-fixture averages the charts + // display — calculateAverageVariationData already excludes DNF results + const dataSource = usePerPackageData + ? chartData.perPackageCountChartData.data + : chartData.chartData.data; - // Filter by enabled fixtures if provided - const filteredVariationData = enabledFixtures - ? variationData.filter((item) => enabledFixtures.has(item.fixture)) - : variationData; - - filteredVariationData.forEach((fixtureResult: FixtureResult) => { - const times: Array<{ pm: PackageManager; time: number }> = []; - const dnfPMs: PackageManager[] = []; - - // First pass: collect successful times and DNFs - (availablePackageManagers as PackageManager[]).forEach((pm) => { - const time = fixtureResult[pm]; - const dnfKey = `${pm}_dnf` as keyof FixtureResult; - if (fixtureResult[dnfKey] === true) { - dnfPMs.push(pm); - return; - } - if (typeof time === "number" && time > 0) { - times.push({ pm, time }); - } - }); + const averagedData = calculateAverageVariationData( + chartData, + usePerPackageData, + { + variationNames: variationsToUse, + dataSource: dataSource as Record, + packageManagers: availablePackageManagers as PackageManager[], + }, + ); - // Skip this fixture entirely if ALL PMs DNF'd - if (times.length === 0) return; + // Filter by enabled fixtures if provided + const filteredData = enabledFixtures + ? averagedData.filter((item) => enabledFixtures.has(item.fixture)) + : averagedData; - // Find the slowest successful time for DNF penalty - const slowestTime = Math.max(...times.map((t) => t.time)); + filteredData.forEach((fixtureResult: FixtureResult) => { + const times: Array<{ pm: PackageManager; time: number }> = []; - // Apply DNF penalty: assign slowest successful time - dnfPMs.forEach((pm) => { - times.push({ pm, time: slowestTime }); - }); + (availablePackageManagers as PackageManager[]).forEach((pm) => { + const time = fixtureResult[pm]; + if (typeof time === "number" && time > 0) { + times.push({ pm, time }); + } + }); - // Accumulate stats for all PMs (successful + penalized DNFs) - times.forEach(({ pm, time }) => { - const stats = packageManagerStats[pm]; - if (stats) { - stats.totalTime += time; - stats.testCount++; - } - }); + if (times.length === 0) return; - // Determine the winner (lowest time, only among successful PMs) - const successfulTimes = times.filter((t) => !dnfPMs.includes(t.pm)); - successfulTimes.sort((a, b) => a.time - b.time); - if (successfulTimes.length > 0) { - const winnerStats = packageManagerStats[successfulTimes[0].pm]; - if (winnerStats) winnerStats.wins++; + times.forEach(({ pm, time }) => { + const stats = packageManagerStats[pm]; + if (stats) { + stats.totalTime += time; + stats.testCount++; } }); + + // Determine the winner (lowest averaged time for this fixture) + times.sort((a, b) => a.time - b.time); + const winnerStats = packageManagerStats[times[0].pm]; + if (winnerStats) winnerStats.wins++; }); // Calculate final rankings @@ -384,19 +371,11 @@ export const calculateLeaderboard = ( }; }); - // Determine if we're showing the average/default leaderboard - const isAverageView = !specificVariation || specificVariation === "average"; - - // Filter out PMs with no data, then sort: - // - Average view: sort by wins first (most wins = #1), then average time as tiebreaker - // - Specific variant views: sort by average time (lower is better), then wins as tiebreaker + // Filter out PMs with no data, then sort by average time (lower is + // better) so card order matches the displayed values, wins as tiebreaker return leaderboard .filter((item) => item.totalTests > 0) .sort((a, b) => { - if (isAverageView) { - if (a.wins !== b.wins) return b.wins - a.wins; - return a.averageTime - b.averageTime; - } if (a.averageTime !== b.averageTime) return a.averageTime - b.averageTime; return b.wins - a.wins; }); From 261ea13c6704888bb5378cc47aa9064690c4afc6 Mon Sep 17 00:00:00 2001 From: ruy-bot <278561507+ruy-bot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:13:55 +0000 Subject: [PATCH 2/3] chore: gitignore .claude local agent config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches the vlt.io/vltpkg convention — CLAUDE.md and .claude/ skills stay local and untracked. Co-Authored-By: Claude Fable 5 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e768a75197..8aa57f60fc 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ charts # ignore latest in app for testing /app/latest/ + +# local agent config (kept untracked, same as vlt.io/vltpkg) +.claude From af7a3862b8502213d7d2118c60ffc4ca79d56972 Mon Sep 17 00:00:00 2001 From: ruy-bot <278561507+ruy-bot[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:10:40 +0000 Subject: [PATCH 3/3] fix: keep original card values, only change ranking to average time Address review: the leaderboard card numbers were correct (DNF runs imputed as the slowest successful time, same as the Performance Over Time chart data) and are reverted to the original calculation. Only the ordering changes: cards are ranked by that average time in every view, with wins as a tiebreaker, instead of by wins. Co-Authored-By: Claude Fable 5 --- app/src/lib/utils.ts | 88 +++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/app/src/lib/utils.ts b/app/src/lib/utils.ts index ad019cf5ee..abaa13b5d9 100644 --- a/app/src/lib/utils.ts +++ b/app/src/lib/utils.ts @@ -298,51 +298,65 @@ export const calculateLeaderboard = ( ?.variations.filter((v) => v !== "average") || []; } - // Calculate performance from the same per-fixture averages the charts - // display — calculateAverageVariationData already excludes DNF results - const dataSource = usePerPackageData - ? chartData.perPackageCountChartData.data - : chartData.chartData.data; + // Calculate performance — DNF runs are imputed as the slowest successful + // time for that fixture, matching the "Performance Over Time" chart data + variationsToUse.forEach((variation) => { + const dataSource = usePerPackageData + ? chartData.perPackageCountChartData.data + : chartData.chartData.data; + const variationData = dataSource[variation]; + if (!variationData) return; - const averagedData = calculateAverageVariationData( - chartData, - usePerPackageData, - { - variationNames: variationsToUse, - dataSource: dataSource as Record, - packageManagers: availablePackageManagers as PackageManager[], - }, - ); + // Filter by enabled fixtures if provided + const filteredVariationData = enabledFixtures + ? variationData.filter((item) => enabledFixtures.has(item.fixture)) + : variationData; + + filteredVariationData.forEach((fixtureResult: FixtureResult) => { + const times: Array<{ pm: PackageManager; time: number }> = []; + const dnfPMs: PackageManager[] = []; + + // First pass: collect successful times and DNFs + (availablePackageManagers as PackageManager[]).forEach((pm) => { + const time = fixtureResult[pm]; + const dnfKey = `${pm}_dnf` as keyof FixtureResult; + if (fixtureResult[dnfKey] === true) { + dnfPMs.push(pm); + return; + } + if (typeof time === "number" && time > 0) { + times.push({ pm, time }); + } + }); - // Filter by enabled fixtures if provided - const filteredData = enabledFixtures - ? averagedData.filter((item) => enabledFixtures.has(item.fixture)) - : averagedData; + // Skip this fixture entirely if ALL PMs DNF'd + if (times.length === 0) return; - filteredData.forEach((fixtureResult: FixtureResult) => { - const times: Array<{ pm: PackageManager; time: number }> = []; + // Find the slowest successful time for DNF penalty + const slowestTime = Math.max(...times.map((t) => t.time)); - (availablePackageManagers as PackageManager[]).forEach((pm) => { - const time = fixtureResult[pm]; - if (typeof time === "number" && time > 0) { - times.push({ pm, time }); - } - }); + // Apply DNF penalty: assign slowest successful time + dnfPMs.forEach((pm) => { + times.push({ pm, time: slowestTime }); + }); - if (times.length === 0) return; + // Accumulate stats for all PMs (successful + penalized DNFs) + times.forEach(({ pm, time }) => { + const stats = packageManagerStats[pm]; + if (stats) { + stats.totalTime += time; + stats.testCount++; + } + }); - times.forEach(({ pm, time }) => { - const stats = packageManagerStats[pm]; - if (stats) { - stats.totalTime += time; - stats.testCount++; + // Determine the winner (lowest time, only among successful PMs) + const successfulTimes = times.filter((t) => !dnfPMs.includes(t.pm)); + successfulTimes.sort((a, b) => a.time - b.time); + if (successfulTimes.length > 0) { + const winnerStats = packageManagerStats[successfulTimes[0].pm]; + if (winnerStats) winnerStats.wins++; } }); - - // Determine the winner (lowest averaged time for this fixture) - times.sort((a, b) => a.time - b.time); - const winnerStats = packageManagerStats[times[0].pm]; - if (winnerStats) winnerStats.wins++; }); // Calculate final rankings