Competition/Round architecture: full platform rewrite (Phases 1-9)
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
786
docs/claude-architecture-redesign/02-gap-analysis.md
Normal file
786
docs/claude-architecture-redesign/02-gap-analysis.md
Normal file
@@ -0,0 +1,786 @@
|
||||
# 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**
|
||||
Reference in New Issue
Block a user