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

40 KiB

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
  2. Per-Step Deep Analysis
  3. Cross-Cutting Gap Analysis
  4. Integration Gaps
  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:
    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:
    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:

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:

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:

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:

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:
    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:
    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:
    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():
    // 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.

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 preferencesessential for workload balancing
  3. Round 1 read-only enforcement + cumulative file viewdata integrity and jury UX
  4. Individual jury confirmation + results freezecompliance 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