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'); }