MOPC-App/docs/unified-architecture-redesign/03-competition-flow.md

99 KiB
Raw Permalink Blame History

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
  2. Competition Flow Diagram
  3. R1: Intake (Application Window)
  4. R2: Filtering (Eligibility Screening)
  5. R3: Evaluation (Jury 1 — Semi-Finalist Selection)
  6. R4: Submission (Semi-Finalist Documents)
  7. R5: Evaluation (Jury 2 — Finalist Selection)
  8. R6: Mentoring (Finalist Collaboration)
  9. R7: Live Finals (Jury 3 — Live Ceremony)
  10. R8: Deliberation (Final Winner Confirmation)
  11. Cross-Cutting Behaviors
  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

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:

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:

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:

// 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

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:

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:

const emailToProjects = new Map<string, Array<{ id: string; title: string }>>()

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:

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:

// 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

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):

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):

// Before evaluating any project, juror MUST declare COI
async function checkCOI(jurorId: string, projectId: string): Promise<boolean> {
  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:

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:

// 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:

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

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<string, string>
  }

  // 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):

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:

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):

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:

// 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:

// 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:

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)

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:

// 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)

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:

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:

// 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)

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):

// 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:

// 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<string, number>()
  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:

// 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<string, number>()
  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:

// 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:

// 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:

// 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:

// 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):

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:

// 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:

// 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:

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:

// 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:

// 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

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

// 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.