# Gap Analysis: Current System vs. Target 8-Step Competition Flow

**Document Version:** 1.0
**Date:** 2026-02-15
**Author:** Architecture Review (Claude)

---

## Executive Summary

This gap analysis compares the **current MOPC platform** (pipeline-based, stage-engine architecture) against the **target 8-step competition flow** required for the 2026 Monaco Ocean Protection Challenge.

**Key Findings:**
- **Foundation is Strong**: Pipeline/Track/Stage architecture, stage-engine transitions, AI filtering, jury assignment, and live voting infrastructure are all in place.
- **Critical Gaps**: Multi-jury support (named jury groups with overlap), multi-round submission windows with read-only enforcement, per-juror capacity constraints (hard cap vs soft cap + buffer), category ratio preferences, countdown timers, and mentoring workspace features are **missing or incomplete**.
- **Integration Gaps**: The current system treats each stage independently; the target flow requires **cross-stage coordination** (e.g., Round 1 docs become read-only in Round 2, jury sees cumulative files).

---

## Table of Contents

1. [Feature-by-Feature Comparison Table](#1-feature-by-feature-comparison-table)
2. [Per-Step Deep Analysis](#2-per-step-deep-analysis)
3. [Cross-Cutting Gap Analysis](#3-cross-cutting-gap-analysis)
4. [Integration Gaps](#4-integration-gaps)
5. [Priority Matrix](#5-priority-matrix)

---

## 1. Feature-by-Feature Comparison Table

| Feature | Required by Flow | Current Status | Gap Level | Notes | File References |
|---------|-----------------|----------------|-----------|-------|-----------------|
| **Intake (Submission Round 1)** |
| Public submission form | Applicants upload Round 1 docs, deadline enforcement | ✅ Exists | **None** | `applicantRouter.saveSubmission()` handles create/update, deadline checked via `Stage.windowCloseAt` | `src/server/routers/applicant.ts:126` |
| Configurable deadline behavior | Grace periods, late submission flags | ✅ Exists | **None** | `GracePeriod` model, `isLate` flag on `ProjectFile` | `prisma/schema.prisma:703-728`, `ProjectFile.isLate:606` |
| File requirements per stage | Specify required file types, max size, mime types | ✅ Exists | **None** | `FileRequirement` model linked to stages | `prisma/schema.prisma:569-588` |
| Draft support | Save progress without submitting | ✅ Exists | **None** | `isDraft`, `draftDataJson`, `draftExpiresAt` on `Project` | `prisma/schema.prisma:528-530` |
| **AI Filtering** |
| Automated eligibility screening | Run deterministic + AI rules, band by confidence | ✅ Exists | **None** | `stage-filtering.ts` with banding logic, `FilteringResult` outcome | `src/server/services/stage-filtering.ts:173-191` |
| Admin override capability | Manually resolve flagged projects | ✅ Exists | **None** | `resolveManualDecision()` updates `finalOutcome`, logs override in `OverrideAction` | `src/server/services/stage-filtering.ts:529-611` |
| Duplicate detection | Flag duplicate submissions (same email) | ✅ Exists | **None** | Built-in duplicate check by `submittedByEmail`, always flags (never auto-rejects) | `src/server/services/stage-filtering.ts:267-289` |
| **Jury 1 (Evaluation Round 1)** |
| Semi-finalist selection | Jury evaluates and votes Yes/No | ✅ Exists | **None** | `Evaluation.binaryDecision` field, evaluation submission flow | `src/server/routers/evaluation.ts:130-200` |
| Hard cap per juror | Max N projects per juror (enforced) | ⚠️ **Partial** | **Partial** | `User.maxAssignments` exists but used as global limit, not stage-specific hard cap | `prisma/schema.prisma:249` |
| Soft cap + buffer | Target N, allow up to N+buffer with warning | ❌ **Missing** | **Missing** | No concept of soft cap vs hard cap, no buffer configuration | — |
| Category ratio preferences per juror | Juror wants X% Startup / Y% Concept | ❌ **Missing** | **Missing** | No `User.preferredCategoryRatio` or equivalent | — |
| Explicit Jury 1 group | Named jury entity with members | ❌ **Missing** | **Missing** | All JURY_MEMBER users are global pool, no stage-scoped jury groups | — |
| **Semi-finalist Submission (Submission Round 2)** |
| New doc requirements | Round 2 has different file requirements | ✅ Exists | **None** | Each stage can have its own `FileRequirement` list | `prisma/schema.prisma:569-588` |
| Round 1 docs become read-only | Applicants can't edit/delete Round 1 files | ❌ **Missing** | **Missing** | No `ProjectFile.isReadOnly` or `FileRequirement.allowEdits` field | — |
| Jury sees both rounds | Jury can access Round 1 + Round 2 files | ⚠️ **Partial** | **Partial** | File access checks in `fileRouter.getDownloadUrl()` allow prior stages but complex logic, no explicit "cumulative view" | `src/server/routers/file.ts:66-108` |
| Multi-round submission windows | Distinct open/close dates for Round 1 vs Round 2 | ✅ Exists | **None** | Each stage has `windowOpenAt` / `windowCloseAt` | `prisma/schema.prisma:1888-1889` |
| **Jury 2 (Evaluation Round 2)** |
| Finalist selection | Jury evaluates semifinalists, selects finalists | ✅ Exists | **None** | Same evaluation flow, can configure different form per stage | `prisma/schema.prisma:450-472` |
| Special awards alongside | Run award eligibility + voting in parallel | ✅ Exists | **None** | `SpecialAward` system with `AwardEligibility`, `AwardJuror`, `AwardVote` | `prisma/schema.prisma:1363-1481` |
| Explicit Jury 2 group | Named jury entity, possibly overlapping with Jury 1 | ❌ **Missing** | **Missing** | Same global jury pool issue | — |
| Same cap/ratio features | Per-juror hard cap, soft cap, category ratios | ❌ **Missing** | **Missing** | (Same as Jury 1) | — |
| **Mentoring** |
| Private mentor-team workspace | Chat, file upload, threaded discussions | ⚠️ **Partial** | **Partial** | `MentorMessage` exists but no threading, no file comments, no promotion mechanism | `prisma/schema.prisma:1577-1590` |
| Mentor file upload | Mentor can upload files to project | ❌ **Missing** | **Missing** | No `ProjectFile.uploadedByMentorId` or mentor file upload router endpoint | — |
| Threaded file comments | Comment on specific files with replies | ❌ **Missing** | **Missing** | No `FileComment` model | — |
| File promotion to official submission | Mentor-uploaded file becomes part of official docs | ❌ **Missing** | **Missing** | No promotion workflow or `ProjectFile.promotedFromMentorFileId` | — |
| **Jury 3 Live Finals** |
| Stage manager admin controls | Cursor navigation, pause/resume, queue reorder | ✅ Exists | **None** | `live-control.ts` service with `LiveProgressCursor`, `Cohort` | `src/server/services/live-control.ts:1-619` |
| Jury live voting with notes | Vote during presentation, add notes | ⚠️ **Partial** | **Partial** | `LiveVote` exists but no `notes` field for per-vote commentary | `prisma/schema.prisma:1073-1099` |
| Audience voting | Audience can vote with configurable weight | ✅ Exists | **None** | `AudienceVoter`, `allowAudienceVotes`, `audienceVoteWeight` | `prisma/schema.prisma:1051-1060, 1101-1117` |
| Deliberation period | Time for jury discussion before final vote | ❌ **Missing** | **Missing** | No stage-specific `deliberationDurationMinutes` or deliberation status | — |
| Explicit Jury 3 group | Named jury entity for live finals | ❌ **Missing** | **Missing** | (Same global pool issue) | — |
| **Winner Confirmation** |
| Individual jury member confirmation | Each juror digitally signs off on results | ❌ **Missing** | **Missing** | No `JuryConfirmation` model or per-user signature workflow | — |
| Admin override to force majority | Admin can override and pick winner | ⚠️ **Partial** | **Partial** | `SpecialAward.winnerOverridden` exists, `OverrideAction` logs admin actions, but no explicit "force majority" vs "choose winner" distinction | `prisma/schema.prisma:1388-1389, 2024-2040` |
| Results frozen with audit trail | Immutable record of final decision | ⚠️ **Partial** | **Partial** | `DecisionAuditLog` exists, `OverrideAction` tracks changes, but no `ResultsSnapshot` or explicit freeze mechanism | `prisma/schema.prisma:2042-2057` |
| **Cross-Cutting Features** |
| Multi-jury support (named entities) | Jury 1, Jury 2, Jury 3 with overlapping members | ❌ **Missing** | **Missing** | No `JuryGroup` or `JuryMembership` model | — |
| Countdown timers on dashboards | Show time remaining until deadline | ❌ **Missing** | **Missing** | Backend has `windowCloseAt` but no tRPC endpoint for countdown state | — |
| Email reminders as deadlines approach | Automated reminders at 72h, 24h, 1h | ⚠️ **Partial** | **Partial** | `processEvaluationReminders()` exists for jury, `ReminderLog` tracks sent reminders, but no applicant deadline reminders | `prisma/schema.prisma:1487-1501` |
| Full audit trail for all decisions | Every action logged, immutable | ✅ Exists | **None** | `DecisionAuditLog`, `OverrideAction`, `AuditLog` comprehensive | `prisma/schema.prisma:754-783, 2024-2057` |

**Legend:**
- ✅ **Exists** = Feature fully implemented
- ⚠️ **Partial** = Feature partially implemented, needs extension
- ❌ **Missing** = Feature does not exist

---

## 2. Per-Step Deep Analysis

### Step 1: Intake (Submission Round 1)

**What the Flow Requires:**
- Applicants submit initial docs (executive summary, pitch deck, video)
- Public submission form with deadline enforcement
- Configurable grace periods for late submissions
- Draft support to save progress without submitting
- File type/size validation per requirement

**What Currently Exists:**
- ✅ **Public submission form**: `applicantRouter.saveSubmission()` creates/updates projects, `isDraft` flag allows partial saves
- ✅ **Deadline enforcement**: `Stage.windowOpenAt` / `windowCloseAt` enforced in `evaluationRouter.submit()` and applicant submission logic
- ✅ **Grace periods**: `GracePeriod` model per stage/user, `extendedUntil` overrides default deadline
- ✅ **File requirements**: `FileRequirement` linked to stages, defines `acceptedMimeTypes`, `maxSizeMB`, `isRequired`
- ✅ **Late submission tracking**: `ProjectFile.isLate` flag set if uploaded after deadline

**What's Missing:**
- (None — intake is fully functional)

**What Needs Modification:**
- (None — intake meets requirements)

**File References:**
- `src/server/routers/applicant.ts:126-200` (saveSubmission)
- `prisma/schema.prisma:569-588` (FileRequirement)
- `prisma/schema.prisma:703-728` (GracePeriod)

---

### Step 2: AI Filtering

**What the Flow Requires:**
- Automated eligibility screening using deterministic rules (field checks, doc checks) + AI rubric
- Confidence banding: high confidence auto-pass, low confidence auto-reject, medium confidence flagged for manual review
- Admin override capability to resolve flagged projects
- Duplicate submission detection (never auto-reject, always flag)

**What Currently Exists:**
- ✅ **Filtering service**: `stage-filtering.ts` runs deterministic rules first, then AI screening if deterministic passes
- ✅ **Confidence banding**: `bandByConfidence()` function with thresholds 0.75 (pass) / 0.25 (reject), middle = flagged
- ✅ **Manual queue**: `getManualQueue()` returns flagged projects, `resolveManualDecision()` sets `finalOutcome`
- ✅ **Duplicate detection**: Built-in check by `submittedByEmail`, groups duplicates, always flags (never auto-rejects)
- ✅ **FilteringResult model**: Stores `outcome` (PASSED/FILTERED_OUT/FLAGGED), `ruleResultsJson`, `aiScreeningJson`, `finalOutcome` after override

**What's Missing:**
- (None — filtering is fully functional)

**What Needs Modification:**
- (None — filtering meets requirements)

**File References:**
- `src/server/services/stage-filtering.ts:1-647` (full filtering pipeline)
- `prisma/schema.prisma:1190-1237` (FilteringRule, FilteringResult)

---

### Step 3: Jury 1 (Evaluation Round 1)

**What the Flow Requires:**
- Semi-finalist selection with hard/soft caps per juror
- Per-juror hard cap (e.g., max 20 projects, enforced)
- Per-juror soft cap + buffer (e.g., target 15, allow up to 18 with warning)
- Per-juror category ratio preferences (e.g., "I want 60% Startup / 40% Concept")
- Explicit Jury 1 group (named entity, distinct from Jury 2/Jury 3)

**What Currently Exists:**
- ✅ **Evaluation flow**: `evaluationRouter.submit()` accepts `binaryDecision` for yes/no semifinalist vote
- ✅ **Assignment system**: `stage-assignment.ts` generates assignments with workload balancing
- ⚠️ **Per-juror max**: `User.maxAssignments` exists but treated as global limit across all stages, not stage-specific hard cap
- ⚠️ **Workload scoring**: `calculateWorkloadScore()` in `stage-assignment.ts` uses `preferredWorkload` but not distinct soft vs hard cap
- ❌ **Soft cap + buffer**: No configuration for soft cap + buffer (e.g., target 15, allow up to 18)
- ❌ **Category ratio preferences**: No `User.preferredCategoryRatioJson` or similar field
- ❌ **Named jury groups**: All `JURY_MEMBER` users are a global pool, no `JuryGroup` model to create Jury 1, Jury 2, Jury 3 as separate entities

**What's Missing:**
1. **Soft cap + buffer**: Need `User.targetAssignments` (soft cap) and `User.maxAssignments` (hard cap), with UI warning when juror is in buffer zone
2. **Category ratio preferences**: Need `User.preferredCategoryRatioJson: { STARTUP: 0.6, BUSINESS_CONCEPT: 0.4 }` and assignment scoring that respects ratios
3. **Named jury groups**: Need `JuryGroup` model with `name`, `stageId`, `members[]`, so assignment can be scoped to "Jury 1" vs "Jury 2"

**What Needs Modification:**
- **Assignment service**: Update `stage-assignment.ts` to:
  - Filter jury pool by `JuryGroup.members` for the stage
  - Check both soft cap (warning) and hard cap (reject) when assigning
  - Score assignments based on `preferredCategoryRatioJson` to balance category distribution per juror
- **Schema**: Add `JuryGroup`, `JuryMembership`, modify `User` to have `targetAssignments` and `preferredCategoryRatioJson`
- **Admin UI**: Jury group management, per-juror cap/ratio configuration

**File References:**
- `src/server/services/stage-assignment.ts:1-777` (assignment algorithm)
- `src/server/routers/evaluation.ts:130-200` (evaluation submission)
- `prisma/schema.prisma:241-357` (User model)

---

### Step 4: Semi-finalist Submission (Submission Round 2)

**What the Flow Requires:**
- New doc requirements (e.g., detailed business plan, updated pitch deck)
- Round 1 docs become **read-only** for applicants (no edit/delete)
- Jury sees **both rounds** (cumulative file view)
- Multi-round submission windows (Round 2 opens after Jury 1 closes)

**What Currently Exists:**
- ✅ **Multi-round file requirements**: Each stage can define its own `FileRequirement` list
- ✅ **Multi-round windows**: `Stage.windowOpenAt` / `windowCloseAt` per stage
- ⚠️ **Jury file access**: `fileRouter.getDownloadUrl()` checks if juror has assignment to project, allows access to files from prior stages in same track (lines 66-108), but logic is implicit and complex
- ❌ **Read-only enforcement**: No `ProjectFile.isReadOnly` or `FileRequirement.allowEdits` field
- ❌ **Cumulative view**: No explicit "show all files from all prior stages" flag on stages

**What's Missing:**
1. **Read-only flag**: Need `ProjectFile.isReadOnlyForApplicant: Boolean` set when stage transitions, or `FileRequirement.allowEdits: Boolean` to control mutability
2. **Cumulative view**: Need `Stage.showPriorStageFiles: Boolean` or `Stage.cumulativeFileView: Boolean` to make jury file access explicit
3. **File versioning**: Current `replacedById` allows versioning but doesn't enforce read-only from prior rounds

**What Needs Modification:**
- **Applicant file upload**: Check `isReadOnlyForApplicant` before allowing delete/replace
- **File router**: Simplify jury file access by checking `Stage.cumulativeFileView` instead of complex prior-stage logic
- **Stage transition**: When project moves from Round 1 to Round 2, mark all Round 1 files as `isReadOnlyForApplicant: true`
- **Schema**: Add `ProjectFile.isReadOnlyForApplicant`, `Stage.cumulativeFileView`

**File References:**
- `src/server/routers/file.ts:12-125` (file download authorization)
- `prisma/schema.prisma:590-624` (ProjectFile)
- `prisma/schema.prisma:1879-1922` (Stage)

---

### Step 5: Jury 2 (Evaluation Round 2)

**What the Flow Requires:**
- Finalist selection (same evaluation mechanics as Jury 1)
- Special awards eligibility + voting alongside main track
- Explicit Jury 2 group (named entity, may overlap with Jury 1)
- Same per-juror caps and category ratio features as Jury 1

**What Currently Exists:**
- ✅ **Evaluation flow**: Identical to Jury 1, `binaryDecision` for finalist vote
- ✅ **Special awards**: Full system with `SpecialAward`, `AwardEligibility`, `AwardJuror`, `AwardVote`, AI eligibility screening
- ✅ **Award tracks**: `Track.kind: AWARD` allows award-specific stages to run in parallel
- ❌ **Named Jury 2 group**: Same global jury pool issue as Jury 1

**What's Missing:**
- (Same as Jury 1: named jury groups, soft cap + buffer, category ratio preferences)

**What Needs Modification:**
- (Same as Jury 1: jury group scoping, cap/ratio logic in assignment service)

**File References:**
- `src/server/routers/specialAward.ts:1-150` (award management)
- `prisma/schema.prisma:1363-1481` (award models)

---

### Step 6: Mentoring

**What the Flow Requires:**
- Private mentor-team workspace with:
  - Chat/messaging (already exists)
  - Mentor file upload (mentor uploads docs for team to review)
  - Threaded file comments (comment on specific files with replies)
  - File promotion (mentor-uploaded file becomes part of official submission)

**What Currently Exists:**
- ✅ **Mentor assignment**: `MentorAssignment` model, AI-suggested matching, manual assignment
- ✅ **Mentor messages**: `MentorMessage` model for chat messages between mentor and team
- ❌ **Mentor file upload**: No `ProjectFile.uploadedByMentorId` or mentor file upload endpoint
- ❌ **Threaded file comments**: No `FileComment` model with `parentCommentId` for threading
- ❌ **File promotion**: No workflow to promote mentor-uploaded file to official project submission

**What's Missing:**
1. **Mentor file upload**: Need `ProjectFile.uploadedByMentorId: String?`, extend `fileRouter.getUploadUrl()` to allow mentors to upload
2. **File comments**: Need `FileComment` model:
   ```prisma
   model FileComment {
     id             String   @id @default(cuid())
     fileId         String
     file           ProjectFile @relation(...)
     authorId       String
     author         User @relation(...)
     content        String @db.Text
     parentCommentId String?
     parentComment  FileComment? @relation("CommentReplies", ...)
     replies        FileComment[] @relation("CommentReplies")
     createdAt      DateTime @default(now())
   }
   ```
3. **File promotion**: Need `ProjectFile.promotedFromMentorFileId: String?` and a promotion workflow (admin/team approves mentor file as official doc)

**What Needs Modification:**
- **File router**: Add `mentorUploadFile` mutation, authorization check for mentor role
- **Mentor router**: Add `addFileComment`, `promoteFileToOfficial` mutations
- **Schema**: Add `FileComment`, modify `ProjectFile` to link mentor uploads and promotions

**File References:**
- `src/server/routers/mentor.ts:1-200` (mentor operations)
- `prisma/schema.prisma:1145-1172` (MentorAssignment)
- `prisma/schema.prisma:1577-1590` (MentorMessage)

---

### Step 7: Jury 3 Live Finals

**What the Flow Requires:**
- Stage manager admin controls (cursor navigation, pause/resume, queue reorder) — **ALREADY EXISTS**
- Jury live voting with notes (vote + add commentary per vote)
- Audience voting — **ALREADY EXISTS**
- Deliberation period (pause for jury discussion before final vote)
- Explicit Jury 3 group (named entity for live finals)

**What Currently Exists:**
- ✅ **Live control service**: `live-control.ts` with `LiveProgressCursor`, session management, cursor navigation, queue reordering
- ✅ **Live voting**: `LiveVote` model, jury/audience voting, criteria-based scoring
- ✅ **Cohort management**: `Cohort` groups projects for voting windows
- ⚠️ **Vote notes**: `LiveVote` has no `notes` or `commentary` field for per-vote notes
- ❌ **Deliberation period**: No `Cohort.deliberationDurationMinutes` or deliberation status
- ❌ **Named Jury 3 group**: Same global jury pool issue

**What's Missing:**
1. **Vote notes**: Add `LiveVote.notes: String?` for jury commentary during voting
2. **Deliberation period**: Add `Cohort.deliberationDurationMinutes: Int?`, `Cohort.deliberationStartedAt: DateTime?`, `Cohort.deliberationEndedAt: DateTime?`
3. **Named Jury 3 group**: (Same as Jury 1/Jury 2)

**What Needs Modification:**
- **LiveVote model**: Add `notes` field
- **Cohort model**: Add deliberation fields
- **Live voting router**: Add `startDeliberation()`, `endDeliberation()` procedures
- **Live control service**: Add deliberation status checks to prevent voting during deliberation

**File References:**
- `src/server/services/live-control.ts:1-619` (live session management)
- `src/server/routers/live-voting.ts:1-150` (live voting procedures)
- `prisma/schema.prisma:1035-1071, 1969-2006` (LiveVotingSession, Cohort)

---

### Step 8: Winner Confirmation

**What the Flow Requires:**
- Individual jury member confirmation (each juror digitally signs off on results)
- Admin override to force majority or choose winner
- Results frozen with immutable audit trail

**What Currently Exists:**
- ⚠️ **Admin override**: `SpecialAward.winnerOverridden` flag, `OverrideAction` logs admin actions, but no explicit "force majority" vs "choose winner" distinction
- ⚠️ **Audit trail**: `DecisionAuditLog`, `OverrideAction` comprehensive, but no explicit `ResultsSnapshot` or freeze mechanism
- ❌ **Individual jury confirmation**: No `JuryConfirmation` model for per-user digital signatures

**What's Missing:**
1. **Jury confirmation**: Need `JuryConfirmation` model:
   ```prisma
   model JuryConfirmation {
     id            String   @id @default(cuid())
     stageId       String
     stage         Stage    @relation(...)
     userId        String
     user          User     @relation(...)
     confirmedAt   DateTime @default(now())
     signature     String   // Digital signature or consent hash
     ipAddress     String?
     userAgent     String?
   }
   ```
2. **Results freeze**: Need `Stage.resultsFrozenAt: DateTime?` to mark results as immutable
3. **Override modes**: Add `OverrideAction.overrideMode: Enum(FORCE_MAJORITY, CHOOSE_WINNER)` for clarity

**What Needs Modification:**
- **Live voting router**: Add `confirmResults()` procedure for jury members to sign off
- **Admin router**: Add `freezeResults()` procedure, check `resultsFrozenAt` before allowing further changes
- **Override service**: Update `OverrideAction` creation to include `overrideMode`

**File References:**
- `prisma/schema.prisma:1363-1418` (SpecialAward with winner override)
- `prisma/schema.prisma:2024-2040` (OverrideAction)
- `prisma/schema.prisma:2042-2057` (DecisionAuditLog)

---

## 3. Cross-Cutting Gap Analysis

### Multi-Jury Support (Named Jury Entities with Overlap)

**Requirement:**
- Create named jury groups (Jury 1, Jury 2, Jury 3) with explicit membership lists
- Allow jurors to be members of multiple groups (e.g., Juror A is in Jury 1 and Jury 3 but not Jury 2)
- Scope assignments, evaluations, and live voting to specific jury groups

**Current State:**
- All users with `role: JURY_MEMBER` are treated as a global pool
- No scoping of jury to specific stages or rounds
- `stage-assignment.ts` queries all active jury members without filtering by group

**Gap:**
- ❌ No `JuryGroup` model
- ❌ No `JuryMembership` model to link users to groups
- ❌ No stage-level configuration to specify which jury group evaluates that stage

**Required Schema Changes:**
```prisma
model JuryGroup {
  id          String   @id @default(cuid())
  programId   String
  program     Program  @relation(...)
  name        String   // "Jury 1", "Jury 2", "Jury 3"
  description String?
  createdAt   DateTime @default(now())

  memberships JuryMembership[]
  stages      Stage[] // One-to-many: stages can specify which jury group evaluates them
}

model JuryMembership {
  id           String   @id @default(cuid())
  juryGroupId  String
  juryGroup    JuryGroup @relation(...)
  userId       String
  user         User @relation(...)
  joinedAt     DateTime @default(now())

  @@unique([juryGroupId, userId])
}

// Extend Stage model:
model Stage {
  // ... existing fields
  juryGroupId String?
  juryGroup   JuryGroup? @relation(...)
}
```

**Impact:**
- **High** — Affects assignment generation, evaluation authorization, live voting eligibility
- **Requires**: New admin UI for jury group management, updates to all jury-related queries/mutations

---

### Multi-Round Submission Windows

**Requirement:**
- Distinct submission windows for Round 1 (Intake), Round 2 (Semi-finalist submission)
- Round 1 files become read-only after Round 1 closes
- Jury sees cumulative files from all prior rounds

**Current State:**
- ✅ Each stage has `windowOpenAt` / `windowCloseAt` (multi-round windows exist)
- ⚠️ File access is complex and implicit (checks prior stages in track but no clear flag)
- ❌ No read-only enforcement for applicants after stage transition

**Gap:**
- ❌ No `ProjectFile.isReadOnlyForApplicant` field
- ❌ No `Stage.cumulativeFileView` flag for jury access
- ❌ No automated mechanism to mark files as read-only on stage transition

**Required Schema Changes:**
```prisma
model ProjectFile {
  // ... existing fields
  isReadOnlyForApplicant Boolean @default(false)
}

model Stage {
  // ... existing fields
  cumulativeFileView Boolean @default(false) // If true, jury sees files from all prior stages in track
}
```

**Impact:**
- **Medium** — Affects file upload/delete authorization, jury file listing queries
- **Requires**: Stage transition hook to mark files as read-only, applicant file UI updates, jury file view updates

---

### Per-Juror Hard Cap vs Soft Cap + Buffer

**Requirement:**
- **Hard cap**: Max N projects (e.g., 20), enforced, cannot exceed
- **Soft cap**: Target N projects (e.g., 15), preferred, can exceed with warning
- **Buffer**: Soft cap to hard cap range (e.g., 15-18), shows warning in UI

**Current State:**
- ⚠️ `User.maxAssignments` exists but treated as global hard cap
- ⚠️ `User.preferredWorkload` used in assignment scoring but not enforced as soft cap
- ❌ No buffer concept, no UI warning when juror is over target

**Gap:**
- ❌ No distinction between soft cap and hard cap
- ❌ No buffer configuration or warning mechanism

**Required Schema Changes:**
```prisma
model User {
  // ... existing fields
  targetAssignments Int? // Soft cap (preferred target)
  maxAssignments    Int? // Hard cap (absolute max, enforced)
  // preferredWorkload is deprecated in favor of targetAssignments
}
```

**Assignment Logic Changes:**
- Update `stage-assignment.ts`:
  - Filter candidates to exclude jurors at `maxAssignments`
  - Score jurors higher if below `targetAssignments`, lower if between `targetAssignments` and `maxAssignments` (buffer zone)
  - UI shows warning icon for jurors in buffer zone (target < current < max)

**Impact:**
- **Medium** — Affects assignment generation and admin UI for jury workload
- **Requires**: Update assignment service, admin assignment UI to show soft/hard cap status

---

### Per-Juror Category Ratio Preferences

**Requirement:**
- Juror specifies preferred category distribution (e.g., "I want 60% Startup / 40% Business Concept")
- Assignment algorithm respects these preferences when assigning projects

**Current State:**
- ❌ No category ratio configuration per juror
- ⚠️ Assignment scoring uses tag overlap and workload but not category distribution

**Gap:**
- ❌ No `User.preferredCategoryRatioJson` field
- ❌ Assignment algorithm doesn't score based on category distribution

**Required Schema Changes:**
```prisma
model User {
  // ... existing fields
  preferredCategoryRatioJson Json? @db.JsonB // { "STARTUP": 0.6, "BUSINESS_CONCEPT": 0.4 }
}
```

**Assignment Logic Changes:**
- Update `stage-assignment.ts`:
  - For each juror, calculate current category distribution of assigned projects
  - Score candidates higher if assigning this project would bring juror's distribution closer to `preferredCategoryRatioJson`
  - Example: Juror wants 60/40 Startup/Concept, currently has 70/30, algorithm prefers assigning Concept projects to rebalance

**Impact:**
- **Medium** — Affects assignment generation quality, requires juror onboarding to set preferences
- **Requires**: Update assignment algorithm, admin UI for juror profile editing, onboarding flow

---

### Countdown Timers on Dashboards

**Requirement:**
- Applicant dashboard shows countdown to submission deadline
- Jury dashboard shows countdown to evaluation deadline
- Admin dashboard shows countdown to stage window close

**Current State:**
- ✅ Backend has `Stage.windowCloseAt` timestamp
- ❌ No tRPC endpoint to fetch countdown state (time remaining, status: open/closing soon/closed)
- ❌ Frontend has no countdown component

**Gap:**
- ❌ No `stageRouter.getCountdown()` or similar procedure
- ❌ No frontend countdown component

**Required Changes:**
- Add tRPC procedure:
  ```typescript
  stageRouter.getCountdown: protectedProcedure
    .input(z.object({ stageId: z.string() }))
    .query(async ({ ctx, input }) => {
      const stage = await ctx.prisma.stage.findUniqueOrThrow({ where: { id: input.stageId } })
      const now = new Date()
      const closeAt = stage.windowCloseAt
      if (!closeAt) return { status: 'no_deadline', timeRemaining: null }
      const remaining = closeAt.getTime() - now.getTime()
      if (remaining <= 0) return { status: 'closed', timeRemaining: 0 }
      return {
        status: remaining < 3600000 ? 'closing_soon' : 'open', // < 1 hour = closing soon
        timeRemaining: remaining,
        closeAt,
      }
    })
  ```
- Frontend: Countdown component that polls `getCountdown()` and displays "X days Y hours Z minutes remaining"

**Impact:**
- **Low** — UX improvement, no data model changes
- **Requires**: New tRPC procedure, frontend countdown component, dashboard integration

---

### Email Reminders as Deadlines Approach

**Requirement:**
- Automated email reminders at 72 hours, 24 hours, 1 hour before deadline
- For applicants (submission deadlines) and jury (evaluation deadlines)

**Current State:**
- ⚠️ `processEvaluationReminders()` exists for jury reminders
- ⚠️ `ReminderLog` tracks sent reminders to prevent duplicates
- ❌ No applicant deadline reminder cron job
- ❌ No configurable reminder intervals (hardcoded to 3 days, 24h, 1h in evaluation reminders)

**Gap:**
- ❌ No applicant reminder service
- ❌ No configurable reminder intervals per stage

**Required Changes:**
- Add `Stage.reminderIntervalsJson: Json?` // `[72, 24, 1]` (hours before deadline)
- Add `src/server/services/applicant-reminders.ts`:
  ```typescript
  export async function processApplicantReminders(prisma: PrismaClient) {
    const now = new Date()
    const stages = await prisma.stage.findMany({
      where: { status: 'STAGE_ACTIVE', windowCloseAt: { gte: now } },
    })
    for (const stage of stages) {
      const intervals = (stage.reminderIntervalsJson as number[]) ?? [72, 24, 1]
      for (const hoursBeforeDeadline of intervals) {
        const reminderTime = new Date(stage.windowCloseAt!.getTime() - hoursBeforeDeadline * 3600000)
        if (now >= reminderTime && now < new Date(reminderTime.getTime() + 3600000)) {
          // Send reminders to all applicants with draft projects in this stage
          // Check ReminderLog to avoid duplicates
        }
      }
    }
  }
  ```
- Add cron job in `src/app/api/cron/applicant-reminders/route.ts`

**Impact:**
- **Medium** — Improves applicant engagement, reduces late submissions
- **Requires**: New service, new cron endpoint, extend `ReminderLog` model if needed

---

### Admin Override Capability at Every Step

**Requirement:**
- Admin can override any automated decision (filtering, assignment, voting results)
- Override is logged with reason code and reason text in `OverrideAction`

**Current State:**
- ✅ Filtering: `resolveManualDecision()` overrides flagged projects
- ✅ Assignment: Manual assignment creation bypasses AI
- ⚠️ Live voting: `SpecialAward.winnerOverridden` flag exists but no explicit override flow for live voting results
- ⚠️ Stage transitions: No override capability to force projects between stages

**Gap:**
- ❌ No admin UI to override stage transitions (force project to next stage even if guard fails)
- ❌ No admin override for live voting results (admin can pick winner but not documented as override)

**Required Changes:**
- Add `stageRouter.overrideTransition()` procedure:
  ```typescript
  overrideTransition: adminProcedure
    .input(z.object({
      projectId: z.string(),
      fromStageId: z.string(),
      toStageId: z.string(),
      reasonCode: z.nativeEnum(OverrideReasonCode),
      reasonText: z.string(),
    }))
    .mutation(async ({ ctx, input }) => {
      // Force executeTransition() without validation
      // Log in OverrideAction
    })
  ```
- Add `liveVotingRouter.overrideWinner()` procedure (similar flow)

**Impact:**
- **Low** — Fills gaps in admin control, already mostly exists
- **Requires**: New admin procedures, UI buttons for override actions

---

## 4. Integration Gaps

### Cross-Stage File Visibility

**Issue:**
- Current file access is stage-scoped. Jury assigned to Round 2 can technically access Round 1 files (via complex `fileRouter.getDownloadUrl()` logic checking prior stages), but this is implicit and fragile.
- No clear flag to say "Round 2 jury should see Round 1 + Round 2 files" vs "Round 2 jury should only see Round 2 files".

**Required:**
- Add `Stage.cumulativeFileView: Boolean` — if true, jury sees files from all prior stages in the track.
- Simplify `fileRouter.getDownloadUrl()` authorization logic to check this flag instead of manual prior-stage traversal.

**Impact:**
- **Medium** — Simplifies file access logic, makes jury file view behavior explicit.

---

### Round 1 to Round 2 Transition (File Read-Only Enforcement)

**Issue:**
- When a project transitions from Round 1 (Intake) to Round 2 (Semi-finalist submission), Round 1 files should become read-only for applicants.
- Currently, no mechanism enforces this. Applicants could theoretically delete/replace Round 1 files during Round 2.

**Required:**
- Stage transition hook in `stage-engine.ts` `executeTransition()`:
  ```typescript
  // After creating destination PSS:
  if (fromStage.stageType === 'INTAKE' && toStage.stageType === 'INTAKE') {
    // Mark all project files uploaded in fromStage as read-only for applicant
    await tx.projectFile.updateMany({
      where: { projectId, roundId: fromStageRoundId },
      data: { isReadOnlyForApplicant: true },
    })
  }
  ```
- Applicant file upload/delete checks: Reject if `ProjectFile.isReadOnlyForApplicant: true`.

**Impact:**
- **High** — Ensures data integrity, prevents applicants from tampering with prior round submissions.

---

### Jury Group Scoping Across All Jury-Related Operations

**Issue:**
- Assignments, evaluations, live voting all currently use global jury pool.
- Once `JuryGroup` is introduced, must update every jury-related query/mutation to filter by `Stage.juryGroupId`.

**Affected Areas:**
1. **Assignment generation**: `stage-assignment.ts` `previewStageAssignment()` must filter `prisma.user.findMany({ where: { role: 'JURY_MEMBER', ... } })` to `prisma.juryMembership.findMany({ where: { juryGroupId: stage.juryGroupId } })`.
2. **Evaluation authorization**: `evaluationRouter.submit()` must verify `assignment.userId` is a member of `stage.juryGroupId`.
3. **Live voting authorization**: `liveVotingRouter.submitVote()` must verify juror is in `stage.juryGroupId`.
4. **Admin assignment UI**: Dropdown to select jurors must filter by jury group.

**Impact:**
- **High** — Pervasive change across all jury-related features.
- **Requires**: Careful migration plan, extensive testing.

---

### Countdown Timer Backend Support

**Issue:**
- Dashboards need real-time countdown to deadlines, but no backend service provides this.
- Frontend would need to poll `Stage.windowCloseAt` directly and calculate client-side, or use a tRPC subscription.

**Required:**
- Add `stageRouter.getCountdown()` procedure (described in Cross-Cutting section).
- Frontend uses `trpc.stage.getCountdown.useQuery()` with `refetchInterval: 60000` (1 minute polling).
- Optionally: WebSocket subscription for real-time updates (out of scope for now, polling is sufficient).

**Impact:**
- **Low** — Backend is simple, frontend polling handles real-time updates.

---

## 5. Priority Matrix

Features ranked by **Business Impact** (High/Medium/Low) x **Implementation Effort** (High/Medium/Low).

| Feature | Business Impact | Implementation Effort | Priority Quadrant | Notes |
|---------|----------------|----------------------|-------------------|-------|
| **Multi-jury support (named groups)** | **High** | **High** | **Critical** | Required for all 3 jury rounds, affects assignments/evaluations/voting |
| **Round 1 docs read-only enforcement** | **High** | **Low** | **Quick Win** | Data integrity essential, simple flag + hook |
| **Per-juror hard cap vs soft cap + buffer** | **High** | **Medium** | **Critical** | Ensures balanced workload, prevents burnout |
| **Per-juror category ratio preferences** | **Medium** | **Medium** | **Important** | Improves assignment quality, enhances juror satisfaction |
| **Jury vote notes (live finals)** | **Medium** | **Low** | **Quick Win** | Enhances deliberation, simple schema change |
| **Deliberation period (live finals)** | **Medium** | **Low** | **Quick Win** | Required for live finals flow, simple cohort fields |
| **Individual jury confirmation** | **High** | **Medium** | **Critical** | Legal/compliance requirement for final results |
| **Results freeze mechanism** | **High** | **Low** | **Quick Win** | Immutable audit trail, simple timestamp flag |
| **Cumulative file view flag** | **Medium** | **Low** | **Quick Win** | Simplifies jury file access logic |
| **Mentor file upload** | **Medium** | **Medium** | **Important** | Enhances mentoring, requires file router extension |
| **Threaded file comments** | **Low** | **Medium** | **Nice to Have** | Improves collaboration, but not blocking |
| **File promotion workflow** | **Low** | **Medium** | **Nice to Have** | Advanced feature, can defer to later phase |
| **Countdown timers (UI)** | **Low** | **Low** | **Nice to Have** | UX improvement, no data model changes |
| **Applicant deadline reminders** | **Medium** | **Low** | **Quick Win** | Reduces late submissions, simple cron job |
| **Admin override for stage transitions** | **Low** | **Low** | **Nice to Have** | Edge case, manual workaround exists |

**Priority Quadrants:**
- **Critical (High Impact / High Effort)**: Multi-jury support, jury confirmation — **must do**, high planning required
- **Quick Wins (High Impact / Low Effort)**: Read-only enforcement, results freeze, deliberation period — **do first**
- **Important (Medium Impact / Medium Effort)**: Caps/ratios, mentor file upload — **do after quick wins**
- **Nice to Have (Low Impact / Any Effort)**: File comments threading, countdown timers — **defer or phase 2**

---

## Conclusion

The current MOPC platform has a **solid foundation** with the pipeline/track/stage architecture, stage-engine transitions, AI filtering, jury assignment, and live voting infrastructure fully implemented. The **critical gaps** are:

1. **Multi-jury support** (named jury entities with overlap) — **highest priority**, affects all jury-related features
2. **Per-juror caps and category ratio preferences** — **essential for workload balancing**
3. **Round 1 read-only enforcement + cumulative file view** — **data integrity and jury UX**
4. **Individual jury confirmation + results freeze** — **compliance and audit requirements**
5. **Mentoring workspace features** (file upload, comments, promotion) — **enhances mentoring but lower priority**

**Recommended Approach:**
- **Phase 1 (Quick Wins)**: Read-only enforcement, results freeze, deliberation period, vote notes, applicant reminders — **2-3 weeks**
- **Phase 2 (Critical)**: Multi-jury support, jury confirmation — **4-6 weeks** (complex, pervasive changes)
- **Phase 3 (Important)**: Caps/ratios, mentor file upload — **3-4 weeks**
- **Phase 4 (Nice to Have)**: Threaded comments, file promotion, countdown timers — **defer to post-MVP**

Total estimated effort for Phases 1-3: **9-13 weeks** (assumes single developer, includes testing).

---

**End of Gap Analysis Document**
