# 03. Competition Flow — The Eight-Round Monaco System **Document Version**: 1.0 **Last Updated**: 2026-02-15 **Status**: Architecture Specification --- ## Table of Contents 1. [Overview](#1-overview) 2. [Competition Flow Diagram](#2-competition-flow-diagram) 3. [R1: Intake (Application Window)](#3-r1-intake-application-window) 4. [R2: Filtering (Eligibility Screening)](#4-r2-filtering-eligibility-screening) 5. [R3: Evaluation (Jury 1 — Semi-Finalist Selection)](#5-r3-evaluation-jury-1--semi-finalist-selection) 6. [R4: Submission (Semi-Finalist Documents)](#6-r4-submission-semi-finalist-documents) 7. [R5: Evaluation (Jury 2 — Finalist Selection)](#7-r5-evaluation-jury-2--finalist-selection) 8. [R6: Mentoring (Finalist Collaboration)](#8-r6-mentoring-finalist-collaboration) 9. [R7: Live Finals (Jury 3 — Live Ceremony)](#9-r7-live-finals-jury-3--live-ceremony) 10. [R8: Deliberation (Final Winner Confirmation)](#10-r8-deliberation-final-winner-confirmation) 11. [Cross-Cutting Behaviors](#11-cross-cutting-behaviors) 12. [Monaco 2026 Reference Configuration](#12-monaco-2026-reference-configuration) --- ## 1. Overview ### 1.1 Purpose of This Document This document specifies the complete operational flow of a Monaco Ocean Protection Challenge competition from first application through final winner confirmation. It defines eight distinct round types, their purpose, configuration, behaviors, and advancement criteria. **Key Principles:** - **Sequential Advancement**: Projects progress through rounds based on explicit status changes - **Independent Juries**: Three distinct jury groups evaluate at different stages - **Multi-Window Documents**: Teams submit documents across multiple submission windows that lock over time - **Full Audit Trail**: Every advancement decision, override, and state change is logged - **Admin Control**: Admins can intervene at any stage with mandatory audit justification ### 1.2 Round vs Stage vs Track Terminology **Competition/Round Architecture (Redesigned):** ``` Competition (MOPC 2026) ├─ Round 1: Application Window (type: INTAKE) ├─ Round 2: AI Screening (type: FILTERING) ├─ Round 3: Jury 1 Evaluation (type: EVALUATION) ├─ Round 4: Semi-Finalist Submission (type: SUBMISSION) ├─ Round 5: Jury 2 Evaluation (type: EVALUATION) ├─ Round 6: Mentoring (type: MENTORING) ├─ Round 7: Live Finals (type: LIVE_FINAL) └─ Round 8: Deliberation (type: CONFIRMATION) ``` **Previous System (Pipeline/Track/Stage):** - Pipeline → Competition - Track → Eliminated (rounds are sequential, not parallel) - Stage → Round - StageType → RoundType **Throughout this document:** - **Competition** = the entire contest (e.g., "MOPC 2026") - **Round** = one phase of the competition (e.g., "Jury 1 Evaluation") - **RoundType** = the kind of round (INTAKE, FILTERING, EVALUATION, etc.) - **Stage** = legacy term, avoid using ### 1.3 Project Status Evolution ``` Project.status progression: DRAFT → SUBMITTED → PENDING → UNDER_REVIEW → SEMI_FINALIST → FINALIST → WINNER ↓ ↓ ↓ ↓ ↓ ↓ (Round 1) (Round 2) (Round 3) (Round 5) (Round 7) (Round 8) Alternative paths: SUBMITTED → FILTERED_OUT (Round 2 rejection) PENDING → REJECTED (Round 3 rejection) SEMI_FINALIST → REJECTED (Round 5 rejection) FINALIST → NOT_SELECTED (Round 7/8 not chosen as winner) ``` ### 1.4 ProjectRoundState vs Project.status **ProjectRoundState** (per-round): - Tracks a project's state within a specific round - Values: `PENDING`, `IN_PROGRESS`, `PASSED`, `FAILED`, `WITHDRAWN` - Example: A project can be `PASSED` in Round 3 but `PENDING` in Round 5 **Project.status** (global): - Tracks the project's overall competition status - Updated when crossing major milestones (semi-finalist, finalist, winner) - Example: When `ProjectRoundState.status = PASSED` in Round 3, `Project.status → SEMI_FINALIST` --- ## 2. Competition Flow Diagram ``` ┌────────────────────────────────────────────────────────────────────────────┐ │ MOPC 2026 COMPETITION FLOW │ └────────────────────────────────────────────────────────────────────────────┘ R1: INTAKE (Application Window) ├─ Opens: 2026-02-01 ├─ Closes: 2026-05-31 ├─ Participants: 150 applicants (all categories) ├─ Creates: SubmissionWindow 1 ("Application Documents") ├─ File requirements: Executive Summary, Business Plan, Team CV ├─ Deadline policy: FLAG (late submissions marked but accepted) └─ Output: 150 projects with status SUBMITTED ├─ 90 STARTUP └─ 60 BUSINESS_CONCEPT ↓ R2: FILTERING (Eligibility Screening) ├─ Trigger: Admin-initiated after R1 closes ├─ No user deadline (automated/instant) ├─ Rules: Field-based + document-check + AI screening ├─ AI screening: GPT-4 with rubric (batch processing) ├─ Duplicate detection: Email-based (flags for review) └─ Output: Projects split into three categories ├─ 120 PASSED (eligible, advance to R3) ├─ 15 FLAGGED (manual admin review required) └─ 15 FILTERED_OUT (auto-rejected, visible to admins with reasons) ↓ R3: EVALUATION (Jury 1 — Semi-Finalist Selection) ├─ Opens: 2026-06-05 ├─ Closes: 2026-06-25 ├─ Jury: Jury 1 (8 members) ├─ Participants: 120 eligible projects ├─ Assignment: 3 evaluations per project (hard/soft cap system) ├─ Scoring: Criteria-based (Innovation, Feasibility, Impact, Team) ├─ Visible documents: Window 1 ONLY ├─ AI recommendation: Generates ranked shortlist per category └─ Output: Projects advancing become SEMI_FINALIST ├─ Admin selects top 20 STARTUP (from ranked list) ├─ Admin selects top 20 BUSINESS_CONCEPT └─ 80 projects status → REJECTED ↓ R4: SUBMISSION (Semi-Finalist Documents) ├─ Opens: 2026-06-28 (Window 1 auto-locks at this moment) ├─ Closes: 2026-07-20 ├─ Creates: SubmissionWindow 2 ("Semi-Finalist Materials") ├─ Participants: 40 semi-finalists (20 STARTUP, 20 BUSINESS_CONCEPT) ├─ File requirements: Updated Pitch Deck, Video Pitch, Financial Projections ├─ Deadline policy: HARD (no late submissions) ├─ Previous window state: Window 1 becomes read-only for applicants └─ Output: 40 projects with complete Round 2 submission bundles ├─ Window 1 files: locked, visible to jury └─ Window 2 files: locked after deadline ↓ R5: EVALUATION (Jury 2 — Finalist Selection + Special Awards) ├─ Opens: 2026-07-24 ├─ Closes: 2026-08-10 ├─ Jury: Jury 2 (12 members, may overlap with Jury 1) ├─ Participants: 40 semi-finalists ├─ Assignment: 3-5 evaluations per project ├─ Scoring: Criteria-based (Business Model, Team, Presentation, Viability) ├─ Visible documents: Window 1 AND Window 2 (tabbed interface) ├─ Special awards: Run alongside (see Section 7.7) ├─ AI recommendation: Ranked shortlist + suggested top N finalists └─ Output: Projects advancing become FINALIST ├─ Admin selects top 10 STARTUP ├─ Admin selects top 10 BUSINESS_CONCEPT ├─ Special award winners selected separately └─ 20 projects status → REJECTED ↓ R6: MENTORING (Finalist Collaboration) ├─ Opens: 2026-08-15 ├─ Closes: 2026-08-31 ├─ Participants: 20 finalists who requested mentoring ├─ Assignment: 1 mentor per team (max 3 teams per mentor) ├─ Workspace: Private chat, file upload, threaded comments ├─ File promotion: Mentoring files can be promoted to official submissions ├─ NO judging or scoring (collaboration layer only) └─ Output: Better-prepared finalist submissions └─ Some mentoring files promoted → Window 2 (replaces previous versions) ↓ R7: LIVE_FINAL (Jury 3 — Live Ceremony) ├─ Event date: 2026-09-15 ├─ Jury: Jury 3 (8 members, may overlap with Jury 1/2) ├─ Participants: 20 finalists (10 STARTUP, 10 BUSINESS_CONCEPT) ├─ Presentation: 8-minute pitch + 5-minute Q&A per project ├─ Category windows: STARTUP window first, then BUSINESS_CONCEPT window ├─ Voting: Numeric scoring (1-10) with optional criteria ├─ Audience voting: Optional, weighted (typically 20% audience, 80% jury) ├─ Visible documents: Window 1 + Window 2 (all prior submissions) ├─ Deliberation: 30-minute period per category after voting └─ Output: Jury 3 scores + audience totals ├─ Recommended winners per category (highest weighted score) └─ Tied projects flagged for deliberation tie-breaker ↓ R8: DELIBERATION (Final Winner Confirmation) ├─ Trigger: After R7 voting completes ├─ Mode: SINGLE_WINNER_VOTE (each juror picks one winner per category) ├─ Deliberation session: Per category (STARTUP and BUSINESS_CONCEPT independent) ├─ Collective ranking shown: Yes (transparency option) ├─ Tie-breaking: Runoff vote (if tied) → Admin break (if still tied) ├─ Admin override: Enabled (must provide mandatory reason + audit) └─ Output: Final winners confirmed with ResultLock snapshot ├─ 1 STARTUP winner (status → WINNER) ├─ 1 BUSINESS_CONCEPT winner (status → WINNER) ├─ Special award winners (determined by award-specific juries) └─ ResultUnlockEvent: Super-admin only, mandatory reason ┌────────────────────────────────────────────────────────────────────────────┐ │ END OF COMPETITION │ │ Winners announced publicly, prizes awarded, case studies │ └────────────────────────────────────────────────────────────────────────────┘ ``` **Timeline Summary:** - **R1 (Intake)**: 4 months (Feb 1 - May 31) - **R2 (Filtering)**: 1 week (Jun 1 - Jun 5, automated) - **R3 (Jury 1)**: 3 weeks (Jun 5 - Jun 25) - **R4 (Submission)**: 3 weeks (Jun 28 - Jul 20) - **R5 (Jury 2)**: 3 weeks (Jul 24 - Aug 10) - **R6 (Mentoring)**: 2 weeks (Aug 15 - Aug 31) - **R7 (Live Finals)**: 1 day (Sep 15) - **R8 (Deliberation)**: Immediate (Sep 15-16) **Total duration**: ~7.5 months from application open to winner announcement --- ## 3. R1: Intake (Application Window) ### 3.1 Purpose The Intake round is the first phase of the competition. It opens a submission window where teams apply by uploading required documents and completing an application form. **Key Objectives:** - Collect applications from teams worldwide - Capture project metadata (title, description, category, team info) - Receive required documentation (executive summary, business plan, optional video) - Support draft-save-and-continue workflow - Enforce submission deadlines with configurable late policies ### 3.2 Prerequisites **Before R1 can open:** - Competition created with `Competition.status = ACTIVE` - Round 1 configured with `RoundType = INTAKE` - SubmissionWindow 1 created with file requirements - Round 1 linked to SubmissionWindow 1 via `Round.submissionWindowId` ### 3.3 Configuration Shape (IntakeConfig) Reference: `src/types/round-configs.ts` ```typescript type IntakeConfig = { // Application form (currently hardcoded, future: dynamic form builder) applicationFormId: string // Links to ApplicationForm template // Submission window reference submissionWindowId: string // Which SubmissionWindow to use // Deadline behavior deadlinePolicy: 'HARD' | 'FLAG' | 'GRACE' gracePeriodMinutes?: number // For GRACE policy (e.g., 180 = 3 hours) // Draft system allowDraftSubmissions: boolean // Save-and-continue enabled draftExpiryDays: number // Auto-delete abandoned drafts after N days // Team profile requireTeamProfile: boolean // Require team member info maxTeamSize: number // Max team members (including lead) minTeamSize: number // Min team members (default: 1) // Notifications autoConfirmReceipt: boolean // Email confirmation on submission reminderEmailSchedule: number[] // Days before deadline: [7, 3, 1] // Public access publicFormEnabled: boolean // Allow external application link publicFormSlug?: string // Custom slug for public URL // Category quotas (STARTUP vs BUSINESS_CONCEPT) categoryQuotasEnabled: boolean categoryQuotas?: { STARTUP: number // Max startups accepted BUSINESS_CONCEPT: number // Max concepts accepted } quotaOverflowPolicy?: 'reject' | 'waitlist' // Custom fields (future: dynamic form builder) customFields?: CustomFieldDef[] } ``` ### 3.4 Key Behaviors **Multi-Step Application Form:** 1. **Step 1**: Project information (title, description, category, ocean issue) 2. **Step 2**: Team members (name, email, role, title) 3. **Step 3**: Document upload (executive summary, business plan, video pitch) 4. **Step 4**: Review & submit (confirmation checkboxes, final submit button) **Auto-Save:** - Client debounces and auto-saves form data every 30 seconds - Draft status tracked via `Project.isDraft = true` - Draft expiry: `draftExpiresAt = now + draftExpiryDays` **Deadline Enforcement:** ```typescript if (deadlinePolicy === 'HARD') { // No submissions after windowCloseAt if (now > windowCloseAt) return { canSubmit: false } } if (deadlinePolicy === 'FLAG') { // Accept late submissions, mark as late if (now > windowCloseAt) { // Create ProjectFile with isLate = true } } if (deadlinePolicy === 'GRACE') { // Accept within grace period, then hard reject const graceDeadline = windowCloseAt + gracePeriodMinutes if (now > graceDeadline) return { canSubmit: false } if (now > windowCloseAt) { // Create ProjectFile with isLate = true } } ``` **Category Quotas:** ```typescript async function checkCategoryQuota(category: 'STARTUP' | 'BUSINESS_CONCEPT') { const submittedCount = await prisma.project.count({ where: { competitionId, competitionCategory: category, isDraft: false } }) if (submittedCount >= categoryQuotas[category]) { if (quotaOverflowPolicy === 'waitlist') { // Create project with status WAITLISTED } else { // Reject submission throw new Error(`Quota full for ${category}`) } } } ``` ### 3.5 Admin Controls **Intake Dashboard:** - View all submissions (real-time count) - Filter by category, status (draft/submitted/late) - Search by project title or team name - Export submissions to CSV - Extend deadline for individual applicants (GracePeriod record) **Admin Override:** - Can upload documents on behalf of applicants - Can replace/delete files (with provenance tracking) - Can extend submission window globally (update `windowCloseAt`) - Can manually admit projects beyond quota ### 3.6 Output & Advancement Criteria **Round 1 Closes:** - All projects with `isDraft = false` become eligible for Round 2 - Projects with `isDraft = true` are excluded (abandoned drafts) **ProjectRoundState updates:** ```typescript // For each submitted project: await prisma.projectRoundState.create({ data: { projectId: project.id, roundId: round1.id, status: 'PASSED', // All submitted projects pass Round 1 enteredAt: project.submittedAt } }) // Update global status: await prisma.project.update({ where: { id: project.id }, data: { status: 'SUBMITTED' } }) ``` **Advancement to R2:** - Automatic when R1 window closes (or admin manually triggers R2) - All projects with `ProjectRoundState.status = PASSED` in R1 become `PENDING` in R2 --- ## 4. R2: Filtering (Eligibility Screening) ### 4.1 Purpose The Filtering round performs automated screening of applications to identify eligible projects, detect duplicates, and flag edge cases for admin review. **Key Objectives:** - Automated eligibility checks (field-based rules, document checks) - AI-powered screening (GPT rubric evaluation with confidence banding) - Duplicate detection (email-based cross-application similarity) - Manual review queue for flagged projects - Admin override system with full audit trail ### 4.2 Prerequisites **Before R2 can run:** - Round 1 (INTAKE) has closed - All submitted projects have `ProjectRoundState.status = PASSED` in R1 - FilteringRule records configured for Round 2 - Round 2 configured with `RoundType = FILTERING` ### 4.3 Configuration Shape (FilteringConfig) Reference: `src/types/round-configs.ts` ```typescript type FilteringConfig = { // Rule engine rules: FilterRuleDef[] // Configured rules (can be empty) // AI screening aiScreeningEnabled: boolean aiRubricPrompt: string // Custom rubric for AI aiConfidenceThresholds: { high: number // Above this = auto-pass (default: 0.85) medium: number // Above this = flag (default: 0.6) low: number // Below this = auto-reject (default: 0.4) } aiBatchSize: number // Projects per AI batch (default: 20, max: 50) aiParallelBatches: number // Concurrent batches (default: 1, max: 10) // Duplicate detection duplicateDetectionEnabled: boolean duplicateThreshold: number // Email similarity threshold (0-1, default: 1.0) duplicateAction: 'FLAG' | 'AUTO_REJECT' // Default: FLAG // Advancement behavior autoAdvancePassingProjects: boolean // Auto-advance PASSED projects to R3 manualReviewRequired: boolean // All results require admin approval // Eligibility criteria eligibilityCriteria: EligibilityCriteria[] // Category-specific rules categorySpecificRules: { STARTUP?: CategoryRuleSet BUSINESS_CONCEPT?: CategoryRuleSet } } ``` ### 4.4 Key Behaviors **Rule Evaluation Order:** ``` 1. Built-in Duplicate Detection (if enabled) ↓ 2. FIELD_CHECK rules (sorted by priority ascending) ↓ 3. DOCUMENT_CHECK rules (sorted by priority ascending) ↓ 4. AI_SCORE rules (if aiScreeningEnabled) — batch processed ↓ 5. Determine final outcome: PASSED | FILTERED_OUT | FLAGGED ``` **Rule Combination Logic:** ```typescript let finalOutcome: 'PASSED' | 'FILTERED_OUT' | 'FLAGGED' = 'PASSED' for (const rule of rules.sort((a, b) => a.priority - b.priority)) { const result = evaluateRule(rule, project) if (!result.passed) { if (rule.action === 'REJECT') { finalOutcome = 'FILTERED_OUT' break // Short-circuit } else if (rule.action === 'FLAG') { finalOutcome = 'FLAGGED' // Continue to next rule } } } // Override: Duplicates always flagged (never auto-rejected) if (isDuplicate && finalOutcome === 'FILTERED_OUT') { finalOutcome = 'FLAGGED' } ``` **AI Screening Pipeline:** ``` 1. Load projects with ProjectRoundState PENDING/IN_PROGRESS 2. Anonymize project data (strip PII via anonymization.ts) 3. Batch projects (configurable size: 1-50, default 20) 4. Parallel processing (configurable: 1-10 concurrent batches) 5. OpenAI API call (GPT-4o with rubric) 6. Parse JSON response: { project_id, meets_criteria, confidence, reasoning, quality_score, spam_risk } 7. Map anonymous IDs → real project IDs 8. Band by confidence threshold: - confidence ≥ 0.85 + meets_criteria → PASSED - confidence 0.6-0.84 → FLAGGED - confidence ≤ 0.39 + !meets_criteria → FILTERED_OUT 9. Store results in FilteringResult table 10. Log token usage (AIUsageLog) ``` **Duplicate Detection:** ```typescript const emailToProjects = new Map>() for (const project of projects) { const email = (project.submittedByEmail ?? '').toLowerCase().trim() if (!email) continue if (!emailToProjects.has(email)) emailToProjects.set(email, []) emailToProjects.get(email)!.push({ id: project.id, title: project.title }) } // Flag all projects in groups of size > 1 emailToProjects.forEach((group) => { if (group.length <= 1) return for (const p of group) { duplicateProjectIds.add(p.id) // Store metadata: { isDuplicate: true, siblingProjectIds: [...], duplicateNote: "..." } } }) ``` ### 4.5 Admin Controls **Filtering Dashboard:** - Results summary (passed/rejected/flagged counts) - AI usage stats (tokens, cost, processing time) - Manual review queue (flagged projects only) - Per-project detail view: - Rule results (which rules passed/failed) - AI screening JSON (confidence, reasoning, quality score) - Duplicate metadata (sibling project IDs) - Batch actions: - Approve all flagged - Reject all flagged - Override individual decisions **Manual Override:** ```typescript async function resolveManualDecision( filteringResultId: string, outcome: 'PASSED' | 'FILTERED_OUT', reason: string, actorId: string ) { await prisma.filteringResult.update({ where: { id: filteringResultId }, data: { finalOutcome: outcome, overriddenBy: actorId, overriddenAt: new Date(), overrideReason: reason } }) // Update ProjectRoundState await prisma.projectRoundState.update({ where: { roundId_projectId: { roundId: round2.id, projectId } }, data: { status: outcome === 'PASSED' ? 'PASSED' : 'FAILED' } }) // Audit log await createAuditLog({ action: 'FILTERING_MANUAL_DECISION', userId: actorId, entityType: 'FilteringResult', entityId: filteringResultId, metadata: { outcome, reason } }) } ``` ### 4.6 Output & Advancement Criteria **Round 2 Completes:** - All projects have `FilteringResult.outcome` (or `FilteringResult.finalOutcome` if overridden) - Projects split into three buckets: - **PASSED**: Advance to Round 3 (Jury 1) - **FILTERED_OUT**: Excluded from competition (status → REJECTED) - **FLAGGED**: Awaiting admin review (manual queue) **ProjectRoundState updates:** ```typescript // For each project: if (finalOutcome === 'PASSED') { await prisma.projectRoundState.update({ where: { roundId_projectId: { roundId: round2.id, projectId } }, data: { status: 'PASSED' } }) } if (finalOutcome === 'FILTERED_OUT') { await prisma.projectRoundState.update({ where: { roundId_projectId: { roundId: round2.id, projectId } }, data: { status: 'FAILED' } }) await prisma.project.update({ where: { id: projectId }, data: { status: 'REJECTED' } }) } if (finalOutcome === 'FLAGGED') { // Remains in PENDING state until admin resolves } ``` **Advancement to R3:** - Manual trigger (admin clicks "Advance Passing Projects") - All projects with `ProjectRoundState.status = PASSED` in R2 become `PENDING` in R3 --- ## 5. R3: Evaluation (Jury 1 — Semi-Finalist Selection) ### 5.1 Purpose The first evaluation round where Jury 1 reviews eligible projects and selects semi-finalists. **Key Objectives:** - Expert jury evaluation of all passing projects from R2 - Criteria-based scoring (Innovation, Feasibility, Impact, Team) - Independent evaluations (3 per project) - AI-generated ranked shortlist per category - Admin-confirmed semi-finalist selection ### 5.2 Prerequisites **Before R3 can open:** - Round 2 (FILTERING) completed - All eligible projects have `ProjectRoundState.status = PASSED` in R2 - Jury 1 (JuryGroup) created with members assigned - Round 3 configured with `RoundType = EVALUATION` - Round 3 linked to Jury 1 via `Round.juryGroupId` - Assignment algorithm run (or admin manually assigns) - RoundSubmissionVisibility configured (R3 sees Window 1 only) ### 5.3 Configuration Shape (EvaluationConfig) Reference: `src/types/round-configs.ts` ```typescript type EvaluationConfig = { // Assignment settings requiredReviewsPerProject: number // How many jurors review each project (default: 3) // Scoring mode scoringMode: 'criteria' | 'global' | 'binary' requireFeedback: boolean // Must provide text feedback (default: true) // COI (Conflict of Interest) coiRequired: boolean // Must declare COI before evaluating (default: true) // Peer review peerReviewEnabled: boolean // Jurors see anonymized peer evaluations after submission anonymizationLevel: 'fully_anonymous' | 'show_initials' | 'named' // AI features aiSummaryEnabled: boolean // Generate AI-powered evaluation summaries aiAssignmentEnabled: boolean // Allow AI-suggested jury-project matching // Advancement advancementMode: 'auto_top_n' | 'admin_selection' | 'ai_recommended' advancementConfig: { perCategory: boolean // Separate counts per STARTUP / BUSINESS_CONCEPT startupCount: number // How many startups advance (default: 10) conceptCount: number // How many concepts advance tieBreaker: 'admin_decides' | 'highest_individual' | 'revote' } } ``` ### 5.4 Key Behaviors **Assignment System (Hard/Soft Cap Logic):** ```typescript function canAssignMore( jurorId: string, projectCategory: 'STARTUP' | 'BUSINESS_CONCEPT', currentLoad: LoadTracker, limits: EffectiveLimits ): { allowed: boolean; penalty: number; reason?: string } { const total = currentLoad.total(jurorId) const catLoad = currentLoad.byCategory(jurorId, projectCategory) // 1. HARD cap check if (limits.capMode === 'HARD' && total >= limits.maxAssignments) { return { allowed: false, penalty: 0, reason: 'Hard cap reached' } } // 2. SOFT cap check (can exceed by buffer) let overflowPenalty = 0 if (limits.capMode === 'SOFT') { if (total >= limits.maxAssignments + limits.softCapBuffer) { return { allowed: false, penalty: 0, reason: 'Soft cap + buffer exceeded' } } if (total >= limits.maxAssignments) { // In buffer zone — apply increasing penalty overflowPenalty = (total - limits.maxAssignments + 1) * 15 } } // 3. Category quota check if (limits.categoryQuotasEnabled && limits.categoryQuotas) { const quota = limits.categoryQuotas[projectCategory] if (quota) { if (catLoad >= quota.max) { return { allowed: false, penalty: 0, reason: `Category ${projectCategory} max reached` } } if (catLoad < quota.min) { overflowPenalty -= 15 // Bonus for under-min } } } // 4. Ratio preference alignment if (limits.preferredStartupRatio != null && total > 0) { const currentStartupRatio = currentLoad.byCategory(jurorId, 'STARTUP') / total const isStartup = projectCategory === 'STARTUP' const wantMore = isStartup ? currentStartupRatio < limits.preferredStartupRatio : currentStartupRatio > limits.preferredStartupRatio if (wantMore) overflowPenalty -= 10 // Bonus else overflowPenalty += 10 // Penalty } return { allowed: true, penalty: overflowPenalty } } ``` **COI Declaration (Blocking):** ```typescript // Before evaluating any project, juror MUST declare COI async function checkCOI(jurorId: string, projectId: string): Promise { const coi = await prisma.conflictOfInterest.findFirst({ where: { userId: jurorId, projectId } }) if (!coi) { // Show blocking dialog: "Do you have a conflict of interest?" return false // Block evaluation } if (coi.hasConflict) { // Assignment flagged, admin notified, juror may be reassigned return false // Block evaluation } return true // Can proceed } ``` **Evaluation Submission:** ```typescript async function submitEvaluation( assignmentId: string, data: { scores, feedback, recommendation }, userId: string ) { // Validate window is open (or juror has grace period) const assignment = await prisma.assignment.findUnique({ where: { id: assignmentId }, include: { round: true } }) if (now > assignment.round.windowCloseAt) { // Check grace period const grace = await prisma.gracePeriod.findFirst({ where: { roundId: assignment.roundId, userId, extendedUntil: { gt: now } } }) if (!grace) throw new Error('Evaluation window closed') } // Create evaluation record await prisma.evaluation.create({ data: { assignmentId, userId, scores: data.scores, feedback: data.feedback, recommendation: data.recommendation, status: 'SUBMITTED', submittedAt: new Date() } }) } ``` **AI Ranked Shortlist:** ```typescript // After all evaluations submitted async function generateAIShortlist(roundId: string) { // Fetch all projects with evaluations const projects = await prisma.project.findMany({ where: { projectRoundStates: { some: { roundId, status: 'IN_PROGRESS' } } }, include: { evaluations: { where: { assignmentId: { in: assignmentIds } } } } }) // Anonymize + send to OpenAI const anonymized = anonymizeProjects(projects) const prompt = ` Analyze these projects based on jury scores and feedback. Rank them per category (STARTUP, BUSINESS_CONCEPT). For each project: { rank, reasoning, strengths, weaknesses, recommendation } ` const response = await openai.chat.completions.create({ model: 'gpt-4o', messages: [{ role: 'system', content: prompt }, { role: 'user', content: JSON.stringify(anonymized) }] }) // Store AI recommendation return parseAIRankedShortlist(response) } ``` ### 5.5 Admin Controls **Evaluation Dashboard:** - Completion stats (evaluations submitted / total required) - Per-juror progress (assigned projects, completed evaluations) - Per-project status (evaluations received / required) - Results visualization: - Ranked list per category (average score, consensus, review count) - AI recommended shortlist (if aiSummaryEnabled) - Score distribution charts - Override tools: - Manually select advancing projects (drag to reorder) - Accept AI recommendation - Set custom cutoff line - Force-advance or force-reject individual projects **Advancement Decision:** ```typescript async function confirmAdvancement( roundId: string, selectedProjectIds: string[], actorId: string ) { // Update ProjectRoundState for (const projectId of selectedProjectIds) { await prisma.projectRoundState.update({ where: { roundId_projectId: { roundId, projectId } }, data: { status: 'PASSED' } }) // Update global status await prisma.project.update({ where: { id: projectId }, data: { status: 'SEMI_FINALIST' } }) } // Reject non-selected projects const allProjects = await prisma.projectRoundState.findMany({ where: { roundId, status: 'IN_PROGRESS' } }) const rejectedIds = allProjects .map(prs => prs.projectId) .filter(id => !selectedProjectIds.includes(id)) for (const projectId of rejectedIds) { await prisma.projectRoundState.update({ where: { roundId_projectId: { roundId, projectId } }, data: { status: 'FAILED' } }) await prisma.project.update({ where: { id: projectId }, data: { status: 'REJECTED' } }) } // Send notifications await notifyAdvancingTeams(selectedProjectIds) await notifyRejectedTeams(rejectedIds) // Audit log await createAuditLog({ action: 'ADVANCEMENT_CONFIRMED', userId: actorId, entityType: 'Round', entityId: roundId, metadata: { selectedCount: selectedProjectIds.length, rejectedCount: rejectedIds.length } }) } ``` ### 5.6 Output & Advancement Criteria **Round 3 Completes:** - Admin has selected semi-finalists (typically top N per category) - Selected projects: `ProjectRoundState.status = PASSED`, `Project.status = SEMI_FINALIST` - Rejected projects: `ProjectRoundState.status = FAILED`, `Project.status = REJECTED` **Advancement to R4:** - All projects with `ProjectRoundState.status = PASSED` in R3 become eligible for R4 (SUBMISSION) - Round 4 opens automatically or via admin trigger --- ## 6. R4: Submission (Semi-Finalist Documents) ### 6.1 Purpose The second submission window where semi-finalists submit additional documents required for Jury 2 evaluation. **Key Objectives:** - Collect new documents from advancing teams (video pitch, updated materials) - Lock previous submission window (Window 1) to read-only for applicants - Maintain separate file requirements per window - Prevent editing of Round 1 materials while Round 2 is open - Full admin control over all windows regardless of lock state ### 6.2 Prerequisites **Before R4 can open:** - Round 3 (EVALUATION) completed - Semi-finalists selected with `ProjectRoundState.status = PASSED` in R3 - SubmissionWindow 2 created with file requirements - Round 4 configured with `RoundType = SUBMISSION` - Round 4 linked to SubmissionWindow 2 via `Round.submissionWindowId` ### 6.3 Configuration Shape (SubmissionConfig) Reference: `src/types/round-configs.ts` ```typescript type SubmissionConfig = { // Eligibility eligibleStatuses: ProjectRoundStateValue[] // Which statuses from previous round can submit // Default: ['PASSED'] // Notifications notifyEligibleTeams: boolean // Email teams when window opens (default: true) // Locking lockPreviousWindows: boolean // Lock all previous windows when this opens (default: true) // Optional: Custom email template notificationTemplate?: { subject: string bodyHtml: string variables: Record } // Optional: Window configuration windowConfig?: { name: string description: string openDate: string // ISO 8601 closeDate: string // ISO 8601 latePolicy: 'HARD' | 'FLAG' | 'GRACE' gracePeriodHours?: number } // Optional: File requirements fileRequirements?: Array<{ label: string description?: string isRequired: boolean allowedFileTypes: string[] maxSizeMB: number displayOrder: number }> } ``` ### 6.4 Key Behaviors **Window Opening (Automatic Lock):** ```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 } } } }) 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 } }) 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) } } ``` **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 } }) // Admins bypass all checks if (user?.role === 'SUPER_ADMIN' || user?.role === 'PROGRAM_ADMIN') { return { allowed: true } } // Check: User owns this project? const project = await ctx.prisma.project.findFirst({ where: { id: projectId, applicantId: userId } }) if (!project) { return { allowed: false, reason: 'Unauthorized' } } // Check: Window locked? const window = await ctx.prisma.submissionWindow.findUnique({ where: { id: windowId } }) if (window.isLocked) { return { allowed: false, reason: 'This submission window is now closed.' } } // Check: Window not yet open? const now = new Date() if (now < window.openDate) { return { allowed: false, reason: 'Submission window has not opened yet.' } } // Check: Deadline enforcement (HARD/FLAG/GRACE) if (now > window.closeDate) { if (window.latePolicy === 'HARD') { return { allowed: false, reason: 'Deadline has passed. No late submissions allowed.' } } if (window.latePolicy === 'FLAG') { return { allowed: true } // Will be marked late } if (window.latePolicy === 'GRACE') { if (window.lockDate && now > window.lockDate) { return { allowed: false, reason: 'Grace period has ended.' } } return { allowed: true } // Within grace period } } return { allowed: true } } ``` **Applicant View (Locked vs Open Windows):** **Before R4 opens (R3 just finished):** ``` ┌─ Round 1: Application Documents (OPEN) ─────────────┐ │ ✅ Pitch Deck │ │ [View] [Download] [Replace] ← Can still edit │ │ │ │ ✅ Budget │ │ [View] [Download] [Replace] │ └──────────────────────────────────────────────────────┘ ``` **After R4 opens (Window 1 auto-locks):** ``` ┌─ 🔒 Round 1: Application Documents (LOCKED) ────────┐ │ ✅ Pitch Deck │ │ [View] [Download] ← No Replace button │ │ │ │ ✅ Budget │ │ [View] [Download] │ │ │ │ ℹ️ These documents are locked. Contact admin if │ │ you need to make changes. │ └──────────────────────────────────────────────────────┘ ┌─ 📤 Round 2: Semi-Finalist Materials (OPEN) ────────┐ │ ❌ Updated Pitch Deck (Required) │ │ [Choose File] or Drag & Drop Here │ │ │ │ ❌ Video Pitch (Required) │ │ [Choose File] or Drag & Drop Here │ │ │ │ ❌ Financial Projections (Required) │ │ [Choose File] or Drag & Drop Here │ └──────────────────────────────────────────────────────┘ ``` ### 6.5 Admin Controls **Submission Dashboard:** - View all eligible teams (semi-finalists) - Completion status (files submitted / required) - Late submission indicators - Upload files on behalf of teams - Replace/delete files (with provenance) - Unlock windows (emergency override) **File Replacement (Admin Only):** ```typescript async function adminReplaceFile( projectFileId: string, newFile: { fileName, mimeType, size, bucket, objectKey }, actorId: string, reason: string ) { // Mark old file as superseded await prisma.projectFile.update({ where: { id: projectFileId }, data: { supersededBy: newFileId, supersededAt: new Date() } }) // Create new file record const newProjectFile = await prisma.projectFile.create({ data: { projectId, submissionWindowId, requirementId, fileName: newFile.fileName, mimeType: newFile.mimeType, sizeBytes: newFile.size, storagePath: newFile.objectKey, uploadedBy: actorId, uploadedAt: new Date(), version: oldFile.version + 1 } }) // Audit log await createAuditLog({ action: 'FILE_REPLACED_BY_ADMIN', userId: actorId, entityType: 'ProjectFile', entityId: newProjectFile.id, metadata: { oldFileId: projectFileId, reason } }) } ``` ### 6.6 Output & Advancement Criteria **Round 4 Closes:** - All semi-finalists have submitted required documents (or flagged as incomplete) - Window 2 locked automatically (or admin manually locks) **ProjectRoundState updates:** ```typescript // For each semi-finalist who completed submission: await prisma.projectRoundState.update({ where: { roundId_projectId: { roundId: round4.id, projectId } }, data: { status: 'PASSED' } }) // For incomplete submissions: await prisma.projectRoundState.update({ where: { roundId_projectId: { roundId: round4.id, projectId } }, data: { status: 'FAILED' } // Or admin can override }) ``` **Advancement to R5:** - All projects with `ProjectRoundState.status = PASSED` in R4 become `PENDING` in R5 --- ## 7. R5: Evaluation (Jury 2 — Finalist Selection) ### 7.1 Purpose The second evaluation round where Jury 2 reviews semi-finalists and selects finalists. Special awards may run alongside. **Key Objectives:** - Expert jury evaluation of semi-finalists - Access to both Round 1 and Round 2 documents (multi-window visibility) - Criteria-based scoring (Business Model, Team, Presentation, Viability) - Special award eligibility and selection (parallel process) - AI-generated ranked shortlist + suggested top N finalists - Admin-confirmed finalist selection ### 7.2 Prerequisites **Before R5 can open:** - Round 4 (SUBMISSION) completed - All semi-finalists have `ProjectRoundState.status = PASSED` in R4 - Jury 2 (JuryGroup) created with members assigned - Round 5 configured with `RoundType = EVALUATION` - Round 5 linked to Jury 2 via `Round.juryGroupId` - RoundSubmissionVisibility configured (R5 sees Window 1 AND Window 2) ### 7.3 Configuration Shape Same as R3 (EvaluationConfig), but with: - `advancementConfig.startupCount`: Lower than R3 (e.g., 10 instead of 20) - `advancementConfig.conceptCount`: Lower than R3 (e.g., 10 instead of 20) ### 7.4 Key Behaviors **Multi-Window Document Visibility:** ```typescript // Fetch all visible files for a project in R5 const visibleWindows = await prisma.roundSubmissionVisibility.findMany({ where: { evaluationRoundId: round5.id }, include: { submissionWindow: { include: { fileRequirements: { orderBy: { displayOrder: 'asc' } } } } }, orderBy: { displayOrder: 'asc' } }) // Returns two windows: // [ // { windowId: 'window_1', displayLabel: 'Round 1 Application', displayOrder: 1 }, // { windowId: 'window_2', displayLabel: 'Semi-Final Submissions', displayOrder: 2 } // ] // UI shows tabbed interface: // Tab 1: "Round 1 Application" (Pitch Deck, Budget, Team CV from Window 1) // Tab 2: "Semi-Final Submissions" (Updated Pitch, Video, Financials from Window 2) ``` **Jury View (Multi-Window):** ``` ┌─ PROJECT DOCUMENTS ──────────────────────────────────┐ │ [ Round 1 Application ] [ Semi-Final Materials ] │ │ ▲ Active tab │ └──────────────────────────────────────────────────────┘ ┌─ Round 1 Application ────────────────────────────────┐ │ 📄 Pitch Deck (PDF, 2.4 MB) │ │ Uploaded: Jan 25, 2026 │ │ [View] [Download] │ │ │ │ 📄 Budget (XLSX, 850 KB) │ │ Uploaded: Feb 1, 2026 │ │ [View] [Download] │ │ │ │ 📄 Team CV (PDF, 1.2 MB) │ │ Uploaded: Jan 25, 2026 │ │ [View] [Download] │ └──────────────────────────────────────────────────────┘ (Click "Semi-Final Materials" tab) ┌─ Semi-Final Materials ───────────────────────────────┐ │ 📄 Updated Pitch Deck (PDF, 3.1 MB) │ │ Uploaded: Mar 10, 2026 │ │ [View] [Download] │ │ │ │ 🎥 Video Pitch (MP4, 85.2 MB) │ │ Uploaded: Mar 1, 2026 │ │ [View] [Download] │ │ │ │ 📄 Financial Projections (XLSX, 1.5 MB) │ │ Uploaded: Mar 16, 2026 ⚠️ LATE SUBMISSION │ │ [View] [Download] │ │ ⚠️ This file was submitted after the deadline │ └──────────────────────────────────────────────────────┘ ``` ### 7.5 Special Awards Integration **How Special Awards Work Alongside R5:** During the Jury 2 evaluation round, special awards can run in parallel: **Award Configuration:** ```typescript type SpecialAward = { id: string name: string // "Innovation Award", "Impact Award" evaluationRoundId: string // Links to R5 (runs alongside Jury 2) eligibilityMode: 'STAY_IN_MAIN' | 'SEPARATE_POOL' juryGroupId: string // Award-specific jury (or subset of Jury 2) votingMode: 'PICK_WINNER' | 'RANKED' | 'SCORED' } ``` **Award Eligibility Flow:** ``` 1. Before R5 opens: Admin runs award eligibility (AI or manual) - Projects flagged as eligible for specific awards - Mode A (SEPARATE_POOL): Projects may be pulled from main (admin confirms) - Mode B (STAY_IN_MAIN): Projects remain in main, flagged "eligible for award" 2. During R5 evaluation window: - Main Jury 2: Evaluates all semi-finalists - Award juries: Evaluate award-eligible projects (parallel) - Award jury members see their award assignments alongside regular evaluations 3. After R5 closes: - Main results: Jury 2 selects finalists - Award results: Award juries select award winners - Both processes independent (separate deliberations, separate confirmations) ``` **Award Jury Dashboard:** ``` ┌─ MY EVALUATIONS ─────────────────────────────────────┐ │ Main Competition (Jury 2) │ │ ├─ Project A (STARTUP) — ✅ Completed │ │ ├─ Project B (STARTUP) — ⏳ In Progress │ │ └─ Project C (CONCEPT) — ⬜ Pending │ │ │ │ Innovation Award │ │ ├─ Project X (Award Eligible) — ✅ Completed │ │ └─ Project Y (Award Eligible) — ⏳ In Progress │ └──────────────────────────────────────────────────────┘ ``` ### 7.6 Admin Controls Same as R3, plus: - **Special Award Management**: - Run award eligibility (AI or manual filtering) - Assign award juries - View award voting progress - Confirm award winners (separate from main finalists) ### 7.7 Output & Advancement Criteria **Round 5 Completes:** - Admin has selected finalists (typically top 10 per category) - Selected projects: `ProjectRoundState.status = PASSED`, `Project.status = FINALIST` - Rejected projects: `ProjectRoundState.status = FAILED`, `Project.status = REJECTED` - Special award winners: Separate tracking (SpecialAwardWinner records) **Advancement to R6:** - All projects with `ProjectRoundState.status = PASSED` in R5 become eligible for R6 (MENTORING) - Only projects with `wantsMentorship = true` get mentor assignments --- ## 8. R6: Mentoring (Finalist Collaboration) ### 8.1 Purpose The Mentoring round is NOT a judging stage — it is a collaboration layer that provides finalist teams with a private workspace to refine submissions with guidance from an assigned mentor. **Key Objectives:** - One-on-one mentor-team collaboration - Private workspace with chat, file upload, threaded comments - File promotion from workspace to official submission - Better-prepared finalists for Live Finals (R7) ### 8.2 Prerequisites **Before R6 can open:** - Round 5 (EVALUATION) completed - Finalists selected with `ProjectRoundState.status = PASSED` in R5 - Mentors recruited and added to platform with role `MENTOR` - Round 6 configured with `RoundType = MENTORING` ### 8.3 Configuration Shape (MentoringConfig) ```typescript type MentoringConfig = { // Who gets mentoring eligibility: 'all_advancing' | 'requested_only' // Workspace features chatEnabled: boolean // Bidirectional messaging (default: true) fileUploadEnabled: boolean // Mentor + team can upload files (default: true) fileCommentsEnabled: boolean // Threaded comments on files (default: true) filePromotionEnabled: boolean // Promote workspace file to official submission (default: true) // Promotion target promotionTargetWindowId: string | null // Which SubmissionWindow promoted files go to // Auto-assignment autoAssignMentors: boolean // Use AI/algorithm to assign (default: false) maxProjectsPerMentor: number // Mentor workload cap (default: 3) // Notifications notifyTeamsOnOpen: boolean // Email teams when mentoring opens (default: true) notifyMentorsOnAssign: boolean // Email mentors when assigned (default: true) reminderBeforeClose: number[] // Days before close to remind (default: [7, 3, 1]) } ``` ### 8.4 Key Behaviors **Workspace Activation:** ```typescript // When mentor assigned and R6 opens: await prisma.mentorAssignment.update({ where: { id: assignmentId }, data: { workspaceEnabled: true, workspaceOpenAt: round.windowOpenAt, workspaceCloseAt: round.windowCloseAt } }) ``` **File Promotion Flow:** ``` 1. Team member (or admin) clicks "Promote →" on a workspace file 2. Dialog appears: - Select target submission window (Window 2) - Select requirement slot (e.g., "Business Plan") - Confirm replacement (if slot already has a file) 3. On confirmation: a. Create new ProjectFile record: - projectId: team's project ID - submissionWindowId: selected window - requirementId: selected requirement slot - fileName, mimeType, size: copied from MentorFile - bucket, objectKey: SAME as MentorFile (no file duplication) - version: incremented from previous file in slot b. Mark previous file as superseded: - Old ProjectFile: supersededBy = new file ID, supersededAt = now c. Update MentorFile flags: - isPromoted: true - promotedToFileId: new ProjectFile ID - promotedAt: now - promotedByUserId: actor ID d. Audit log entry: - action: "MENTOR_FILE_PROMOTED" - details: { mentorFileId, projectFileId, submissionWindowId, requirementId, replacedFileId } ``` **Privacy Model:** ``` Visibility Matrix: ┌──────────────────┬────────┬──────────┬───────┬──────┐ │ Content │ Mentor │ Team │ Admin │ Jury │ ├──────────────────┼────────┼──────────┼───────┼──────┤ │ Chat messages │ ✅ │ ✅ │ ✅ │ ❌ │ │ Workspace files │ ✅ │ ✅ │ ✅ │ ❌ │ │ File comments │ ✅ │ ✅ │ ✅ │ ❌ │ │ Mentor notes │ ✅ │ ❌ │ ✅* │ ❌ │ │ Promoted files │ ✅ │ ✅ │ ✅ │ ✅** │ └──────────────────┴────────┴──────────┴───────┴──────┘ * Only if MentorNote.isVisibleToAdmin = true ** Promoted files become official submissions visible to Jury 3 ``` ### 8.5 Admin Controls **Mentoring Dashboard:** - View all mentor assignments - Track activity (messages sent, files uploaded, milestones completed) - Reassign mentors (if mentor goes inactive) - View any workspace (read-only or full edit access) - Promote files on behalf of teams - Extend mentoring window (per team or globally) ### 8.6 Output & Advancement Criteria **Round 6 Closes:** - Mentoring workspaces become read-only - Promoted files remain as official submissions in Window 2 - All finalists remain with `ProjectRoundState.status = PASSED` in R6 **Advancement to R7:** - All projects with `ProjectRoundState.status = PASSED` in R6 become eligible for R7 (LIVE_FINAL) --- ## 9. R7: Live Finals (Jury 3 — Live Ceremony) ### 9.1 Purpose The Live Finals round orchestrates the live ceremony where Jury 3 evaluates finalist presentations in real-time, with optional audience participation. **Key Objectives:** - Real-time stage manager controls (presentation cursor, timing, pause/resume) - Jury voting with multiple modes (numeric, ranking, binary) - Optional audience voting with weighted scores - Per-category presentation windows (STARTUP, then BUSINESS_CONCEPT) - Deliberation period for jury discussion - Live results display or ceremony reveal ### 9.2 Prerequisites **Before R7 can open:** - Round 6 (MENTORING) completed (or skipped) - All finalists have `ProjectRoundState.status = PASSED` in R5/R6 - Jury 3 (JuryGroup) created with members assigned - Round 7 configured with `RoundType = LIVE_FINAL` - LiveVotingSession created for R7 - Presentation order configured (per category) ### 9.3 Configuration Shape (LiveFinalConfig) ```typescript type LiveFinalConfig = { // Jury juryGroupId: string // Which jury evaluates (Jury 3) // Voting mode votingMode: 'NUMERIC' | 'RANKING' | 'BINARY' // Numeric mode settings numericScale?: { min: number // Default: 1 max: number // Default: 10 allowDecimals: boolean // Default: false } // Criteria-based voting (optional enhancement) criteriaEnabled?: boolean criteriaJson?: LiveVotingCriterion[] importFromEvalForm?: string // Import criteria from prior evaluation form // Ranking mode settings rankingSettings?: { maxRankedProjects: number // Top N projects each juror ranks pointsSystem: 'DESCENDING' | 'BORDA' // 3-2-1 or Borda count } // Audience voting audienceVotingEnabled: boolean audienceVotingWeight: number // 0-100, percentage weight juryVotingWeight: number // complement (must sum to 100) audienceVotingMode: 'PER_PROJECT' | 'FAVORITES' | 'CATEGORY_FAVORITES' audienceMaxFavorites?: number // For FAVORITES mode audienceRequireIdentification: boolean audienceAntiSpamMeasures: { ipRateLimit: boolean deviceFingerprint: boolean emailVerification: boolean } // Timing presentationDurationMinutes: number // Per project presentation time qaDurationMinutes: number // Per project Q&A time // Deliberation deliberationEnabled: boolean deliberationDurationMinutes: number deliberationAllowsVoteRevision: boolean // Can jury change votes during deliberation? // Category windows categoryWindowsEnabled: boolean // Separate windows per category categoryWindows: CategoryWindow[] // Results display showLiveResults: boolean // Real-time leaderboard showLiveScores: boolean // Show actual scores vs just rankings anonymizeJuryVotes: boolean // Hide individual jury votes from audience requireAllJuryVotes: boolean // Voting can't end until all jury members vote // Overrides adminCanOverrideVotes: boolean adminCanAdjustWeights: boolean // Mid-ceremony weight adjustment // Presentation order presentationOrderMode: 'MANUAL' | 'RANDOM' | 'SCORE_BASED' | 'CATEGORY_SPLIT' } type CategoryWindow = { category: 'STARTUP' | 'BUSINESS_CONCEPT' projectOrder: string[] // Ordered project IDs startTime?: string // Scheduled start (ISO 8601) endTime?: string // Scheduled end deliberationMinutes?: number // Override global deliberation } ``` ### 9.4 Key Behaviors **Ceremony State Machine:** ``` NOT_STARTED → (start session) → IN_PROGRESS → (deliberation starts) → DELIBERATION → (voting ends) → COMPLETED NOT_STARTED: - Session created but not started - Projects ordered (manual or automatic) - Jury and audience links generated IN_PROGRESS: - Presentations ongoing - Per-project state: WAITING → PRESENTING → Q_AND_A → VOTING → VOTED → SCORED - Admin can pause, skip, reorder on the fly DELIBERATION: - Timer running for deliberation period - Jury can discuss (optional chat/discussion interface) - Votes may be revised (if deliberationAllowsVoteRevision=true) COMPLETED: - All voting finished - Results calculated - Ceremony locked (or unlocked for result reveal) ``` **Stage Manager Controls:** - Start/pause/resume ceremony - Jump to specific project - Skip project (emergency) - Reorder queue (drag-and-drop) - Open/close voting window - Extend timer (+1 min, +5 min) - Start deliberation period - Force end session - Override individual votes (if enabled) **Jury Voting Interface:** ``` ┌─ VOTING PANEL (Numeric Mode: 1-10) ─────────────────┐ │ │ │ How would you rate this project overall? │ │ │ │ ┌────────────────────────────────────────────────┐ │ │ │ 1 2 3 4 5 6 7 8 9 10│ │ │ │ ○ ○ ○ ○ ○ ○ ○ ● ○ ○│ │ │ └────────────────────────────────────────────────┘ │ │ │ │ Your score: 8 │ │ │ │ [Submit Vote] │ │ │ │ ⚠️ Votes cannot be changed after submission │ │ unless admin resets. │ └──────────────────────────────────────────────────────┘ ``` **Weighted Score Calculation:** ```typescript function calculateWeightedScore( juryScores: number[], audienceScore: number, config: LiveFinalConfig ): number { const juryAvg = juryScores.reduce((sum, s) => sum + s, 0) / juryScores.length const juryWeight = config.juryVotingWeight / 100 const audienceWeight = config.audienceVotingWeight / 100 return (juryAvg * juryWeight) + (audienceScore * audienceWeight) } ``` ### 9.5 Admin Controls **Stage Manager Dashboard:** - Ceremony status (NOT_STARTED, IN_PROGRESS, DELIBERATION, COMPLETED) - Current project indicator - Presentation/Q&A/Voting timers - Jury vote count (submitted / total) - Audience vote count - Live leaderboard (if enabled) - Category window controls - Emergency controls (pause, skip, reset) ### 9.6 Output & Advancement Criteria **Round 7 Completes:** - All finalists have Jury 3 scores + audience scores (if enabled) - Weighted scores calculated per project - Recommended winners per category (highest weighted score) - Tied projects flagged for R8 deliberation **ProjectRoundState updates:** ```typescript // All finalists remain PASSED in R7: await prisma.projectRoundState.updateMany({ where: { roundId: round7.id }, data: { status: 'PASSED' } }) // Scores stored in LiveVote table // Winners not yet final — wait for R8 deliberation ``` **Advancement to R8:** - All projects with `ProjectRoundState.status = PASSED` in R7 become eligible for R8 (DELIBERATION) --- ## 10. R8: Deliberation (Final Winner Confirmation) ### 10.1 Purpose The Deliberation round replaces the "all jury agree + admin approval" confirmation model. It provides a structured deliberation session where Jury 3 formally selects winners per category. **Key Objectives:** - Formal winner selection per category (STARTUP and BUSINESS_CONCEPT independent) - Deliberation session with voting modes (single winner vote, full ranking) - Tie-breaking mechanisms (runoff vote, admin break) - Admin override with mandatory audit justification - ResultLock snapshot for final results ### 10.2 Prerequisites **Before R8 can open:** - Round 7 (LIVE_FINAL) completed - All finalists have Jury 3 scores - Deliberation session created per category ### 10.3 Configuration Shape (DeliberationConfig) ```typescript type DeliberationConfig = { // Mode mode: 'SINGLE_WINNER_VOTE' | 'FULL_RANKING' // Visibility showCollectiveRankings: boolean // Show aggregate rankings to jury during deliberation // Tie-breaking tieBreakMethod: 'RUNOFF_VOTE' | 'ADMIN_BREAK' | 'ADMIN_OVERRIDE' // Admin controls adminCanOverride: boolean // Admin can override entire result adminOverrideRequiresReason: boolean // Mandatory reason for override (default: true) // Result locking autoLockOnFinalize: boolean // Create ResultLock snapshot immediately (default: true) unlockRequiresSuperAdmin: boolean // Only super-admin can unlock (default: true) } ``` ### 10.4 Key Behaviors **Deliberation Session (Per Category):** ```typescript // Create one DeliberationSession per category await prisma.deliberationSession.create({ data: { roundId: round8.id, category: 'STARTUP', mode: 'SINGLE_WINNER_VOTE', status: 'PENDING', projects: { connect: startupFinalists.map(p => ({ id: p.id })) } } }) await prisma.deliberationSession.create({ data: { roundId: round8.id, category: 'BUSINESS_CONCEPT', mode: 'SINGLE_WINNER_VOTE', status: 'PENDING', projects: { connect: conceptFinalists.map(p => ({ id: p.id })) } } }) ``` **Voting Modes:** **SINGLE_WINNER_VOTE:** ```typescript // Each juror picks one winner // Project with most votes = proposed winner // Others ranked by vote count async function tallyWinnerVotes(sessionId: string) { const votes = await prisma.deliberationVote.findMany({ where: { sessionId }, select: { projectId: true } }) const voteCounts = new Map() for (const vote of votes) { voteCounts.set(vote.projectId, (voteCounts.get(vote.projectId) || 0) + 1) } const sorted = Array.from(voteCounts.entries()) .sort((a, b) => b[1] - a[1]) const winner = sorted[0] const isTied = sorted.length > 1 && sorted[1][1] === winner[1] return { winner: winner[0], voteCount: winner[1], isTied, tiedProjects: isTied ? sorted.filter(s => s[1] === winner[1]).map(s => s[0]) : [] } } ``` **FULL_RANKING:** ```typescript // Each juror submits ordinal ranks (1st, 2nd, 3rd...) // Aggregated via Borda count async function tallyRankingVotes(sessionId: string) { const votes = await prisma.deliberationVote.findMany({ where: { sessionId }, select: { projectId: true, rank: true } }) const bordaScores = new Map() const totalProjects = new Set(votes.map(v => v.projectId)).size for (const vote of votes) { const points = totalProjects - vote.rank + 1 // 1st = N points, 2nd = N-1, etc. bordaScores.set(vote.projectId, (bordaScores.get(vote.projectId) || 0) + points) } const sorted = Array.from(bordaScores.entries()) .sort((a, b) => b[1] - a[1]) const winner = sorted[0] const isTied = sorted.length > 1 && sorted[1][1] === winner[1] return { winner: winner[0], bordaScore: winner[1], isTied, tiedProjects: isTied ? sorted.filter(s => s[1] === winner[1]).map(s => s[0]) : [] } } ``` **Tie-Breaking:** **Runoff Vote:** ```typescript // If tied, create new voting round with only tied projects async function createRunoffVote(sessionId: string, tiedProjectIds: string[]) { await prisma.deliberationSession.update({ where: { id: sessionId }, data: { status: 'RUNOFF', runoffProjects: tiedProjectIds } }) // Notify jury: "Runoff vote required for tied projects" // Jury votes again, only on tied projects } ``` **Admin Break:** ```typescript // Admin manually selects winner from tied projects async function adminBreakTie(sessionId: string, winnerId: string, actorId: string, reason: string) { await prisma.deliberationSession.update({ where: { id: sessionId }, data: { finalWinnerId: winnerId, tieBreakMethod: 'ADMIN_BREAK', tieBreakReason: reason, tieBreakByUserId: actorId, tieBreakAt: new Date(), status: 'COMPLETED' } }) // Audit log await createAuditLog({ action: 'TIE_BREAK_ADMIN', userId: actorId, entityType: 'DeliberationSession', entityId: sessionId, metadata: { winnerId, reason } }) } ``` **Admin Override:** ```typescript // Admin overrides entire result (not just tie-break) async function adminOverrideResult(sessionId: string, winnerId: string, actorId: string, reason: string) { // Mandatory reason requirement if (!reason || reason.trim().length < 10) { throw new Error('Admin override requires a detailed reason (min 10 characters)') } await prisma.deliberationSession.update({ where: { id: sessionId }, data: { finalWinnerId: winnerId, wasOverridden: true, overrideReason: reason, overrideByUserId: actorId, overrideAt: new Date(), status: 'COMPLETED' } }) // Audit log await createAuditLog({ action: 'DELIBERATION_ADMIN_OVERRIDE', userId: actorId, entityType: 'DeliberationSession', entityId: sessionId, metadata: { winnerId, reason } }) } ``` **Result Lock:** ```typescript // After deliberation finalized, create immutable snapshot async function finalizeDeliberation(sessionId: string, actorId: string) { const session = await prisma.deliberationSession.findUnique({ where: { id: sessionId }, include: { votes: true, finalWinner: true } }) // Create ResultLock snapshot const resultLock = await prisma.resultLock.create({ data: { roundId: round8.id, category: session.category, winnerId: session.finalWinnerId, snapshotJson: { sessionId: session.id, votes: session.votes, finalWinner: session.finalWinner, tieBreakMethod: session.tieBreakMethod, wasOverridden: session.wasOverridden, timestamp: new Date() }, lockedBy: actorId, lockedAt: new Date() } }) // Update Project.status → WINNER await prisma.project.update({ where: { id: session.finalWinnerId }, data: { status: 'WINNER' } }) return resultLock } ``` **Result Unlock (Super-Admin Only):** ```typescript async function unlockResult(resultLockId: string, actorId: string, reason: string) { // Check: Is user super-admin? const user = await prisma.user.findUnique({ where: { id: actorId } }) if (user.role !== 'SUPER_ADMIN') { throw new Error('Only super-admins can unlock results') } // Mandatory reason if (!reason || reason.trim().length < 10) { throw new Error('Result unlock requires a detailed reason (min 10 characters)') } // Create unlock event (audit) await prisma.resultUnlockEvent.create({ data: { resultLockId, unlockedBy: actorId, unlockedAt: new Date(), reason } }) // Update result lock await prisma.resultLock.update({ where: { id: resultLockId }, data: { isUnlocked: true, unlockedBy: actorId, unlockedAt: new Date(), unlockReason: reason } }) // Audit log await createAuditLog({ action: 'RESULT_UNLOCKED', userId: actorId, entityType: 'ResultLock', entityId: resultLockId, metadata: { reason } }) } ``` ### 10.5 Admin Controls **Deliberation Dashboard:** - View deliberation session status per category - Monitor jury votes (submitted / total) - View collective rankings (if enabled) - Resolve ties (runoff vote or admin break) - Override result (with mandatory reason) - Finalize and lock results - Unlock results (super-admin only, mandatory reason) ### 10.6 Output & Advancement Criteria **Round 8 Completes:** - Final winners selected per category - Winners: `Project.status = WINNER` - Non-winners: `Project.status = NOT_SELECTED` (or remain FINALIST) - ResultLock snapshots created (immutable audit trail) - Special award winners: Separate tracking (SpecialAwardWinner records) **End of Competition:** - Winners announced publicly - Prizes awarded - Case studies published --- ## 11. Cross-Cutting Behaviors These behaviors apply across multiple rounds and are fundamental to the competition system. ### 11.1 Time Windows & Reminders **Deadline Countdown Display:** - Every round with a deadline shows countdown on relevant dashboards - Color-coded urgency: - **Green** (>7 days): "14 days remaining" - **Yellow** (3-7 days): "⚠️ 5 days remaining — Please submit soon!" - **Orange** (<3 days): "🚨 URGENT: 2 days remaining" - **Red** (<24 hours): "🚨 CRITICAL: 12 hours remaining" (pulsing animation) **Email Reminder Schedule:** ```typescript // Configured per round in IntakeConfig, SubmissionConfig, EvaluationConfig reminderEmailSchedule: [7, 3, 1] // Days before deadline // Example reminder emails: // - 7 days before: "Reminder: Deadline in 7 days" // - 3 days before: "⚠️ Important: Deadline in 3 days" // - 1 day before: "🚨 URGENT: Deadline tomorrow" ``` ### 11.2 Admin Override **Override Authority:** - Admins can intervene at ANY point in the competition - Override types: - Eligibility decisions (R2 filtering) - Assignment changes (R3/R5 jury assignments) - Stage advancement (manually advance/reject projects) - Finalist selection (R3/R5 override AI recommendations) - Winner confirmation (R8 override deliberation result) **Mandatory Audit:** ```typescript // All admin overrides require audit justification type AdminOverride = { action: string // "ELIGIBILITY_OVERRIDE", "ADVANCEMENT_OVERRIDE", etc. entityType: string // "Project", "FilteringResult", etc. entityId: string overrideType: 'APPROVE' | 'REJECT' | 'MODIFY' reason: string // Mandatory, min 10 characters previousValue: unknown // State before override newValue: unknown // State after override actorId: string timestamp: Date } // Stored in DecisionAuditLog table await prisma.decisionAuditLog.create({ data: { action: 'ADMIN_OVERRIDE', userId: actorId, entityType, entityId, metadata: { overrideType, reason, previousValue, newValue } } }) ``` ### 11.3 AI Ranked Shortlist **When Generated:** - End of Round 3 (Jury 1 evaluation) - End of Round 5 (Jury 2 evaluation) - End of any special award evaluation round **How It Works:** ```typescript async function generateAIShortlist(roundId: string, category: 'STARTUP' | 'BUSINESS_CONCEPT') { // Fetch all projects with evaluations const projects = await prisma.project.findMany({ where: { competitionCategory: category, projectRoundStates: { some: { roundId, status: 'IN_PROGRESS' } } }, include: { evaluations: { where: { assignmentId: { in: assignmentIds } }, include: { assignment: { include: { user: true } } } } } }) // Anonymize project data const anonymized = anonymizeProjects(projects) // Send to OpenAI const prompt = ` You are evaluating projects for the Monaco Ocean Protection Challenge. For each project, you have: - Average jury score (1-10) - Individual jury scores - Jury feedback (anonymized) - Project description (anonymized) Task: 1. Rank all projects from best to worst 2. For each project, provide: - Recommended rank (1st, 2nd, 3rd, etc.) - Key strengths (2-3 bullet points) - Key weaknesses (2-3 bullet points) - Overall assessment (1-2 sentences) - Recommendation: "Advance" / "Borderline" / "Do not advance" Return JSON: { rankings: [ { project_id, rank, strengths, weaknesses, assessment, recommendation } ] } ` const response = await openai.chat.completions.create({ model: 'gpt-4o', messages: [ { role: 'system', content: prompt }, { role: 'user', content: JSON.stringify(anonymized) } ], response_format: { type: 'json_object' } }) // Parse and de-anonymize const aiShortlist = parseAIShortlist(response, anonymized.mappings) // Store in database await prisma.aiShortlist.create({ data: { roundId, category, rankingsJson: aiShortlist, generatedAt: new Date() } }) return aiShortlist } ``` **Admin UI:** ``` ┌─ AI RECOMMENDED SHORTLIST (STARTUP) ────────────────┐ │ │ │ 1. OceanClean AI │ │ Strengths: │ │ • Strong technical innovation │ │ • Clear market need │ │ • Experienced team │ │ Weaknesses: │ │ • Financial projections overly optimistic │ │ • Limited pilot data │ │ Recommendation: Advance │ │ │ │ 2. BlueCarbon Solutions │ │ Strengths: │ │ • Proven business model │ │ • Strong partnerships │ │ Weaknesses: │ │ • Incremental innovation │ │ Recommendation: Advance │ │ │ │ ... (continues for all projects) │ │ │ │ [Accept AI Recommendation] [Edit Shortlist] │ └──────────────────────────────────────────────────────┘ ``` **Admin Can:** - Accept AI recommendation - Override AI recommendation (drag to reorder, add/remove projects) - Ignore AI recommendation (manual selection only) ### 11.4 Multi-Round Document Handling **Applicant View:** ``` ┌─ MY DOCUMENTS ───────────────────────────────────────┐ │ │ │ 🔒 Round 1: Application Documents (LOCKED) │ │ Submitted: Jan 25, 2026 │ │ Status: Locked (read-only) │ │ [View Files] │ │ │ │ 🔒 Round 2: Semi-Finalist Materials (LOCKED) │ │ Submitted: Mar 15, 2026 │ │ Status: Locked (read-only) │ │ [View Files] │ │ │ │ ℹ️ Locked documents cannot be edited. │ │ Contact admin if you need to make changes. │ └──────────────────────────────────────────────────────┘ ``` **Jury View (Multi-Window):** ``` ┌─ PROJECT DOCUMENTS ──────────────────────────────────┐ │ [ Round 1 ] [ Round 2 ] [ Mentoring Promoted ] │ │ ▲ │ └──────────────────────────────────────────────────────┘ Tab 1: Round 1 Application (Window 1 files) Tab 2: Semi-Final Submission (Window 2 files) Tab 3: Mentoring Promoted (Files promoted from mentoring workspace) ``` **Admin View:** ``` ┌─ ADMIN FILE MANAGEMENT ──────────────────────────────┐ │ │ │ Project: OceanClean AI │ │ │ │ Window 1: Application Documents (LOCKED) │ │ ├─ Pitch Deck.pdf │ │ │ [View] [Download] [Replace] [Delete] [History] │ │ ├─ Budget.xlsx │ │ │ [View] [Download] [Replace] [Delete] [History] │ │ └─ Team CV.pdf │ │ [View] [Download] [Replace] [Delete] [History] │ │ │ │ Window 2: Semi-Finalist Materials (LOCKED) │ │ ├─ Updated Pitch.pdf (v2, supersedes v1) │ │ │ [View] [Download] [Replace] [Delete] [History] │ │ └─ Video Pitch.mp4 │ │ [View] [Download] [Replace] [Delete] [History] │ │ │ │ ✅ Admin can manage files in ANY window regardless │ │ of lock state. All changes are audited. │ └──────────────────────────────────────────────────────┘ ``` **File Replacement Provenance:** ```typescript // When admin replaces a file: 1. Old file: supersededBy = new file ID, supersededAt = now 2. New file: version = old file version + 1 3. Audit log: action = "FILE_REPLACED_BY_ADMIN", metadata = { oldFileId, newFileId, reason } 4. Both files remain in database (full audit trail) 5. Jury/applicant views show new file only (unless admin enables version history view) ``` ### 11.5 Multi-Jury Structure **Jury Groups:** ``` Jury 1 (Jury Group ID: jury-1) ├─ Purpose: Semi-finalist selection (R3) ├─ Members: 8 judges (e.g., Alice, Bob, Carol, David, Emma, Frank, Grace, Henry) ├─ Assignment: 3 evaluations per project └─ Scoring: Criteria-based (Innovation, Feasibility, Impact, Team) Jury 2 (Jury Group ID: jury-2) ├─ Purpose: Finalist selection (R5) ├─ Members: 12 judges (may overlap with Jury 1) │ Example: Alice, Bob, Carol, David (from Jury 1) + 8 new judges ├─ Assignment: 3-5 evaluations per project └─ Scoring: Criteria-based (Business Model, Team, Presentation, Viability) Jury 3 (Jury Group ID: jury-3) ├─ Purpose: Live Finals voting (R7) ├─ Members: 8 judges (may overlap with Jury 1/2) │ Example: Alice, Bob, Carol, David (from Jury 1) + 4 new judges ├─ Voting: Numeric (1-10) or ranking └─ Deliberation: Final winner confirmation (R8) Special Award Juries (Multiple) ├─ Innovation Award Jury (jury-award-innovation) │ ├─ Purpose: Select Innovation Award winner │ └─ Members: Subset of Jury 2 or dedicated judges └─ Impact Award Jury (jury-award-impact) ├─ Purpose: Select Impact Award winner └─ Members: Dedicated judges (may overlap with main juries) ``` **Judge Can Be in Multiple Juries:** ``` Judge Alice: ├─ Member of Jury 1 (R3) ├─ Member of Jury 2 (R5) ├─ Member of Jury 3 (R7) └─ Member of Innovation Award Jury (R5 parallel) Alice's Dashboard: ├─ Round 3 Assignments (Jury 1): 20 projects ├─ Round 5 Assignments (Jury 2): 15 projects ├─ Round 5 Award Assignments (Innovation Award): 5 projects └─ Round 7 Live Finals (Jury 3): 10 projects (voting interface) ``` **Independent Jury Assignments:** - Each jury group has its own assignment algorithm run - Cap/quota limits are per-jury-group (not global per judge) - Example: Alice has cap 20 for Jury 1, cap 15 for Jury 2, cap 5 for Award Jury - Total workload across all juries tracked but not enforced (admin's responsibility to balance) ### 11.6 Score Independence **During Active Evaluation:** ``` Round 3 (Jury 1) evaluation: ├─ Jury 1 scores are HIDDEN from Jury 2 and Jury 3 ├─ Jury 1 members cannot see each other's scores until submission └─ Admin can see all scores at any time Round 5 (Jury 2) evaluation: ├─ Jury 2 scores are HIDDEN from Jury 3 ├─ Jury 2 can optionally see Jury 1 scores (if config.showPriorJuryData = true) ├─ Jury 2 members cannot see each other's scores until submission └─ Admin can see all scores at any time Round 7 (Live Finals) evaluation: ├─ Jury 3 scores are HIDDEN from audience (if config.anonymizeJuryVotes = true) ├─ Jury 3 can optionally see Jury 1/2 scores (if config.showPriorJuryData = true) ├─ Jury 3 can see each other's scores AFTER voting (peer review) └─ Admin can see all scores at any time ``` **showPriorJuryData Toggle:** ```typescript // In EvaluationConfig for R5: { showPriorJuryData: true // Jury 2 can see Jury 1 scores + feedback } // In LiveFinalConfig for R7: { showPriorJuryData: true // Jury 3 can see Jury 1 + Jury 2 scores + feedback } ``` **Jury View with Prior Data:** ``` ┌─ EVALUATING: OceanClean AI ──────────────────────────┐ │ │ │ [Documents] [Scoring] [Feedback] [Prior Jury Data] │ │ ▲ New tab │ └──────────────────────────────────────────────────────┘ ┌─ PRIOR JURY DATA ────────────────────────────────────┐ │ │ │ Jury 1 Results (Round 3 — Semi-Finalist Selection) │ │ ├─ Average Score: 8.2 / 10 │ │ ├─ Consensus: 0.85 (high agreement) │ │ ├─ Individual Scores: 9, 8, 8 (3 evaluations) │ │ └─ Feedback Summary: │ │ "Strong technical innovation, clear market need, │ │ experienced team. Financial projections overly │ │ optimistic, limited pilot data." │ │ │ │ Jury 2 Results (Round 5 — Finalist Selection) │ │ ├─ Average Score: 8.5 / 10 │ │ ├─ Consensus: 0.90 (very high agreement) │ │ ├─ Individual Scores: 9, 8, 9, 8, 8 (5 evaluations) │ │ └─ Feedback Summary: │ │ "Excellent business model, strong partnerships, │ │ impressive presentation. Some concerns about │ │ scalability." │ │ │ │ ℹ️ This data is for context only. Your evaluation │ │ should be independent. │ └──────────────────────────────────────────────────────┘ ``` **Admin Reports (Cross-Jury Comparison):** ``` ┌─ CROSS-JURY SCORE ANALYSIS ──────────────────────────┐ │ │ │ Project: OceanClean AI │ │ │ │ ┌────────┬──────────┬───────────┬──────────────┐ │ │ │ Jury │ Avg Score│ Consensus │ Trend │ │ │ ├────────┼──────────┼───────────┼──────────────┤ │ │ │ Jury 1 │ 8.2 │ 0.85 │ — │ │ │ │ Jury 2 │ 8.5 │ 0.90 │ ↗ +0.3 │ │ │ │ Jury 3 │ 8.8 │ 0.95 │ ↗ +0.3 │ │ │ └────────┴──────────┴───────────┴──────────────┘ │ │ │ │ Interpretation: │ │ ✅ Consistent upward trend across juries │ │ ✅ High consensus in all rounds │ │ ✅ Strong candidate for winner │ └──────────────────────────────────────────────────────┘ ``` --- ## 12. Monaco 2026 Reference Configuration This section provides a concrete example of how the Monaco Ocean Protection Challenge 2026 competition is configured using all eight rounds. ### 12.1 Competition Profile ```typescript Competition { id: "comp-mopc-2026" name: "Monaco Ocean Protection Challenge 2026" slug: "mopc-2026" description: "Annual competition for ocean conservation innovation" startDate: "2026-02-01" endDate: "2026-09-30" status: "ACTIVE" categories: ["STARTUP", "BUSINESS_CONCEPT"] programId: "program-mopc" } ``` ### 12.2 Round Configuration ```typescript // Round 1: Application Window (INTAKE) Round { id: "round-1-intake" competitionId: "comp-mopc-2026" name: "Application Window" slug: "application-window" roundType: "INTAKE" sortOrder: 0 windowOpenAt: "2026-02-01T00:00:00Z" windowCloseAt: "2026-05-31T23:59:59Z" submissionWindowId: "window-1" configJson: { deadlinePolicy: "FLAG", gracePeriodMinutes: null, allowDraftSubmissions: true, draftExpiryDays: 30, requireTeamProfile: true, maxTeamSize: 5, minTeamSize: 1, autoConfirmReceipt: true, reminderEmailSchedule: [7, 3, 1], publicFormEnabled: true, categoryQuotasEnabled: false } } // Linked SubmissionWindow 1 SubmissionWindow { id: "window-1" competitionId: "comp-mopc-2026" roundId: "round-1-intake" name: "Application Documents" description: "Initial application materials for all applicants" openDate: "2026-02-01T00:00:00Z" closeDate: "2026-05-31T23:59:59Z" latePolicy: "FLAG" gracePeriodHours: null lockOnClose: false // Not locked yet (will lock when R4 opens) fileRequirements: [ { label: "Executive Summary", description: "PDF executive summary (max 2 pages)", isRequired: true, allowedFileTypes: ["pdf"], maxSizeMB: 10, displayOrder: 0 }, { label: "Business Plan", description: "Full business plan or project proposal (PDF)", isRequired: true, allowedFileTypes: ["pdf"], maxSizeMB: 50, displayOrder: 1 }, { label: "Team CV", description: "Team member biographies (PDF)", isRequired: false, allowedFileTypes: ["pdf"], maxSizeMB: 5, displayOrder: 2 } ] } // Round 2: AI Screening (FILTERING) Round { id: "round-2-filtering" competitionId: "comp-mopc-2026" name: "AI Screening & Eligibility Check" slug: "ai-screening" roundType: "FILTERING" sortOrder: 1 windowOpenAt: null // No user-facing window (automated) windowCloseAt: null configJson: { rules: [ { name: "Startups Must Be < 5 Years Old", ruleType: "FIELD_CHECK", config: { conditions: [ { field: "competitionCategory", operator: "equals", value: "STARTUP" }, { field: "foundedAt", operator: "newer_than_years", value: 5 } ], logic: "AND" }, priority: 10, isActive: true, action: "REJECT" }, { name: "Must Upload Executive Summary", ruleType: "DOCUMENT_CHECK", config: { requiredFileTypes: ["pdf"], minFileCount: 2 }, priority: 20, isActive: true, action: "FLAG" } ], aiScreeningEnabled: true, aiRubricPrompt: "Project must demonstrate measurable ocean conservation impact with clear metrics and realistic timeline. Reject spam or unrelated projects.", aiConfidenceThresholds: { high: 0.85, medium: 0.6, low: 0.4 }, aiBatchSize: 20, aiParallelBatches: 2, duplicateDetectionEnabled: true, duplicateThreshold: 1.0, duplicateAction: "FLAG", autoAdvancePassingProjects: false, manualReviewRequired: true } } // Round 3: Jury 1 — Semi-Finalist Selection (EVALUATION) Round { id: "round-3-jury-1" competitionId: "comp-mopc-2026" name: "Jury 1 — Semi-Finalist Selection" slug: "jury-1-evaluation" roundType: "EVALUATION" sortOrder: 2 windowOpenAt: "2026-06-05T00:00:00Z" windowCloseAt: "2026-06-25T23:59:59Z" juryGroupId: "jury-1" configJson: { requiredReviewsPerProject: 3, scoringMode: "criteria", requireFeedback: true, coiRequired: true, peerReviewEnabled: false, anonymizationLevel: "fully_anonymous", aiSummaryEnabled: true, aiAssignmentEnabled: true, advancementMode: "admin_selection", advancementConfig: { perCategory: true, startupCount: 20, conceptCount: 20, tieBreaker: "admin_decides" } } } // Jury 1 Group JuryGroup { id: "jury-1" name: "Jury 1 — Technical Panel" defaultMaxAssignments: 25 defaultCapMode: "SOFT" softCapBuffer: 10 categoryQuotasEnabled: true defaultCategoryQuotas: { STARTUP: { min: 3, max: 15 }, BUSINESS_CONCEPT: { min: 3, max: 15 } } allowJurorCapAdjustment: true allowJurorRatioAdjustment: true } // RoundSubmissionVisibility for R3 (sees Window 1 only) RoundSubmissionVisibility { id: "vis-r3-w1" evaluationRoundId: "round-3-jury-1" submissionWindowId: "window-1" displayLabel: "Application Documents" displayOrder: 1 } // Round 4: Semi-Finalist Submission (SUBMISSION) Round { id: "round-4-submission" competitionId: "comp-mopc-2026" name: "Semi-Finalist Materials" slug: "semi-finalist-submission" roundType: "SUBMISSION" sortOrder: 3 windowOpenAt: "2026-06-28T00:00:00Z" windowCloseAt: "2026-07-20T23:59:59Z" submissionWindowId: "window-2" configJson: { eligibleStatuses: ["PASSED"], notifyEligibleTeams: true, lockPreviousWindows: true, // Window 1 auto-locks when R4 opens windowConfig: { name: "Semi-Finalist Materials", description: "Additional documents for semi-finalists", openDate: "2026-06-28T00:00:00Z", closeDate: "2026-07-20T23:59:59Z", latePolicy: "HARD", gracePeriodHours: null }, fileRequirements: [ { label: "Updated Pitch Deck", description: "PDF pitch deck with updates since Round 1", isRequired: true, allowedFileTypes: ["pdf"], maxSizeMB: 15, displayOrder: 0 }, { label: "Video Pitch", description: "3-minute video pitch (MP4 or MOV)", isRequired: true, allowedFileTypes: ["mp4", "mov"], maxSizeMB: 100, displayOrder: 1 }, { label: "Financial Projections", description: "3-year revenue and cost forecast (Excel or PDF)", isRequired: true, allowedFileTypes: ["xlsx", "pdf"], maxSizeMB: 10, displayOrder: 2 } ] } } // Linked SubmissionWindow 2 SubmissionWindow { id: "window-2" competitionId: "comp-mopc-2026" roundId: "round-4-submission" name: "Semi-Finalist Materials" description: "Additional documents for semi-finalists" openDate: "2026-06-28T00:00:00Z" closeDate: "2026-07-20T23:59:59Z" latePolicy: "HARD" gracePeriodHours: null lockOnClose: true // Auto-locks after deadline fileRequirements: [ { label: "Updated Pitch Deck", description: "PDF pitch deck with updates since Round 1", isRequired: true, allowedFileTypes: ["pdf"], maxSizeMB: 15, displayOrder: 0 }, { label: "Video Pitch", description: "3-minute video pitch (MP4 or MOV)", isRequired: true, allowedFileTypes: ["mp4", "mov"], maxSizeMB: 100, displayOrder: 1 }, { label: "Financial Projections", description: "3-year revenue and cost forecast (Excel or PDF)", isRequired: true, allowedFileTypes: ["xlsx", "pdf"], maxSizeMB: 10, displayOrder: 2 } ] } // Round 5: Jury 2 — Finalist Selection (EVALUATION) Round { id: "round-5-jury-2" competitionId: "comp-mopc-2026" name: "Jury 2 — Finalist Selection" slug: "jury-2-evaluation" roundType: "EVALUATION" sortOrder: 4 windowOpenAt: "2026-07-24T00:00:00Z" windowCloseAt: "2026-08-10T23:59:59Z" juryGroupId: "jury-2" configJson: { requiredReviewsPerProject: 5, // More depth than R3 scoringMode: "criteria", requireFeedback: true, coiRequired: true, peerReviewEnabled: true, // Jurors can see anonymized peer evaluations anonymizationLevel: "show_initials", aiSummaryEnabled: true, aiAssignmentEnabled: true, advancementMode: "ai_recommended", advancementConfig: { perCategory: true, startupCount: 10, conceptCount: 10, tieBreaker: "admin_decides" } } } // Jury 2 Group JuryGroup { id: "jury-2" name: "Jury 2 — Selection Panel" defaultMaxAssignments: 15 defaultCapMode: "SOFT" softCapBuffer: 5 categoryQuotasEnabled: true defaultCategoryQuotas: { STARTUP: { min: 2, max: 10 }, BUSINESS_CONCEPT: { min: 2, max: 10 } } allowJurorCapAdjustment: true allowJurorRatioAdjustment: true } // RoundSubmissionVisibility for R5 (sees Window 1 + Window 2) RoundSubmissionVisibility { id: "vis-r5-w1" evaluationRoundId: "round-5-jury-2" submissionWindowId: "window-1" displayLabel: "Round 1 Application" displayOrder: 1 } RoundSubmissionVisibility { id: "vis-r5-w2" evaluationRoundId: "round-5-jury-2" submissionWindowId: "window-2" displayLabel: "Semi-Final Submissions" displayOrder: 2 } // Round 6: Mentoring (MENTORING) Round { id: "round-6-mentoring" competitionId: "comp-mopc-2026" name: "Finalist Mentoring" slug: "mentoring" roundType: "MENTORING" sortOrder: 5 windowOpenAt: "2026-08-15T00:00:00Z" windowCloseAt: "2026-08-31T23:59:59Z" configJson: { eligibility: "requested_only", chatEnabled: true, fileUploadEnabled: true, fileCommentsEnabled: true, filePromotionEnabled: true, promotionTargetWindowId: "window-2", autoAssignMentors: false, maxProjectsPerMentor: 3, notifyTeamsOnOpen: true, notifyMentorsOnAssign: true, reminderBeforeClose: [7, 3, 1] } } // Round 7: Live Finals (LIVE_FINAL) Round { id: "round-7-live-finals" competitionId: "comp-mopc-2026" name: "Live Finals Ceremony" slug: "live-finals" roundType: "LIVE_FINAL" sortOrder: 6 windowOpenAt: "2026-09-15T00:00:00Z" // Event day windowCloseAt: "2026-09-15T23:59:59Z" juryGroupId: "jury-3" configJson: { juryGroupId: "jury-3", votingMode: "NUMERIC", numericScale: { min: 1, max: 10, allowDecimals: false }, criteriaEnabled: false, audienceVotingEnabled: true, audienceVotingWeight: 20, juryVotingWeight: 80, audienceVotingMode: "CATEGORY_FAVORITES", audienceMaxFavorites: 3, audienceRequireIdentification: true, audienceAntiSpamMeasures: { ipRateLimit: true, deviceFingerprint: true, emailVerification: false }, presentationDurationMinutes: 8, qaDurationMinutes: 5, deliberationEnabled: true, deliberationDurationMinutes: 30, deliberationAllowsVoteRevision: false, categoryWindowsEnabled: true, categoryWindows: [ { category: "STARTUP", projectOrder: ["proj-1", "proj-2", "proj-3", "proj-4", "proj-5", "proj-6", "proj-7", "proj-8", "proj-9", "proj-10"], startTime: "2026-09-15T18:00:00Z", deliberationMinutes: 30 }, { category: "BUSINESS_CONCEPT", projectOrder: ["proj-11", "proj-12", "proj-13", "proj-14", "proj-15", "proj-16", "proj-17", "proj-18", "proj-19", "proj-20"], startTime: "2026-09-15T20:00:00Z", deliberationMinutes: 30 } ], showLiveResults: true, showLiveScores: false, // Show rankings but not actual scores anonymizeJuryVotes: true, requireAllJuryVotes: true, adminCanOverrideVotes: true, adminCanAdjustWeights: false, presentationOrderMode: "MANUAL" } } // Jury 3 Group JuryGroup { id: "jury-3" name: "Jury 3 — Live Finals Panel" // No assignment caps (voting only) } // RoundSubmissionVisibility for R7 (sees Window 1 + Window 2) RoundSubmissionVisibility { id: "vis-r7-w1" evaluationRoundId: "round-7-live-finals" submissionWindowId: "window-1" displayLabel: "Original Application (January)" displayOrder: 1 } RoundSubmissionVisibility { id: "vis-r7-w2" evaluationRoundId: "round-7-live-finals" submissionWindowId: "window-2" displayLabel: "Semi-Final Submission (July)" displayOrder: 2 } // Round 8: Deliberation (CONFIRMATION) Round { id: "round-8-deliberation" competitionId: "comp-mopc-2026" name: "Final Winner Confirmation" slug: "deliberation" roundType: "CONFIRMATION" sortOrder: 7 windowOpenAt: "2026-09-15T22:00:00Z" // Immediately after R7 windowCloseAt: "2026-09-16T23:59:59Z" configJson: { mode: "SINGLE_WINNER_VOTE", showCollectiveRankings: true, tieBreakMethod: "RUNOFF_VOTE", adminCanOverride: true, adminOverrideRequiresReason: true, autoLockOnFinalize: true, unlockRequiresSuperAdmin: true } } ``` ### 12.3 Expected Project Flow ``` 150 applicants submit (R1) ↓ (filtering) 120 projects pass eligibility (R2) ↓ (jury 1 evaluation) 40 semi-finalists selected (20 STARTUP, 20 BUSINESS_CONCEPT) (R3) ↓ (semi-finalist submission) 40 semi-finalists submit Round 2 docs (R4) ↓ (jury 2 evaluation) 20 finalists selected (10 STARTUP, 10 BUSINESS_CONCEPT) (R5) ↓ (mentoring) 15 finalists receive mentoring (5 opt out) (R6) ↓ (live finals) 20 finalists present live (R7) ↓ (deliberation) 2 winners confirmed (1 STARTUP, 1 BUSINESS_CONCEPT) (R8) ``` --- **End of Document** This comprehensive specification defines the complete operational flow of a Monaco Ocean Protection Challenge competition across eight distinct rounds. Each round type is fully detailed with purpose, configuration, behaviors, admin controls, and advancement criteria. The cross-cutting behaviors section documents universal patterns that apply across all rounds.