feat: round finalization with ranking-based outcomes + award pool notifications
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m0s

- processRoundClose EVALUATION uses ranking scores + advanceMode config
  (threshold vs count) to auto-set proposedOutcome instead of defaulting all to PASSED
- Advancement emails generate invite tokens for passwordless users with
  "Create Your Account" CTA; rejection emails have no link
- Finalization UI shows account stats (invite vs dashboard link counts)
- Fixed getFinalizationSummary ranking query (was using non-existent rankingsJson)
- New award pool notification system: getAwardSelectionNotificationTemplate email,
  notifyEligibleProjects mutation with invite token generation,
  "Notify Pool" button on award detail page with custom message dialog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 19:14:41 +01:00
parent 7735f3ecdf
commit cfee3bc8a9
48 changed files with 5294 additions and 676 deletions

View File

@@ -90,7 +90,7 @@ const updateProjectSchema = z.object({
'SEMIFINALIST',
'FINALIST',
'REJECTED',
]),
]).optional(),
tags: z.array(z.string()),
competitionCategory: z.string().optional(),
oceanIssue: z.string().optional(),
@@ -186,7 +186,7 @@ function EditProjectContent({ projectId }: { projectId: string }) {
title: '',
teamName: '',
description: '',
status: 'SUBMITTED',
status: undefined,
tags: [],
competitionCategory: '',
oceanIssue: '',
@@ -221,7 +221,7 @@ function EditProjectContent({ projectId }: { projectId: string }) {
const tags = form.watch('tags')
const selectedStatus = form.watch('status')
const previousStatus = (project?.status ?? 'SUBMITTED') as UpdateProjectForm['status']
const statusTriggersNotifications = ['SEMIFINALIST', 'FINALIST', 'REJECTED'].includes(selectedStatus)
const statusTriggersNotifications = !!selectedStatus && ['SEMIFINALIST', 'FINALIST', 'REJECTED'].includes(selectedStatus)
const requiresStatusNotificationConfirmation = Boolean(
project && selectedStatus !== previousStatus && statusTriggersNotifications
)
@@ -439,7 +439,7 @@ function EditProjectContent({ projectId }: { projectId: string }) {
>
<FormControl>
<SelectTrigger>
<SelectValue />
<SelectValue placeholder="Select status..." />
</SelectTrigger>
</FormControl>
<SelectContent>

View File

@@ -296,8 +296,8 @@ export default function ProjectsPage() {
const [selectedProgramForTagging, setSelectedProgramForTagging] = useState<string>('')
const [activeTaggingJobId, setActiveTaggingJobId] = useState<string | null>(null)
// Fetch programs and rounds for the AI tagging dialog
const { data: programs } = trpc.program.list.useQuery()
// Fetch programs and rounds for the AI tagging dialog + assign-to-round
const { data: programs } = trpc.program.list.useQuery({ includeStages: true })
// Start tagging job mutation
const startTaggingJob = trpc.tag.startTaggingJob.useMutation({