GuardRateLeaderboard / src /lib /data /ranking.ts
Anton Malykhin
fix: stabilize benchmark leaders, model benchmark sorting, and HF bucket reads
644ba85
Raw
History Blame Contribute Delete
4.57 kB
import { average, geometricMean, sizeNumber } from '$lib/helpers/leaderboard';
import type { RankingDataset } from '$lib/types/ranking-data';
import type { BenchmarkScope, GuardModel } from '$lib/types/leaderboard';
import type {
BenchmarkDetail,
LanguageFilter,
LeaderboardRow,
SortDirection,
SortKey
} from '$lib/types/ranking';
function isFiniteNumber(value: number | null): value is number {
return typeof value === 'number' && Number.isFinite(value);
}
export function rankingScopeGroups(scope: BenchmarkScope, dataset: RankingDataset) {
if (scope === 'robust') return dataset.robustGroups;
if (scope === 'en') {
return dataset.allGroups.filter((group) => dataset.groupLanguages[group]?.includes('en'));
}
return [...dataset.allGroups];
}
export function filterRankingGroups(
groups: readonly string[],
selectedGroups: readonly string[],
allGroupsSelected: boolean,
selectedLanguage: LanguageFilter,
groupLanguages: RankingDataset['groupLanguages']
) {
let filtered = [...groups];
if (!allGroupsSelected) {
filtered = filtered.filter((group) => selectedGroups.includes(group));
}
if (selectedLanguage === 'en') {
filtered = filtered.filter((group) => groupLanguages[group]?.includes('en'));
}
if (selectedLanguage === 'ru') {
filtered = filtered.filter((group) => groupLanguages[group]?.includes('ru'));
}
return filtered.length ? filtered : [...groups];
}
export function deriveRankingRow(
model: GuardModel,
groups: readonly string[],
useOverall: boolean,
allGroupCount: number
): LeaderboardRow {
if (useOverall) {
return {
model,
integral: model.integral,
minGroup: model.minGroup,
fpr: model.fpr,
fnr: model.fnr,
f1: model.f1,
p95: model.p95,
size: model.size,
covered: allGroupCount
};
}
const selectedScores = groups.map((group) => model.groups[group]).filter(Boolean);
const scores = selectedScores.map((group) => group.score).filter(isFiniteNumber);
const scopedIntegral = geometricMean(scores);
return {
model,
integral: scopedIntegral ?? model.integral,
minGroup: scores.length ? Math.min(...scores) : model.minGroup,
fpr: average(selectedScores.map((group) => group.fpr)),
fnr: average(selectedScores.map((group) => group.fnr)),
f1: model.f1,
p95: model.p95,
size: model.size,
covered: selectedScores.length
};
}
export function buildRankingRows(
groups: readonly string[],
useOverall: boolean,
sourceModels: readonly GuardModel[],
allGroupCount: number
) {
return sourceModels.map((model) => deriveRankingRow(model, groups, useOverall, allGroupCount));
}
export function rankingSortValue(row: LeaderboardRow, key: SortKey) {
if (key === 'integral') return row.integral;
if (key === 'minGroup') return row.minGroup;
if (key === 'fpr') return row.fpr;
if (key === 'fnr') return row.fnr;
if (key === 'p95') return row.p95;
return sizeNumber(row.size);
}
export function compareRankingRows(
a: LeaderboardRow,
b: LeaderboardRow,
key: SortKey,
direction: SortDirection
) {
const first = rankingSortValue(a, key);
const second = rankingSortValue(b, key);
const firstMissing = !Number.isFinite(first);
const secondMissing = !Number.isFinite(second);
if (firstMissing && secondMissing) return 0;
if (firstMissing) return 1;
if (secondMissing) return -1;
const multiplier = direction === 'asc' ? 1 : -1;
return (Number(first) - Number(second)) * multiplier;
}
export function benchmarkDetails(model: GuardModel, groups: readonly string[]): BenchmarkDetail[] {
return groups
.map((group) => ({
name: group,
score: model.groups[group]?.score ?? null,
minDatasetScore: model.groups[group]?.minDatasetScore ?? null,
fpr: model.groups[group]?.fpr ?? null,
fnr: model.groups[group]?.fnr ?? null,
f1: model.groups[group]?.f1 ?? null
}))
.filter((group) => group.score !== null || group.fpr !== null || group.fnr !== null)
.sort((a, b) => (b.score ?? -1) - (a.score ?? -1));
}
export function rankingRowsToCsv(items: readonly LeaderboardRow[]) {
const header = [
'rank',
'model',
'size',
'integral',
'min_group',
'overall_fpr',
'overall_fnr',
'overall_f1',
'p95_ms',
'run_date'
];
const lines = items.map((row, index) =>
[
index + 1,
row.model.name,
row.size,
row.integral.toFixed(4),
row.minGroup.toFixed(4),
row.fpr?.toFixed(4) ?? '',
row.fnr?.toFixed(4) ?? '',
row.f1.toFixed(4),
row.p95.toFixed(1),
row.model.runDate
]
.map((cell) => `"${String(cell).replaceAll('"', '""')}"`)
.join(',')
);
return [header.join(','), ...lines].join('\n');
}