2053 lines
86 KiB
Markdown
2053 lines
86 KiB
Markdown
|
|
# 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)?
|