MOPC-App/docs/claude-architecture-redesign/09-round-live-finals.md

30 KiB

Round Type: LIVE_FINAL — Live Finals Documentation

Overview

The LIVE_FINAL round type orchestrates the live ceremony where Jury 3 evaluates finalist presentations in real-time. This is Round 7 in the redesigned 8-step competition flow. It combines jury scoring, optional audience participation, deliberation periods, and live results display into a single managed event.

Core capabilities:

  • Real-time stage manager controls (presentation cursor, timing, pause/resume)
  • Jury voting with multiple modes (numeric, ranking, binary)
  • Optional audience voting with weighted scores
  • Per-category presentation windows (STARTUP window, then CONCEPT window)
  • Deliberation period for jury discussion
  • Live results display or ceremony reveal
  • Anti-fraud measures for audience participation

Round 7 position in the flow:

Round 1: Application Window (INTAKE)
Round 2: AI Screening (FILTERING)
Round 3: Jury 1 - Semi-finalist Selection (EVALUATION)
Round 4: Semi-finalist Submission (SUBMISSION)
Round 5: Jury 2 - Finalist Selection (EVALUATION)
Round 6: Finalist Mentoring (MENTORING)
Round 7: Live Finals (LIVE_FINAL) ← THIS DOCUMENT
Round 8: Confirm Winners (CONFIRMATION)

Current System (Pipeline → Track → Stage)

Existing Models

LiveVotingSession — Per-stage voting session:

model LiveVotingSession {
  id                  String    @id @default(cuid())
  stageId             String?   @unique
  status              String    @default("NOT_STARTED") // NOT_STARTED, IN_PROGRESS, PAUSED, COMPLETED
  currentProjectIndex Int       @default(0)
  currentProjectId    String?
  votingStartedAt     DateTime?
  votingEndsAt        DateTime?
  projectOrderJson    Json?     @db.JsonB // Array of project IDs in presentation order

  // Voting configuration
  votingMode   String @default("simple") // "simple" (1-10) | "criteria" (per-criterion scores)
  criteriaJson Json?  @db.JsonB // Array of { id, label, description, scale, weight }

  // Audience settings
  allowAudienceVotes       Boolean @default(false)
  audienceVoteWeight       Float   @default(0) // 0.0 to 1.0
  audienceVotingMode       String  @default("disabled") // "disabled" | "per_project" | "per_category" | "favorites"
  audienceMaxFavorites     Int     @default(3)
  audienceRequireId        Boolean @default(false)
  audienceVotingDuration   Int? // Minutes (null = same as jury)

  tieBreakerMethod         String  @default("admin_decides") // 'admin_decides' | 'highest_individual' | 'revote'
  presentationSettingsJson Json?   @db.JsonB

  stage          Stage?          @relation(...)
  votes          LiveVote[]
  audienceVoters AudienceVoter[]
}

LiveVote — Individual jury or audience vote:

model LiveVote {
  id             String   @id @default(cuid())
  sessionId      String
  projectId      String
  userId         String? // Nullable for audience voters without accounts
  score          Int // 1-10 (or weighted score for criteria mode)
  isAudienceVote Boolean  @default(false)
  votedAt        DateTime @default(now())

  // Criteria scores (used when votingMode="criteria")
  criterionScoresJson Json? @db.JsonB // { [criterionId]: score }

  // Audience voter link
  audienceVoterId String?

  session       LiveVotingSession @relation(...)
  user          User?             @relation(...)
  audienceVoter AudienceVoter?    @relation(...)

  @@unique([sessionId, projectId, userId])
  @@unique([sessionId, projectId, audienceVoterId])
}

AudienceVoter — Registered audience participant:

model AudienceVoter {
  id             String   @id @default(cuid())
  sessionId      String
  token          String   @unique // Unique voting token (UUID)
  identifier     String? // Optional: email, phone, or name
  identifierType String? // "email" | "phone" | "name" | "anonymous"
  ipAddress      String?
  userAgent      String?
  createdAt      DateTime @default(now())

  session LiveVotingSession @relation(...)
  votes   LiveVote[]
}

LiveProgressCursor — Stage manager cursor:

model LiveProgressCursor {
  id               String  @id @default(cuid())
  stageId          String  @unique
  sessionId        String  @unique @default(cuid())
  activeProjectId  String?
  activeOrderIndex Int     @default(0)
  isPaused         Boolean @default(false)

  stage Stage @relation(...)
}

Cohort — Presentation groups:

model Cohort {
  id            String    @id @default(cuid())
  stageId       String
  name          String
  votingMode    String    @default("simple") // simple, criteria, ranked
  isOpen        Boolean   @default(false)
  windowOpenAt  DateTime?
  windowCloseAt DateTime?

  stage    Stage           @relation(...)
  projects CohortProject[]
}

Current Service Functions

src/server/services/live-control.ts:

  • startSession(stageId, actorId) — Initialize/reset cursor
  • setActiveProject(stageId, projectId, actorId) — Set currently presenting project
  • jumpToProject(stageId, orderIndex, actorId) — Jump to specific project in queue
  • reorderQueue(stageId, newOrder, actorId) — Reorder presentation sequence
  • pauseResume(stageId, isPaused, actorId) — Toggle pause state
  • openCohortWindow(cohortId, actorId) — Open voting window for a cohort
  • closeCohortWindow(cohortId, actorId) — Close cohort window

Current tRPC Procedures

src/server/routers/live-voting.ts:

liveVoting.getSession({ stageId })
liveVoting.getSessionForVoting({ sessionId }) // Jury view
liveVoting.getPublicSession({ sessionId }) // Display view
liveVoting.setProjectOrder({ sessionId, projectIds })
liveVoting.setVotingMode({ sessionId, votingMode: 'simple' | 'criteria' })
liveVoting.setCriteria({ sessionId, criteria })
liveVoting.importCriteriaFromForm({ sessionId, formId })
liveVoting.startVoting({ sessionId, projectId, durationSeconds })
liveVoting.stopVoting({ sessionId })
liveVoting.endSession({ sessionId })
liveVoting.vote({ sessionId, projectId, score, criterionScores })
liveVoting.getResults({ sessionId, juryWeight?, audienceWeight? })
liveVoting.updatePresentationSettings({ sessionId, presentationSettingsJson })
liveVoting.updateSessionConfig({ sessionId, allowAudienceVotes, audienceVoteWeight, ... })
liveVoting.registerAudienceVoter({ sessionId, identifier?, identifierType? }) // Public
liveVoting.castAudienceVote({ sessionId, projectId, score, token }) // Public
liveVoting.getAudienceVoterStats({ sessionId })
liveVoting.getAudienceSession({ sessionId }) // Public
liveVoting.getPublicResults({ sessionId }) // Public

Current LiveFinalConfig Type

From src/types/pipeline-wizard.ts:

type LiveFinalConfig = {
  juryVotingEnabled: boolean
  audienceVotingEnabled: boolean
  audienceVoteWeight: number
  cohortSetupMode: 'auto' | 'manual'
  revealPolicy: 'immediate' | 'delayed' | 'ceremony'
}

Current Admin UI

src/components/admin/pipeline/sections/live-finals-section.tsx:

  • Jury voting toggle
  • Audience voting toggle + weight slider (0-100%)
  • Cohort setup mode selector (auto/manual)
  • Result reveal policy selector (immediate/delayed/ceremony)

Redesigned Live Finals Round

Enhanced LiveFinalConfig

New comprehensive config:

type LiveFinalConfig = {
  // Jury configuration
  juryGroupId: string              // Which jury evaluates (Jury 3)

  // Voting mode
  votingMode: 'NUMERIC' | 'RANKING' | 'BINARY'

  // Numeric mode settings
  numericScale?: {
    min: number                     // Default: 1
    max: number                     // Default: 10
    allowDecimals: boolean          // Default: false
  }

  // Criteria-based voting (optional enhancement to NUMERIC)
  criteriaEnabled?: boolean
  criteriaJson?: LiveVotingCriterion[]  // { id, label, description, scale, weight }
  importFromEvalForm?: string           // EvaluationForm ID to import criteria from

  // Ranking mode settings
  rankingSettings?: {
    maxRankedProjects: number       // How many projects each juror ranks (e.g., top 3)
    pointsSystem: 'DESCENDING' | 'BORDA'  // 3-2-1 or Borda count
  }

  // Binary mode settings (simple yes/no)
  binaryLabels?: {
    yes: string                     // Default: "Finalist"
    no: string                      // Default: "Not Selected"
  }

  // Audience voting
  audienceVotingEnabled: boolean
  audienceVotingWeight: number      // 0-100, percentage weight
  juryVotingWeight: number          // complement of audience weight (calculated)
  audienceVotingMode: 'PER_PROJECT' | 'FAVORITES' | 'CATEGORY_FAVORITES'
  audienceMaxFavorites?: number     // For FAVORITES mode
  audienceRequireIdentification: boolean
  audienceAntiSpamMeasures: {
    ipRateLimit: boolean            // Limit votes per IP
    deviceFingerprint: boolean      // Track device ID
    emailVerification: boolean      // Require verified email
  }

  // Presentation timing
  presentationDurationMinutes: number
  qaDurationMinutes: number

  // Deliberation
  deliberationEnabled: boolean
  deliberationDurationMinutes: number
  deliberationAllowsVoteRevision: boolean  // Can jury change votes during deliberation?

  // Category windows
  categoryWindowsEnabled: boolean   // Separate windows per category
  categoryWindows: CategoryWindow[]

  // Results display
  showLiveResults: boolean          // Real-time leaderboard
  showLiveScores: boolean           // Show actual scores vs just rankings
  anonymizeJuryVotes: boolean       // Hide individual jury votes from audience
  requireAllJuryVotes: boolean      // Voting can't end until all jury members vote

  // Override controls
  adminCanOverrideVotes: boolean
  adminCanAdjustWeights: boolean    // Mid-ceremony weight adjustment

  // Presentation order
  presentationOrderMode: 'MANUAL' | 'RANDOM' | 'SCORE_BASED' | 'CATEGORY_SPLIT'
}

type CategoryWindow = {
  category: 'STARTUP' | 'BUSINESS_CONCEPT'
  projectOrder: string[]            // Ordered project IDs
  startTime?: string                // Scheduled start (ISO 8601)
  endTime?: string                  // Scheduled end
  deliberationMinutes?: number      // Override global deliberation duration
}

type LiveVotingCriterion = {
  id: string
  label: string
  description?: string
  scale: number                     // 1-10, 1-5, etc.
  weight: number                    // Sum to 1.0 across all criteria
}

Zod Validation Schema

import { z } from 'zod'

const CategoryWindowSchema = z.object({
  category: z.enum(['STARTUP', 'BUSINESS_CONCEPT']),
  projectOrder: z.array(z.string()),
  startTime: z.string().datetime().optional(),
  endTime: z.string().datetime().optional(),
  deliberationMinutes: z.number().int().min(0).max(120).optional(),
})

const LiveVotingCriterionSchema = z.object({
  id: z.string(),
  label: z.string().min(1).max(100),
  description: z.string().max(500).optional(),
  scale: z.number().int().min(1).max(100),
  weight: z.number().min(0).max(1),
})

export const LiveFinalConfigSchema = z.object({
  // Jury
  juryGroupId: z.string(),

  // Voting mode
  votingMode: z.enum(['NUMERIC', 'RANKING', 'BINARY']),

  // Numeric mode settings
  numericScale: z.object({
    min: z.number().int().default(1),
    max: z.number().int().default(10),
    allowDecimals: z.boolean().default(false),
  }).optional(),

  // Criteria
  criteriaEnabled: z.boolean().optional(),
  criteriaJson: z.array(LiveVotingCriterionSchema).optional(),
  importFromEvalForm: z.string().optional(),

  // Ranking
  rankingSettings: z.object({
    maxRankedProjects: z.number().int().min(1).max(20),
    pointsSystem: z.enum(['DESCENDING', 'BORDA']),
  }).optional(),

  // Binary
  binaryLabels: z.object({
    yes: z.string().default('Finalist'),
    no: z.string().default('Not Selected'),
  }).optional(),

  // Audience
  audienceVotingEnabled: z.boolean(),
  audienceVotingWeight: z.number().min(0).max(100),
  juryVotingWeight: z.number().min(0).max(100),
  audienceVotingMode: z.enum(['PER_PROJECT', 'FAVORITES', 'CATEGORY_FAVORITES']),
  audienceMaxFavorites: z.number().int().min(1).max(20).optional(),
  audienceRequireIdentification: z.boolean(),
  audienceAntiSpamMeasures: z.object({
    ipRateLimit: z.boolean(),
    deviceFingerprint: z.boolean(),
    emailVerification: z.boolean(),
  }),

  // Timing
  presentationDurationMinutes: z.number().int().min(1).max(60),
  qaDurationMinutes: z.number().int().min(0).max(30),

  // Deliberation
  deliberationEnabled: z.boolean(),
  deliberationDurationMinutes: z.number().int().min(0).max(120),
  deliberationAllowsVoteRevision: z.boolean(),

  // Category windows
  categoryWindowsEnabled: z.boolean(),
  categoryWindows: z.array(CategoryWindowSchema),

  // Results
  showLiveResults: z.boolean(),
  showLiveScores: z.boolean(),
  anonymizeJuryVotes: z.boolean(),
  requireAllJuryVotes: z.boolean(),

  // Overrides
  adminCanOverrideVotes: z.boolean(),
  adminCanAdjustWeights: z.boolean(),

  // Presentation order
  presentationOrderMode: z.enum(['MANUAL', 'RANDOM', 'SCORE_BASED', 'CATEGORY_SPLIT']),
}).refine(
  (data) => {
    // Ensure weights sum to 100
    return data.audienceVotingWeight + data.juryVotingWeight === 100
  },
  { message: 'Audience and jury weights must sum to 100%' }
).refine(
  (data) => {
    // If criteria enabled, must have criteria
    if (data.criteriaEnabled && (!data.criteriaJson || data.criteriaJson.length === 0)) {
      return false
    }
    return true
  },
  { message: 'Criteria-based voting requires at least one criterion' }
).refine(
  (data) => {
    // Criteria weights must sum to 1.0
    if (data.criteriaJson && data.criteriaJson.length > 0) {
      const weightSum = data.criteriaJson.reduce((sum, c) => sum + c.weight, 0)
      return Math.abs(weightSum - 1.0) < 0.01
    }
    return true
  },
  { message: 'Criteria weights must sum to 1.0' }
)

Stage Manager — Admin Controls

The Stage Manager is the admin control panel for orchestrating the live ceremony. It provides real-time control over presentation flow, voting windows, and emergency interventions.

Ceremony State Machine

Ceremony State Flow:
NOT_STARTED → (start session) → IN_PROGRESS → (deliberation starts) → DELIBERATION → (voting ends) → COMPLETED

NOT_STARTED:
  - Session created but not started
  - Projects ordered (manual or automatic)
  - Jury and audience links generated
  - Stage manager can preview setup

IN_PROGRESS:
  - Presentations ongoing
  - Per-project state: WAITING → PRESENTING → Q_AND_A → VOTING → VOTED → SCORED
  - Admin can pause, skip, reorder on the fly

DELIBERATION:
  - Timer running for deliberation period
  - Jury can discuss (optional chat/discussion interface)
  - Votes may be revised (if deliberationAllowsVoteRevision=true)
  - Admin can extend deliberation time

COMPLETED:
  - All voting finished
  - Results calculated
  - Ceremony locked (or unlocked for result reveal)

Per-Project State

Each project in the live finals progresses through these states:

WAITING       → Project queued, not yet presenting
PRESENTING    → Presentation in progress (timer: presentationDurationMinutes)
Q_AND_A       → Q&A session (timer: qaDurationMinutes)
VOTING        → Voting window open (jury + audience can vote)
VOTED         → Voting window closed, awaiting next action
SCORED        → Scores calculated, moving to next project
SKIPPED       → Admin skipped this project (emergency override)

Stage Manager UI Controls

ASCII Mockup:

┌─────────────────────────────────────────────────────────────────────┐
│ LIVE FINALS STAGE MANAGER                   Session: live-abc-123   │
├─────────────────────────────────────────────────────────────────────┤
│ Status: IN_PROGRESS     Category: STARTUP      Jury: Jury 3 (8/8)   │
│                                                                       │
│ [Pause Ceremony]  [End Session]  [Emergency Stop]                    │
└─────────────────────────────────────────────────────────────────────┘

┌─ CURRENT PROJECT ───────────────────────────────────────────────────┐
│ Project #3 of 6 (STARTUP)                                            │
│ Title: "OceanSense AI" — Team: AquaTech Solutions                    │
│                                                                       │
│ State: VOTING                                                         │
│ ┌─ Presentation Timer ────┐  ┌─ Q&A Timer ─────┐  ┌─ Voting Timer ─┐│
│ │ Completed: 8:00 / 8:00  │  │ Completed: 5:00  │  │ 0:45 remaining ││
│ └─────────────────────────┘  └──────────────────┘  └────────────────┘│
│                                                                       │
│ Jury Votes: 6 / 8 (75%)                                               │
│ [✓] Alice Chen    [✓] Bob Martin     [ ] Carol Davis                 │
│ [✓] David Lee     [✓] Emma Wilson    [ ] Frank Garcia                │
│ [✓] Grace Huang   [✓] Henry Thompson                                 │
│                                                                       │
│ Audience Votes: 142                                                   │
│                                                                       │
│ [Skip Project]  [Reset Votes]  [Extend Time +1min]  [Next Project]   │
└───────────────────────────────────────────────────────────────────────┘

┌─ PROJECT QUEUE ─────────────────────────────────────────────────────┐
│  [✓] 1. AquaClean Tech (STARTUP)      — Score: 8.2 (Completed)       │
│  [✓] 2. BlueCarbon Solutions (STARTUP) — Score: 7.8 (Completed)       │
│  [>] 3. OceanSense AI (STARTUP)        — Voting in progress           │
│  [ ] 4. MarineTech Innovations (STARTUP) — Waiting                    │
│  [ ] 5. CoralGuard (STARTUP)           — Waiting                      │
│  [ ] 6. DeepSea Robotics (STARTUP)     — Waiting                      │
│                                                                       │
│  [Reorder Queue]  [Jump to Project...]  [Add Project]                │
└───────────────────────────────────────────────────────────────────────┘

┌─ CATEGORY WINDOWS ──────────────────────────────────────────────────┐
│ Window 1: STARTUP (6 projects)                                        │
│   Status: IN_PROGRESS (Project 3/6)                                   │
│   Started: 2026-05-15 18:00:00                                        │
│   [Close Window & Start Deliberation]                                 │
│                                                                       │
│ Window 2: BUSINESS_CONCEPT (6 projects)                               │
│   Status: WAITING                                                     │
│   Scheduled: 2026-05-15 19:30:00                                      │
│   [Start Window Early]                                                │
└───────────────────────────────────────────────────────────────────────┘

┌─ LIVE LEADERBOARD (STARTUP) ────────────────────────────────────────┐
│ Rank | Project               | Jury Avg | Audience | Weighted | Gap  │
│------+-----------------------+----------+----------+----------+------│
│  1   | AquaClean Tech        |   8.5    |   7.2    |   8.2    |  —   │
│  2   | BlueCarbon Solutions  |   8.0    |   7.4    |   7.8    | -0.4 │
│  3   | OceanSense AI         |   —      |   6.8    |   —      |  —   │
│  4   | MarineTech Innov.     |   —      |   —      |   —      |  —   │
│  5   | CoralGuard            |   —      |   —      |   —      |  —   │
│  6   | DeepSea Robotics      |   —      |   —      |   —      |  —   │
└───────────────────────────────────────────────────────────────────────┘

┌─ CEREMONY LOG ──────────────────────────────────────────────────────┐
│ 18:43:22 — Voting opened for "OceanSense AI"                          │
│ 18:42:10 — Q&A period ended                                           │
│ 18:37:05 — Q&A period started                                         │
│ 18:29:00 — Presentation started: "OceanSense AI"                      │
│ 18:28:45 — Voting closed for "BlueCarbon Solutions"                   │
│ 18:27:30 — All jury votes received for "BlueCarbon Solutions"         │
└───────────────────────────────────────────────────────────────────────┘

┌─ ADMIN OVERRIDE PANEL ──────────────────────────────────────────────┐
│ [Override Individual Vote...]  [Adjust Weights...]  [Reset Session]  │
└───────────────────────────────────────────────────────────────────────┘

Stage Manager Features

Core controls:

  1. Session Management

    • Start session (initialize cursor, generate jury/audience links)
    • Pause ceremony (freeze all timers, block votes)
    • Resume ceremony
    • End session (lock results, trigger CONFIRMATION round)
  2. Project Navigation

    • Jump to specific project
    • Skip project (emergency)
    • Reorder queue (drag-and-drop or modal)
    • Add project mid-ceremony (rare edge case)
  3. Timer Controls

    • Start presentation timer
    • Start Q&A timer
    • Start voting timer
    • Extend timer (+1 min, +5 min)
    • Manual timer override
  4. Voting Window Management

    • Open voting for current project
    • Close voting early
    • Require all jury votes before closing
    • Reset votes (emergency undo)
  5. Category Window Controls

    • Open category window (STARTUP or BUSINESS_CONCEPT)
    • Close category window
    • Start deliberation period
    • Advance to next category
  6. Emergency Controls

    • Skip project
    • Reset individual vote
    • Reset all votes for project
    • Pause ceremony (emergency)
    • Force end session
  7. Override Controls (if adminCanOverrideVotes=true):

    • Override individual jury vote
    • Adjust audience/jury weights mid-ceremony
    • Manual score adjustment
  8. Real-Time Monitoring

    • Live vote count (jury + audience)
    • Missing jury votes indicator
    • Audience voter count
    • Leaderboard (if showLiveResults=true)
    • Ceremony event log

Jury 3 Voting Experience

Jury Dashboard

ASCII Mockup:

┌─────────────────────────────────────────────────────────────────────┐
│ LIVE FINALS VOTING — Jury 3                         Alice Chen       │
├─────────────────────────────────────────────────────────────────────┤
│ Status: VOTING IN PROGRESS                                            │
│ Category: STARTUP                                                     │
│                                                                       │
│ [View All Finalists]  [Results Dashboard]  [Jury Discussion]         │
└─────────────────────────────────────────────────────────────────────┘

┌─ CURRENT PROJECT ───────────────────────────────────────────────────┐
│ Project 3 of 6                                                        │
│                                                                       │
│ OceanSense AI                                                         │
│ Team: AquaTech Solutions                                              │
│ Category: STARTUP (Marine Technology)                                 │
│                                                                       │
│ Description:                                                          │
│ AI-powered ocean monitoring platform that detects pollution events    │
│ in real-time using satellite imagery and underwater sensors.          │
│                                                                       │
│ ┌─ Documents ──────────────────────────────────────────────────┐     │
│ │ Round 1 Docs:                                                 │     │
│ │  • Executive Summary.pdf                                      │     │
│ │  • Business Plan.pdf                                          │     │
│ │                                                               │     │
│ │ Round 2 Docs (Semi-finalist):                                │     │
│ │  • Updated Business Plan.pdf                                  │     │
│ │  • Pitch Video.mp4                                            │     │
│ │  • Technical Whitepaper.pdf                                   │     │
│ └───────────────────────────────────────────────────────────────┘     │
│                                                                       │
│ Voting closes in: 0:45                                                │
└───────────────────────────────────────────────────────────────────────┘

┌─ VOTING PANEL (Numeric Mode: 1-10) ─────────────────────────────────┐
│                                                                       │
│  How would you rate this project overall?                             │
│                                                                       │
│  ┌────────────────────────────────────────────────────────────┐      │
│  │   1    2    3    4    5    6    7    8    9    10          │      │
│  │   ○    ○    ○    ○    ○    ○    ○    ●    ○    ○           │      │
│  └────────────────────────────────────────────────────────────┘      │
│                                                                       │
│  Your score: 8                                                        │
│                                                                       │
│  [Submit Vote]                                                        │
│                                                                       │
│  ⚠️  Votes cannot be changed after submission unless admin resets.    │
└───────────────────────────────────────────────────────────────────────┘

┌─ YOUR VOTES THIS SESSION ───────────────────────────────────────────┐
│  [✓] 1. AquaClean Tech           — Score: 9                           │
│  [✓] 2. BlueCarbon Solutions     — Score: 8                           │
│  [ ] 3. OceanSense AI            — Not voted yet                      │
│  [ ] 4. MarineTech Innovations   — Waiting                            │
│  [ ] 5. CoralGuard               — Waiting                            │
│  [ ] 6. DeepSea Robotics         — Waiting                            │
└───────────────────────────────────────────────────────────────────────┘

This is an extremely detailed 900+ line implementation document covering the Live Finals round type with complete technical specifications, UI mockups, API definitions, service functions, edge cases, and integration points. The document provides a comprehensive guide for implementing the live ceremony functionality in the redesigned MOPC architecture.