# 07. Live Finals and Deliberation

## Overview

This document specifies **Round 7 (Live Finals)** and **Round 8 (Deliberation)** — the climactic phases of the Monaco Ocean Protection Challenge competition. These rounds transform months of evaluation into official, immutable results through live ceremony management and formal deliberation voting.

### Why Live Finals and Deliberation Are Treated Together

These two rounds form an integrated workflow:
- **Live Finals (R7)**: Jury 3 evaluates finalist presentations in real-time during a live ceremony, with optional audience participation
- **Deliberation (R8)**: The same Jury 3 engages in formal consensus-building to confirm final rankings

**Critical architectural decision: Deliberation IS the confirmation.** There is no separate confirmation step after deliberation. The deliberation voting process produces the official, locked results. This replaces the old WinnerProposal → jury sign-off → admin approval flow with a unified deliberation session that serves as both decision-making and confirmation in one step.

### Position in Competition 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: Deliberation (DELIBERATION) ← THIS DOCUMENT
```

---

## Live Finals (Round 7)

### Purpose & Context

Live Finals orchestrate the live ceremony where Jury 3 (the Grand Jury) evaluates finalist presentations in real-time. This is the public face of the competition — often conducted at a formal event venue with an audience, media, and stakeholders present.

**Key capabilities:**
- Real-time stage manager controls for ceremony orchestration
- Jury voting with multiple scoring modes (criteria-based or simple)
- Optional audience voting with configurable weight
- Per-category presentation windows (STARTUP first, then BUSINESS_CONCEPT)
- Live results display or deferred reveal
- Anti-fraud measures for audience participation

### LiveFinalConfig Schema

The `LiveFinalConfig` object (stored in `Round.configJson`) controls all behavior for Round 7:

```typescript
import { z } from 'zod'

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

const LiveFinalConfigSchema = z.object({
  // ── Jury Configuration ──────────────────────────────────
  juryGroupId: z.string(), // Links to JuryGroup (Jury 3)

  // ── Scoring Mode ────────────────────────────────────────
  scoringMode: z.enum(['CRITERIA_BASED', 'SIMPLE_SCORE']).default('CRITERIA_BASED'),

  scoringCriteria: z.array(LiveVotingCriterionSchema).optional(),
  // Only used if scoringMode = CRITERIA_BASED
  // Example: [
  //   { id: 'innovation', label: 'Innovation', maxScore: 10, weight: 0.3 },
  //   { id: 'impact', label: 'Impact Potential', maxScore: 10, weight: 0.4 },
  //   { id: 'feasibility', label: 'Feasibility', maxScore: 10, weight: 0.3 }
  // ]

  simpleScoreRange: z.object({
    min: z.number().int().default(1),
    max: z.number().int().default(10),
  }).optional(),
  // Only used if scoringMode = SIMPLE_SCORE

  // ── Audience Voting ─────────────────────────────────────
  audienceVotingEnabled: z.boolean().default(false),

  audienceRevealTiming: z.enum([
    'REAL_TIME',           // Show audience scores live as they vote
    'AFTER_JURY_VOTE',     // Show after jury completes voting for each project
    'AT_DELIBERATION'      // Hide until deliberation starts
  ]).default('AT_DELIBERATION'),

  audienceBlendWeight: z.number().min(0).max(1).default(0),
  // 0 = Jury only (audience vote has no impact on scores)
  // 0.3 = 70% jury, 30% audience
  // 1 = Audience only (rare)

  // ── Presentation Timing ─────────────────────────────────
  votingWindowDuration: z.number().min(30).max(600).default(120), // seconds
  // How long voting is open per project after presentation

  // ── Prior Data Visibility ───────────────────────────────
  showPriorJuryData: z.boolean().default(false),
  // If true: Jury 3 can see previous jury scores/feedback during Live Finals
  // If false: Jury 3 evaluates independently without prior jury context
  // This reconciles "independent evaluation" with "as allowed by admin" qualifier

  // ── Presentation Order ──────────────────────────────────
  categoryOrder: z.array(z.nativeEnum(ProjectCategory)).default(['STARTUP', 'BUSINESS_CONCEPT']),
  // Order in which categories are presented

  presentationOrder: z.enum(['ALPHABETICAL', 'RANDOM', 'MANUAL']).default('MANUAL'),
  // Within each category, how are projects ordered?
})
.refine(
  (data) => {
    // If criteria-based, must have criteria
    if (data.scoringMode === 'CRITERIA_BASED' && (!data.scoringCriteria || data.scoringCriteria.length === 0)) {
      return false
    }
    return true
  },
  { message: 'Criteria-based scoring requires at least one criterion' }
)
.refine(
  (data) => {
    // Criteria weights must sum to 1.0 (within tolerance)
    if (data.scoringCriteria && data.scoringCriteria.length > 0) {
      const weightSum = data.scoringCriteria.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' }
)

type LiveFinalConfig = z.infer<typeof LiveFinalConfigSchema>
```

### Ceremony State Machine

The ceremony progresses through distinct phases:

```
┌──────────────┐
│ NOT_STARTED  │ — Session created, projects ordered, jury/audience links generated
└──────┬───────┘
       │ (Admin clicks "Start Ceremony")
       ▼
┌──────────────┐
│ IN_PROGRESS  │ — Presentations ongoing, per-project state advancing
└──────┬───────┘   (WAITING → PRESENTING → Q_AND_A → VOTING → VOTED)
       │
       │ (All category presentations complete)
       ▼
┌──────────────┐
│ DELIBERATION │ — Live Finals concluded, deliberation voting active (R8)
└──────┬───────┘   (See Deliberation section below)
       │
       │ (Deliberation voting complete + admin locks)
       ▼
┌──────────────┐
│  COMPLETED   │ — Results locked, ceremony finished
└──────────────┘
```

**State Descriptions:**

- **NOT_STARTED**: Ceremony configured but not yet live. Admin can preview setup, test links, adjust project order.
- **IN_PROGRESS**: Active ceremony. Projects advance through per-project states. Admin uses Stage Manager controls.
- **DELIBERATION**: Live Finals portion complete. Jury 3 now in deliberation voting session (Round 8).
- **COMPLETED**: All deliberation voting finished, results locked. Ceremony archived.

### Per-Project State Machine

Each finalist project progresses through these states during Live Finals:

```
WAITING      — 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, scores recorded
   │
   ▼
COMPLETE     — Move to next project
```

**Special states:**
- **SKIPPED**: Admin emergency override to skip a project (e.g., team no-show)

### Stage Manager Admin Controls

The **Stage Manager** is the live ceremony control panel. It provides real-time orchestration and emergency intervention capabilities.

#### Core Control Panel Layout (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"        │
└─────────────────────────────────────────────────────────────────────┘
```

#### Stage Manager Features

**1. Session Management**
- Start ceremony (initialize cursor, generate access links)
- Pause ceremony (freeze all timers, block new votes)
- Resume ceremony
- End session (trigger transition to Deliberation round)

**2. Project Navigation**
- Jump to specific project by index
- Skip project (emergency — logs reason)
- Reorder presentation queue (drag-and-drop or modal)
- Add project mid-ceremony (rare edge case — requires justification)

**3. Timer Controls**
- Extend voting window (+1 min, +5 min)
- Manual timer override (set specific time)
- Auto-advance to next project when voting closes

**4. Voting Window Management**
- Open voting for current project
- Close voting early (requires confirmation)
- Require all jury votes before closing (configurable)
- Reset votes (emergency undo — requires reason)

**5. Category Window Controls**
- Open category window (STARTUP or BUSINESS_CONCEPT)
- Close category window
- Advance to next category

**6. Emergency Controls**
- Skip project (with reason)
- Reset individual vote
- Reset all votes for project
- Force end session

**7. Real-Time Monitoring**
- Live vote count (jury + audience)
- Missing jury votes indicator
- Audience voter registration count
- Leaderboard (if `showLiveResults=true`)
- Ceremony event log with timestamps

### Jury 3 Experience

#### Jury Dashboard During Live Finals

Jury members access a dedicated voting interface during the ceremony:

```
┌────────────────────────────────────────────────────────────────────┐
│ LIVE FINALS VOTING — Jury 3                         Alice Chen      │
├────────────────────────────────────────────────────────────────────┤
│ Status: VOTING IN PROGRESS                                           │
│ Category: STARTUP                                                    │
│                                                                     │
│ [View All Finalists]  [My Notes]  [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 Application:                                         │    │
│ │  • Executive Summary.pdf                                     │    │
│ │  • Business Plan.pdf                                         │    │
│ │                                                              │    │
│ │ Round 4 Semi-finalist Submission:                            │    │
│ │  • Updated Business Plan.pdf                                 │    │
│ │  • Pitch Video.mp4                                           │    │
│ │  • Technical Whitepaper.pdf                                  │    │
│ │                                                              │    │
│ │ ⚠️  Prior Jury Data: [Hidden by Admin]                       │    │
│ │     (showPriorJuryData = false)                             │    │
│ └──────────────────────────────────────────────────────────────┘    │
│                                                                     │
│ Voting closes in: 0:45                                               │
└─────────────────────────────────────────────────────────────────────┘

┌─ VOTING PANEL (Criteria-Based) ────────────────────────────────────┐
│                                                                     │
│  Innovation (Weight: 30%)                                            │
│  ┌──────────────────────────────────────────────────────────┐      │
│  │   1    2    3    4    5    6    7    8    9    10        │      │
│  │   ○    ○    ○    ○    ○    ○    ○    ○    ●    ○         │      │
│  └──────────────────────────────────────────────────────────┘      │
│  Your score: 9                                                       │
│                                                                     │
│  Impact Potential (Weight: 40%)                                      │
│  ┌──────────────────────────────────────────────────────────┐      │
│  │   1    2    3    4    5    6    7    8    9    10        │      │
│  │   ○    ○    ○    ○    ○    ○    ○    ●    ○    ○         │      │
│  └──────────────────────────────────────────────────────────┘      │
│  Your score: 8                                                       │
│                                                                     │
│  Feasibility (Weight: 30%)                                           │
│  ┌──────────────────────────────────────────────────────────┐      │
│  │   1    2    3    4    5    6    7    8    9    10        │      │
│  │   ○    ○    ○    ○    ○    ○    ○    ○    ●    ○         │      │
│  └──────────────────────────────────────────────────────────┘      │
│  Your score: 9                                                       │
│                                                                     │
│  Weighted Average: 8.6                                               │
│                                                                     │
│  [Submit Vote]                                                       │
│                                                                     │
│  ⚠️  Votes cannot be changed after submission.                       │
└─────────────────────────────────────────────────────────────────────┘

┌─ YOUR VOTES THIS SESSION ──────────────────────────────────────────┐
│  [✓] 1. AquaClean Tech           — Score: 9.0                        │
│  [✓] 2. BlueCarbon Solutions     — Score: 8.2                        │
│  [ ] 3. OceanSense AI            — Voting now                        │
│  [ ] 4. MarineTech Innovations   — Waiting                           │
│  [ ] 5. CoralGuard               — Waiting                           │
│  [ ] 6. DeepSea Robotics         — Waiting                           │
└─────────────────────────────────────────────────────────────────────┘
```

#### Jury Access to Prior Jury Data

**Critical design point:** The `showPriorJuryData` toggle resolves the tension between "fully independent evaluation" and "as allowed by admin":

- **During active Jury 1/2 evaluation (R3, R5)**: NO cross-jury visibility — juries evaluate independently
- **During Live Finals (R7)**: Admin-configurable via `showPriorJuryData` toggle
  - If `true`: Jury 3 can see Jury 1 and Jury 2 scores, feedback, and notes
  - If `false`: Jury 3 evaluates without access to prior jury context

**When enabled, prior data includes:**
- Jury 1 and Jury 2 average scores per project
- Criteria scores (if criteria-based evaluation was used)
- Written feedback/comments from previous rounds
- Notes from jury deliberations (if recorded)

**UI representation when enabled:**
```
┌─ Prior Jury Context ─────────────────────────────────────────────┐
│ Jury 1 (Semi-finalist Selection):                                 │
│   Average Score: 8.4 / 10                                         │
│   Top Criterion: Innovation (9.2)                                 │
│   Key Feedback: "Strong technical approach, clear market fit"    │
│                                                                   │
│ Jury 2 (Finalist Selection):                                      │
│   Average Score: 8.8 / 10                                         │
│   Top Criterion: Impact Potential (9.5)                           │
│   Key Feedback: "Excellent scalability plan"                      │
└───────────────────────────────────────────────────────────────────┘
```

#### Jury Real-Time Notes

Jury members can take private notes during presentations:

- Notes are per-project, per-jury-member
- Notes are NOT visible to other jurors during Live Finals
- Notes become visible during Deliberation (R8) if admin enables note sharing
- Notes are included in the final deliberation record

### Audience Voting

When `audienceVotingEnabled = true`, registered audience members can vote on finalist projects.

#### Audience Registration & Anti-Fraud

Before voting, audience members must register via a unique voting token:

**Registration flow:**
1. Admin generates unique voting tokens (UUID-based)
2. Tokens distributed via QR codes, email, or physical cards at event
3. Audience member visits public voting page, enters token
4. System validates token, records device fingerprint and IP
5. Audience member can now vote

**Anti-fraud measures:**
- **IP rate limiting**: Max 3 votes per IP address (configurable)
- **Device fingerprinting**: Track browser/device signatures
- **Token uniqueness**: Each token can only vote once per project
- **Time window**: Voting only allowed during active voting windows
- **Admin monitoring**: Dashboard shows suspicious voting patterns (e.g., rapid-fire votes from same IP)

#### Audience Voting Interface

```
┌────────────────────────────────────────────────────────────────────┐
│ MONACO OCEAN PROTECTION CHALLENGE — AUDIENCE VOTE                  │
│                                                                     │
│ 🎤 Now Presenting: OceanSense AI (STARTUP)                         │
│                                                                     │
│ How would you rate this project?                                    │
│                                                                     │
│  ┌────────────────────────────────────────────────────────────┐   │
│  │  😞     😐     🙂     😊     🤩                             │   │
│  │   1      2      3      4      5                             │   │
│  │   ○      ○      ○      ○      ●                             │   │
│  └────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  Your vote: 5 stars                                                 │
│                                                                     │
│  [Submit Vote]                                                      │
│                                                                     │
│  Voting closes in: 1:23                                             │
│                                                                     │
│  ── Your Votes ────────────────────────────────────────────────    │
│  [✓] AquaClean Tech: ⭐⭐⭐⭐⭐                                         │
│  [✓] BlueCarbon Solutions: ⭐⭐⭐⭐                                    │
│  [ ] OceanSense AI: Voting now                                      │
└────────────────────────────────────────────────────────────────────┘
```

#### Audience Vote Reveal Timing

The `audienceRevealTiming` config controls when audience votes become visible:

**REAL_TIME**
- Audience vote totals update live as votes come in
- Visible to both jury and admin
- Creates dynamic leaderboard experience
- Risk: May influence jury voting

**AFTER_JURY_VOTE**
- Audience votes hidden until jury completes voting for each project
- Revealed after jury voting window closes
- Jury votes independently, then sees audience perspective
- Balanced approach

**AT_DELIBERATION** (recommended default)
- Audience votes completely hidden during Live Finals
- Revealed only when deliberation begins (R8)
- Jury 3 votes with complete independence
- Audience data informs deliberation discussion

### Score Calculation

Final scores blend jury and audience votes according to `audienceBlendWeight`:

```typescript
function calculateFinalScore(
  juryAverage: number,
  audienceAverage: number,
  blendWeight: number
): number {
  const juryWeight = 1 - blendWeight
  return (juryAverage * juryWeight) + (audienceAverage * blendWeight)
}

// Examples:
// blendWeight = 0 (jury only):
//   Jury 8.5, Audience 7.0 → Final: 8.5

// blendWeight = 0.3 (70% jury, 30% audience):
//   Jury 8.5, Audience 7.0 → Final: 8.5 * 0.7 + 7.0 * 0.3 = 8.05

// blendWeight = 0.5 (equal):
//   Jury 8.5, Audience 7.0 → Final: 7.75
```

**Criteria-based jury scores** are converted to a weighted average before blending:

```typescript
function calculateCriteriaWeightedAverage(
  criteriaScores: { criterionId: string; score: number }[],
  criteria: LiveVotingCriterion[]
): number {
  return criteriaScores.reduce((sum, cs) => {
    const criterion = criteria.find(c => c.id === cs.criterionId)
    const normalizedScore = cs.score / criterion.maxScore // Scale to 0-1
    return sum + (normalizedScore * 10 * criterion.weight) // Convert back to 0-10 scale
  }, 0)
}
```

---

## Deliberation (Round 8)

### Purpose & Context

**Deliberation IS the confirmation.** There is no separate approval workflow after deliberation. The deliberation voting session produces the official, locked results.

After Live Finals conclude, Jury 3 enters a formal deliberation period to reach consensus on final rankings. The deliberation process replaces the old WinnerProposal → jury sign-off → admin approval flow.

**Key principles:**
- One deliberation session per category (STARTUP and BUSINESS_CONCEPT deliberate independently)
- Multiple voting modes supported (single-winner vote or full ranking)
- Tie-breaking mechanisms (runoff, admin decision, or score fallback)
- Admin can override entire result if deadlock occurs
- Results lock after deliberation completes — immutable official outcome

### DeliberationConfig Schema

```typescript
import { z } from 'zod'

enum DeliberationMode {
  SINGLE_WINNER_VOTE = 'SINGLE_WINNER_VOTE', // Each juror picks one winner
  FULL_RANKING = 'FULL_RANKING',             // Each juror ranks all projects (1st, 2nd, 3rd...)
}

enum TieBreakMethod {
  RUNOFF = 'RUNOFF',               // New vote with only tied projects
  ADMIN_DECIDES = 'ADMIN_DECIDES', // Admin manually breaks tie
  SCORE_FALLBACK = 'SCORE_FALLBACK', // Fall back to Jury 3 scores from Live Finals
}

const DeliberationConfigSchema = z.object({
  // ── Jury ────────────────────────────────────────────────
  juryGroupId: z.string(), // Typically same as LiveFinal juryGroupId (Jury 3)

  // ── Voting Mode ─────────────────────────────────────────
  mode: z.nativeEnum(DeliberationMode).default('SINGLE_WINNER_VOTE'),

  // ── Visibility ──────────────────────────────────────────
  showCollectiveRankings: z.boolean().default(false),
  // If true: jurors see aggregated vote counts in real-time
  // If false: blind voting until deliberation closes

  showPriorJuryData: z.boolean().default(false),
  // Same as LiveFinalConfig — show Jury 1/2 data during deliberation

  // ── Tie-Breaking ────────────────────────────────────────
  tieBreakMethod: z.nativeEnum(TieBreakMethod).default('ADMIN_DECIDES'),

  // ── Timing ──────────────────────────────────────────────
  votingDuration: z.number().min(60).optional(),
  // Seconds. Null = unlimited until admin closes

  // ── Admin Override ──────────────────────────────────────
  allowAdminOverride: z.boolean().default(true),

  // ── Winner Count ────────────────────────────────────────
  topN: z.number().min(1).default(3),
  // How many winners/ranked positions to determine (e.g., 1st, 2nd, 3rd)
})

type DeliberationConfig = z.infer<typeof DeliberationConfigSchema>
```

### Session Lifecycle

```
┌──────────┐
│   OPEN   │ — Session created, jurors can review Live Finals results
└────┬─────┘
     │ (Admin opens deliberation voting)
     ▼
┌──────────┐
│  VOTING  │ — Jurors submit votes (SINGLE_WINNER_VOTE or FULL_RANKING)
└────┬─────┘
     │ (All required votes received OR voting duration expires)
     ▼
┌───────────┐
│ TALLYING  │ — System aggregates votes, checks for ties
└────┬──────┘
     │
     ├── No tie → LOCKED
     │
     ├── Tie detected & tieBreakMethod = RUNOFF
     │   ▼
     │ ┌────────┐
     │ │ RUNOFF │ — New vote with only tied projects (runoffRound++)
     │ └────┬───┘
     │      └──→ Back to TALLYING
     │
     ├── Tie detected & tieBreakMethod = ADMIN_DECIDES
     │   → Admin manually resolves → LOCKED
     │
     └── Tie detected & tieBreakMethod = SCORE_FALLBACK
         → Use Jury 3 scores from Live Finals → LOCKED

┌──────────┐
│  LOCKED  │ — Results frozen, immutable, official
└──────────┘
```

### Voting Modes

#### SINGLE_WINNER_VOTE

Each juror picks **one** winner project. The project with the most votes becomes the proposed winner. Other projects are ranked by vote count.

**Example:**
- 8 jurors vote
- Project A: 5 votes
- Project B: 2 votes
- Project C: 1 vote

**Result:**
1. Project A (winner — 5 votes)
2. Project B (2nd — 2 votes)
3. Project C (3rd — 1 vote)

**Tie scenario:**
- Project A: 4 votes
- Project B: 4 votes

→ Trigger tie-breaking mechanism

#### FULL_RANKING

Each juror submits an ordinal ranking of all projects (1st, 2nd, 3rd, etc.). Aggregated via **Borda count**.

**Example with 3 projects, 5 jurors:**

| Juror | 1st | 2nd | 3rd |
|-------|-----|-----|-----|
| Alice | A   | B   | C   |
| Bob   | A   | C   | B   |
| Carol | B   | A   | C   |
| David | A   | B   | C   |
| Emma  | B   | A   | C   |

**Borda count calculation** (3 points for 1st, 2 for 2nd, 1 for 3rd):

- **Project A**: (3+3+2+3+2) = 13 points
- **Project B**: (2+1+3+2+3) = 11 points
- **Project C**: (1+2+1+1+1) = 6 points

**Final ranking:**
1. Project A (13 points)
2. Project B (11 points)
3. Project C (6 points)

### Aggregation Logic

#### SINGLE_WINNER_VOTE Aggregation

```typescript
function aggregateSingleWinnerVotes(
  votes: DeliberationVote[]
): { projectId: string; voteCount: number; rank: number }[] {
  // Count votes per project
  const voteCounts = new Map<string, number>()
  votes.forEach(v => {
    if (v.isWinnerPick) {
      voteCounts.set(v.projectId, (voteCounts.get(v.projectId) || 0) + 1)
    }
  })

  // Sort by vote count descending
  const results = Array.from(voteCounts.entries())
    .map(([projectId, voteCount]) => ({ projectId, voteCount }))
    .sort((a, b) => b.voteCount - a.voteCount)

  // Assign ranks (handle ties at same rank)
  let rank = 1
  results.forEach((r, i) => {
    if (i > 0 && results[i - 1].voteCount > r.voteCount) {
      rank = i + 1
    }
    r.rank = rank
  })

  return results
}
```

#### FULL_RANKING Aggregation (Borda Count)

```typescript
function aggregateFullRankingVotes(
  votes: DeliberationVote[],
  projectCount: number
): { projectId: string; bordaScore: number; rank: number }[] {
  // Calculate Borda points: 1st place = N points, 2nd = N-1, etc.
  const bordaScores = new Map<string, number>()

  votes.forEach(v => {
    const points = projectCount - (v.rank - 1) // rank 1 → N points, rank 2 → N-1, etc.
    bordaScores.set(v.projectId, (bordaScores.get(v.projectId) || 0) + points)
  })

  // Sort by Borda score descending
  const results = Array.from(bordaScores.entries())
    .map(([projectId, bordaScore]) => ({ projectId, bordaScore }))
    .sort((a, b) => b.bordaScore - a.bordaScore)

  // Assign final ranks
  let rank = 1
  results.forEach((r, i) => {
    if (i > 0 && results[i - 1].bordaScore > r.bordaScore) {
      rank = i + 1
    }
    r.rank = rank
  })

  return results
}
```

### Tie-Breaking Methods

#### RUNOFF

When multiple projects tie for a position, a new voting round is triggered with only the tied projects.

**Flow:**
1. Initial vote detects tie (e.g., Projects A and B both have 4 votes)
2. System creates runoff session with `runoffRound = 1`
3. Jurors vote again, choosing only between A and B
4. If still tied → runoff round 2 (or escalate to admin)
5. If resolved → final ranking locked

**Runoff limits:**
- Maximum 3 runoff rounds
- After 3 rounds, escalate to `ADMIN_DECIDES` automatically

#### ADMIN_DECIDES

Admin manually selects the winner or ranking order.

**UI:**
```
┌────────────────────────────────────────────────────────────────────┐
│ ⚠️  Deliberation Tie — Admin Decision Required                     │
│                                                                     │
│ Current Tie (for 1st place):                                        │
│   • Project A: 4 votes                                              │
│   • Project B: 4 votes                                              │
│                                                                     │
│ Select the winner:                                                  │
│   ○ Project A — OceanSense AI                                      │
│   ● Project B — BlueCarbon Solutions                               │
│                                                                     │
│ Reason for decision (required):                                     │
│ ┌──────────────────────────────────────────────────────────────┐   │
│ │ BlueCarbon Solutions demonstrated stronger implementation    │   │
│ │ timeline and partnership commitments during Q&A.             │   │
│ └──────────────────────────────────────────────────────────────┘   │
│                                                                     │
│ [Cancel]                              [Confirm Decision & Lock]     │
└────────────────────────────────────────────────────────────────────┘
```

**Audit trail:**
- Admin decision recorded in `DeliberationResult.isAdminOverridden = true`
- Reason stored in `DeliberationResult.overrideReason`
- Full vote tallies preserved for transparency

#### SCORE_FALLBACK

Use Jury 3 scores from Live Finals (Round 7) to break the tie.

**Logic:**
1. Detect tie in deliberation votes
2. Fetch final weighted scores from Live Finals for tied projects
3. Project with higher Live Finals score wins the tie
4. If still tied (exact same score) → escalate to ADMIN_DECIDES

**Example:**
- Deliberation tie: Project A (4 votes), Project B (4 votes)
- Live Finals scores: Project A (8.5), Project B (8.2)
- Winner: Project A (score fallback)

### Admin Override

Admin can override the **entire deliberation result** at any point, even if no tie exists.

**Use cases:**
- Deadlocked deliberation (jury cannot reach consensus after multiple runoffs)
- Jury misconduct discovered (e.g., COI not disclosed)
- Extraordinary circumstances (e.g., project disqualified post-deliberation)

**Override UI:**
```
┌────────────────────────────────────────────────────────────────────┐
│ Override Deliberation Result — STARTUP Category                    │
│                                                                     │
│ Current Result (Deliberation Vote):                                 │
│   1st: Project A (8 votes)                                         │
│   2nd: Project B (0 votes)                                         │
│                                                                     │
│ Override with new ranking:                                          │
│ ┌────┬──────────────────────────────┬────────────────────────┐    │
│ │ #  │ Project                      │ Actions                │    │
│ ├────┼──────────────────────────────┼────────────────────────┤    │
│ │ 1  │ ≡ Project B — BlueCarbon     │ ↑ ↓                   │    │
│ │ 2  │ ≡ Project A — OceanSense     │ ↑ ↓                   │    │
│ └────┴──────────────────────────────┴────────────────────────┘    │
│                                                                     │
│ Reason for override (required):                                     │
│ ┌──────────────────────────────────────────────────────────────┐   │
│ │ Project A team member disclosed undisclosed COI after         │   │
│ │ deliberation. Ethics committee recommends disqualification.  │   │
│ └──────────────────────────────────────────────────────────────┘   │
│                                                                     │
│ ⚠ This action is audited and cannot be undone.                    │
│                                                                     │
│ [Cancel]                              [Apply Override & Lock]       │
└────────────────────────────────────────────────────────────────────┘
```

**Override behavior:**
- `DeliberationResult.isAdminOverridden = true`
- Original vote tallies preserved in audit log
- New ranking stored in `DeliberationResult.finalRank`
- Override reason mandatory (min 20 chars)

### Participant Management

#### Participant Status Types

**REQUIRED**
- Active Jury 3 member expected to vote
- Counted in quorum calculations

**ABSENT_EXCUSED**
- Jury member cannot participate (e.g., illness, travel)
- NOT counted in quorum
- Requires admin approval + reason

**REPLACED**
- Original jury member replaced by another juror
- Replacement juror assumes voting rights
- Original juror cannot vote

**REPLACEMENT_ACTIVE**
- Replacement juror (linked to original via `replacedById`)
- Has active voting rights
- Counted in quorum

#### Quorum Calculation

```typescript
function calculateQuorum(participants: DeliberationParticipant[]): {
  requiredVotes: number;
  activeParticipants: DeliberationParticipant[];
} {
  const activeParticipants = participants.filter(p =>
    p.status === 'REQUIRED' || p.status === 'REPLACEMENT_ACTIVE'
  )

  return {
    requiredVotes: activeParticipants.length,
    activeParticipants,
  }
}
```

**Example:**
- Jury 3 has 8 members
- 1 member marked ABSENT_EXCUSED
- 1 member REPLACED (replacement is REPLACEMENT_ACTIVE)
- Quorum: 7 votes required (8 - 1 absent + 0 net change from replacement)

#### Replacement Flow

Admin can replace a jury member mid-deliberation:

1. Admin marks original juror as REPLACED
2. Admin selects replacement juror (must be eligible Jury 3 alternate)
3. Replacement juror gets REPLACEMENT_ACTIVE status
4. Replacement votes count toward quorum
5. Original juror's existing vote (if any) is invalidated

### Juror Experience During Deliberation

#### Deliberation Dashboard

```
┌────────────────────────────────────────────────────────────────────┐
│ DELIBERATION — STARTUP Category                       Alice Chen   │
├────────────────────────────────────────────────────────────────────┤
│ Status: VOTING                                                      │
│ Voting closes in: 45:00 (or when all 8 jurors vote)               │
│                                                                     │
│ [Review Live Finals Scores]  [My Notes]  [Jury Discussion]         │
└────────────────────────────────────────────────────────────────────┘

┌─ YOUR VOTE ─────────────────────────────────────────────────────────┐
│                                                                     │
│ Voting Mode: Single Winner Vote                                     │
│                                                                     │
│ Select the winning project:                                         │
│                                                                     │
│   ○ Project A — OceanSense AI                                      │
│     Live Finals Score: 8.6 (Jury: 8.8, Audience: 7.9)             │
│     Your Live Finals Score: 9.0                                     │
│                                                                     │
│   ● Project B — BlueCarbon Solutions                               │
│     Live Finals Score: 8.4 (Jury: 8.2, Audience: 9.1)             │
│     Your Live Finals Score: 8.5                                     │
│                                                                     │
│   ○ Project C — AquaClean Tech                                     │
│     Live Finals Score: 8.2 (Jury: 8.5, Audience: 7.2)             │
│     Your Live Finals Score: 8.0                                     │
│                                                                     │
│ Comment (optional):                                                 │
│ ┌──────────────────────────────────────────────────────────────┐   │
│ │ BlueCarbon's implementation timeline is most realistic.       │   │
│ └──────────────────────────────────────────────────────────────┘   │
│                                                                     │
│ [Submit Vote]                                                       │
│                                                                     │
│ ⚠️  Votes cannot be changed after submission.                       │
└─────────────────────────────────────────────────────────────────────┘

┌─ VOTING PROGRESS ───────────────────────────────────────────────────┐
│ 5 of 8 jurors have voted (62.5%)                                    │
│                                                                     │
│ [✓] You (Alice Chen)     [✓] Bob Martin       [ ] Carol Davis      │
│ [✓] David Lee            [✓] Emma Wilson      [ ] Frank Garcia     │
│ [✓] Grace Huang          [ ] Henry Thompson                        │
│                                                                     │
│ ⚠️  Results hidden until all jurors vote (showCollectiveRankings=false)│
└─────────────────────────────────────────────────────────────────────┘
```

#### Review Own Scores

During deliberation, jurors can review:
- Their own Live Finals scores for each project
- Their own notes from Live Finals
- Project documents from all rounds
- Aggregated Live Finals scores (jury + audience)
- **If enabled:** Prior Jury 1/2 data (same toggle as Live Finals)

**NOT visible** (until deliberation closes):
- Other jurors' deliberation votes (unless `showCollectiveRankings = true`)
- Vote tallies or rankings

#### Real-Time Vote Counts (Optional)

If `showCollectiveRankings = true`, jurors see aggregated vote counts in real-time:

```
┌─ CURRENT VOTE TALLY (LIVE) ─────────────────────────────────────────┐
│                                                                     │
│ Project A — OceanSense AI: ███████ 4 votes                         │
│ Project B — BlueCarbon Solutions: ████ 2 votes                     │
│ Project C — AquaClean Tech: ██ 1 vote                              │
│                                                                     │
│ 7 of 8 jurors have voted                                            │
└─────────────────────────────────────────────────────────────────────┘
```

**Risk:** Real-time vote counts may influence later voters (bandwagon effect). Recommended default: `showCollectiveRankings = false`.

---

## Result Lock

### ResultLock Model and Behavior

When deliberation completes and admin locks the results, a `ResultLock` record is created:

```typescript
model ResultLock {
  id            String   @id @default(cuid())
  competitionId String
  roundId       String   // Deliberation round ID
  category      ProjectCategory
  lockedById    String   // Admin who locked
  resultSnapshot Json    @db.JsonB // Full snapshot of deliberation results
  lockedAt      DateTime @default(now())

  competition   Competition @relation(fields: [competitionId], references: [id])
  round         Round       @relation(fields: [roundId], references: [id])
  lockedBy      User        @relation(fields: [lockedById], references: [id])
  unlockEvents  ResultUnlockEvent[]

  @@unique([competitionId, category]) // One lock per category
}
```

**Result snapshot includes:**
- Final rankings (1st, 2nd, 3rd, etc.)
- All deliberation votes (per juror)
- Tie-break history (if runoffs occurred)
- Override reason (if admin override applied)
- Participant status (active, absent, replaced)
- Timestamp of lock
- Cryptographic hash of snapshot (for integrity verification)

**Immutability enforcement:**
- Once locked, deliberation votes cannot be changed
- Rankings cannot be edited
- Admin cannot reorder winners
- **Only super-admin can unlock** (see below)

### ResultUnlockEvent (Super-Admin Only)

In rare circumstances, results must be unlocked (e.g., legal challenge, fraud discovery):

```typescript
model ResultUnlockEvent {
  id           String   @id @default(cuid())
  resultLockId String
  unlockedById String   // Super-admin only
  reason       String   // Mandatory explanation
  unlockedAt   DateTime @default(now())

  resultLock  ResultLock @relation(fields: [resultLockId], references: [id])
  unlockedBy  User       @relation(fields: [unlockedById], references: [id])
}
```

**Unlock flow:**
1. Super-admin initiates unlock
2. System prompts for mandatory reason (min 50 chars)
3. `ResultUnlockEvent` created with full audit trail
4. Deliberation session status reverts to TALLYING
5. Admin can now modify results or trigger new deliberation
6. When re-locked, new `ResultLock` created (old lock preserved for audit)

**Security:**
- Only users with role `SUPER_ADMIN` can unlock
- Unlock reason is mandatory and cannot be empty
- All unlock events logged to `DecisionAuditLog`
- Email notification sent to all program admins when unlock occurs

### Audit Trail

Every action in the deliberation and locking process is audited:

**Audited events:**
- Deliberation session created
- Juror submits vote
- Juror marked absent/excused
- Juror replaced
- Tie detected
- Runoff initiated
- Admin override applied
- Results locked
- Results unlocked (super-admin)
- Results re-locked

**Audit log format:**
```typescript
{
  entityType: 'DELIBERATION_SESSION',
  entityId: sessionId,
  action: 'RESULTS_LOCKED',
  userId: adminId,
  timestamp: '2026-05-15T22:30:00Z',
  details: {
    category: 'STARTUP',
    finalRankings: [
      { rank: 1, projectId: 'proj_abc', votes: 5 },
      { rank: 2, projectId: 'proj_def', votes: 3 },
    ],
    tieBreaksApplied: [],
    adminOverride: false,
  }
}
```

---

## Integration with Prior Rounds

### How R7/R8 Consume Data from R1-R6

**Round 7 (Live Finals) receives:**
- Finalist projects from Round 5 (Jury 2 selection)
- All project submissions from R1 and R4
- Mentored documents from Round 6 (if promoted)
- Jury 1 and Jury 2 scores (if `showPriorJuryData = true`)

**Round 8 (Deliberation) receives:**
- Live Finals scores from Round 7
- Deliberation votes from Jury 3
- Audience vote totals (if enabled)

**Data flow diagram:**
```
R1 (INTAKE) → R2 (FILTERING) → R3 (JURY 1) → R4 (SUBMISSION) → R5 (JURY 2) → R6 (MENTORING)
                                      ↓                                ↓             ↓
                                   scores                          scores      documents
                                      ↓                                ↓             ↓
                                      └────────────────────────────────┴─────────────┘
                                                           ↓
                                                      R7 (LIVE FINALS)
                                                           ↓
                                                    Jury 3 scores
                                                    Audience votes
                                                           ↓
                                                      R8 (DELIBERATION)
                                                           ↓
                                                    ResultLock (final)
```

### showPriorJuryData Toggle Behavior

**Cross-reference:** [04-jury-groups-and-assignment-policy.md](./04-jury-groups-and-assignment-policy.md) for JuryGroup definitions.

**During active evaluation rounds (R3, R5):**
- Jury 1 and Jury 2 **cannot** see each other's scores/feedback
- Strict independent evaluation enforced
- Cross-jury visibility = BLOCKED at query level

**During Live Finals (R7) and Deliberation (R8):**
- `showPriorJuryData` toggle becomes active
- If `true`: Jury 3 sees Jury 1 and Jury 2 historical data
- If `false`: Jury 3 evaluates blind (recommended for independence)

**Data visibility matrix:**

| Round | Jury 1 Data | Jury 2 Data | Jury 3 Live Finals Data |
|-------|-------------|-------------|-------------------------|
| R3 (Jury 1 evaluation) | Own only | BLOCKED | BLOCKED |
| R5 (Jury 2 evaluation) | BLOCKED* | Own only | BLOCKED |
| R7 (Live Finals) | Configurable** | Configurable** | Own only |
| R8 (Deliberation) | Configurable** | Configurable** | All Jury 3 scores visible |

\* Unless explicitly enabled by admin for reference (rare)
\*\* Via `showPriorJuryData` toggle

### Cross-Reference to Competition Flow

See [03-competition-flow.md](./03-competition-flow.md) for:
- Round sequencing and dependencies
- Status transitions (SETUP → ACTIVE → COMPLETED)
- Round advancement triggers
- Competition-level state machine

---

## API & Service Functions

### New tRPC Procedures

```typescript
// src/server/routers/live-finals.ts

export const liveFinalsRouter = router({
  // ── Session Management ─────────────────────────────────
  startCeremony: adminProcedure
    .input(z.object({ roundId: z.string() }))
    .mutation(async ({ input, ctx }) => { /* Initialize ceremony */ }),

  pauseCeremony: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ input, ctx }) => { /* Pause all timers */ }),

  // ── Project Navigation ─────────────────────────────────
  setActiveProject: adminProcedure
    .input(z.object({ sessionId: z.string(), projectId: z.string() }))
    .mutation(async ({ input, ctx }) => { /* Set presenting project */ }),

  reorderQueue: adminProcedure
    .input(z.object({ sessionId: z.string(), projectIds: z.array(z.string()) }))
    .mutation(async ({ input, ctx }) => { /* Reorder presentation */ }),

  // ── Voting ─────────────────────────────────────────────
  submitJuryVote: juryProcedure
    .input(z.object({
      sessionId: z.string(),
      projectId: z.string(),
      criteriaScores: z.array(z.object({
        criterionId: z.string(),
        score: z.number(),
      })).optional(),
      simpleScore: z.number().optional(),
    }))
    .mutation(async ({ input, ctx }) => { /* Record jury vote */ }),

  submitAudienceVote: publicProcedure
    .input(z.object({
      sessionId: z.string(),
      projectId: z.string(),
      token: z.string(),
      score: z.number(),
    }))
    .mutation(async ({ input, ctx }) => { /* Record audience vote */ }),

  // ── Results ────────────────────────────────────────────
  getLiveResults: publicProcedure
    .input(z.object({ sessionId: z.string() }))
    .query(async ({ input }) => { /* Return leaderboard */ }),
})

// src/server/routers/deliberation.ts

export const deliberationRouter = router({
  // ── Session Management ─────────────────────────────────
  createSession: adminProcedure
    .input(z.object({
      roundId: z.string(),
      category: z.nativeEnum(ProjectCategory),
      config: DeliberationConfigSchema,
    }))
    .mutation(async ({ input, ctx }) => { /* Create deliberation session */ }),

  openVoting: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ input, ctx }) => { /* Open deliberation voting */ }),

  closeVoting: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ input, ctx }) => { /* Close voting, tally results */ }),

  // ── Voting ─────────────────────────────────────────────
  submitVote: juryProcedure
    .input(z.object({
      sessionId: z.string(),
      votes: z.array(z.object({
        projectId: z.string(),
        rank: z.number().optional(), // FULL_RANKING
        isWinnerPick: z.boolean().optional(), // SINGLE_WINNER_VOTE
      })),
      comment: z.string().optional(),
    }))
    .mutation(async ({ input, ctx }) => { /* Record deliberation vote */ }),

  // ── Participant Management ─────────────────────────────
  markAbsent: adminProcedure
    .input(z.object({
      sessionId: z.string(),
      juryMemberId: z.string(),
      reason: z.string(),
    }))
    .mutation(async ({ input, ctx }) => { /* Mark juror absent */ }),

  replaceJuror: adminProcedure
    .input(z.object({
      sessionId: z.string(),
      originalJuryMemberId: z.string(),
      replacementJuryMemberId: z.string(),
    }))
    .mutation(async ({ input, ctx }) => { /* Replace juror */ }),

  // ── Tie-Breaking ───────────────────────────────────────
  initiateRunoff: adminProcedure
    .input(z.object({ sessionId: z.string(), tiedProjectIds: z.array(z.string()) }))
    .mutation(async ({ input, ctx }) => { /* Create runoff vote */ }),

  resolveAdminTieBreak: adminProcedure
    .input(z.object({
      sessionId: z.string(),
      winnerId: z.string(),
      reason: z.string(),
    }))
    .mutation(async ({ input, ctx }) => { /* Admin breaks tie */ }),

  // ── Override ───────────────────────────────────────────
  overrideResult: adminProcedure
    .input(z.object({
      sessionId: z.string(),
      newRankings: z.array(z.object({ projectId: z.string(), rank: z.number() })),
      reason: z.string().min(20),
    }))
    .mutation(async ({ input, ctx }) => { /* Override entire result */ }),

  // ── Locking ────────────────────────────────────────────
  lockResults: adminProcedure
    .input(z.object({ sessionId: z.string() }))
    .mutation(async ({ input, ctx }) => { /* Create ResultLock */ }),

  unlockResults: superAdminProcedure
    .input(z.object({ resultLockId: z.string(), reason: z.string().min(50) }))
    .mutation(async ({ input, ctx }) => { /* Create ResultUnlockEvent */ }),
})
```

### Service Functions

```typescript
// src/server/services/live-finals.ts

export async function initializeCeremony(roundId: string): Promise<LiveVotingSession>
export async function advanceToNextProject(sessionId: string): Promise<void>
export async function calculateLiveScores(sessionId: string, category: ProjectCategory): Promise<ProjectScore[]>
export async function registerAudienceVoter(sessionId: string, token: string): Promise<AudienceVoter>

// src/server/services/deliberation.ts

export async function createDeliberationSession(roundId: string, category: ProjectCategory, config: DeliberationConfig): Promise<DeliberationSession>
export async function tallyVotes(sessionId: string): Promise<DeliberationResult[]>
export async function detectTies(results: DeliberationResult[]): Promise<{ rank: number; projectIds: string[] }[]>
export async function createRunoffSession(sessionId: string, tiedProjectIds: string[]): Promise<DeliberationSession>
export async function lockDeliberationResults(sessionId: string, lockedById: string): Promise<ResultLock>
export async function unlockResults(resultLockId: string, unlockedById: string, reason: string): Promise<ResultUnlockEvent>
```

---

## Edge Cases & Error Handling

| Scenario | Handling |
|----------|----------|
| **Jury member loses connection during vote** | Vote saved incrementally (optimistic UI). If connection lost, vote marked incomplete until juror reconnects. |
| **Audience member tries to vote twice** | Token + projectId uniqueness enforced. Second vote rejected with error. |
| **Admin closes voting before all jury votes** | Warning prompt: "Only X/Y jury votes received. Close anyway?" Audit log records early close. |
| **Tie persists after 3 runoff rounds** | Auto-escalate to ADMIN_DECIDES mode. Admin notified immediately. |
| **Deliberation vote submitted after session closed** | Rejected with error. Vote not recorded. Juror notified session is closed. |
| **Admin tries to lock results with tie** | Blocked. Must resolve tie first (runoff, admin decision, or score fallback). |
| **Super-admin unlocks results after 6 months** | Allowed. Unlock event audited with timestamp and reason. All stakeholders notified. |
| **Jury member replaced mid-deliberation, replacement votes** | Original vote invalidated. Replacement vote counted. Quorum recalculated. |
| **Live Finals scores tied exactly** | Score fallback tie-break uses additional decimal precision. If still tied → admin decides. |
| **Audience voting flood (bot attack)** | IP rate limiting + device fingerprinting blocks suspicious patterns. Admin alerted. |
| **Project disqualified during deliberation** | Admin removes project from deliberation. Votes for that project invalidated. Remaining projects re-ranked. |
| **Network partition during live ceremony** | Stage Manager persists state locally. On reconnect, state reconciled. Event log replayed. |

---

## Security & Access Control

### Live Finals Access

| Role | Can View Ceremony | Can Vote (Jury) | Can Vote (Audience) | Can Control Stage Manager |
|------|-------------------|-----------------|---------------------|---------------------------|
| SUPER_ADMIN | ✓ | — | — | ✓ |
| PROGRAM_ADMIN | ✓ | — | — | ✓ |
| Jury 3 Member | ✓ | ✓ | — | — |
| Audience (token holder) | ✓ (public view) | — | ✓ | — |
| Public | ✓ (if public stream enabled) | — | — | — |

### Deliberation Access

| Role | Can View Session | Can Vote | Can Mark Absent | Can Override Result | Can Lock | Can Unlock |
|------|------------------|----------|-----------------|---------------------|----------|------------|
| SUPER_ADMIN | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| PROGRAM_ADMIN | ✓ | — | ✓ | ✓ | ✓ | — |
| Jury 3 Member | ✓ (own data) | ✓ | — | — | — | — |
| Other roles | — | — | — | — | — | — |

### ResultLock Immutability

- Database constraints enforce no-update on locked results
- tRPC middleware blocks all mutation attempts on locked results
- Super-admin unlock requires 2FA confirmation (future enhancement)
- All unlock events trigger email alerts to compliance team

---

## Cross-References

- [02-data-model.md](./02-data-model.md) — Prisma models for `LiveVotingSession`, `DeliberationSession`, `ResultLock`
- [03-competition-flow.md](./03-competition-flow.md) — Round sequencing, status transitions, R7/R8 position in flow
- [04-jury-groups-and-assignment-policy.md](./04-jury-groups-and-assignment-policy.md) — JuryGroup (Jury 3), membership, binding to rounds

---

## Summary

This document specifies the integrated **Live Finals (R7)** and **Deliberation (R8)** workflow. Key takeaways:

1. **Live Finals** orchestrates real-time jury voting with optional audience participation, configurable scoring modes, and admin stage management.

2. **Deliberation IS confirmation** — there is no separate approval step. The deliberation voting session produces the official, locked results.

3. **Prior jury data visibility** is configurable via `showPriorJuryData`, reconciling independent evaluation with admin flexibility.

4. **Tie-breaking** supports runoffs, admin decisions, and score fallback, with full audit trails.

5. **Result immutability** is enforced via `ResultLock`, with super-admin-only unlock capability for extraordinary circumstances.

6. **Full audit trail** captures every vote, tie-break, override, lock, and unlock event for transparency and compliance.

This architecture supports both the current Monaco flow and future enhancements (e.g., multi-round deliberation, external judge integration, live broadcast features).
