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