feat: finalization tab respects ranking overrides, grouped by category
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m2s
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m2s
- processRoundClose now applies reordersJson drag-reorder overrides when building the evaluation pass set (was ignoring admin reorders) - Finalization tab groups proposed outcomes by category (Startup/Concept) with per-group pass/reject/total counts - Added category filter dropdown alongside the existing outcome filter - Removed legacy "Advance Top N" button and dialog from ranking page (replaced by the finalization workflow) - Fix project edit status defaultValue showing empty placeholder Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -111,14 +111,15 @@ export async function processRoundClose(
|
||||
|
||||
let processed = 0
|
||||
|
||||
// Pre-compute pass set for EVALUATION rounds using ranking scores + config
|
||||
// Pre-compute pass set for EVALUATION rounds using ranking scores + config.
|
||||
// Respects admin drag-reorder overrides stored in reordersJson.
|
||||
let evaluationPassSet: Set<string> | null = null
|
||||
if ((round.roundType as RoundType) === 'EVALUATION') {
|
||||
evaluationPassSet = new Set<string>()
|
||||
const snapshot = await prisma.rankingSnapshot.findFirst({
|
||||
where: { roundId },
|
||||
orderBy: { createdAt: 'desc' as const },
|
||||
select: { startupRankingJson: true, conceptRankingJson: true },
|
||||
select: { startupRankingJson: true, conceptRankingJson: true, reordersJson: true },
|
||||
})
|
||||
if (snapshot) {
|
||||
const config = (round.configJson as Record<string, unknown>) ?? {}
|
||||
@@ -131,21 +132,40 @@ export async function processRoundClose(
|
||||
const startupRanked = (snapshot.startupRankingJson ?? []) as RankEntry[]
|
||||
const conceptRanked = (snapshot.conceptRankingJson ?? []) as RankEntry[]
|
||||
|
||||
// Apply admin drag-reorder overrides (reordersJson is append-only, latest per category wins)
|
||||
type ReorderEvent = { category: 'STARTUP' | 'BUSINESS_CONCEPT'; orderedProjectIds: string[] }
|
||||
const reorders = (snapshot.reordersJson as ReorderEvent[] | null) ?? []
|
||||
const latestStartupReorder = [...reorders].reverse().find((r) => r.category === 'STARTUP')
|
||||
const latestConceptReorder = [...reorders].reverse().find((r) => r.category === 'BUSINESS_CONCEPT')
|
||||
|
||||
// Build effective order: if admin reordered, use that; otherwise use computed rank order
|
||||
const effectiveStartup = latestStartupReorder
|
||||
? latestStartupReorder.orderedProjectIds
|
||||
: [...startupRanked].sort((a, b) => a.rank - b.rank).map((r) => r.projectId)
|
||||
const effectiveConcept = latestConceptReorder
|
||||
? latestConceptReorder.orderedProjectIds
|
||||
: [...conceptRanked].sort((a, b) => a.rank - b.rank).map((r) => r.projectId)
|
||||
|
||||
// Build score lookup for threshold mode
|
||||
const scoreMap = new Map<string, number>()
|
||||
for (const r of [...startupRanked, ...conceptRanked]) {
|
||||
if (r.avgGlobalScore != null) scoreMap.set(r.projectId, r.avgGlobalScore)
|
||||
}
|
||||
|
||||
if (advanceMode === 'threshold') {
|
||||
for (const r of [...startupRanked, ...conceptRanked]) {
|
||||
if (r.avgGlobalScore != null && r.avgGlobalScore >= advanceScoreThreshold) {
|
||||
evaluationPassSet.add(r.projectId)
|
||||
for (const id of [...effectiveStartup, ...effectiveConcept]) {
|
||||
const score = scoreMap.get(id)
|
||||
if (score != null && score >= advanceScoreThreshold) {
|
||||
evaluationPassSet.add(id)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 'count' mode — top N per category by rank
|
||||
const sortedStartup = [...startupRanked].sort((a, b) => a.rank - b.rank)
|
||||
const sortedConcept = [...conceptRanked].sort((a, b) => a.rank - b.rank)
|
||||
for (let i = 0; i < Math.min(startupAdvanceCount, sortedStartup.length); i++) {
|
||||
evaluationPassSet.add(sortedStartup[i].projectId)
|
||||
// 'count' mode — top N per category using effective (possibly reordered) order
|
||||
for (let i = 0; i < Math.min(startupAdvanceCount, effectiveStartup.length); i++) {
|
||||
evaluationPassSet.add(effectiveStartup[i])
|
||||
}
|
||||
for (let i = 0; i < Math.min(conceptAdvanceCount, sortedConcept.length); i++) {
|
||||
evaluationPassSet.add(sortedConcept[i].projectId)
|
||||
for (let i = 0; i < Math.min(conceptAdvanceCount, effectiveConcept.length); i++) {
|
||||
evaluationPassSet.add(effectiveConcept[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user