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

661 lines
30 KiB
Markdown

# 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:
```prisma
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:
```prisma
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:
```prisma
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:
```prisma
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:
```prisma
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`:
```typescript
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`:
```typescript
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:**
```typescript
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
```typescript
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.