fix: ranking dashboard respects threshold advancement mode
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

- Header shows "Score >= X advance" instead of "Top N" in threshold mode
- Cutoff line placed after last project meeting threshold, not at fixed count
- Projects advancing determined by avg score vs threshold, not position

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 13:25:49 +01:00
parent 2df9c54de2
commit 36045bef9d

View File

@@ -362,6 +362,8 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
const config = roundData.configJson as Record<string, unknown> const config = roundData.configJson as Record<string, unknown>
const advConfig = config.advancementConfig as Record<string, unknown> | undefined const advConfig = config.advancementConfig as Record<string, unknown> | undefined
return { return {
advanceMode: (config.advanceMode as string) ?? 'count',
advanceScoreThreshold: (config.advanceScoreThreshold as number) ?? undefined,
startupAdvanceCount: (advConfig?.startupCount ?? config.startupAdvanceCount ?? 0) as number, startupAdvanceCount: (advConfig?.startupCount ?? config.startupAdvanceCount ?? 0) as number,
conceptAdvanceCount: (advConfig?.conceptCount ?? config.conceptAdvanceCount ?? 0) as number, conceptAdvanceCount: (advConfig?.conceptCount ?? config.conceptAdvanceCount ?? 0) as number,
} }
@@ -785,11 +787,15 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
<CardHeader> <CardHeader>
<CardTitle className="text-sm font-semibold uppercase tracking-wide text-muted-foreground"> <CardTitle className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
{categoryLabels[category]} {categoryLabels[category]}
{evalConfig && (category === 'STARTUP' ? evalConfig.startupAdvanceCount : evalConfig.conceptAdvanceCount) > 0 && ( {evalConfig && evalConfig.advanceMode === 'threshold' && evalConfig.advanceScoreThreshold != null ? (
<span className="ml-2 text-xs font-normal normal-case">
(Score &ge; {evalConfig.advanceScoreThreshold} advance)
</span>
) : evalConfig && (category === 'STARTUP' ? evalConfig.startupAdvanceCount : evalConfig.conceptAdvanceCount) > 0 ? (
<span className="ml-2 text-xs font-normal normal-case"> <span className="ml-2 text-xs font-normal normal-case">
(Top {category === 'STARTUP' ? evalConfig.startupAdvanceCount : evalConfig.conceptAdvanceCount} advance) (Top {category === 'STARTUP' ? evalConfig.startupAdvanceCount : evalConfig.conceptAdvanceCount} advance)
</span> </span>
)} ) : null}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -810,11 +816,24 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
<div className="space-y-2"> <div className="space-y-2">
{localOrder[category].map((projectId, index) => { {localOrder[category].map((projectId, index) => {
const advanceCount = category === 'STARTUP' const isThresholdMode = evalConfig?.advanceMode === 'threshold' && evalConfig.advanceScoreThreshold != null
const advanceCount = isThresholdMode ? 0 : (category === 'STARTUP'
? (evalConfig?.startupAdvanceCount ?? 0) ? (evalConfig?.startupAdvanceCount ?? 0)
: (evalConfig?.conceptAdvanceCount ?? 0) : (evalConfig?.conceptAdvanceCount ?? 0))
const isAdvancing = advanceCount > 0 && index < advanceCount // In threshold mode, check if this project's avg score meets the threshold
const isCutoffRow = advanceCount > 0 && index === advanceCount - 1 const entry = rankingMap.get(projectId)
const projectAvg = entry?.avgGlobalScore ?? 0
const threshold = evalConfig?.advanceScoreThreshold ?? 0
const isAdvancing = isThresholdMode
? projectAvg >= threshold
: (advanceCount > 0 && index < advanceCount)
// Show cutoff line: in threshold mode, after last project above threshold
const nextProjectId = localOrder[category][index + 1]
const nextEntry = nextProjectId ? rankingMap.get(nextProjectId) : null
const nextAvg = nextEntry?.avgGlobalScore ?? 0
const isCutoffRow = isThresholdMode
? (projectAvg >= threshold && nextAvg < threshold)
: (advanceCount > 0 && index === advanceCount - 1)
return ( return (
<React.Fragment key={projectId}> <React.Fragment key={projectId}>
@@ -839,7 +858,7 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
<div className="flex items-center gap-2 py-1"> <div className="flex items-center gap-2 py-1">
<div className="flex-1 border-t-2 border-dashed border-emerald-400/60" /> <div className="flex-1 border-t-2 border-dashed border-emerald-400/60" />
<span className="text-xs font-medium text-emerald-600 dark:text-emerald-400 whitespace-nowrap"> <span className="text-xs font-medium text-emerald-600 dark:text-emerald-400 whitespace-nowrap">
Advancement cutoff Top {advanceCount} Advancement cutoff {isThresholdMode ? `Score ≥ ${threshold}` : `Top ${advanceCount}`}
</span> </span>
<div className="flex-1 border-t-2 border-dashed border-emerald-400/60" /> <div className="flex-1 border-t-2 border-dashed border-emerald-400/60" />
</div> </div>