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

@@ -30,9 +30,9 @@ function ImportPageContent() {
const router = useRouter()
const utils = trpc.useUtils()
const searchParams = useSearchParams()
const stageIdParam = searchParams.get('stage')
const roundIdParam = searchParams.get('stage')
const [selectedStageId, setSelectedStageId] = useState<string>(stageIdParam || '')
const [selectedRoundId, setSelectedRoundId] = useState<string>(roundIdParam || '')
// Fetch active programs with stages
const { data: programs, isLoading: loadingPrograms } = trpc.program.list.useQuery({
@@ -49,7 +49,7 @@ function ImportPageContent() {
}))
) || []
const selectedStage = stages.find((s: { id: string }) => s.id === selectedStageId)
const selectedRound = stages.find((s: { id: string }) => s.id === selectedRoundId)
if (loadingPrograms) {
return <ImportPageSkeleton />
@@ -75,7 +75,7 @@ function ImportPageContent() {
</div>
{/* Stage selection */}
{!selectedStageId && (
{!selectedRoundId && (
<Card>
<CardHeader>
<CardTitle>Select Stage</CardTitle>
@@ -87,17 +87,17 @@ function ImportPageContent() {
{stages.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-center">
<AlertCircle className="h-12 w-12 text-muted-foreground/50" />
<p className="mt-2 font-medium">No Active Stages</p>
<p className="mt-2 font-medium">No Active Rounds</p>
<p className="text-sm text-muted-foreground">
Create a stage first before importing projects
Create a competition with rounds before importing projects
</p>
<Button asChild className="mt-4">
<Link href="/admin/rounds/new-pipeline">Create Pipeline</Link>
<Link href="/admin/competitions">View Competitions</Link>
</Button>
</div>
) : (
<>
<Select value={selectedStageId} onValueChange={setSelectedStageId}>
<Select value={selectedRoundId} onValueChange={setSelectedRoundId}>
<SelectTrigger>
<SelectValue placeholder="Select a stage" />
</SelectTrigger>
@@ -117,11 +117,11 @@ function ImportPageContent() {
<Button
onClick={() => {
if (selectedStageId) {
router.push(`/admin/projects/import?stage=${selectedStageId}`)
if (selectedRoundId) {
router.push(`/admin/projects/import?stage=${selectedRoundId}`)
}
}}
disabled={!selectedStageId}
disabled={!selectedRoundId}
>
Continue
</Button>
@@ -132,14 +132,14 @@ function ImportPageContent() {
)}
{/* Import form */}
{selectedStageId && selectedStage && (
{selectedRoundId && selectedRound && (
<div className="space-y-4">
<div className="flex items-center gap-4">
<FileSpreadsheet className="h-8 w-8 text-muted-foreground" />
<div>
<p className="font-medium">Importing into: {selectedStage.name}</p>
<p className="font-medium">Importing into: {selectedRound.name}</p>
<p className="text-sm text-muted-foreground">
{selectedStage.programName}
{selectedRound.programName}
</p>
</div>
<Button
@@ -147,7 +147,7 @@ function ImportPageContent() {
size="sm"
className="ml-auto"
onClick={() => {
setSelectedStageId('')
setSelectedRoundId('')
router.push('/admin/projects/import')
}}
>
@@ -172,8 +172,8 @@ function ImportPageContent() {
</TabsList>
<TabsContent value="csv" className="mt-4">
<CSVImportForm
programId={selectedStage.programId}
stageName={selectedStage.name}
programId={selectedRound.programId}
stageName={selectedRound.name}
onSuccess={() => {
utils.project.list.invalidate()
utils.program.get.invalidate()
@@ -182,8 +182,8 @@ function ImportPageContent() {
</TabsContent>
<TabsContent value="notion" className="mt-4">
<NotionImportForm
programId={selectedStage.programId}
stageName={selectedStage.name}
programId={selectedRound.programId}
stageName={selectedRound.name}
onSuccess={() => {
utils.project.list.invalidate()
utils.program.get.invalidate()
@@ -192,8 +192,8 @@ function ImportPageContent() {
</TabsContent>
<TabsContent value="typeform" className="mt-4">
<TypeformImportForm
programId={selectedStage.programId}
stageName={selectedStage.name}
programId={selectedRound.programId}
stageName={selectedRound.name}
onSuccess={() => {
utils.project.list.invalidate()
utils.program.get.invalidate()