MOPC-App/docs/claude-architecture-redesign/02-gap-analysis.md

787 lines
40 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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**