MOPC-App/docs/claude-architecture-redesign/07-round-submission.md

2053 lines
86 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 07: SUBMISSION Round — Multi-Window Document Collection System
**Document Version**: 2.0
**Last Updated**: 2026-02-15
**Status**: Architecture Design (Redesign)
---
## Table of Contents
1. [Purpose & Position in Flow](#1-purpose--position-in-flow)
2. [Multi-Round Submission Architecture](#2-multi-round-submission-architecture)
3. [SubmissionConfig Shape](#3-submissionconfig-shape)
4. [Applicant Experience (Detailed)](#4-applicant-experience-detailed)
5. [Jury Cross-Round Visibility (Detailed)](#5-jury-cross-round-visibility-detailed)
6. [Admin Experience](#6-admin-experience)
7. [API Changes](#7-api-changes)
8. [File Promotion from Mentoring](#8-file-promotion-from-mentoring)
9. [Edge Cases](#9-edge-cases)
10. [Implementation Checklist](#10-implementation-checklist)
---
## 1. Purpose & Position in Flow
### 1.1 Core Problem Statement
**Current System Gap**: The MOPC platform currently only supports **one** submission window tied to the INTAKE round. There is no mechanism to:
1. Request **additional documents** from advancing teams mid-competition
2. **Lock previous submissions** as read-only while collecting new materials
3. Keep **Round 1 documents intact** while teams submit Round 2 materials
4. Control which **jury rounds see which documents**
**Real-World Impact**:
- Semi-finalists can't be asked for updated pitch decks without replacing Round 1 materials
- Finalists can't submit presentation materials separately from initial applications
- If teams update files, original submissions are lost (no audit trail)
- Organizers resort to email-based document collection (breaks platform integrity)
### 1.2 SUBMISSION Round Solution
The **SUBMISSION** round is a NEW round type that:
1. **Opens a second (or subsequent) submission window** for advancing projects
2. **Locks previous windows to read-only** for applicants (admin retains full control)
3. **Collects different file requirements** per competition stage
4. **Controls jury visibility** via RoundSubmissionVisibility records
5. **Maintains complete audit trail** across all submission phases
### 1.3 Position in Competition Flow
```
┌──────────────────────────────────────────────────────────────────┐
│ FULL COMPETITION FLOW │
└──────────────────────────────────────────────────────────────────┘
Round 1: INTAKE
├─ Creates SubmissionWindow 1: "Application Documents"
├─ Projects submit: Pitch Deck, Budget, Team CV
├─ Window State: OPEN (Feb 1 - Mar 1)
└─ All applicants participate (150 projects)
Round 2: FILTERING
├─ Admin/AI screens applications using Window 1 documents
├─ 50 projects advance with status = PASSED
└─ Window 1 remains OPEN (teams can still edit)
Round 3: EVALUATION (Jury 1)
├─ Jury evaluates 50 projects using Window 1 documents
├─ Visibility: Window 1 ONLY
├─ 20 projects advance with status = PASSED
└─ Window 1 remains OPEN
Round 4: SUBMISSION ◄──── THIS IS THE NEW ROUND TYPE
├─ Creates SubmissionWindow 2: "Semi-finalist Materials"
├─ When Round 4 starts:
│ ├─ Window 1 LOCKS automatically (read-only for applicants)
│ ├─ Window 2 OPENS (Apr 1 - Apr 15)
│ ├─ Only 20 PASSED projects from Round 3 can submit
│ └─ Email notifications sent to eligible teams
├─ New requirements:
│ ├─ Updated Pitch Deck (required)
│ ├─ Video Pitch (required, max 100MB)
│ ├─ Financial Projections (required)
│ └─ Team Photos (optional)
└─ Deadline policy: GRACE (48 hours after Apr 15)
Round 5: EVALUATION (Jury 2)
├─ Jury evaluates 20 projects using BOTH Window 1 + Window 2
├─ Visibility: Window 1 AND Window 2
├─ UI shows two tabs: "Round 1 Docs" | "Round 2 Docs"
├─ 10 projects advance with status = PASSED
└─ Window 2 LOCKS when Round 5 starts
Round 6: SUBMISSION (Optional Third Window)
├─ Creates SubmissionWindow 3: "Finalist Presentations"
├─ Only 10 PASSED projects from Round 5 can submit
├─ New requirements:
│ ├─ Final Pitch Deck (required)
│ ├─ 3-Minute Video (required)
│ └─ Impact Report (required)
└─ Window 1 + 2 remain LOCKED, Window 3 OPEN
Round 7: LIVE_FINAL
└─ Top 10 present live using Window 3 materials
```
**Key Characteristics**:
| Feature | Behavior |
|---------|----------|
| **Multiple Windows** | Each SUBMISSION round creates exactly ONE new SubmissionWindow |
| **Sequential Locking** | When Window N opens, Window N-1 locks for applicants |
| **Eligibility Filtering** | Only projects with specific statuses from previous round can submit |
| **Independent Requirements** | Each window has its own file requirements (not shared) |
| **Cumulative Visibility** | Later EVALUATION rounds can see multiple windows simultaneously |
| **Admin Override** | Admins always have full control regardless of lock state |
### 1.4 When to Use SUBMISSION Rounds
| Scenario | Use SUBMISSION? | Explanation |
|----------|----------------|-------------|
| **Single-round competition** (one evaluation) | ❌ No | One INTAKE window is sufficient |
| **Two-stage competition** (semi-final + final) | ✅ Yes | INTAKE → EVALUATION → **SUBMISSION** → EVALUATION |
| **Three-stage competition** (screening + semi + final) | ✅ Yes (2x) | INTAKE → EVAL → **SUBMISSION** → EVAL → **SUBMISSION** → EVAL |
| **Requesting updated docs from same pool** | ❌ Maybe | Consider MENTORING with file promotion instead |
| **Post-award deliverables** (winner docs) | ✅ Yes | Create SUBMISSION round after awards announced |
| **Mid-competition clarifications** | ❌ No | Use comments or mentoring workspace |
### 1.5 Relationship to Other Round Types
**SUBMISSION vs INTAKE**:
- **INTAKE** = First submission window (all applicants, competition start)
- **SUBMISSION** = Subsequent submission windows (filtered subset, mid-competition)
- **Both create SubmissionWindow records** (INTAKE creates Window 1, SUBMISSION creates Window 2+)
**SUBMISSION vs EVALUATION**:
- **SUBMISSION** = Document collection phase (no scoring, just uploads)
- **EVALUATION** = Assessment phase (jury scores projects using submitted docs)
- **EVALUATION rounds declare which windows they can see** via RoundSubmissionVisibility
**SUBMISSION vs MENTORING**:
- **MENTORING** = Collaborative workspace (mentor + team iterate on documents)
- **SUBMISSION** = Official competition documents (locked after deadline)
- **Files can be promoted** from MENTORING → SUBMISSION (see Section 8)
**SUBMISSION vs FILTERING**:
- **FILTERING** = Automated screening using existing documents
- **SUBMISSION** = New document collection opportunity
- **FILTERING typically comes BEFORE** SUBMISSION (screen first, then request more docs)
---
## 2. Multi-Round Submission Architecture
### 2.1 Data Model Overview
```prisma
// NEW: SubmissionWindow model
model SubmissionWindow {
id String @id @default(cuid())
competitionId String
roundId String @unique // Each round creates max ONE window
name String // "Application Docs", "Semi-finalist Materials"
description String? @db.Text
// Timing
openDate DateTime
closeDate DateTime
// Deadline policy
latePolicy LateSubmissionPolicy // HARD | FLAG | GRACE
gracePeriodHours Int? // If latePolicy = GRACE
// Locking
lockDate DateTime? // When window becomes read-only for applicants
isLocked Boolean @default(false)
// Relations
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
fileRequirements SubmissionFileRequirement[]
projectFiles ProjectFile[]
visibleToRounds RoundSubmissionVisibility[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([competitionId, roundId])
@@index([competitionId])
@@index([openDate])
@@index([closeDate])
}
enum LateSubmissionPolicy {
HARD // Reject submissions after closeDate
FLAG // Accept late submissions but mark as late
GRACE // Allow submissions during grace period, then hard reject
}
// NEW: SubmissionFileRequirement model
model SubmissionFileRequirement {
id String @id @default(cuid())
submissionWindowId String
label String // "Pitch Deck", "Budget Spreadsheet"
description String? @db.Text
isRequired Boolean @default(true)
allowedFileTypes String[] // ["pdf", "pptx", "docx"]
maxSizeMB Int @default(10)
displayOrder Int
submissionWindow SubmissionWindow @relation(fields: [submissionWindowId], references: [id], onDelete: Cascade)
projectFiles ProjectFile[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([submissionWindowId])
@@index([displayOrder])
}
// NEW: RoundSubmissionVisibility model
model RoundSubmissionVisibility {
id String @id @default(cuid())
evaluationRoundId String // Which EVALUATION round can see docs
submissionWindowId String // Which SubmissionWindow's docs
displayLabel String // "Round 1 Documents", "Semi-final Submissions"
displayOrder Int
evaluationRound Round @relation("EvaluationRoundVisibility", fields: [evaluationRoundId], references: [id], onDelete: Cascade)
submissionWindow SubmissionWindow @relation(fields: [submissionWindowId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([evaluationRoundId, submissionWindowId])
@@index([evaluationRoundId])
@@index([submissionWindowId])
}
// UPDATED: ProjectFile model (add submissionWindowId)
model ProjectFile {
id String @id @default(cuid())
projectId String
submissionWindowId String // NEW: Links to SubmissionWindow
requirementId String? // NEW: Links to SubmissionFileRequirement
filename String
mimeType String
sizeBytes BigInt
storagePath String
uploadedBy String
uploadedAt DateTime @default(now())
isLate Boolean @default(false)
// Versioning
supersededBy String? // Points to newer version of this file
supersededAt DateTime?
// Promotion from mentoring
promotedFrom String? // Points to MentorFile.id
promotedAt DateTime?
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
submissionWindow SubmissionWindow @relation(fields: [submissionWindowId], references: [id], onDelete: Cascade)
requirement SubmissionFileRequirement? @relation(fields: [requirementId], references: [id], onDelete: SetNull)
uploadedByUser User @relation(fields: [uploadedBy], references: [id])
@@index([projectId])
@@index([submissionWindowId])
@@index([requirementId])
@@index([uploadedBy])
}
// UPDATED: Round model (add submissionWindowId)
model Round {
id String @id @default(cuid())
competitionId String
type RoundType
name String
description String? @db.Text
displayOrder Int
// For SUBMISSION rounds
submissionWindowId String? @unique // Links to SubmissionWindow
configJson Json? @db.JsonB // Stores SubmissionConfig for SUBMISSION rounds
// ... other fields ...
submissionWindow SubmissionWindow?
visibleWindows RoundSubmissionVisibility[] @relation("EvaluationRoundVisibility")
}
```
### 2.2 Window Lifecycle States
Each SubmissionWindow progresses through these states:
```
┌─────────────┐
│ PENDING │ Created but openDate not reached
│ │ - Not visible to applicants
│ │ - Admin can edit requirements freely
│ │ - isLocked = false, lockDate = null
└──────┬──────┘
│ openDate arrives (Round transitions to ACTIVE)
┌─────────────┐
│ OPEN │ Accepting submissions
│ │ - Applicants can upload/replace/delete files
│ │ - Admin can edit requirements (with warnings if teams already submitted)
│ │ - Previous windows may be locked
│ │ - isLocked = false
└──────┬──────┘
│ closeDate arrives
┌─────────────┐
│ CLOSED │ Past deadline (policy-dependent)
│ │ - latePolicy = HARD: No uploads allowed
│ │ - latePolicy = FLAG: Uploads allowed, marked as late
│ │ - latePolicy = GRACE: Uploads allowed until lockDate
│ │ - isLocked = depends on policy
└──────┬──────┘
│ lockDate arrives (if GRACE) OR admin manually locks
┌─────────────┐
│ LOCKED │ Read-only for applicants, full control for admins
│ │ - Applicants: view/download only, no uploads/deletes
│ │ - Admins: can still upload/replace/delete on behalf
│ │ - Juries: view-only (if visibility configured)
│ │ - isLocked = true
└─────────────┘
```
**State Transitions**:
| From | To | Trigger | Side Effects |
|------|----|---------| -------------|
| PENDING | OPEN | `openDate` reached | Lock previous windows (if config), notify eligible teams |
| OPEN | CLOSED | `closeDate` reached | Start grace period (if GRACE policy) |
| CLOSED | LOCKED | `lockDate` reached OR admin action | Final state, no more applicant changes |
### 2.3 Multi-Window Coordination Example
**Scenario**: MOPC 2026 with 3 evaluation rounds
```
Competition: MOPC 2026
├─ Round 1: INTAKE
│ └─ SubmissionWindow 1: "Application Documents"
│ ├─ Open: Jan 1 - Feb 1
│ ├─ Close: Feb 1
│ ├─ Late Policy: GRACE (72 hours → lock Feb 4)
│ ├─ Requirements:
│ │ ├─ Pitch Deck (PDF, required, max 10MB)
│ │ ├─ Budget (XLSX/PDF, required, max 5MB)
│ │ └─ Team CV (PDF, optional, max 5MB)
│ └─ Status: OPEN (150 projects submit)
├─ Round 2: FILTERING
│ └─ Screens 150 applications → 50 advance (status = PASSED)
├─ Round 3: EVALUATION (Jury 1)
│ ├─ Visibility: Window 1 ONLY
│ ├─ Jury sees:
│ │ └─ Tab: "Application Documents" (Pitch, Budget, CV)
│ └─ 20 projects advance (status = PASSED)
├─ Round 4: SUBMISSION ◄──── NEW ROUND TYPE
│ └─ SubmissionWindow 2: "Semi-finalist Materials"
│ ├─ Opens: Mar 1 (Window 1 auto-locks at this moment)
│ ├─ Close: Mar 15
│ ├─ Late Policy: HARD (no late submissions)
│ ├─ Eligible: 20 projects with status=PASSED from Round 3
│ ├─ Requirements:
│ │ ├─ Updated Pitch Deck (PDF, required, max 15MB)
│ │ ├─ Video Pitch (MP4/MOV, required, max 100MB)
│ │ ├─ Financial Projections (XLSX/PDF, required, max 10MB)
│ │ └─ Team Photos (JPG/PNG, optional, max 5MB each)
│ └─ Status: OPEN (20 eligible projects can submit)
├─ Round 5: EVALUATION (Jury 2)
│ ├─ Visibility: Window 1 AND Window 2
│ ├─ Jury sees:
│ │ ├─ Tab 1: "Round 1 Application" (Pitch, Budget, CV from Jan)
│ │ └─ Tab 2: "Semi-final Materials" (Updated Pitch, Video, Financials from Mar)
│ └─ 10 projects advance (status = PASSED)
├─ Round 6: SUBMISSION ◄──── SECOND SUBMISSION ROUND
│ └─ SubmissionWindow 3: "Finalist Presentations"
│ ├─ Opens: Apr 1 (Window 2 auto-locks)
│ ├─ Close: Apr 10
│ ├─ Late Policy: FLAG (late submissions marked)
│ ├─ Eligible: 10 projects with status=PASSED from Round 5
│ ├─ Requirements:
│ │ ├─ Final Pitch Deck (PDF/PPTX, required, max 20MB)
│ │ ├─ 3-Minute Video (MP4, required, max 150MB)
│ │ └─ Impact Report (PDF/DOCX, required, max 10MB)
│ └─ Status: OPEN (10 eligible projects can submit)
└─ Round 7: LIVE_FINAL
├─ Visibility: Window 1, Window 2, AND Window 3
├─ Jury sees:
│ ├─ Tab 1: "Original Application" (Jan docs)
│ ├─ Tab 2: "Semi-final Submission" (Mar docs)
│ └─ Tab 3: "Finalist Presentation" (Apr docs)
└─ 3 winners selected
```
**Key Rules**:
1. **One Window Per Round**: Each SUBMISSION/INTAKE round creates exactly ONE SubmissionWindow
2. **Never Delete Windows**: Windows persist forever (audit trail), only lock/unlock
3. **Applicant Access: One-Way Lock**: Can only go from READ-WRITE → READ-ONLY (never reverses)
4. **Admin Access: Always Full**: Admins can upload/delete in any window regardless of lock state
5. **Jury Visibility: Explicit Declaration**: Must create RoundSubmissionVisibility records
### 2.4 Locking Mechanism Implementation
**When a new SubmissionWindow opens**:
```typescript
async function openSubmissionWindow(windowId: string, ctx: Context) {
const window = await ctx.prisma.submissionWindow.findUnique({
where: { id: windowId },
include: {
round: {
select: {
configJson: true,
competitionId: true
}
}
}
});
if (!window) throw new Error("Window not found");
const config = window.round.configJson as SubmissionConfig;
// Step 1: Set this window's openDate to now
await ctx.prisma.submissionWindow.update({
where: { id: windowId },
data: { openDate: new Date() }
});
// Step 2: Lock previous windows if config says so
if (config.lockPreviousWindows) {
const allWindows = await ctx.prisma.submissionWindow.findMany({
where: {
competitionId: window.competitionId,
openDate: { lt: new Date() } // Opened before now
},
select: { id: true, isLocked: true }
});
for (const prevWindow of allWindows) {
if (!prevWindow.isLocked) {
await ctx.prisma.submissionWindow.update({
where: { id: prevWindow.id },
data: {
isLocked: true,
lockDate: new Date()
}
});
}
}
}
// Step 3: Notify eligible teams
if (config.notifyEligibleTeams) {
await notifyEligibleTeams(windowId, ctx);
}
// Step 4: Audit log
await ctx.prisma.decisionAuditLog.create({
data: {
action: "SUBMISSION_WINDOW_OPENED",
userId: ctx.session.user.id,
entityType: "SubmissionWindow",
entityId: windowId,
metadata: {
windowName: window.name,
openDate: new Date(),
previousWindowsLocked: config.lockPreviousWindows
}
}
});
}
```
**Upload Permission Check**:
```typescript
async function checkUploadPermission(
projectId: string,
windowId: string,
userId: string,
ctx: Context
): Promise<{ allowed: boolean; reason?: string }> {
const user = await ctx.prisma.user.findUnique({
where: { id: userId },
select: { role: true }
});
// Admins bypass all checks
if (user?.role === "SUPER_ADMIN" || user?.role === "PROGRAM_ADMIN") {
return { allowed: true };
}
// Check 1: User is applicant for this project?
const project = await ctx.prisma.project.findFirst({
where: {
id: projectId,
applicantId: userId
}
});
if (!project) {
return { allowed: false, reason: "Unauthorized: You do not own this project" };
}
// Check 2: Fetch window
const window = await ctx.prisma.submissionWindow.findUnique({
where: { id: windowId }
});
if (!window) {
return { allowed: false, reason: "Submission window not found" };
}
// Check 3: Window locked?
if (window.isLocked) {
return { allowed: false, reason: "This submission window is now closed." };
}
const now = new Date();
// Check 4: Window not yet open?
if (now < window.openDate) {
return { allowed: false, reason: "Submission window has not opened yet." };
}
// Check 5: Deadline enforcement
if (now > window.closeDate) {
switch (window.latePolicy) {
case "HARD":
return { allowed: false, reason: "Submission deadline has passed. No late submissions allowed." };
case "FLAG":
// Allow upload, will be marked as late
return { allowed: true };
case "GRACE":
if (window.lockDate && now > window.lockDate) {
return { allowed: false, reason: "Grace period has ended. Submissions are now closed." };
}
// Within grace period
return { allowed: true };
}
}
// All checks passed
return { allowed: true };
}
```
### 2.5 File Organization by Window
**Database Structure**:
```typescript
// Project: proj_abc123
// Competition with 3 submission windows
// Window 1: Application Documents (Jan)
ProjectFile {
id: "file_001"
projectId: "proj_abc123"
submissionWindowId: "window_1"
requirementId: "req_pitch_deck_w1"
filename: "OceanCleanup_Pitch.pdf"
uploadedAt: "2026-01-25T14:30:00Z"
uploadedBy: "applicant_user_id"
isLate: false
}
ProjectFile {
id: "file_002"
projectId: "proj_abc123"
submissionWindowId: "window_1"
requirementId: "req_budget_w1"
filename: "Budget_2026.xlsx"
uploadedAt: "2026-02-01T09:15:00Z"
uploadedBy: "applicant_user_id"
isLate: false
}
// Window 2: Semi-finalist Materials (Mar)
ProjectFile {
id: "file_003"
projectId: "proj_abc123"
submissionWindowId: "window_2"
requirementId: "req_video_w2"
filename: "Pitch_Video.mp4"
uploadedAt: "2026-03-10T16:45:00Z"
uploadedBy: "applicant_user_id"
isLate: false
}
ProjectFile {
id: "file_004"
projectId: "proj_abc123"
submissionWindowId: "window_2"
requirementId: "req_updated_deck_w2"
filename: "OceanCleanup_Pitch_v2.pdf"
uploadedAt: "2026-03-15T23:59:00Z"
uploadedBy: "admin_user_id" // Admin uploaded on behalf
isLate: false
}
// Window 3: Finalist Presentation (Apr)
ProjectFile {
id: "file_005"
projectId: "proj_abc123"
submissionWindowId: "window_3"
requirementId: "req_final_deck_w3"
filename: "OceanCleanup_Final.pdf"
uploadedAt: "2026-04-09T18:20:00Z"
uploadedBy: "applicant_user_id"
isLate: false
}
ProjectFile {
id: "file_006"
projectId: "proj_abc123"
submissionWindowId: "window_3"
requirementId: "req_impact_report_w3"
filename: "Impact_Report.pdf"
uploadedAt: "2026-04-11T10:30:00Z" // After deadline (Apr 10)
uploadedBy: "applicant_user_id"
isLate: true // Marked late (FLAG policy)
}
```
**Query Pattern for Jury View** (EVALUATION Round 2):
```typescript
// Fetch all visible files for a project in an evaluation round
const visibleWindows = await ctx.prisma.roundSubmissionVisibility.findMany({
where: { evaluationRoundId: "eval_round_2" },
include: {
submissionWindow: {
include: {
fileRequirements: {
orderBy: { displayOrder: "asc" }
}
}
}
},
orderBy: { displayOrder: "asc" }
});
// Returns:
// [
// {
// id: "vis_1",
// submissionWindow: {
// id: "window_1",
// name: "Application Documents",
// fileRequirements: [...]
// },
// displayLabel: "Round 1 Application",
// displayOrder: 1
// },
// {
// id: "vis_2",
// submissionWindow: {
// id: "window_2",
// name: "Semi-finalist Materials",
// fileRequirements: [...]
// },
// displayLabel: "Semi-final Submissions",
// displayOrder: 2
// }
// ]
const windowIds = visibleWindows.map(v => v.submissionWindowId);
const files = await ctx.prisma.projectFile.findMany({
where: {
projectId: "proj_abc123",
submissionWindowId: { in: windowIds },
supersededBy: null // Only current versions
},
include: {
requirement: true,
submissionWindow: true,
uploadedByUser: {
select: { name: true, role: true }
}
}
});
// Group by window
const filesByWindow = visibleWindows.map(vis => ({
windowId: vis.submissionWindowId,
windowName: vis.displayLabel,
displayOrder: vis.displayOrder,
files: files.filter(f => f.submissionWindowId === vis.submissionWindowId)
}));
```
---
## 3. SubmissionConfig Shape
### 3.1 Type Definition
```typescript
/**
* Configuration for SUBMISSION round type
* Stored in Round.configJson for rounds with type = "SUBMISSION"
*/
type SubmissionConfig = {
/**
* Which project statuses from the PREVIOUS round are eligible to submit
*
* Common values:
* - ["PASSED"] → Only projects that passed previous evaluation
* - ["PASSED", "CONDITIONAL"] → Passed + conditionally advanced
* - ["WINNER", "FINALIST"] → For post-award deliverables
*
* Default: ["PASSED"]
*/
eligibleStatuses: ProjectRoundStateValue[];
/**
* Send email + in-app notifications when submission window opens
*
* If true:
* - Sends to all eligible project applicants
* - Includes deadline, requirements list, upload link
* - Uses template from notificationTemplate if provided
*
* Default: true
*/
notifyEligibleTeams: boolean;
/**
* Lock all previous submission windows when this round starts
*
* If true:
* - All windows with openDate < current window's openDate
* - Set isLocked = true, lockDate = now
* - Applicants can no longer upload/delete (read-only)
* - Admins retain full control
*
* Default: true
*/
lockPreviousWindows: boolean;
/**
* Optional: Custom email template for notifications
*
* Variables available:
* - {projectName}, {teamName}, {deadline}, {windowName}
* - {requirementsList}, {uploadUrl}
*/
notificationTemplate?: {
subject: string;
bodyHtml: string;
variables: Record<string, string>;
};
/**
* Optional: Submission window configuration
* (Can also be set separately via SubmissionWindow model)
*/
windowConfig?: {
name: string;
description: string;
openDate: string; // ISO 8601
closeDate: string; // ISO 8601
latePolicy: "HARD" | "FLAG" | "GRACE";
gracePeriodHours?: number;
};
/**
* Optional: File requirements
* (Can also be set separately via SubmissionFileRequirement model)
*/
fileRequirements?: Array<{
label: string;
description?: string;
isRequired: boolean;
allowedFileTypes: string[]; // ["pdf", "docx", "mp4"]
maxSizeMB: number;
displayOrder: number;
}>;
};
```
### 3.2 Example Configurations
**Example 1: Simple Semi-Finalist Submission**
```json
{
"eligibleStatuses": ["PASSED"],
"notifyEligibleTeams": true,
"lockPreviousWindows": true,
"windowConfig": {
"name": "Semi-Finalist Documents",
"description": "Congratulations on advancing! Please submit the following materials.",
"openDate": "2026-03-01T00:00:00Z",
"closeDate": "2026-03-15T23:59:59Z",
"latePolicy": "HARD"
},
"fileRequirements": [
{
"label": "3-Minute Video Pitch",
"description": "MP4 format, max 100MB. Introduce your team and demonstrate your solution.",
"isRequired": true,
"allowedFileTypes": ["mp4", "mov"],
"maxSizeMB": 100,
"displayOrder": 1
},
{
"label": "Updated Pitch Deck",
"description": "PDF format. Include any updates since your initial application.",
"isRequired": true,
"allowedFileTypes": ["pdf"],
"maxSizeMB": 10,
"displayOrder": 2
},
{
"label": "Financial Projections",
"description": "Excel or PDF. 3-year revenue and cost forecast with assumptions.",
"isRequired": true,
"allowedFileTypes": ["xlsx", "pdf"],
"maxSizeMB": 5,
"displayOrder": 3
}
]
}
```
**Example 2: Flexible Finalist Submission with Grace Period**
```json
{
"eligibleStatuses": ["PASSED", "CONDITIONAL"],
"notifyEligibleTeams": true,
"lockPreviousWindows": true,
"windowConfig": {
"name": "Finalist Presentation Materials",
"description": "Final round submissions for live presentations.",
"openDate": "2026-04-01T00:00:00Z",
"closeDate": "2026-04-10T23:59:59Z",
"latePolicy": "GRACE",
"gracePeriodHours": 48
},
"fileRequirements": [
{
"label": "Final Pitch Deck",
"description": "PDF or PowerPoint, max 20 slides",
"isRequired": true,
"allowedFileTypes": ["pdf", "pptx"],
"maxSizeMB": 15,
"displayOrder": 1
},
{
"label": "Impact Report",
"description": "Summary of expected environmental impact (PDF or Word)",
"isRequired": true,
"allowedFileTypes": ["pdf", "docx"],
"maxSizeMB": 10,
"displayOrder": 2
},
{
"label": "Team Photo",
"description": "High-resolution team photo for program materials (optional)",
"isRequired": false,
"allowedFileTypes": ["jpg", "png"],
"maxSizeMB": 5,
"displayOrder": 3
}
],
"notificationTemplate": {
"subject": "🎉 You're a Finalist! Submit Your Final Materials",
"bodyHtml": "<p>Dear {teamName},</p><p>Congratulations on reaching the finals!</p><p>Please submit your materials by <strong>{deadline}</strong>.</p>",
"variables": {
"teamName": "project.name",
"deadline": "window.closeDate"
}
}
}
```
**Example 3: Post-Award Deliverables (Don't Lock Previous Windows)**
```json
{
"eligibleStatuses": ["WINNER", "FINALIST"],
"notifyEligibleTeams": true,
"lockPreviousWindows": false,
"windowConfig": {
"name": "Winner Deliverables",
"description": "Required documentation for prize disbursement and program promotion.",
"openDate": "2026-06-01T00:00:00Z",
"closeDate": "2026-06-30T23:59:59Z",
"latePolicy": "FLAG"
},
"fileRequirements": [
{
"label": "W-9 Tax Form",
"description": "Required for prize payment (US teams only)",
"isRequired": true,
"allowedFileTypes": ["pdf"],
"maxSizeMB": 2,
"displayOrder": 1
},
{
"label": "High-Res Logo",
"description": "Vector format (SVG preferred) or high-res PNG (min 2000px width)",
"isRequired": true,
"allowedFileTypes": ["svg", "png", "ai"],
"maxSizeMB": 10,
"displayOrder": 2
},
{
"label": "Success Story",
"description": "500-word story about your project for our website and press releases",
"isRequired": true,
"allowedFileTypes": ["pdf", "docx"],
"maxSizeMB": 2,
"displayOrder": 3
},
{
"label": "Promotional Video",
"description": "Optional: 1-2 minute video for social media",
"isRequired": false,
"allowedFileTypes": ["mp4", "mov"],
"maxSizeMB": 200,
"displayOrder": 4
}
]
}
```
### 3.3 Validation Schema
```typescript
import { z } from "zod";
const submissionConfigSchema = z.object({
eligibleStatuses: z.array(
z.enum([
"DRAFT",
"SUBMITTED",
"UNDER_REVIEW",
"PASSED",
"FAILED",
"CONDITIONAL",
"WITHDRAWN",
"WINNER",
"FINALIST"
])
).min(1, "Must specify at least one eligible status"),
notifyEligibleTeams: z.boolean(),
lockPreviousWindows: z.boolean(),
notificationTemplate: z.object({
subject: z.string().min(1).max(200),
bodyHtml: z.string().min(1),
variables: z.record(z.string())
}).optional(),
windowConfig: z.object({
name: z.string().min(1).max(100),
description: z.string().max(1000).optional(),
openDate: z.string().datetime(),
closeDate: z.string().datetime(),
latePolicy: z.enum(["HARD", "FLAG", "GRACE"]),
gracePeriodHours: z.number().int().min(1).max(168).optional()
}).refine(
(data) => new Date(data.closeDate) > new Date(data.openDate),
{ message: "closeDate must be after openDate" }
).refine(
(data) => data.latePolicy !== "GRACE" || data.gracePeriodHours !== undefined,
{ message: "gracePeriodHours required when latePolicy is GRACE" }
).optional(),
fileRequirements: z.array(
z.object({
label: z.string().min(1).max(100),
description: z.string().max(500).optional(),
isRequired: z.boolean(),
allowedFileTypes: z.array(z.string()).min(1),
maxSizeMB: z.number().int().min(1).max(500),
displayOrder: z.number().int().min(0)
})
).optional()
});
type SubmissionConfig = z.infer<typeof submissionConfigSchema>;
```
---
## 4. Applicant Experience (Detailed)
### 4.1 Dashboard Before SUBMISSION Round Opens
**Applicant sees (after advancing from Round 1)**:
```
┌─────────────────────────────────────────────────────────────┐
│ Monaco Ocean Protection Challenge 2026 │
│ │
│ Project: OceanCleanup AI │
│ Status: 🎉 Semi-Finalist (Advancing to Round 2) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 📄 Round 1: Application Documents (Submitted) │
│ │
│ ✅ Pitch Deck │
│ Filename: OceanCleanup_Pitch.pdf │
│ Uploaded: Jan 25, 2026 at 2:30 PM │
│ [View] [Download] [Replace] ← Can still edit │
│ │
│ ✅ Budget │
│ Filename: Budget_2026.xlsx │
│ Uploaded: Feb 1, 2026 at 9:15 AM │
│ [View] [Download] [Replace] │
│ │
│ ✅ Team CV │
│ Filename: Team_Bios.pdf │
│ Uploaded: Jan 25, 2026 at 2:30 PM │
│ [View] [Download] [Replace] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ⏳ Next Steps │
│ │
│ Congratulations on advancing to the semi-finals! │
│ │
│ Additional submission requirements will be announced soon. │
│ You will receive an email notification when the next │
│ submission window opens. │
└─────────────────────────────────────────────────────────────┘
```
**Notes**:
- All Round 1 documents are **still editable** (window not locked yet)
- No indication of upcoming requirements
- "Semi-Finalist" badge visible in project status
- Replace buttons are active
### 4.2 Dashboard After SUBMISSION Round Opens
**When Window 2 opens (March 1, 2026), applicant sees**:
```
┌─────────────────────────────────────────────────────────────┐
│ Monaco Ocean Protection Challenge 2026 │
│ │
│ Project: OceanCleanup AI │
│ Status: 🎉 Semi-Finalist (Round 2 Submission Open) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 🔒 Round 1: Application Documents (Locked) │
│ │
│ These documents were submitted for Round 1 and are now │
│ locked. You can view and download them, but cannot make │
│ changes. Admins retain the ability to manage these files │
│ on your behalf if needed. │
│ │
│ ✅ Pitch Deck │
│ Filename: OceanCleanup_Pitch.pdf │
│ Uploaded: Jan 25, 2026 at 2:30 PM │
│ [View] [Download] ← No Replace button │
│ │
│ ✅ Budget │
│ Filename: Budget_2026.xlsx │
│ Uploaded: Feb 1, 2026 at 9:15 AM │
│ [View] [Download] │
│ │
│ ✅ Team CV │
│ Filename: Team_Bios.pdf │
│ Uploaded: Jan 25, 2026 at 2:30 PM │
│ [View] [Download] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 📤 Round 2: Semi-Finalist Materials │
│ Deadline: March 15, 2026 at 11:59 PM CET │
│ ⏰ 14 days, 15 hours remaining │
│ │
│ Progress: 0 of 3 required files submitted │
│ [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0% │
│ │
│ Congratulations on advancing! Please submit the following │
│ materials by March 15. │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ 3-Minute Video Pitch (Required) │
│ MP4 format, max 100MB. Introduce your team and │
│ demonstrate your solution. │
│ │
│ Not uploaded │
│ [Choose File] or Drag & Drop Here │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ Updated Pitch Deck (Required) │
│ PDF format. Include any updates since your initial │
│ application. │
│ │
│ Not uploaded │
│ [Choose File] or Drag & Drop Here │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ Financial Projections (Required) │
│ Excel or PDF. 3-year revenue and cost forecast with │
│ assumptions. │
│ │
│ Not uploaded │
│ [Choose File] or Drag & Drop Here │
│ │
└─────────────────────────────────────────────────────────────┘
```
**UI Changes**:
1. Round 1 section shows **🔒 lock icon** in header
2. All **Replace/Delete buttons removed** for Window 1 files
3. **New Round 2 section appears** with upload slots
4. **Countdown timer** shows time remaining
5. **Progress bar** shows completion status
6. Red **❌ indicators** for missing required files
### 4.3 File Upload Flow (Detailed)
**Step 1: User clicks "Choose File" for a requirement**
```
┌─────────────────────────────────────────────────────────────┐
│ Upload: 3-Minute Video Pitch │
├─────────────────────────────────────────────────────────────┤
│ │
│ Requirements: │
│ • Accepted file types: MP4, MOV │
│ • Maximum file size: 100 MB │
│ • This file is REQUIRED │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Drag and drop your file here │ │
│ │ or │ │
│ │ Click to Browse │ │
│ │ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ Description: │
│ Introduce your team and demonstrate your solution. Show │
│ how your technology works and its potential impact. │
│ │
│ [Cancel] [Upload] │
└─────────────────────────────────────────────────────────────┘
```
**Step 2: File selected, validation in progress**
```
┌─────────────────────────────────────────────────────────────┐
│ Upload: 3-Minute Video Pitch │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ File selected: Pitch_Video.mp4 │
│ Size: 85.2 MB (within 100 MB limit) │
│ Type: video/mp4 ✓ │
│ │
│ Validating... │
│ │
│ [████████████████████░░░░] 88% Uploading │
│ │
│ [Cancel Upload] │
└─────────────────────────────────────────────────────────────┘
```
**Step 3: Upload complete**
```
┌─────────────────────────────────────────────────────────────┐
│ 📤 Round 2: Semi-Finalist Materials │
│ Deadline: March 15, 2026 at 11:59 PM CET │
│ ⏰ 14 days, 14 hours remaining │
│ │
│ Progress: 1 of 3 required files submitted │
│ [██████████░░░░░░░░░░░░░░░░░░░░░░░] 33% │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ 3-Minute Video Pitch (Required) │
│ MP4 format, max 100MB │
│ │
│ Filename: Pitch_Video.mp4 │
│ Size: 85.2 MB │
│ Uploaded: Mar 1, 2026 at 10:30 AM │
│ [View] [Download] [Replace] [Delete] │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ Updated Pitch Deck (Required) │
│ PDF format │
│ [Choose File] or Drag & Drop Here │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ Financial Projections (Required) │
│ Excel or PDF │
│ [Choose File] or Drag & Drop Here │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Validation Error Example** (file too large):
```
┌─────────────────────────────────────────────────────────────┐
│ ❌ Upload Failed │
├─────────────────────────────────────────────────────────────┤
│ │
│ File: Pitch_Video_HD.mp4 │
│ Size: 152.8 MB │
│ │
│ Error: File size exceeds the maximum limit of 100 MB. │
│ │
│ Please compress your video or upload a smaller version. │
│ │
│ Tip: You can use free tools like HandBrake to compress │
│ video files while maintaining quality. │
│ │
│ [OK] │
└─────────────────────────────────────────────────────────────┘
```
**Validation Error Example** (wrong file type):
```
┌─────────────────────────────────────────────────────────────┐
│ ❌ Upload Failed │
├─────────────────────────────────────────────────────────────┤
│ │
│ File: Budget_Report.docx │
│ Type: application/vnd.openxmlformats-officedocument... │
│ │
│ Error: File type not accepted for this requirement. │
│ │
│ Accepted types: PDF, Excel (XLSX) │
│ │
│ Please convert your document to PDF or Excel format and │
│ try again. │
│ │
│ [OK] │
└─────────────────────────────────────────────────────────────┘
```
### 4.4 Deadline Countdown UI States
**14 days before deadline (Normal)**:
```
⏰ 14 days, 15 hours remaining
```
**7 days before deadline (Normal)**:
```
⏰ 7 days remaining
```
**3 days before deadline (Warning)**:
```
⚠️ 3 days remaining — Please submit soon!
[Highlighted in yellow]
```
**24 hours before deadline (Urgent)**:
```
🚨 URGENT: Less than 1 day remaining
Deadline: Tomorrow at 11:59 PM
[Highlighted in orange]
```
**6 hours before deadline (Critical)**:
```
🚨 CRITICAL: 6 hours remaining
Deadline: Today at 11:59 PM
[Highlighted in red, pulsing animation]
```
**After deadline (HARD policy)**:
```
❌ Deadline passed: March 15, 2026 at 11:59 PM
Submissions are no longer accepted.
If you believe this is an error, please contact the
program administrator.
[Contact Admin]
```
**After deadline (FLAG policy)**:
```
⚠️ Deadline passed: March 15, 2026 at 11:59 PM
Late submissions will be flagged and may impact your
evaluation.
You can still submit, but your files will be marked as
late and the jury will be notified.
[Upload File] (Late submission)
```
**After deadline, within grace period (GRACE policy)**:
```
⏳ Grace Period Active
Deadline was: March 15, 2026 at 11:59 PM
Grace period ends: March 17, 2026 at 11:59 PM
⏰ 1 day, 23 hours remaining in grace period
You can still submit without penalty during this time.
[Upload File]
```
**After grace period ends (GRACE policy)**:
```
❌ Grace period ended: March 17, 2026 at 11:59 PM
Submissions are now closed.
If you have extenuating circumstances, please contact
the program administrator to request an extension.
[Contact Admin]
```
### 4.5 Completion Status Indicators
**Incomplete (missing required files)**:
```
┌─────────────────────────────────────────────────────────────┐
│ 📤 Round 2: Semi-Finalist Materials │
│ Deadline: March 15, 2026 at 11:59 PM CET │
│ │
│ Status: ⚠️ INCOMPLETE │
│ 1 of 3 required files submitted │
│ │
│ [██████████░░░░░░░░░░░░░░░░░░░░░░░] 33% │
│ │
│ You still need to submit: │
│ • Updated Pitch Deck (required) │
│ • Financial Projections (required) │
│ │
│ ⏰ 12 days remaining │
└─────────────────────────────────────────────────────────────┘
```
**Complete (all required files submitted)**:
```
┌─────────────────────────────────────────────────────────────┐
│ 📤 Round 2: Semi-Finalist Materials │
│ Deadline: March 15, 2026 at 11:59 PM CET │
│ │
│ Status: ✅ COMPLETE │
│ 3 of 3 required files submitted │
│ │
│ [██████████████████████████████████████] 100% │
│ │
│ All required files have been submitted. You can still │
│ update or replace files until the deadline. │
│ │
│ ⏰ 12 days remaining │
└─────────────────────────────────────────────────────────────┘
```
**Complete with late submissions**:
```
┌─────────────────────────────────────────────────────────────┐
│ 📤 Round 2: Semi-Finalist Materials │
│ Deadline: March 15, 2026 at 11:59 PM CET (PASSED) │
│ │
│ Status: ⚠️ COMPLETE (1 file submitted late) │
│ 3 of 3 required files submitted │
│ │
│ [██████████████████████████████████████] 100% │
│ │
│ All files submitted, but the Financial Projections file │
│ was submitted after the deadline. This has been flagged │
│ and the jury will be notified during evaluation. │
│ │
│ Submitted late: │
│ • Financial Projections (submitted Mar 16 at 2:15 PM) │
└─────────────────────────────────────────────────────────────┘
```
**Complete with optional files missing**:
```
┌─────────────────────────────────────────────────────────────┐
│ 📤 Round 2: Semi-Finalist Materials │
│ Deadline: March 15, 2026 at 11:59 PM CET │
│ │
│ Status: ✅ COMPLETE │
│ 3 of 3 required files submitted │
│ 0 of 1 optional files submitted │
│ │
│ All required files submitted. Optional: │
│ ○ Team Photos (optional, not submitted) │
│ │
│ ⏰ 12 days remaining │
└─────────────────────────────────────────────────────────────┘
```
### 4.6 Email Notifications
**Email 1: Window Opening Notification**
```
From: Monaco Ocean Protection Challenge <noreply@monaco-opc.com>
To: team@oceancleanup.ai
Subject: 🎉 You're a Semi-Finalist! Submit Your Round 2 Materials
Date: March 1, 2026 at 9:00 AM
───────────────────────────────────────────────────────────
Dear OceanCleanup AI Team,
Congratulations on advancing to the semi-finals of the Monaco
Ocean Protection Challenge 2026!
You have been selected from 150 applicants to advance to the
next round. We were impressed by your innovative approach to
ocean conservation.
NEXT STEPS: SUBMIT ROUND 2 MATERIALS
To continue in the competition, please submit the following
documents by:
📅 DEADLINE: March 15, 2026 at 11:59 PM CET
Required Materials:
✓ 3-Minute Video Pitch (MP4, max 100MB)
Introduce your team and demonstrate your solution
✓ Updated Pitch Deck (PDF, max 10MB)
Include any updates since your initial application
✓ Financial Projections (Excel or PDF, max 5MB)
3-year revenue and cost forecast with assumptions
──────────────────────────────────────────────────────────
[Upload Documents →]
──────────────────────────────────────────────────────────
IMPORTANT NOTES:
• Your Round 1 application documents are now locked and
cannot be edited. They will remain available for viewing.
• You have 14 days to submit your materials.
• All required files must be submitted by the deadline.
• You can update or replace files at any time before the
deadline.
If you have any questions or encounter technical difficulties,
please contact us at support@monaco-opc.com.
Best regards,
Monaco Ocean Protection Challenge Team
───────────────────────────────────────────────────────────
```
**Email 2: Reminder (7 days before)**
```
From: Monaco Ocean Protection Challenge <noreply@monaco-opc.com>
To: team@oceancleanup.ai
Subject: ⏰ Reminder: Semi-Finalist Materials Due in 7 Days
Date: March 8, 2026 at 9:00 AM
───────────────────────────────────────────────────────────
Dear OceanCleanup AI Team,
This is a friendly reminder that your semi-finalist materials
are due in 7 days.
DEADLINE: March 15, 2026 at 11:59 PM CET
Current Status: 1 of 3 required files submitted ⚠️
Missing Files:
❌ Updated Pitch Deck (PDF, required)
❌ Financial Projections (Excel/PDF, required)
──────────────────────────────────────────────────────────
[Complete Your Submission →]
──────────────────────────────────────────────────────────
Don't wait until the last minute! Upload your remaining
documents now to ensure you don't miss the deadline.
Best regards,
Monaco Ocean Protection Challenge Team
```
**Email 3: Urgent Reminder (24 hours before)**
```
From: Monaco Ocean Protection Challenge <noreply@monaco-opc.com>
To: team@oceancleanup.ai
Subject: 🚨 URGENT: Semi-Finalist Materials Due Tomorrow
Date: March 14, 2026 at 9:00 AM
───────────────────────────────────────────────────────────
Dear OceanCleanup AI Team,
URGENT: Your semi-finalist materials are due TOMORROW
(March 15) at 11:59 PM CET.
Current Status: 2 of 3 required files submitted ⚠️
Missing Files:
❌ Financial Projections (Excel/PDF, REQUIRED)
──────────────────────────────────────────────────────────
[Upload Missing File Now →]
──────────────────────────────────────────────────────────
PLEASE ACT IMMEDIATELY. Incomplete submissions may result
in disqualification from the competition.
If you are experiencing technical difficulties, contact us
immediately at support@monaco-opc.com or call +377 XXX XXX.
Best regards,
Monaco Ocean Protection Challenge Team
```
**Email 4: Late Submission Warning (FLAG policy)**
```
From: Monaco Ocean Protection Challenge <noreply@monaco-opc.com>
To: team@oceancleanup.ai
Subject: ⚠️ Late Submission Notice
Date: March 16, 2026 at 10:15 AM
───────────────────────────────────────────────────────────
Dear OceanCleanup AI Team,
We received your Financial Projections file on March 16 at
2:15 PM, which was after the deadline of March 15 at 11:59 PM.
Your submission has been marked as LATE and flagged in our
system. The jury will be notified of the late submission
during evaluation, which may impact your score.
While we understand that unexpected circumstances can arise,
please be mindful of deadlines in future rounds.
Your submission is now complete. Good luck in the next round!
Best regards,
Monaco Ocean Protection Challenge Team
```
---
## 5. Jury Cross-Round Visibility (Detailed)
### 5.1 RoundSubmissionVisibility Configuration
**Purpose**: Control which EVALUATION rounds can see which SubmissionWindows.
**Configuration Example**:
```typescript
// Evaluation Round 1: Jury Screening (sees only Window 1)
await prisma.roundSubmissionVisibility.create({
data: {
evaluationRoundId: "eval_round_1",
submissionWindowId: "window_1",
displayLabel: "Application Documents",
displayOrder: 1
}
});
// Evaluation Round 2: Jury Semi-Finals (sees Window 1 + Window 2)
await prisma.roundSubmissionVisibility.createMany({
data: [
{
evaluationRoundId: "eval_round_2",
submissionWindowId: "window_1",
displayLabel: "Round 1 Application",
displayOrder: 1
},
{
evaluationRoundId: "eval_round_2",
submissionWindowId: "window_2",
displayLabel: "Semi-Final Materials",
displayOrder: 2
}
]
});
// Evaluation Round 3: Jury Finals (sees all three windows)
await prisma.roundSubmissionVisibility.createMany({
data: [
{
evaluationRoundId: "eval_round_3",
submissionWindowId: "window_1",
displayLabel: "Original Application (Jan)",
displayOrder: 1
},
{
evaluationRoundId: "eval_round_3",
submissionWindowId: "window_2",
displayLabel: "Semi-Final Submission (Mar)",
displayOrder: 2
},
{
evaluationRoundId: "eval_round_3",
submissionWindowId: "window_3",
displayLabel: "Finalist Presentation (Apr)",
displayOrder: 3
}
]
});
```
### 5.2 Jury Evaluation View (Round 1 - Single Window)
**Jury sees ONLY Window 1**:
```
┌─────────────────────────────────────────────────────────────┐
│ Evaluating: OceanCleanup AI │
│ Round: Jury Screening (Round 1) │
│ Assigned: Feb 10, 2026 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 📄 Application Documents │
│ │
│ Pitch Deck │
│ Filename: OceanCleanup_Pitch.pdf │
│ Uploaded: Jan 25, 2026 at 2:30 PM │
│ Size: 2.4 MB │
│ [View] [Download] │
│ │
│ Budget │
│ Filename: Budget_2026.xlsx │
│ Uploaded: Feb 1, 2026 at 9:15 AM │
│ Size: 850 KB │
│ [View] [Download] │
│ │
│ Team CV │
│ Filename: Team_Bios.pdf │
│ Uploaded: Jan 25, 2026 at 2:30 PM │
│ Size: 1.2 MB │
│ [View] [Download] │
│ │
│ [Download All Files (ZIP)] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Your Evaluation │
│ │
│ Impact Score (1-10): [_____] │
│ Feasibility Score (1-10): [_____] │
│ Innovation Score (1-10): [_____] │
│ │
│ Comments: │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ Recommendation: │
│ ( ) Pass ( ) Fail ( ) Conditional │
│ │
│ [Save Draft] [Submit Evaluation] │
└─────────────────────────────────────────────────────────────┘
```
**Notes**:
- Jury does NOT see that Window 2 exists
- No indication of future submission rounds
- Clean, focused evaluation on Round 1 materials only
- UI is simple and uncluttered
### 5.3 Jury Evaluation View (Round 2 - Multi-Window)
**Jury sees BOTH Window 1 and Window 2**:
```
┌─────────────────────────────────────────────────────────────┐
│ Evaluating: OceanCleanup AI │
│ Round: Semi-Final Evaluation (Round 2) │
│ Assigned: Mar 20, 2026 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 📂 Project Documents │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [ Round 1 Application ] [ Semi-Final Materials ] │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▲ Active tab │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Tab 1 Active: "Round 1 Application" │
├─────────────────────────────────────────────────────────────┤
│ │
│ 📄 Round 1 Application │
│ │
│ These documents were submitted during the initial │
│ application phase (January 2026). │
│ │
│ Pitch Deck │
│ Filename: OceanCleanup_Pitch.pdf │
│ Uploaded: Jan 25, 2026 at 2:30 PM │
│ Size: 2.4 MB │
│ [View] [Download] │
│ │
│ Budget │
│ Filename: Budget_2026.xlsx │
│ Uploaded: Feb 1, 2026 at 9:15 AM │
│ Size: 850 KB │
│ [View] [Download] │
│ │
│ Team CV │
│ Filename: Team_Bios.pdf │
│ Uploaded: Jan 25, 2026 at 2:30 PM │
│ Size: 1.2 MB │
│ [View] [Download] │
│ │
│ [Download All Round 1 Files (ZIP)] │
└─────────────────────────────────────────────────────────────┘
```
**Click "Semi-Final Materials" tab**:
```
┌─────────────────────────────────────────────────────────────┐
│ 📂 Project Documents │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [ Round 1 Application ] [ Semi-Final Materials ] │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▲ Active tab │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Tab 2 Active: "Semi-Final Materials" │
├─────────────────────────────────────────────────────────────┤
│ │
│ 📄 Semi-Final Materials │
│ │
│ These documents were submitted for the semi-final │
│ evaluation round (March 2026). │
│ │
│ 3-Minute Video Pitch │
│ Filename: Pitch_Video.mp4 │
│ Uploaded: Mar 1, 2026 at 10:30 AM │
│ Size: 85.2 MB │
│ [View] [Download] │
│ │
│ Updated Pitch Deck │
│ Filename: OceanCleanup_Pitch_v2.pdf │
│ Uploaded: Mar 10, 2026 at 4:20 PM │
│ Size: 3.1 MB │
│ [View] [Download] │
│ │
│ Financial Projections │
│ Filename: Financials_2026.xlsx │
│ Uploaded: Mar 16, 2026 at 2:15 PM ⚠️ LATE SUBMISSION │
│ Size: 1.5 MB │
│ [View] [Download] │
│ ⚠️ This file was submitted after the deadline of Mar 15 │
│ │
│ [Download All Round 2 Files (ZIP)] │
└─────────────────────────────────────────────────────────────┘
```
**Evaluation Form (Below Documents)**:
```
┌─────────────────────────────────────────────────────────────┐
│ Your Evaluation │
│ │
Please evaluate based on ALL submitted materials │
│ (both Round 1 and Round 2 documents). │
│ │
│ Business Model Score (1-10): [_____] │
│ Team Strength Score (1-10): [_____] │
│ Presentation Quality (1-10): [_____] │
│ Financial Viability (1-10): [_____] │
│ │
│ Comments: │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ Recommendation: │
│ ( ) Pass ( ) Fail ( ) Conditional │
│ │
│ ⚠️ Note: Financial Projections was submitted late. │
│ │
│ [Save Draft] [Submit Evaluation] │
└─────────────────────────────────────────────────────────────┘
```
**UI Enhancements**:
1. **Tab Navigation**: Clear separation between submission windows
2. **Context Labels**: Each tab shows description and submission date range
3. **Late Indicators**: Flagged submissions clearly visible
4. **Download Options**: Bulk download per window or all files
5. **Submission Metadata**: Upload dates, file sizes, uploaders visible
6. **Badge Indicators**: Late submissions have ⚠️ warning badge
### 5.4 Query Implementation for Jury View
```typescript
// src/server/routers/evaluation.ts
export const evaluationRouter = router({
/**
* Get all visible files for a project in an evaluation round
*/
getVisibleFilesForProject: juryProcedure
.input(z.object({
projectId: z.string().cuid(),
evaluationRoundId: z.string().cuid()
}))
.query(async ({ input, ctx }) => {
// Step 1: Get visible windows for this evaluation round
const visibleWindows = await ctx.prisma.roundSubmissionVisibility.findMany({
where: { evaluationRoundId: input.evaluationRoundId },
include: {
submissionWindow: {
include: {
fileRequirements: {
orderBy: { displayOrder: "asc" }
}
}
}
},
orderBy: { displayOrder: "asc" }
});
if (visibleWindows.length === 0) {
throw new TRPCError({
code: "NOT_FOUND",
message: "No submission windows configured for this evaluation round"
});
}
// Step 2: Fetch files for each visible window
const filesByWindow = await Promise.all(
visibleWindows.map(async (visibility) => {
const files = await ctx.prisma.projectFile.findMany({
where: {
projectId: input.projectId,
submissionWindowId: visibility.submissionWindowId,
supersededBy: null // Only current versions
},
include: {
requirement: true,
uploadedByUser: {
select: {
name: true,
email: true,
role: true
}
}
},
orderBy: { requirement: { displayOrder: "asc" } }
});
// Generate download URLs
const filesWithUrls = await Promise.all(
files.map(async (file) => ({
id: file.id,
filename: file.filename,
requirementLabel: file.requirement?.label || "Untitled",
uploadedAt: file.uploadedAt,
uploadedBy: file.uploadedByUser.name,
uploadedByRole: file.uploadedByUser.role,
sizeBytes: file.sizeBytes,
isLate: file.isLate,
downloadUrl: await getPresignedDownloadUrl(file.id)
}))
);
return {
windowId: visibility.submissionWindowId,
windowName: visibility.displayLabel,
windowDescription: visibility.submissionWindow.description,
displayOrder: visibility.displayOrder,
files: filesWithUrls
};
})
);
return filesByWindow;
})
});
```
**Frontend Usage**:
```tsx
// components/jury/ProjectFilesView.tsx
export function ProjectFilesView({ projectId, evaluationRoundId }: Props) {
const { data: filesByWindow, isLoading } = trpc.evaluation.getVisibleFilesForProject.useQuery({
projectId,
evaluationRoundId
});
if (isLoading) return <LoadingSpinner />;
return (
<div>
<Tabs defaultValue={filesByWindow?.[0]?.windowId}>
<TabsList>
{filesByWindow?.map((window) => (
<TabsTrigger key={window.windowId} value={window.windowId}>
{window.windowName}
</TabsTrigger>
))}
</TabsList>
{filesByWindow?.map((window) => (
<TabsContent key={window.windowId} value={window.windowId}>
<div className="space-y-4">
<p className="text-muted-foreground">{window.windowDescription}</p>
{window.files.map((file) => (
<FileCard
key={file.id}
file={file}
showLateIndicator={file.isLate}
/>
))}
<Button variant="outline">
Download All {window.windowName} Files (ZIP)
</Button>
</div>
</TabsContent>
))}
</Tabs>
</div>
);
}
```
### 5.5 Admin Visibility Configuration UI
**Admin panel for configuring which windows Jury 2 can see**:
```
┌─────────────────────────────────────────────────────────────┐
│ Configure Document Visibility: Semi-Final Evaluation │
└─────────────────────────────────────────────────────────────┘
Which submission windows should jury members see during this
evaluation round?
┌─────────────────────────────────────────────────────────────┐
│ Available Submission Windows: │
│ │
│ ☑ Window 1: Application Documents │
│ Created: Jan 1, 2026 │
│ Closed: Feb 1, 2026 │
│ │
│ Display as: [Round 1 Application______________] │
│ Order: [1] │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ☑ Window 2: Semi-Finalist Materials │
│ Created: Mar 1, 2026 │
│ Closed: Mar 15, 2026 │
│ │
│ Display as: [Semi-Final Submissions___________] │
│ Order: [2] │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ☐ Window 3: Finalist Presentations │
│ Status: Not yet created │
│ (This window will be created in a future round) │
│ │
└─────────────────────────────────────────────────────────────┘
Preview: Jury will see 2 tabs in this order:
1. Round 1 Application
2. Semi-Final Submissions
[Cancel] [Save Configuration]
```
**Validation Rules**:
- At least one window must be selected
- Display labels must be unique within an evaluation round
- Display order must be positive integers
- Cannot select a window that hasn't opened yet (optional warning)
- Cannot select a window from a different competition
---
## 6. Admin Experience
(Content continues in next part due to length...)
Would you like me to continue with the rest of the document (sections 6-10)?