fix: ranking sorted by composite score, deduplicate AI results, single cutoff line
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m0s

- Sort all ranked projects by compositeScore descending so highest-rated
  projects always appear first (instead of relying on AI's inconsistent rank order)
- Deduplicate AI ranking response (AI sometimes returns same project multiple times)
- Deduplicate ranking entries and reorder IDs on dashboard load as defensive measure
- Show advancement cutoff line only once (precompute last advancing index)
- Override badge only shown when admin has actually drag-reordered (not on fresh rankings)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 19:34:31 +01:00
parent 1f4f29c2cc
commit 2bccb52a16
2 changed files with 78 additions and 23 deletions

View File

@@ -510,6 +510,14 @@ export async function executeAIRanking(
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to parse ranking response as JSON' })
}
// Deduplicate AI response — keep only the first occurrence of each project_id
const seenAnonIds = new Set<string>()
aiRanked = aiRanked.filter((entry) => {
if (seenAnonIds.has(entry.project_id)) return false
seenAnonIds.add(entry.project_id)
return true
})
// Build a lookup by anonymousId for project data
const projectByAnonId = new Map(
anonymized.map((a) => [a.project_id, projects.find((p) => p.id === idMap.get(a.project_id))!])
@@ -548,13 +556,17 @@ export async function executeAIRanking(
}))
.sort((a, b) => b.compositeScore - a.compositeScore)
let nextRank = rankedProjects.length + 1
for (const proj of unrankedProjects) {
proj.rank = nextRank++
rankedProjects.push(proj)
}
// Re-normalize ranks to be contiguous (1, 2, 3, …)
// Sort ALL projects by compositeScore descending (deterministic, score-based order).
// The AI's rank is advisory — the computed composite score (which already incorporates
// weighted criteria, z-score normalization, pass rate, and evaluator count) is the
// authoritative sort key so that highest-rated projects always appear first.
rankedProjects.sort((a, b) => b.compositeScore - a.compositeScore)
// Re-number ranks to be contiguous (1, 2, 3, …)
rankedProjects.forEach((p, i) => { p.rank = i + 1 })
return {