Exclude SEPARATE_POOL award projects from main pool finalization
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m47s

- finalizeResults now queries confirmed SEPARATE_POOL shortlist and
  excludes those projects from the main pool ELIGIBLE set
- Finalize confirmation dialog shows breakdown: main pool vs award-routed
- Finalize toast includes award-routed count
- Audit log records routedToAwards count

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-02-18 10:28:45 +01:00
parent 35f30af7ce
commit fc7a37094b
2 changed files with 26 additions and 5 deletions

View File

@@ -250,6 +250,7 @@ export function FilteringDashboard({ competitionId, roundId }: FilteringDashboar
utils.project.list.invalidate() utils.project.list.invalidate()
toast.success( toast.success(
`Finalized: ${data.passed} passed, ${data.filteredOut} filtered out` + `Finalized: ${data.passed} passed, ${data.filteredOut} filtered out` +
(data.routedToAwards > 0 ? `, ${data.routedToAwards} routed to award tracks` : '') +
(data.advancedToStageName ? `. Next round: ${data.advancedToStageName}` : '') (data.advancedToStageName ? `. Next round: ${data.advancedToStageName}` : '')
) )
if (data.categoryWarnings.length > 0) { if (data.categoryWarnings.length > 0) {
@@ -483,7 +484,12 @@ export function FilteringDashboard({ competitionId, roundId }: FilteringDashboar
This will mark PASSED projects as eligible and FILTERED_OUT projects as rejected. This will mark PASSED projects as eligible and FILTERED_OUT projects as rejected.
{stats && ( {stats && (
<span className="block mt-2 font-medium"> <span className="block mt-2 font-medium">
{stats.passed} will pass, {stats.filteredOut} will be filtered out, {stats.flagged} flagged for review. {stats.passed - (stats.routedToAwards || 0)} will pass to main pool, {stats.filteredOut} will be filtered out{stats.flagged > 0 ? `, ${stats.flagged} flagged for review` : ''}.
{(stats.routedToAwards || 0) > 0 && (
<span className="block text-amber-700">
{stats.routedToAwards} already routed to award tracks (excluded from main pool).
</span>
)}
</span> </span>
)} )}
This action can be reversed but requires manual intervention. This action can be reversed but requires manual intervention.

View File

@@ -1153,6 +1153,19 @@ export const filteringRouter = router({
const passedIds = passedResults.map((r) => r.projectId) const passedIds = passedResults.map((r) => r.projectId)
// Exclude projects confirmed for SEPARATE_POOL award tracks from main pool
const awardRouted = await ctx.prisma.awardEligibility.findMany({
where: {
projectId: { in: passedIds },
shortlisted: true,
confirmedAt: { not: null },
award: { eligibilityMode: 'SEPARATE_POOL' },
},
select: { projectId: true },
})
const awardRoutedIds = new Set(awardRouted.map((a) => a.projectId))
const mainPoolPassedIds = passedIds.filter((id) => !awardRoutedIds.has(id))
const operations: Prisma.PrismaPromise<unknown>[] = [] const operations: Prisma.PrismaPromise<unknown>[] = []
if (filteredOutIds.length > 0) { if (filteredOutIds.length > 0) {
@@ -1164,10 +1177,10 @@ export const filteringRouter = router({
) )
} }
if (passedIds.length > 0) { if (mainPoolPassedIds.length > 0) {
operations.push( operations.push(
ctx.prisma.project.updateMany({ ctx.prisma.project.updateMany({
where: { id: { in: passedIds } }, where: { id: { in: mainPoolPassedIds } },
data: { status: 'ELIGIBLE' }, data: { status: 'ELIGIBLE' },
}) })
) )
@@ -1197,7 +1210,8 @@ export const filteringRouter = router({
entityId: input.roundId, entityId: input.roundId,
detailsJson: { detailsJson: {
action: 'FINALIZE_FILTERING', action: 'FINALIZE_FILTERING',
passed: passedIds.length, passed: mainPoolPassedIds.length,
routedToAwards: awardRoutedIds.size,
filteredOut: filteredOutIds.length, filteredOut: filteredOutIds.length,
demotedToFlagged: demotedIds.length, demotedToFlagged: demotedIds.length,
categoryTargets: input.categoryTargets || null, categoryTargets: input.categoryTargets || null,
@@ -1207,7 +1221,8 @@ export const filteringRouter = router({
}) })
return { return {
passed: passedIds.length, passed: mainPoolPassedIds.length,
routedToAwards: awardRoutedIds.size,
filteredOut: filteredOutIds.length, filteredOut: filteredOutIds.length,
demotedToFlagged: demotedIds.length, demotedToFlagged: demotedIds.length,
categoryCounts, categoryCounts,