# 02. Data Model

## Overview

This document defines the complete Prisma schema and Zod configuration contracts for the MOPC platform redesign. The data model uses **Competition** and **Round** terminology (replacing Pipeline/Stage/Track), implements a 5-layer policy precedence system, and introduces new models for deliberation, mentoring workspace, result locking, and assignment governance.

**Key Design Principles:**
- Competition-centric flow (no intermediate Track layer for main competition)
- Round-type-specific typed configurations (Zod-validated)
- First-class Jury Group entities with per-member policy overrides
- Multi-round submission windows with explicit file requirements
- Deliberation system for final winner confirmation (replaces WinnerProposal)
- Result locking with audit trail
- Mentoring workspace with file promotion to official submissions

---

## Entity Relationship Summary

```
Program (1) ──── (N) Competition
Competition (1) ──── (N) Round
Competition (1) ──── (N) JuryGroup
Competition (1) ──── (N) SubmissionWindow
Competition (1) ──── (N) SpecialAward
Competition (1) ──── (N) DeliberationSession

Round (N) ──── (1) JuryGroup (optional — for EVALUATION, LIVE_FINAL, DELIBERATION)
Round (N) ──── (1) SubmissionWindow (optional — for INTAKE, SUBMISSION)
Round (1) ──── (N) RoundSubmissionVisibility ──── (N) SubmissionWindow

JuryGroup (1) ──── (N) JuryGroupMember ──── (1) User
JuryGroup (1) ──── (N) Assignment

SubmissionWindow (1) ──── (N) SubmissionFileRequirement
SubmissionWindow (1) ──── (N) ProjectFile

Project (1) ──── (N) ProjectRoundState ──── (1) Round
Project (1) ──── (N) Assignment ──── (1) Evaluation
Project (1) ──── (N) ProjectFile
Project (1) ──── (0..1) MentorAssignment ──── (N) MentorFile ──── (N) MentorFileComment
                                       ──── (N) MentorMessage

SpecialAward (N) ──── (1) JuryGroup (optional)
SpecialAward (N) ──── (1) Round (evaluationRound — runs alongside)

DeliberationSession (1) ──── (N) DeliberationVote
DeliberationSession (1) ──── (N) DeliberationResult
DeliberationSession (1) ──── (N) DeliberationParticipant

ResultLock (1) ──── (N) ResultUnlockEvent
```

---

## Core Models

### Competition (replaces Pipeline)

```prisma
model Competition {
  id           String             @id @default(cuid())
  programId    String
  name         String             // "MOPC 2026 Competition"
  slug         String             @unique // "mopc-2026"
  status       CompetitionStatus  @default(DRAFT)

  // Competition-wide settings (typed, not generic JSON)
  categoryMode             String  @default("SHARED")  // "SHARED" (both categories same flow) | "SPLIT" (separate finalist counts)
  startupFinalistCount     Int     @default(3)
  conceptFinalistCount     Int     @default(3)

  // Notification preferences
  notifyOnRoundAdvance     Boolean @default(true)
  notifyOnDeadlineApproach Boolean @default(true)
  deadlineReminderDays     Int[]   @default([7, 3, 1])  // Days before deadline to send reminders

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  program               Program                @relation(fields: [programId], references: [id], onDelete: Cascade)
  rounds                Round[]
  juryGroups            JuryGroup[]
  submissionWindows     SubmissionWindow[]
  specialAwards         SpecialAward[]
  deliberationSessions  DeliberationSession[]
  resultLocks           ResultLock[]

  @@index([programId])
  @@index([status])
}

enum CompetitionStatus {
  DRAFT
  ACTIVE
  CLOSED
  ARCHIVED
}
```

### Round (replaces Stage)

```prisma
model Round {
  id              String      @id @default(cuid())
  competitionId   String
  name            String      // "Jury 1 - Semi-finalist Selection"
  slug            String      // "jury-1-semifinalist"
  roundType       RoundType
  status          RoundStatus @default(ROUND_DRAFT)
  sortOrder       Int         @default(0)

  // Time windows
  windowOpenAt    DateTime?
  windowCloseAt   DateTime?

  // Round-type-specific configuration (validated by Zod per RoundType)
  configJson      Json?       @db.JsonB

  // Optional analytics tag — semantic label for grouping/reporting (e.g., "jury1_selection", "semifinal_docs")
  purposeKey      String?     // Free-form, not an enum — allows flexible analytics without schema changes

  // Links to other entities
  juryGroupId         String?      // Which jury evaluates this round (EVALUATION, LIVE_FINAL, DELIBERATION)
  submissionWindowId  String?      // Which submission window this round collects docs for (INTAKE, SUBMISSION)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  competition              Competition                @relation(fields: [competitionId], references: [id], onDelete: Cascade)
  juryGroup                JuryGroup?                 @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
  submissionWindow         SubmissionWindow?          @relation(fields: [submissionWindowId], references: [id], onDelete: SetNull)

  projectRoundStates       ProjectRoundState[]
  assignments              Assignment[]
  evaluationForms          EvaluationForm[]
  filteringRules           FilteringRule[]
  filteringResults         FilteringResult[]
  filteringJobs            FilteringJob[]
  evaluationSummaries      EvaluationSummary[]
  evaluationDiscussions    EvaluationDiscussion[]
  gracePeriods             GracePeriod[]
  liveCursor               LiveProgressCursor?
  liveVotingSession        LiveVotingSession?
  advancementRules         AdvancementRule[]
  resultLocks              ResultLock[]

  // Visible submission windows (which doc rounds jury can see)
  visibleSubmissionWindows RoundSubmissionVisibility[]

  @@unique([competitionId, slug])
  @@unique([competitionId, sortOrder])
  @@index([competitionId])
  @@index([roundType])
  @@index([status])
}

enum RoundType {
  INTAKE          // Application window — collect initial submissions
  FILTERING       // AI screening — automated eligibility check
  EVALUATION      // Jury evaluation — scoring, feedback, advancement decision
  SUBMISSION      // New submission window — additional docs from advancing teams
  MENTORING       // Mentor-team collaboration period
  LIVE_FINAL      // Live ceremony — real-time voting, audience participation
  DELIBERATION    // Winner confirmation — jury consensus + admin approval
}

enum RoundStatus {
  ROUND_DRAFT     // Being configured, not visible to participants
  ROUND_ACTIVE    // Open/in progress
  ROUND_CLOSED    // Window closed, results pending or finalized
  ROUND_ARCHIVED  // Historical, read-only
}
```

### ProjectRoundState (replaces ProjectStageState)

```prisma
model ProjectRoundState {
  id           String                 @id @default(cuid())
  projectId    String
  roundId      String
  state        ProjectRoundStateValue @default(PENDING)
  enteredAt    DateTime               @default(now())
  exitedAt     DateTime?
  metadataJson Json?                  @db.JsonB   // Round-type-specific state data

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  project  Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
  round    Round   @relation(fields: [roundId], references: [id], onDelete: Cascade)

  @@unique([projectId, roundId])
  @@index([projectId])
  @@index([roundId])
  @@index([state])
}

enum ProjectRoundStateValue {
  PENDING       // Entered round, awaiting action
  IN_PROGRESS   // Active (submission in progress, evaluation ongoing)
  PASSED        // Cleared this round, eligible to advance
  REJECTED      // Did not pass this round
  COMPLETED     // Round fully complete for this project
  WITHDRAWN     // Project withdrew
}
```

### AdvancementRule (replaces StageTransition)

```prisma
model AdvancementRule {
  id              String              @id @default(cuid())
  roundId         String              // The round this rule applies to (source round)
  targetRoundId   String?             // Where projects advance to (null = next round by sortOrder)

  ruleType        AdvancementRuleType
  configJson      Json                @db.JsonB   // Rule-type-specific config

  isDefault       Boolean             @default(true)  // Default advancement path
  sortOrder       Int                 @default(0)     // Priority when multiple rules exist

  createdAt DateTime @default(now())

  round       Round  @relation(fields: [roundId], references: [id], onDelete: Cascade)

  @@index([roundId])
}

enum AdvancementRuleType {
  AUTO_ADVANCE      // All PASSED projects advance to next round automatically
  SCORE_THRESHOLD   // Projects above score threshold advance
  TOP_N             // Top N projects per category advance
  ADMIN_SELECTION   // Admin manually selects who advances
  AI_RECOMMENDED    // AI suggests advancement, admin confirms
}
```

**AdvancementRule configJson shapes:**

```typescript
// AUTO_ADVANCE
{ trigger: "on_round_close" | "immediate" }

// SCORE_THRESHOLD
{ minScore: 7.0, metric: "average_global" | "weighted_criteria" }

// TOP_N
{
  perCategory: true,
  counts: { STARTUP: 10, BUSINESS_CONCEPT: 10 },
  tieBreaker: "admin_decides" | "highest_individual" | "revote"
}

// ADMIN_SELECTION
{ requireAIRecommendation: true, showRankings: true }

// AI_RECOMMENDED
{ topN: 10, confidenceThreshold: 0.7, requireAdminApproval: true }
```

### Program

```prisma
model Program {
  id           String        @id @default(cuid())
  name         String        // e.g., "Monaco Ocean Protection Challenge"
  slug         String?       @unique // URL-friendly identifier
  year         Int           // e.g., 2026
  status       ProgramStatus @default(DRAFT)
  description  String?
  settingsJson Json?         @db.JsonB

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  projects          Project[]
  learningResources LearningResource[]
  partners          Partner[]
  specialAwards     SpecialAward[]
  taggingJobs       TaggingJob[]
  wizardTemplates   WizardTemplate[]
  mentorMilestones  MentorMilestone[]
  competitions      Competition[]

  @@unique([name, year])
  @@index([status])
}

enum ProgramStatus {
  DRAFT
  ACTIVE
  ARCHIVED
}
```

---

## Jury & Assignment Models

### JuryGroup

```prisma
model JuryGroup {
  id              String @id @default(cuid())
  competitionId   String
  name            String      // "Jury 1", "Jury 2", "Jury 3", "Innovation Award Jury"
  slug            String      // "jury-1", "jury-2"
  description     String?     @db.Text
  sortOrder       Int         @default(0)

  // Default assignment configuration for this jury
  defaultMaxAssignments    Int     @default(20)
  defaultCapMode           CapMode @default(SOFT)
  softCapBuffer            Int     @default(2)    // Extra assignments above cap for load balancing

  // Default category quotas (per juror)
  categoryQuotasEnabled    Boolean @default(false)
  defaultCategoryQuotas    Json?   @db.JsonB   // { "STARTUP": { "min": 2, "max": 15 }, "BUSINESS_CONCEPT": { "min": 2, "max": 15 } }

  // Onboarding: can jurors adjust their own cap/ratio during onboarding?
  allowJurorCapAdjustment     Boolean @default(false)
  allowJurorRatioAdjustment   Boolean @default(false)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  competition Competition       @relation(fields: [competitionId], references: [id], onDelete: Cascade)
  members     JuryGroupMember[]
  rounds      Round[]           // Rounds this jury is assigned to
  assignments Assignment[]      // Assignments made through this jury group
  awards      SpecialAward[]    // Awards using this jury

  @@unique([competitionId, slug])
  @@index([competitionId])
}

enum CapMode {
  HARD    // Absolute maximum — AI/algorithm cannot exceed
  SOFT    // Target maximum — can exceed by softCapBuffer for load balancing
  NONE    // No cap — unlimited assignments
}
```

### JuryGroupMember

```prisma
model JuryGroupMember {
  id              String              @id @default(cuid())
  juryGroupId     String
  userId          String
  role            JuryGroupMemberRole @default(MEMBER)  // CHAIR (lead), MEMBER, OBSERVER (view-only)
  joinedAt        DateTime            @default(now())

  // Per-juror overrides (null = use group defaults)
  maxAssignmentsOverride   Int?
  capModeOverride          CapMode?
  categoryQuotasOverride   Json?   @db.JsonB  // Same shape as JuryGroup.defaultCategoryQuotas

  // Juror preferences (set during onboarding)
  preferredStartupRatio    Float?  // 0.0 to 1.0 — desired % of startups (e.g., 0.6 = 60% startups)
  availabilityNotes        String? @db.Text

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  juryGroup           JuryGroup            @relation(fields: [juryGroupId], references: [id], onDelete: Cascade)
  user                User                 @relation(fields: [userId], references: [id], onDelete: Cascade)
  assignmentIntents   AssignmentIntent[]
  deliberationVotes   DeliberationVote[]
  deliberationParticipations DeliberationParticipant[]

  @@unique([juryGroupId, userId])
  @@index([juryGroupId])
  @@index([userId])
}
```

### Assignment (modified)

```prisma
model Assignment {
  id        String  @id @default(cuid())
  userId    String
  projectId String
  roundId   String

  // Assignment info
  method      AssignmentMethod @default(MANUAL)
  isRequired  Boolean          @default(true)
  isCompleted Boolean          @default(false)

  // AI assignment metadata
  aiConfidenceScore   Float?
  expertiseMatchScore Float?
  aiReasoning         String? @db.Text

  // Jury group link
  juryGroupId   String?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  user               User                @relation(fields: [userId], references: [id], onDelete: Cascade)
  project            Project             @relation(fields: [projectId], references: [id], onDelete: Cascade)
  round              Round               @relation(fields: [roundId], references: [id], onDelete: Cascade)
  juryGroup          JuryGroup?          @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
  evaluation         Evaluation?
  conflictOfInterest ConflictOfInterest?
  exceptions         AssignmentException[]

  @@unique([userId, projectId, roundId])
  @@index([userId])
  @@index([projectId])
  @@index([roundId])
  @@index([juryGroupId])
  @@index([isCompleted])
}

enum AssignmentMethod {
  MANUAL
  BULK
  AI_SUGGESTED
  AI_AUTO
  ALGORITHM
}
```

### AssignmentIntent

```prisma
model AssignmentIntent {
  id                String   @id @default(cuid())
  juryGroupMemberId String
  roundId           String
  projectId         String
  source            AssignmentIntentSource   // How this intent was created
  status            AssignmentIntentStatus  @default(PENDING)  // Lifecycle: PENDING → HONORED/OVERRIDDEN/EXPIRED

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  juryGroupMember JuryGroupMember @relation(fields: [juryGroupMemberId], references: [id], onDelete: Cascade)
  round           Round           @relation(fields: [roundId], references: [id], onDelete: Cascade)
  project         Project         @relation(fields: [projectId], references: [id], onDelete: Cascade)

  @@unique([juryGroupMemberId, roundId, projectId])
  @@index([roundId])
  @@index([projectId])
  @@index([status])
}

enum AssignmentIntentSource {
  INVITE         // Created during member invite (pre-assignment)
  ADMIN          // Admin-created intent
  SYSTEM         // System-generated (e.g., from algorithm)
}

enum AssignmentIntentStatus {
  PENDING     // Created, awaiting assignment algorithm execution
  HONORED     // Assignment algorithm materialized this intent into an Assignment record
  OVERRIDDEN  // Admin changed the assignment, superseding this intent
  EXPIRED     // Round completed without this intent being honored
  CANCELLED   // Explicitly cancelled by admin or system
}
```

### AssignmentException

```prisma
model AssignmentException {
  id           String   @id @default(cuid())
  assignmentId String
  reason       String   @db.Text // Why exception was granted
  overCapBy    Int      // How many assignments over cap
  approvedById String
  createdAt    DateTime @default(now())

  // Relations
  assignment Assignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
  approvedBy User       @relation("AssignmentExceptionApprover", fields: [approvedById], references: [id])

  @@index([assignmentId])
  @@index([approvedById])
}
```

---

## Submission & Document Models

### SubmissionWindow

```prisma
model SubmissionWindow {
  id              String         @id @default(cuid())
  competitionId   String
  name            String         // "Round 1 Application Docs", "Semi-finalist Additional Docs"
  slug            String         // "round-1-docs"
  roundNumber     Int            // 1, 2, 3... (sequential)
  sortOrder       Int            @default(0)

  // Window timing
  windowOpenAt    DateTime?
  windowCloseAt   DateTime?

  // Deadline behavior
  deadlinePolicy  DeadlinePolicy @default(FLAG)
  graceHours      Int?           // Hours after windowCloseAt where late submissions accepted

  // Locking behavior
  lockOnClose     Boolean        @default(true)  // Applicants can't edit after window closes
  isLocked        Boolean        @default(false) // Manual lock toggle — admin can lock/unlock independently of window close

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  competition      Competition                @relation(fields: [competitionId], references: [id], onDelete: Cascade)
  fileRequirements SubmissionFileRequirement[]
  projectFiles     ProjectFile[]
  rounds           Round[]                    // Rounds that collect submissions for this window
  visibility       RoundSubmissionVisibility[] // Which evaluation rounds can see these docs

  @@unique([competitionId, slug])
  @@unique([competitionId, roundNumber])
  @@index([competitionId])
}

enum DeadlinePolicy {
  HARD    // Submissions rejected after close
  FLAG    // Submissions accepted but marked late
  GRACE   // Grace period after close, then hard cutoff
}
```

### SubmissionFileRequirement

```prisma
model SubmissionFileRequirement {
  id                  String @id @default(cuid())
  submissionWindowId  String
  name                String           // "Executive Summary", "Business Plan", "Video Pitch"
  description         String?          @db.Text
  acceptedMimeTypes   String[]         // ["application/pdf", "video/*"]
  maxSizeMB           Int?             // Size limit
  isRequired          Boolean @default(true)
  sortOrder           Int     @default(0)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  submissionWindow SubmissionWindow @relation(fields: [submissionWindowId], references: [id], onDelete: Cascade)
  files            ProjectFile[]    // Files uploaded against this requirement

  @@index([submissionWindowId])
}
```

### RoundSubmissionVisibility

```prisma
model RoundSubmissionVisibility {
  id                  String @id @default(cuid())
  roundId             String
  submissionWindowId  String

  canView             Boolean @default(true)   // Jury can see these docs
  displayLabel        String?                  // "Round 1 Docs", "Round 2 Docs" (shown to jury)

  // Relations
  round            Round            @relation(fields: [roundId], references: [id], onDelete: Cascade)
  submissionWindow SubmissionWindow @relation(fields: [submissionWindowId], references: [id], onDelete: Cascade)

  @@unique([roundId, submissionWindowId])
  @@index([roundId])
}
```

### ProjectFile (modified)

```prisma
model ProjectFile {
  id              String @id @default(cuid())
  projectId       String

  // Link to SubmissionWindow instead of legacy roundId
  submissionWindowId  String?
  requirementId       String?          // Links to SubmissionFileRequirement

  fileType        FileType
  fileName        String
  mimeType        String
  size            Int
  bucket          String
  objectKey       String

  isLate          Boolean  @default(false)
  version         Int      @default(1)
  replacedById    String?  @unique

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  project            Project                    @relation(fields: [projectId], references: [id], onDelete: Cascade)
  submissionWindow   SubmissionWindow?          @relation(fields: [submissionWindowId], references: [id], onDelete: SetNull)
  requirement        SubmissionFileRequirement? @relation(fields: [requirementId], references: [id], onDelete: SetNull)
  replacedBy         ProjectFile?               @relation("FileVersion", fields: [replacedById], references: [id])
  previousVersion    ProjectFile?               @relation("FileVersion")
  promotedFrom       MentorFile?                @relation("PromotedFromMentorFile")

  @@index([projectId])
  @@index([submissionWindowId])
  @@index([requirementId])
}

enum FileType {
  EXEC_SUMMARY
  PRESENTATION
  VIDEO
  OTHER
  BUSINESS_PLAN
  VIDEO_PITCH
  SUPPORTING_DOC
}
```

### MentorFile (NEW)

```prisma
model MentorFile {
  id                   String @id @default(cuid())
  mentorAssignmentId   String
  uploadedByUserId     String

  fileName             String
  mimeType             String
  size                 Int
  bucket               String
  objectKey            String
  description          String?  @db.Text

  // Promotion to official submission
  isPromoted           Boolean @default(false)
  promotedToFileId     String?  @unique          // Links to the ProjectFile created on promotion
  promotedAt           DateTime?
  promotedByUserId     String?

  createdAt DateTime @default(now())

  // Relations
  mentorAssignment     MentorAssignment         @relation(fields: [mentorAssignmentId], references: [id], onDelete: Cascade)
  uploadedBy           User                     @relation("MentorFileUploader", fields: [uploadedByUserId], references: [id])
  promotedBy           User?                    @relation("MentorFilePromoter", fields: [promotedByUserId], references: [id])
  promotedFile         ProjectFile?             @relation("PromotedFromMentorFile", fields: [promotedToFileId], references: [id], onDelete: SetNull)
  comments             MentorFileComment[]
  promotionEvents      SubmissionPromotionEvent[]

  @@index([mentorAssignmentId])
  @@index([uploadedByUserId])
}
```

### MentorFileComment (NEW)

```prisma
model MentorFileComment {
  id           String @id @default(cuid())
  mentorFileId String
  authorId     String
  content      String @db.Text

  // Threading support
  parentCommentId String?     // null = top-level comment, non-null = reply

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  mentorFile    MentorFile         @relation(fields: [mentorFileId], references: [id], onDelete: Cascade)
  author        User               @relation("MentorFileCommentAuthor", fields: [authorId], references: [id])
  parentComment MentorFileComment? @relation("CommentThread", fields: [parentCommentId], references: [id], onDelete: Cascade)
  replies       MentorFileComment[] @relation("CommentThread")

  @@index([mentorFileId])
  @@index([authorId])
  @@index([parentCommentId])
}
```

### MentorMessage (NEW)

```prisma
model MentorMessage {
  id          String             @id @default(cuid())
  workspaceId String             // Links to MentorAssignment
  senderId    String
  senderRole  MentorMessageRole  // MENTOR | APPLICANT | ADMIN
  content     String             @db.Text
  createdAt   DateTime           @default(now())

  // Relations
  workspace MentorAssignment @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
  sender    User             @relation("MentorMessageSender", fields: [senderId], references: [id])

  @@index([workspaceId])
  @@index([senderId])
  @@index([createdAt])
}

enum MentorMessageRole {
  MENTOR
  APPLICANT
  ADMIN
}
```

### SubmissionPromotionEvent (NEW)

```prisma
model SubmissionPromotionEvent {
  id               String   @id @default(cuid())
  projectId        String
  roundId          String
  slotKey          String   // Which requirement slot this promoted file fills
  sourceType       SubmissionPromotionSource
  sourceFileId     String?  // MentorFile ID if promoted from mentoring workspace
  promotedById     String
  createdAt        DateTime @default(now())

  // Relations
  project      Project      @relation(fields: [projectId], references: [id], onDelete: Cascade)
  round        Round        @relation(fields: [roundId], references: [id], onDelete: Cascade)
  sourceFile   MentorFile?  @relation(fields: [sourceFileId], references: [id], onDelete: SetNull)
  promotedBy   User         @relation("SubmissionPromoter", fields: [promotedById], references: [id])

  @@index([projectId])
  @@index([roundId])
  @@index([sourceFileId])
}

enum SubmissionPromotionSource {
  MENTOR_FILE          // Promoted from mentoring workspace
  ADMIN_REPLACEMENT    // Admin directly replaced official submission
}
```

### MentorAssignment (modified)

```prisma
model MentorAssignment {
  id        String @id @default(cuid())
  projectId String @unique // One mentor per project
  mentorId  String // User with MENTOR role or expertise

  // Assignment tracking
  method     AssignmentMethod @default(MANUAL)
  assignedAt DateTime         @default(now())
  assignedBy String?

  // AI assignment metadata
  aiConfidenceScore   Float?
  expertiseMatchScore Float?
  aiReasoning         String? @db.Text

  // Tracking
  completionStatus String    @default("in_progress") // 'in_progress' | 'completed' | 'paused'
  lastViewedAt     DateTime?

  // Workspace activation
  workspaceEnabled   Boolean   @default(false)   // Activated when MENTORING round opens
  workspaceOpenAt    DateTime?                   // When mentoring files/chat becomes available
  workspaceCloseAt   DateTime?                   // When workspace access ends

  // Relations
  project              Project                     @relation(fields: [projectId], references: [id], onDelete: Cascade)
  mentor               User                        @relation("MentorAssignment", fields: [mentorId], references: [id])
  notes                MentorNote[]
  milestoneCompletions MentorMilestoneCompletion[]
  messages             MentorMessage[]
  files                MentorFile[]

  @@index([mentorId])
  @@index([method])
}
```

---

## Evaluation & Scoring Models

### Evaluation

```prisma
model Evaluation {
  id              String @id @default(cuid())
  assignmentId    String @unique
  formId          String
  status          EvaluationStatus @default(NOT_STARTED)

  // Scores
  criterionScoresJson Json?    @db.JsonB
  globalScore         Int?
  binaryDecision      Boolean?
  feedbackText        String?  @db.Text

  version     Int       @default(1)
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt
  submittedAt DateTime?

  // Relations
  assignment Assignment     @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
  form       EvaluationForm @relation(fields: [formId], references: [id], onDelete: Cascade)

  @@index([status])
  @@index([submittedAt])
  @@index([formId])
}

enum EvaluationStatus {
  NOT_STARTED
  DRAFT
  SUBMITTED
  LOCKED
}
```

### EvaluationForm

```prisma
model EvaluationForm {
  id      String  @id @default(cuid())
  roundId String
  version Int     @default(1)

  // Form configuration
  criteriaJson Json    @db.JsonB  // Array of { id, label, description, scale, weight, required }
  scalesJson   Json?   @db.JsonB  // { "1-5": { min, max, labels }, "1-10": { min, max, labels } }
  isActive     Boolean @default(false)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  round       Round        @relation(fields: [roundId], references: [id], onDelete: Cascade)
  evaluations Evaluation[]

  @@unique([roundId, version])
  @@index([roundId, isActive])
}
```

### ConflictOfInterest

```prisma
model ConflictOfInterest {
  id           String   @id @default(cuid())
  assignmentId String   @unique
  userId       String
  projectId    String
  hasConflict  Boolean  @default(false)
  conflictType String?  // "financial", "personal", "organizational", "other"
  description  String?  @db.Text
  declaredAt   DateTime @default(now())

  // Admin review
  reviewedById String?
  reviewedAt   DateTime?
  reviewAction String?  // "cleared", "reassigned", "noted"

  // Relations
  assignment Assignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
  user       User       @relation(fields: [userId], references: [id])
  reviewedBy User?      @relation("COIReviewedBy", fields: [reviewedById], references: [id])

  @@index([userId])
  @@index([hasConflict])
}
```

### EvaluationSummary

```prisma
model EvaluationSummary {
  id            String   @id @default(cuid())
  projectId     String
  roundId       String
  summaryJson   Json     @db.JsonB
  generatedAt   DateTime @default(now())
  generatedById String
  model         String
  tokensUsed    Int

  // Relations
  project     Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
  round       Round   @relation(fields: [roundId], references: [id], onDelete: Cascade)
  generatedBy User    @relation("EvaluationSummaryGeneratedBy", fields: [generatedById], references: [id])

  @@unique([projectId, roundId])
  @@index([roundId])
}
```

---

## Deliberation Models (NEW - replaces WinnerProposal/WinnerApproval)

### DeliberationSession

```prisma
model DeliberationSession {
  id                    String             @id @default(cuid())
  competitionId         String
  roundId               String
  category              ProjectCategory
  mode                  DeliberationMode   // SINGLE_WINNER_VOTE | FULL_RANKING
  showCollectiveRankings Boolean           @default(false)
  showPriorJuryData     Boolean            @default(false)
  status                DeliberationStatus // OPEN | VOTING | TALLYING | RUNOFF | LOCKED
  tieBreakMethod        TieBreakMethod     // RUNOFF | ADMIN_DECIDES | SCORE_FALLBACK
  adminOverrideResult   Json?              @db.JsonB
  createdAt             DateTime           @default(now())
  updatedAt             DateTime           @updatedAt

  // Relations
  competition  Competition               @relation(fields: [competitionId], references: [id], onDelete: Cascade)
  round        Round                     @relation(fields: [roundId], references: [id], onDelete: Cascade)
  votes        DeliberationVote[]
  results      DeliberationResult[]
  participants DeliberationParticipant[]

  @@index([competitionId])
  @@index([roundId])
  @@index([status])
}

enum DeliberationMode {
  SINGLE_WINNER_VOTE  // Each jury member votes for one winner
  FULL_RANKING        // Each jury member ranks all projects
}

enum DeliberationStatus {
  OPEN      // Session created, waiting for votes
  VOTING    // Active voting window
  TALLYING  // Counting votes
  RUNOFF    // Tie detected, runoff voting in progress
  LOCKED    // Results finalized, no more changes
}

enum TieBreakMethod {
  RUNOFF           // Create new voting round for tied projects
  ADMIN_DECIDES    // Admin manually breaks tie
  SCORE_FALLBACK   // Use previous round scores to break tie
}

enum ProjectCategory {
  STARTUP           // Existing companies
  BUSINESS_CONCEPT  // Students/graduates
}
```

### DeliberationVote

```prisma
model DeliberationVote {
  id            String   @id @default(cuid())
  sessionId     String
  juryMemberId  String
  projectId     String
  rank          Int?     // ordinal rank in FULL_RANKING mode
  isWinnerPick  Boolean  @default(false) // true in SINGLE_WINNER_VOTE mode
  runoffRound   Int      @default(0)     // 0 = initial, 1+ = runoff rounds
  createdAt     DateTime @default(now())

  // Relations
  session      DeliberationSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
  juryMember   JuryGroupMember     @relation(fields: [juryMemberId], references: [id], onDelete: Cascade)
  project      Project             @relation(fields: [projectId], references: [id], onDelete: Cascade)

  @@unique([sessionId, juryMemberId, projectId, runoffRound])
  @@index([sessionId])
  @@index([juryMemberId])
  @@index([projectId])
}
```

### DeliberationResult

```prisma
model DeliberationResult {
  id                String   @id @default(cuid())
  sessionId         String
  projectId         String
  finalRank         Int
  voteCount         Int      @default(0)
  isAdminOverridden Boolean  @default(false)
  overrideReason    String?  @db.Text

  // Relations
  session DeliberationSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
  project Project             @relation(fields: [projectId], references: [id], onDelete: Cascade)

  @@unique([sessionId, projectId])
  @@index([sessionId])
  @@index([projectId])
}
```

### DeliberationParticipant

```prisma
model DeliberationParticipant {
  id             String                        @id @default(cuid())
  sessionId      String
  userId         String
  status         DeliberationParticipantStatus // REQUIRED | ABSENT_EXCUSED | REPLACED | REPLACEMENT_ACTIVE
  replacedById   String?

  // Relations
  session     DeliberationSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
  user        JuryGroupMember     @relation(fields: [userId], references: [id], onDelete: Cascade)
  replacedBy  User?               @relation("DeliberationReplacement", fields: [replacedById], references: [id])

  @@unique([sessionId, userId])
  @@index([sessionId])
  @@index([userId])
}

enum DeliberationParticipantStatus {
  REQUIRED            // Expected to participate
  ABSENT_EXCUSED      // Absent with permission
  REPLACED            // Original member replaced
  REPLACEMENT_ACTIVE  // Acting as replacement
}
```

---

## Result & Audit Models

### ResultLock (NEW)

```prisma
model ResultLock {
  id             String   @id @default(cuid())
  competitionId  String
  roundId        String
  category       ProjectCategory
  lockedById     String
  resultSnapshot Json     @db.JsonB  // Frozen result data
  lockedAt       DateTime @default(now())

  // Relations
  competition   Competition         @relation(fields: [competitionId], references: [id], onDelete: Cascade)
  round         Round               @relation(fields: [roundId], references: [id], onDelete: Cascade)
  lockedBy      User                @relation("ResultLockCreator", fields: [lockedById], references: [id])
  unlockEvents  ResultUnlockEvent[]

  @@index([competitionId])
  @@index([roundId])
  @@index([category])
}
```

### ResultUnlockEvent (NEW)

```prisma
model ResultUnlockEvent {
  id            String   @id @default(cuid())
  resultLockId  String
  unlockedById  String
  reason        String   @db.Text
  unlockedAt    DateTime @default(now())

  // Relations
  resultLock ResultLock @relation(fields: [resultLockId], references: [id], onDelete: Cascade)
  unlockedBy User       @relation("ResultUnlocker", fields: [unlockedById], references: [id])

  @@index([resultLockId])
  @@index([unlockedById])
}
```

### DecisionAuditLog

```prisma
model DecisionAuditLog {
  id           String  @id @default(cuid())
  eventType    String  // round.transitioned, routing.executed, filtering.completed, etc.
  entityType   String
  entityId     String
  actorId      String?
  detailsJson  Json?   @db.JsonB
  snapshotJson Json?   @db.JsonB // State at time of decision

  createdAt DateTime @default(now())

  @@index([eventType])
  @@index([entityType, entityId])
  @@index([actorId])
  @@index([createdAt])
}
```

**Audit `detailsJson` convention**: All admin override events MUST include `beforeState` and `afterState` fields for diff tracking:

```typescript
// Example: admin overrides eligibility
{
  action: "override_eligibility",
  beforeState: { eligible: false, aiConfidence: 0.35 },
  afterState: { eligible: true },
  reason: "Project meets criteria despite low AI confidence — manual review confirms eligibility"
}

// Example: admin overrides assignment
{
  action: "override_assignment",
  beforeState: { assignedToUserId: "user-a", method: "ALGORITHM" },
  afterState: { assignedToUserId: "user-b", method: "MANUAL" },
  reason: "Reassigned due to scheduling conflict"
}
```

This convention enables:
- Automated diff reports for compliance
- Undo/revert capability for admin overrides
- Before/after comparison in audit UI

---

## Special Award Models

### SpecialAward (modified)

```prisma
model SpecialAward {
  id          String      @id @default(cuid())
  competitionId   String   // Links to Competition
  name        String
  description String?     @db.Text
  criteriaText    String? @db.Text          // For AI eligibility

  // Award mode
  eligibilityMode  AwardEligibilityMode @default(STAY_IN_MAIN)

  // Scoring/voting
  scoringMode     AwardScoringMode @default(PICK_WINNER)
  maxRankedPicks  Int?                      // For RANKED mode

  // Decision
  decisionMode    String @default("JURY_VOTE")  // "JURY_VOTE" | "AWARD_MASTER_DECISION" | "ADMIN_DECISION"

  // Status
  status          AwardStatus @default(DRAFT)

  // Voting window
  votingStartAt   DateTime?
  votingEndAt     DateTime?

  // Runs alongside which evaluation round
  evaluationRoundId String?

  // Jury (can be its own group or share a competition jury group)
  juryGroupId     String?

  // Winner
  winnerProjectId    String?
  winnerOverridden   Boolean @default(false)

  // AI
  useAiEligibility   Boolean @default(false)

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  competition      Competition        @relation(fields: [competitionId], references: [id], onDelete: Cascade)
  evaluationRound  Round?             @relation(fields: [evaluationRoundId], references: [id], onDelete: SetNull)
  juryGroup        JuryGroup?         @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
  winnerProject    Project?           @relation("AwardWinner", fields: [winnerProjectId], references: [id], onDelete: SetNull)
  eligibilities    AwardEligibility[]
  jurors           AwardJuror[]
  votes            AwardVote[]

  @@index([competitionId])
  @@index([status])
  @@index([evaluationRoundId])
}

enum AwardEligibilityMode {
  SEPARATE_POOL    // Projects pulled out of main flow into award-only track
  STAY_IN_MAIN     // Projects remain in main competition, flagged as award-eligible
}

enum AwardStatus {
  DRAFT
  NOMINATIONS_OPEN
  VOTING_OPEN
  CLOSED
  ARCHIVED
}

enum AwardScoringMode {
  PICK_WINNER
  RANKED
  SCORED
}
```

### AwardEligibility

```prisma
model AwardEligibility {
  id              String            @id @default(cuid())
  awardId         String
  projectId       String
  method          EligibilityMethod @default(AUTO)
  eligible        Boolean           @default(false)
  aiReasoningJson Json?             @db.JsonB

  // Admin override
  overriddenBy String?
  overriddenAt DateTime?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  award            SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
  project          Project      @relation(fields: [projectId], references: [id], onDelete: Cascade)
  overriddenByUser User?        @relation("AwardEligibilityOverriddenBy", fields: [overriddenBy], references: [id], onDelete: SetNull)

  @@unique([awardId, projectId])
  @@index([awardId])
  @@index([projectId])
  @@index([eligible])
}

enum EligibilityMethod {
  AUTO
  MANUAL
}
```

### AwardJuror

```prisma
model AwardJuror {
  id      String @id @default(cuid())
  awardId String
  userId  String

  createdAt DateTime @default(now())

  // Relations
  award SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
  user  User         @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([awardId, userId])
  @@index([awardId])
  @@index([userId])
}
```

### AwardVote

```prisma
model AwardVote {
  id        String   @id @default(cuid())
  awardId   String
  userId    String
  projectId String
  rank      Int?     // For RANKED mode
  votedAt   DateTime @default(now())

  // Relations
  award   SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
  user    User         @relation(fields: [userId], references: [id], onDelete: Cascade)
  project Project      @relation(fields: [projectId], references: [id], onDelete: Cascade)

  @@unique([awardId, userId, projectId])
  @@index([awardId])
  @@index([userId])
  @@index([projectId])
}
```

---

## Policy & Override Models

### 5-Layer Policy Precedence

Policy resolution follows this precedence order (highest to lowest):

| Layer | Description | Example Use Case |
|-------|-------------|------------------|
| **1. Admin Override** | Explicit override action with mandatory reason | Admin grants exception to assign 25 projects to a jury member with cap of 20 |
| **2. Per-Member Override** | Individual jury member policy settings | Jury member sets personal cap to 15 instead of group default 20 |
| **3. Jury Group Default** | JuryGroup-level configuration | Jury 1 has defaultMaxAssignments=20, softCapBuffer=2 |
| **4. Program Default** | Program-wide settings in Program.settingsJson | All competitions in MOPC 2026 default to 3 finalists per category |
| **5. System Default** | Platform-wide hardcoded defaults | Default CapMode is SOFT if not specified |

**Resolution Logic:**
- Each policy query returns: `{ value, source: "admin_override" | "member" | "jury_group" | "program" | "system", explanation: string }`
- UI shows policy source and allows drilling into why a value was chosen
- All policy evaluations are logged in DecisionAuditLog

### Policy Models

Policy configuration is embedded in existing models:

**System Defaults** → Hardcoded in application config
**Program Defaults** → `Program.settingsJson`
**Jury Group Defaults** → `JuryGroup.defaultMaxAssignments`, `JuryGroup.defaultCapMode`, etc.
**Per-Member Overrides** → `JuryGroupMember.maxAssignmentsOverride`, `JuryGroupMember.capModeOverride`, etc.
**Admin Overrides** → `AssignmentException`, `FilteringResult.overriddenBy`, `AwardEligibility.overriddenBy`

---

## Enum Definitions

```prisma
enum CompetitionStatus {
  DRAFT
  ACTIVE
  CLOSED
  ARCHIVED
}

enum RoundType {
  INTAKE
  FILTERING
  EVALUATION
  SUBMISSION
  MENTORING
  LIVE_FINAL
  DELIBERATION
}

enum RoundStatus {
  ROUND_DRAFT
  ROUND_ACTIVE
  ROUND_CLOSED
  ROUND_ARCHIVED
}

enum ProjectRoundStateValue {
  PENDING
  IN_PROGRESS
  PASSED
  REJECTED
  COMPLETED
  WITHDRAWN
}

enum AdvancementRuleType {
  AUTO_ADVANCE
  SCORE_THRESHOLD
  TOP_N
  ADMIN_SELECTION
  AI_RECOMMENDED
}

enum CapMode {
  HARD
  SOFT
  NONE
}

enum AssignmentMethod {
  MANUAL
  BULK
  AI_SUGGESTED
  AI_AUTO
  ALGORITHM
}

enum AssignmentIntentSource {
  INVITE
  ADMIN
  SYSTEM
}

enum AssignmentIntentStatus {
  PENDING
  HONORED
  OVERRIDDEN
  EXPIRED
  CANCELLED
}

enum JuryGroupMemberRole {
  CHAIR       // Jury lead — can manage session, see aggregate data
  MEMBER      // Regular juror — votes, scores, provides feedback
  OBSERVER    // View-only — can see evaluations/deliberations but cannot vote
}

enum DeadlinePolicy {
  HARD
  FLAG
  GRACE
}

enum FileType {
  EXEC_SUMMARY
  PRESENTATION
  VIDEO
  OTHER
  BUSINESS_PLAN
  VIDEO_PITCH
  SUPPORTING_DOC
}

enum MentorMessageRole {
  MENTOR
  APPLICANT
  ADMIN
}

enum SubmissionPromotionSource {
  MENTOR_FILE
  ADMIN_REPLACEMENT
}

enum EvaluationStatus {
  NOT_STARTED
  DRAFT
  SUBMITTED
  LOCKED
}

enum DeliberationMode {
  SINGLE_WINNER_VOTE
  FULL_RANKING
}

enum DeliberationStatus {
  OPEN
  VOTING
  TALLYING
  RUNOFF
  LOCKED
}

enum TieBreakMethod {
  RUNOFF
  ADMIN_DECIDES
  SCORE_FALLBACK
}

enum DeliberationParticipantStatus {
  REQUIRED
  ABSENT_EXCUSED
  REPLACED
  REPLACEMENT_ACTIVE
}

enum ProjectCategory {
  STARTUP
  BUSINESS_CONCEPT
}

enum AwardEligibilityMode {
  SEPARATE_POOL
  STAY_IN_MAIN
}

enum AwardStatus {
  DRAFT
  NOMINATIONS_OPEN
  VOTING_OPEN
  CLOSED
  ARCHIVED
}

enum AwardScoringMode {
  PICK_WINNER
  RANKED
  SCORED
}

enum EligibilityMethod {
  AUTO
  MANUAL
}
```

---

## Typed Config Schemas

Each RoundType has a specific Zod-validated config schema stored in `Round.configJson`.

### 1. IntakeConfig

```typescript
import { z } from 'zod';

export const IntakeConfigSchema = z.object({
  // Submission behavior
  allowDrafts: z.boolean().default(true),
  draftExpiryDays: z.number().int().positive().default(30),

  // Accepted categories
  acceptedCategories: z.array(z.enum(['STARTUP', 'BUSINESS_CONCEPT'])).default(['STARTUP', 'BUSINESS_CONCEPT']),

  // File constraints
  maxFileSize: z.number().int().positive().default(50), // MB per file
  allowedFileTypes: z.array(z.string()).default(['application/pdf']),
  maxFilesPerSlot: z.number().int().positive().default(1),

  // Late submission notification
  lateSubmissionNotification: z.boolean().default(true), // Notify admin when late submissions arrive

  // File constraints (applied to all slots in this intake window)
  maxFileSizeMB: z.number().int().positive().default(50),
  maxFilesPerSlot: z.number().int().positive().default(1),
  allowedMimeTypes: z.array(z.string()).default(['application/pdf']),

  // Late submission notification
  lateSubmissionNotification: z.boolean().default(true), // Notify admin when late submissions arrive (FLAG policy)

  // Public form settings
  publicFormEnabled: z.boolean().default(false),
  customFields: z.array(z.object({
    id: z.string(),
    label: z.string(),
    type: z.enum(['text', 'textarea', 'select', 'checkbox', 'date']),
    required: z.boolean().default(false),
    options: z.array(z.string()).optional(),
  })).default([]),
});

export type IntakeConfig = z.infer<typeof IntakeConfigSchema>;
```

### 2. FilteringConfig

```typescript
export const FilteringConfigSchema = z.object({
  // Rule engine
  rules: z.array(z.object({
    id: z.string(),
    name: z.string(),
    ruleType: z.enum(['FIELD_BASED', 'DOCUMENT_CHECK']),
    conditions: z.any(), // Rule-specific conditions
    action: z.enum(['PASS', 'REJECT', 'FLAG']),
  })).default([]),

  // AI screening
  aiScreeningEnabled: z.boolean().default(true),
  aiCriteriaText: z.string().optional(),
  aiConfidenceThresholds: z.object({
    high: z.number().min(0).max(1).default(0.85),   // Auto-pass
    medium: z.number().min(0).max(1).default(0.6),  // Flag for review
    low: z.number().min(0).max(1).default(0.4),     // Auto-reject
  }).default({
    high: 0.85,
    medium: 0.6,
    low: 0.4,
  }),

  // Manual queue
  manualReviewEnabled: z.boolean().default(true),

  // Auto-advancement
  autoAdvanceEligible: z.boolean().default(false), // Passing projects auto-advance without admin review

  // Duplicate detection
  duplicateDetectionEnabled: z.boolean().default(true), // Flag potential duplicate submissions

  // Auto-advance eligible projects (skip admin review for clear passes)
  autoAdvanceEligible: z.boolean().default(false),

  // Duplicate detection
  duplicateDetectionEnabled: z.boolean().default(true),

  // Batch processing
  batchSize: z.number().int().positive().default(20),
});

export type FilteringConfig = z.infer<typeof FilteringConfigSchema>;
```

### 3. EvaluationConfig

```typescript
export const EvaluationConfigSchema = z.object({
  // Assignment settings
  requiredReviewsPerProject: z.number().int().positive().default(3),

  // Scoring
  scoringMode: z.enum(['criteria', 'global', 'binary']).default('criteria'),
  requireFeedback: z.boolean().default(true),
  feedbackMinLength: z.number().int().nonnegative().default(0), // 0 = no minimum
  requireAllCriteriaScored: z.boolean().default(true), // Block submission if any criterion is blank

  // COI
  coiRequired: z.boolean().default(true),

  // Feedback
  feedbackMinLength: z.number().int().nonnegative().default(0), // 0 = no minimum
  requireAllCriteriaScored: z.boolean().default(true),

  // Peer review
  peerReviewEnabled: z.boolean().default(false),
  anonymizationLevel: z.enum(['fully_anonymous', 'show_initials', 'named']).default('fully_anonymous'),

  // AI features
  aiSummaryEnabled: z.boolean().default(false),
  generateAiShortlist: z.boolean().default(true), // AI-ranked shortlist per category at round end
  generateAiShortlist: z.boolean().default(false), // Generate AI-ranked shortlist at round end

  // Advancement
  advancementMode: z.enum(['auto_top_n', 'admin_selection', 'ai_recommended']).default('admin_selection'),
  advancementConfig: z.object({
    perCategory: z.boolean().default(true),
    startupCount: z.number().int().nonnegative().default(10),
    conceptCount: z.number().int().nonnegative().default(10),
    tieBreaker: z.enum(['admin_decides', 'highest_individual', 'revote']).default('admin_decides'),
  }).optional(),
});

export type EvaluationConfig = z.infer<typeof EvaluationConfigSchema>;
```

### 4. SubmissionConfig

```typescript
export const SubmissionConfigSchema = z.object({
  // Who can submit
  eligibleStatuses: z.array(z.enum(['PENDING', 'IN_PROGRESS', 'PASSED', 'REJECTED', 'COMPLETED', 'WITHDRAWN'])).default(['PASSED']),

  // Notification
  notifyEligibleTeams: z.boolean().default(true),

  // Lock previous rounds
  lockPreviousWindows: z.boolean().default(true),
});

export type SubmissionConfig = z.infer<typeof SubmissionConfigSchema>;
```

### 5. MentoringConfig

```typescript
export const MentoringConfigSchema = z.object({
  // Eligibility
  eligibility: z.enum(['all_advancing', 'requested_only']).default('requested_only'),

  // Workspace features
  chatEnabled: z.boolean().default(true),
  fileUploadEnabled: z.boolean().default(true),
  fileCommentsEnabled: z.boolean().default(true),
  filePromotionEnabled: z.boolean().default(true),

  // Promotion target
  promotionTargetWindowId: z.string().optional(),

  // Auto-assignment
  autoAssignMentors: z.boolean().default(false),
});

export type MentoringConfig = z.infer<typeof MentoringConfigSchema>;
```

### 6. LiveFinalConfig

```typescript
export const LiveFinalConfigSchema = z.object({
  // Jury voting
  juryVotingEnabled: z.boolean().default(true),
  votingMode: z.enum(['simple', 'criteria']).default('simple'),

  // Audience voting
  audienceVotingEnabled: z.boolean().default(false),
  audienceVoteWeight: z.number().min(0).max(1).default(0),
  audienceVotingMode: z.enum(['per_project', 'per_category', 'favorites']).default('per_project'),
  audienceMaxFavorites: z.number().int().positive().default(3),
  audienceRequireIdentification: z.boolean().default(false),

  // Deliberation
  deliberationEnabled: z.boolean().default(false),
  deliberationDurationMinutes: z.number().int().positive().default(30),
  showAudienceVotesToJury: z.boolean().default(false),

  // Presentation
  presentationOrderMode: z.enum(['manual', 'random', 'score_based']).default('manual'),
  presentationDurationMinutes: z.number().int().positive().default(15), // Per-project presentation time
  qaDurationMinutes: z.number().int().nonnegative().default(5), // Per-project Q&A time (0 = no Q&A)

  // Results reveal
  revealPolicy: z.enum(['immediate', 'delayed', 'ceremony']).default('ceremony'),
});

export type LiveFinalConfig = z.infer<typeof LiveFinalConfigSchema>;
```

### 7. DeliberationConfig

```typescript
export const DeliberationConfigSchema = z.object({
  // Jury group
  juryGroupId: z.string(),

  // Mode
  mode: z.enum(['SINGLE_WINNER_VOTE', 'FULL_RANKING']).default('SINGLE_WINNER_VOTE'),

  // Display
  showCollectiveRankings: z.boolean().default(false),
  showPriorJuryData: z.boolean().default(false), // Show Jury 1/2 scores/feedback to deliberation jury

  // Tie handling
  tieBreakMethod: z.enum(['RUNOFF', 'ADMIN_DECIDES', 'SCORE_FALLBACK']).default('ADMIN_DECIDES'),

  // Timing
  votingDuration: z.number().int().positive().default(60), // minutes

  // Winner count
  topN: z.number().int().positive().default(3), // How many winners per category (podium size)

  // Admin control
  allowAdminOverride: z.boolean().default(true),
});

export type DeliberationConfig = z.infer<typeof DeliberationConfigSchema>;
```

---

## Migration Notes

### New Models Added
- **Competition** — Replaces Pipeline
- **Round** — Replaces Stage
- **JuryGroup**, **JuryGroupMember** — New jury management system
- **SubmissionWindow**, **SubmissionFileRequirement**, **RoundSubmissionVisibility** — Multi-round document submission
- **MentorFile**, **MentorFileComment**, **MentorMessage** — Mentoring workspace
- **DeliberationSession**, **DeliberationVote**, **DeliberationResult**, **DeliberationParticipant** — Winner confirmation
- **ResultLock**, **ResultUnlockEvent** — Result immutability
- **AssignmentIntent**, **AssignmentException** — Assignment governance
- **SubmissionPromotionEvent** — Mentoring file promotion tracking
- **AdvancementRule** — Replaces StageTransition

### Renamed Models
- `Pipeline` → `Competition`
- `Stage` → `Round`
- `StageType` → `RoundType`
- `StageStatus` → `RoundStatus`
- `ProjectStageState` → `ProjectRoundState`
- `ProjectStageStateValue` → `ProjectRoundStateValue`

### Removed Models
- **Track** — Eliminated (main flow is linear; awards are SpecialAward entities)
- **StageTransition** — Replaced by AdvancementRule
- **WinnerProposal**, **WinnerApproval** — Replaced by Deliberation system

### New Enums Added
- `JuryGroupMemberRole` (CHAIR, MEMBER, OBSERVER) — replaces boolean `isLead`
- `AssignmentIntentStatus` (PENDING, HONORED, OVERRIDDEN, EXPIRED, CANCELLED) — replaces String status

### Modified Models
- **Assignment** — Added `juryGroupId` link
- **Round** — Added `purposeKey` (optional analytics tag)
- **JuryGroupMember** — Changed `isLead: Boolean` to `role: JuryGroupMemberRole`
- **AssignmentIntent** — Changed `status: String` to `status: AssignmentIntentStatus` (proper enum with lifecycle)
- **SubmissionWindow** — Added `isLocked: Boolean` (manual lock override independent of window close)
- **ProjectFile** — Changed `roundId` to `submissionWindowId`, added `requirementId`, added `replacedById` self-reference
- **SpecialAward** — Added `competitionId`, `evaluationRoundId`, `juryGroupId`
- **MentorAssignment** — Added workspace fields (`workspaceEnabled`, `workspaceOpenAt`, etc.)
- **Project** — Added `competitionId` (direct link to competition)
- **DecisionAuditLog** — Documented `beforeState`/`afterState` convention in `detailsJson`

### Field Renames
All `stageId` foreign keys → `roundId`
All `trackId` references → Removed

---

## Cross-References

- See [03-competition-flow.md](./03-competition-flow.md) for end-to-end competition orchestration
- See [04-jury-system.md](./04-jury-system.md) for jury management and assignment logic
- See [05-submission-system.md](./05-submission-system.md) for multi-round document handling
- See [06-mentoring-workspace.md](./06-mentoring-workspace.md) for mentoring file collaboration
- See [07-deliberation-flow.md](./07-deliberation-flow.md) for winner confirmation process
- See [08-result-locking.md](./08-result-locking.md) for result immutability and unlock audit
