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 cursorsetActiveProject(stageId, projectId, actorId)— Set currently presenting projectjumpToProject(stageId, orderIndex, actorId)— Jump to specific project in queuereorderQueue(stageId, newOrder, actorId)— Reorder presentation sequencepauseResume(stageId, isPaused, actorId)— Toggle pause stateopenCohortWindow(cohortId, actorId)— Open voting window for a cohortcloseCohortWindow(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:
-
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)
-
Project Navigation
- Jump to specific project
- Skip project (emergency)
- Reorder queue (drag-and-drop or modal)
- Add project mid-ceremony (rare edge case)
-
Timer Controls
- Start presentation timer
- Start Q&A timer
- Start voting timer
- Extend timer (+1 min, +5 min)
- Manual timer override
-
Voting Window Management
- Open voting for current project
- Close voting early
- Require all jury votes before closing
- Reset votes (emergency undo)
-
Category Window Controls
- Open category window (STARTUP or BUSINESS_CONCEPT)
- Close category window
- Start deliberation period
- Advance to next category
-
Emergency Controls
- Skip project
- Reset individual vote
- Reset all votes for project
- Pause ceremony (emergency)
- Force end session
-
Override Controls (if
adminCanOverrideVotes=true):- Override individual jury vote
- Adjust audience/jury weights mid-ceremony
- Manual score adjustment
-
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.