Competition/Round architecture: full platform rewrite (Phases 1-9)
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s

Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 23:04:15 +01:00
parent 9ab4717f96
commit 6ca39c976b
349 changed files with 69938 additions and 28767 deletions

View File

@@ -64,12 +64,12 @@ import {
import { toast } from 'sonner'
import { formatDate } from '@/lib/utils'
type RecipientType = 'ALL' | 'ROLE' | 'STAGE_JURY' | 'PROGRAM_TEAM' | 'USER'
type RecipientType = 'ALL' | 'ROLE' | 'ROUND_JURY' | 'PROGRAM_TEAM' | 'USER'
const RECIPIENT_TYPE_OPTIONS: { value: RecipientType; label: string }[] = [
{ value: 'ALL', label: 'All Users' },
{ value: 'ROLE', label: 'By Role' },
{ value: 'STAGE_JURY', label: 'Stage Jury' },
{ value: 'ROUND_JURY', label: 'Round Jury' },
{ value: 'PROGRAM_TEAM', label: 'Program Team' },
{ value: 'USER', label: 'Specific User' },
]
@@ -79,7 +79,7 @@ const ROLES = ['JURY_MEMBER', 'MENTOR', 'OBSERVER', 'APPLICANT', 'PROGRAM_ADMIN'
export default function MessagesPage() {
const [recipientType, setRecipientType] = useState<RecipientType>('ALL')
const [selectedRole, setSelectedRole] = useState('')
const [stageId, setStageId] = useState('')
const [roundId, setStageId] = useState('')
const [selectedProgramId, setSelectedProgramId] = useState('')
const [selectedUserId, setSelectedUserId] = useState('')
const [subject, setSubject] = useState('')
@@ -173,10 +173,10 @@ export default function MessagesPage() {
const roleLabel = selectedRole ? selectedRole.replace(/_/g, ' ') : ''
return roleLabel ? `All ${roleLabel}s` : 'By Role (none selected)'
}
case 'STAGE_JURY': {
if (!stageId) return 'Stage Jury (none selected)'
case 'ROUND_JURY': {
if (!roundId) return 'Stage Jury (none selected)'
const stage = rounds?.find(
(r) => r.id === stageId
(r) => r.id === roundId
)
return stage
? `Jury of ${stage.program ? `${stage.program.name} - ` : ''}${stage.name}`
@@ -217,7 +217,7 @@ export default function MessagesPage() {
toast.error('Please select a role')
return
}
if (recipientType === 'STAGE_JURY' && !stageId) {
if (recipientType === 'ROUND_JURY' && !roundId) {
toast.error('Please select a stage')
return
}
@@ -237,7 +237,7 @@ export default function MessagesPage() {
sendMutation.mutate({
recipientType,
recipientFilter: buildRecipientFilter(),
stageId: stageId || undefined,
roundId: roundId || undefined,
subject: subject.trim(),
body: body.trim(),
deliveryChannels,
@@ -332,10 +332,10 @@ export default function MessagesPage() {
</div>
)}
{recipientType === 'STAGE_JURY' && (
{recipientType === 'ROUND_JURY' && (
<div className="space-y-2">
<Label>Select Stage</Label>
<Select value={stageId} onValueChange={setStageId}>
<Select value={roundId} onValueChange={setStageId}>
<SelectTrigger>
<SelectValue placeholder="Choose a stage..." />
</SelectTrigger>