Competition/Round architecture: full platform rewrite (Phases 1-9)
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
61
docs/unified-architecture-redesign/00-README.md
Normal file
61
docs/unified-architecture-redesign/00-README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Unified Architecture Redesign — Monaco Ocean Protection Challenge
|
||||
|
||||
> **Status**: Design specification (pre-implementation)
|
||||
> **Date**: 2026-02-15
|
||||
> **Scope**: Complete redesign of the round/pipeline system for multi-round competition management
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document set specifies the architecture redesign for the MOPC platform's competition system. The core change eliminates the Track abstraction layer and renames Pipeline → **Competition**, Stage → **Round**, producing a flat, linear round sequence with typed configurations per round type.
|
||||
|
||||
The design supports the full Monaco 2026 competition flow: Intake → AI Filtering → Jury 1 Evaluation → Semifinal Submission → Jury 2 Evaluation + Special Awards → Mentoring → Live Finals (Jury 3) → Deliberation → Result Lock.
|
||||
|
||||
---
|
||||
|
||||
## Document Index
|
||||
|
||||
| # | Document | Description |
|
||||
|---|----------|-------------|
|
||||
| 01 | [Architecture & Decisions](./01-architecture-and-decisions.md) | Core decisions, ADRs, guiding principles, before/after architecture |
|
||||
| 02 | [Data Model](./02-data-model.md) | Complete Prisma schema, typed config Zod schemas, enum definitions |
|
||||
| 03 | [Competition Flow](./03-competition-flow.md) | Round-by-round specification (R1–R8) with cross-cutting behaviors |
|
||||
| 04 | [Jury Groups & Assignment Policy](./04-jury-groups-and-assignment-policy.md) | JuryGroup model, cap modes, assignment algorithm, policy precedence |
|
||||
| 05 | [Special Awards](./05-special-awards.md) | Award modes (stay-in-main vs separate pool), per-award pipelines |
|
||||
| 06 | [Mentoring & Document Lifecycle](./06-mentoring-and-document-lifecycle.md) | Multi-round submissions, mentor workspace, file promotion |
|
||||
| 07 | [Live Finals & Deliberation](./07-live-finals-and-deliberation.md) | Stage manager, Jury 3 live experience, deliberation voting, ResultLock |
|
||||
| 08 | [Platform Integration Matrix](./08-platform-integration-matrix.md) | Page-by-page impact map for admin, jury, applicant, mentor UIs |
|
||||
| 09 | [Implementation Roadmap](./09-implementation-roadmap.md) | 9-phase plan with dependencies, durations, rollback points |
|
||||
| 10 | [Migration Strategy](./10-migration-strategy.md) | 4-phase schema migration, feature flags, data mapping, rollback |
|
||||
| 11 | [Testing & QA](./11-testing-and-qa.md) | Test pyramid, Monaco flow test matrix, deliberation test matrix |
|
||||
| 12 | [Observability & Release Gates](./12-observability-and-release-gates.md) | Metrics, structured logging, alerts, release gates A–F |
|
||||
| 13 | [Open Questions & Governance](./13-open-questions-and-governance.md) | Resolved decisions, remaining P1/P2 questions, delivery governance |
|
||||
|
||||
## Recommended Reading Order
|
||||
|
||||
1. **01 Architecture & Decisions** — understand the "why" and core decisions
|
||||
2. **02 Data Model** — the schema that everything builds on
|
||||
3. **03 Competition Flow** — how the 8 rounds work end-to-end
|
||||
4. **04–07** — deep dives into specific subsystems (read as needed)
|
||||
5. **08 Integration Matrix** — understand the blast radius across the platform
|
||||
6. **09–10** — implementation and migration planning
|
||||
7. **11–12** — quality and operational readiness
|
||||
8. **13 Open Questions** — remaining decisions and governance
|
||||
|
||||
## Key Terminology
|
||||
|
||||
| Old Term | New Term | Notes |
|
||||
|----------|----------|-------|
|
||||
| Pipeline | **Competition** | Top-level competition round container |
|
||||
| Stage | **Round** | Individual phase within a competition |
|
||||
| Track | *(eliminated)* | Replaced by SpecialAward with routing modes |
|
||||
| StageType | **RoundType** | INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION |
|
||||
| configJson | **Typed Config** | Per-round-type Zod-validated configuration |
|
||||
| WinnerProposal | **DeliberationSession** | New deliberation voting model |
|
||||
| WinnerApproval | **DeliberationVote** | Per-juror vote in deliberation |
|
||||
| Confirmation | **Deliberation** | Deliberation IS the confirmation step |
|
||||
|
||||
## Cross-References
|
||||
|
||||
All documents cross-reference each other by filename (e.g., "see [02-data-model.md](./02-data-model.md)"). Model names, config types, and enum values are consistent across all documents.
|
||||
@@ -0,0 +1,283 @@
|
||||
# Architecture & Decisions
|
||||
|
||||
## Overview
|
||||
|
||||
This document captures the core architectural decisions for the MOPC platform redesign. The redesign replaces the current Pipeline → Track → Stage model with a flatter Competition → Round model, introduces typed configurations per round type, and promotes juries, submission windows, and deliberation to first-class entities.
|
||||
|
||||
---
|
||||
|
||||
## Current Problems
|
||||
|
||||
| Problem | Impact |
|
||||
|---------|--------|
|
||||
| **3-level nesting** (Pipeline → Track → Stage) | Cognitive overhead for admins; unnecessary abstraction for a linear flow |
|
||||
| **Generic `configJson` blobs** per stage type | No type safety; hard to know what's configurable without reading code |
|
||||
| **No explicit jury entities** | Juries are implicit (per-stage assignments); can't manage "Jury 1" as an entity |
|
||||
| **Single submission round** | No way to open a second submission window for semi-finalists |
|
||||
| **Track layer for main flow** | MAIN track adds indirection with no value for a linear competition |
|
||||
| **No mentoring workspace** | Mentor file exchange exists but no comments, messaging, or promotion to submission |
|
||||
| **No winner confirmation/deliberation** | No multi-party agreement step to cement winners |
|
||||
| **Missing round types** | Can't model "Semi-finalist Submission", "Mentoring", or "Deliberation" steps |
|
||||
|
||||
---
|
||||
|
||||
## Before & After Architecture
|
||||
|
||||
### BEFORE (Current System)
|
||||
|
||||
```
|
||||
Program
|
||||
└── Pipeline (generic container)
|
||||
├── Track: "Main Competition" (MAIN)
|
||||
│ ├── Stage: "Intake" (INTAKE, configJson: {...})
|
||||
│ ├── Stage: "Filtering" (FILTER, configJson: {...})
|
||||
│ ├── Stage: "Evaluation" (EVALUATION, configJson: {...})
|
||||
│ ├── Stage: "Selection" (SELECTION, configJson: {...})
|
||||
│ ├── Stage: "Live Finals" (LIVE_FINAL, configJson: {...})
|
||||
│ └── Stage: "Results" (RESULTS, configJson: {...})
|
||||
├── Track: "Award 1" (AWARD)
|
||||
└── Track: "Award 2" (AWARD)
|
||||
|
||||
Juries: implicit (per-stage assignments, no named entity)
|
||||
Submissions: single round (one INTAKE stage)
|
||||
Mentoring: basic (messages + notes, no workspace)
|
||||
Winner confirmation: none
|
||||
```
|
||||
|
||||
### AFTER (Redesigned System)
|
||||
|
||||
```
|
||||
Program
|
||||
└── Competition (replaces Pipeline, purpose-built)
|
||||
├── Rounds (linear sequence, replaces Track + Stage):
|
||||
│ ├── R1: "Application Window" ─────── (INTAKE)
|
||||
│ ├── R2: "AI Screening" ──────────── (FILTERING)
|
||||
│ ├── R3: "Jury 1 - Semi-finalist" ── (EVALUATION) ── juryGroupId: jury-1
|
||||
│ ├── R4: "Semi-finalist Docs" ─────── (SUBMISSION)
|
||||
│ ├── R5: "Jury 2 - Finalist" ──────── (EVALUATION) ── juryGroupId: jury-2
|
||||
│ ├── R6: "Finalist Mentoring" ─────── (MENTORING)
|
||||
│ ├── R7: "Live Finals" ────────────── (LIVE_FINAL) ── juryGroupId: jury-3
|
||||
│ └── R8: "Deliberation" ───────────── (DELIBERATION)
|
||||
│
|
||||
├── Jury Groups (explicit, named entities):
|
||||
│ ├── "Jury 1" ── members, caps, ratios ── linked to R3
|
||||
│ ├── "Jury 2" ── members, caps, ratios ── linked to R5
|
||||
│ └── "Jury 3" ── members ── linked to R7 + R8
|
||||
│
|
||||
├── Submission Windows (multi-round):
|
||||
│ ├── Window 1: "Round 1 Docs" ── requirements: [Exec Summary, Business Plan]
|
||||
│ └── Window 2: "Round 2 Docs" ── requirements: [Updated Plan, Video Pitch]
|
||||
│
|
||||
└── Special Awards (standalone entities):
|
||||
├── "Innovation Award" ── mode: STAY_IN_MAIN
|
||||
└── "Impact Award" ── mode: SEPARATE_POOL
|
||||
|
||||
Juries: first-class JuryGroup entities with members, caps, ratios
|
||||
Submissions: multi-round with per-window file requirements
|
||||
Mentoring: full workspace with messaging, file exchange, comments, promotion
|
||||
Deliberation: structured voting with multiple modes, tie-breaking, result lock
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Guiding Principles
|
||||
|
||||
| # | Principle | Description |
|
||||
|---|-----------|-------------|
|
||||
| 1 | **Domain over abstraction** | Models map directly to competition concepts (Jury 1, Round 2, Submission Window). No unnecessary intermediate layers. |
|
||||
| 2 | **Linear by default** | The main competition flow is sequential. Branching exists only for special awards (standalone entities). |
|
||||
| 3 | **Typed configs over JSON blobs** | Each round type has an explicit Zod-validated configuration schema. No more guessing what fields are available. |
|
||||
| 4 | **Explicit entities** | Juries, submission windows, deliberation sessions, and mentor workspaces are first-class database models. |
|
||||
| 5 | **Admin override everywhere** | Any automated decision can be manually overridden with full audit trail. |
|
||||
| 6 | **Deep integration** | Jury groups link to rounds, rounds link to submissions, submissions link to evaluations. No orphaned features. |
|
||||
| 7 | **No silent contract drift** | All schema changes, config shape changes, and behavior changes go through review. Late-stage changes require explicit architecture sign-off. |
|
||||
| 8 | **Score independence** | Juries evaluate independently. Cross-jury visibility is admin-configurable, not automatic. |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Decision Records (ADRs)
|
||||
|
||||
### ADR-01: Eliminate the Track Layer
|
||||
|
||||
**Decision:** Remove the `Track` model entirely. The main competition is a flat sequence of Rounds. Special awards become standalone entities with routing modes.
|
||||
|
||||
**Rationale:** The MOPC competition has one main flow (R1→R8). The Track concept (MAIN/AWARD/SHOWCASE with RoutingMode and DecisionMode) was designed for branching flows that don't exist. Awards don't need their own track — they're parallel processes that reference the same projects.
|
||||
|
||||
**Impact:**
|
||||
- `Track` model deleted
|
||||
- `TrackKind`, `RoutingMode` enums deleted
|
||||
- `ProjectStageState.trackId` removed
|
||||
- Special awards modeled as standalone `SpecialAward` entities with `STAY_IN_MAIN` / `SEPARATE_POOL` modes
|
||||
|
||||
---
|
||||
|
||||
### ADR-02: Rename Pipeline → Competition, Stage → Round
|
||||
|
||||
**Decision:** Rename `Pipeline` to `Competition` and `Stage` to `Round` throughout the codebase.
|
||||
|
||||
**Rationale:** "Competition" and "Round" map directly to how admins and participants think about the system. A competition has rounds. This reduces cognitive overhead in every conversation, document, and UI label.
|
||||
|
||||
**Impact:**
|
||||
- All database models, tRPC routers, services, and UI pages renamed
|
||||
- Migration adds new tables, backfills, then drops old tables
|
||||
- See [10-migration-strategy.md](./10-migration-strategy.md) for full mapping
|
||||
|
||||
---
|
||||
|
||||
### ADR-03: Typed Configuration per Round Type
|
||||
|
||||
**Decision:** Replace the generic `configJson: Json` blob with 7 typed Zod schemas — one per RoundType.
|
||||
|
||||
**Rationale:** `configJson` provided flexibility at the cost of discoverability and safety. Developers and admins couldn't know what fields were available without reading service code. Typed configs give compile-time and runtime validation.
|
||||
|
||||
**Impact:**
|
||||
- 7 Zod schemas: `IntakeConfig`, `FilteringConfig`, `EvaluationConfig`, `SubmissionConfig`, `MentoringConfig`, `LiveFinalConfig`, `DeliberationConfig`
|
||||
- `configJson` field preserved for storage but validated against the appropriate schema on read/write
|
||||
- Admin UI round configuration forms generated from schema definitions
|
||||
- See [02-data-model.md](./02-data-model.md) for full schema definitions
|
||||
|
||||
---
|
||||
|
||||
### ADR-04: JuryGroup as First-Class Entity
|
||||
|
||||
**Decision:** Create `JuryGroup` and `JuryGroupMember` models as named, manageable entities with caps, ratios, and policy configuration.
|
||||
|
||||
**Rationale:** Currently, juries are implicit — a set of assignments for a stage. This makes it impossible to manage "Jury 1" as a thing, configure per-juror caps, or support jury member overlap across rounds. Making JuryGroups explicit enables a dedicated "Juries" admin section.
|
||||
|
||||
**Impact:**
|
||||
- New `JuryGroup` model with label, competition binding, and default policies
|
||||
- New `JuryGroupMember` model with per-member cap overrides and ratio preferences
|
||||
- Members can belong to multiple JuryGroups (Jury 1 + Jury 2 + award juries)
|
||||
- See [04-jury-groups-and-assignment-policy.md](./04-jury-groups-and-assignment-policy.md) for details
|
||||
|
||||
---
|
||||
|
||||
### ADR-05: Deliberation as Confirmation
|
||||
|
||||
**Decision:** Replace the WinnerProposal → jury sign-off → admin approval confirmation flow with a structured deliberation voting system. Deliberation IS the confirmation — no separate step needed.
|
||||
|
||||
**Rationale:** The original confirmation flow required unanimous jury agreement plus admin approval, which is rigid. The deliberation model supports two configurable voting modes (SINGLE_WINNER_VOTE and FULL_RANKING), multiple tie-breaking methods, admin override, and per-category independence — all while serving as the final agreement mechanism.
|
||||
|
||||
**Impact:**
|
||||
- `WinnerProposal`, `WinnerApproval` models removed
|
||||
- New models: `DeliberationSession`, `DeliberationVote`, `DeliberationResult`, `DeliberationParticipant`
|
||||
- New enums: `DeliberationMode`, `DeliberationStatus`, `TieBreakMethod`, `DeliberationParticipantStatus`
|
||||
- `ResultLock` model cements the outcome
|
||||
- See [07-live-finals-and-deliberation.md](./07-live-finals-and-deliberation.md) for the full deliberation specification
|
||||
|
||||
---
|
||||
|
||||
### ADR-06: Score Independence with Configurable Visibility
|
||||
|
||||
**Decision:** Juries are fully independent during active evaluation. During Live Finals and Deliberation, prior jury data is visible to Jury 3 only if admin enables `showPriorJuryData`. All cross-jury data is available in reports.
|
||||
|
||||
**Rationale:** Independent evaluation prevents bias during scoring. However, during the live finals phase, Jury 3 may benefit from seeing the evaluation history. This is a judgment call that should be made by the program admin per competition.
|
||||
|
||||
**Impact:**
|
||||
- No cross-jury queries in jury evaluation pages
|
||||
- `showPriorJuryData` toggle on `LiveFinalConfig` and `DeliberationSession`
|
||||
- Reports section has full cross-jury analytics for internal use
|
||||
- See [03-competition-flow.md](./03-competition-flow.md) cross-cutting behaviors
|
||||
|
||||
---
|
||||
|
||||
### ADR-07: Top-N Configurable Winner Model
|
||||
|
||||
**Decision:** Winners are Top N (configurable, default 3) per category, all projects ranked within their category, with podium UI and cross-category comparison view.
|
||||
|
||||
**Rationale:** Different competitions may want different numbers of winners. All finalist projects should be ranked regardless — this provides maximum flexibility for award ceremonies and reporting.
|
||||
|
||||
**Impact:**
|
||||
- `topN` field in `DeliberationConfig`
|
||||
- `finalRank` field in `DeliberationResult`
|
||||
- UI: podium display for top 3, full ranking table, cross-category comparison view
|
||||
|
||||
---
|
||||
|
||||
### ADR-08: 5-Layer Policy Precedence
|
||||
|
||||
**Decision:** Assignment and configuration policies resolve through a 5-layer precedence chain.
|
||||
|
||||
**Rationale:** Different levels of the system need to set defaults while allowing overrides. A judge might have a program-wide cap default but need a competition-specific override.
|
||||
|
||||
| Layer | Scope | Example |
|
||||
|-------|-------|---------|
|
||||
| 1. System default | Platform-wide | softCapBuffer = 10 |
|
||||
| 2. Program default | Per-program settings | defaultCapMode = SOFT |
|
||||
| 3. Jury group default | Per-JuryGroup | maxProjectsPerJuror = 15 |
|
||||
| 4. Per-member override | Individual judge | judge-A.maxProjects = 20 |
|
||||
| 5. Admin override | Always wins | force-assign project X to judge-A |
|
||||
|
||||
**Impact:**
|
||||
- Policy resolution function consulted during assignment
|
||||
- Admin overrides logged to `DecisionAuditLog`
|
||||
- See [04-jury-groups-and-assignment-policy.md](./04-jury-groups-and-assignment-policy.md) for implementation
|
||||
|
||||
---
|
||||
|
||||
### ADR-09: AI Ranked Shortlist at Every Evaluation Round
|
||||
|
||||
**Decision:** AI generates a recommended ranked shortlist per category at the end of every evaluation round (Jury 1, Jury 2, and any award evaluation). Admin can always override.
|
||||
|
||||
**Rationale:** Consistent AI assistance across all evaluation rounds reduces admin workload and provides data-driven recommendations.
|
||||
|
||||
**Impact:**
|
||||
- `generateAiShortlist` flag in `EvaluationConfig`
|
||||
- AI shortlist service invoked at round completion
|
||||
- Admin UI shows AI recommendations alongside manual selection controls
|
||||
|
||||
---
|
||||
|
||||
### ADR-10: Assignment Intent Lifecycle Management
|
||||
|
||||
**Decision:** Track assignment intents through a full lifecycle (PENDING → HONORED → OVERRIDDEN → EXPIRED → CANCELLED) rather than a simple "create and forget" approach.
|
||||
|
||||
**Rationale:** Intents created at invite time may not be fulfilled due to COI conflicts, cap limits, or admin changes. Without lifecycle tracking, stale intents accumulate with no visibility into why they weren't honored. The lifecycle state machine provides clear audit trails and enables admin dashboards showing intent fulfillment rates.
|
||||
|
||||
**Impact:**
|
||||
- `AssignmentIntentStatus` enum with 5 states (all terminal except PENDING)
|
||||
- Algorithm must check and honor PENDING intents before general assignment
|
||||
- Round close triggers batch expiry of unmatched intents
|
||||
- See [04-jury-groups-and-assignment-policy.md](./04-jury-groups-and-assignment-policy.md)
|
||||
|
||||
---
|
||||
|
||||
### ADR-11: Rejected — Submission Bundle State Tracking
|
||||
|
||||
**Decision:** Do NOT implement a formal `SubmissionBundle` entity with state machine (INCOMPLETE → COMPLETE → LOCKED). Instead, derive completeness from slot requirements vs. uploaded files.
|
||||
|
||||
**Rationale:** The Codex plan proposed a `SubmissionBundle` model that tracked aggregate state across all file slots. This adds a model, a state machine, and synchronization logic (must update bundle state whenever any file changes). The simpler approach — checking `required slots - uploaded files` at query time — achieves the same result without the maintenance burden. The `SubmissionWindow.lockOnClose` flag handles the lock lifecycle.
|
||||
|
||||
**Impact:**
|
||||
- No `SubmissionBundle` model needed
|
||||
- Completeness is a computed property, not a stored state
|
||||
- `SubmissionWindow` + `FileRequirement` + `ProjectFile` are sufficient
|
||||
|
||||
---
|
||||
|
||||
### ADR-12: Optional Purpose Keys for Analytics Grouping
|
||||
|
||||
**Decision:** Add an optional `Round.purposeKey: String?` field for analytics grouping rather than a new `PurposeKey` enum.
|
||||
|
||||
**Rationale:** Different programs may want to compare rounds across competitions (e.g., "all jury-1 selections across 2025 and 2026"). A `purposeKey` like "jury1_selection" enables this without making it a structural dependency. A free-text string is preferred over an enum because new analytics categories shouldn't require schema migrations.
|
||||
|
||||
**Impact:**
|
||||
- `Round.purposeKey` is optional and has no runtime behavior
|
||||
- Used only for cross-competition analytics queries and reporting
|
||||
- Convention: use snake_case descriptive keys (e.g., "jury1_selection", "semifinal_docs", "live_finals")
|
||||
|
||||
---
|
||||
|
||||
## Governance Policy
|
||||
|
||||
### No Silent Contract Drift
|
||||
|
||||
Any change to the following after Phase 0 (Contract Freeze) requires explicit architecture review:
|
||||
|
||||
- Prisma model additions or field changes
|
||||
- Zod config schema modifications
|
||||
- RoundType enum changes
|
||||
- tRPC procedure signature changes
|
||||
- Assignment policy behavior changes
|
||||
|
||||
Changes are not blocked — they require a documented decision with rationale, impact analysis, and sign-off from the architecture owner. See [13-open-questions-and-governance.md](./13-open-questions-and-governance.md) for governance process.
|
||||
1801
docs/unified-architecture-redesign/02-data-model.md
Normal file
1801
docs/unified-architecture-redesign/02-data-model.md
Normal file
File diff suppressed because it is too large
Load Diff
2949
docs/unified-architecture-redesign/03-competition-flow.md
Normal file
2949
docs/unified-architecture-redesign/03-competition-flow.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,349 @@
|
||||
# Jury Groups & Assignment Policy
|
||||
|
||||
## Overview
|
||||
|
||||
Jury groups are first-class entities in the redesigned system. Each jury (Jury 1, Jury 2, Jury 3, award juries) is an explicit `JuryGroup` with named members, configurable assignment caps, category ratio preferences, and policy overrides. This document covers the data model, assignment algorithm, policy precedence, and admin controls.
|
||||
|
||||
---
|
||||
|
||||
## Data Model
|
||||
|
||||
### JuryGroup
|
||||
|
||||
```prisma
|
||||
model JuryGroup {
|
||||
id String @id @default(cuid())
|
||||
competitionId String
|
||||
label String // "Jury 1", "Jury 2", "Live Finals Jury", custom per program
|
||||
description String?
|
||||
roundId String? // which round this jury evaluates (nullable for award juries)
|
||||
|
||||
// Default policies for this group
|
||||
defaultCapMode CapMode @default(SOFT)
|
||||
defaultMaxProjects Int @default(15)
|
||||
softCapBuffer Int @default(10)
|
||||
defaultCategoryBias Json? // { STARTUP: 0.6, BUSINESS_CONCEPT: 0.4 } or null for no preference
|
||||
allowOnboardingSelfService Boolean @default(true) // judges can adjust cap/ratio during onboarding
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
competition Competition @relation(fields: [competitionId], references: [id])
|
||||
round Round? @relation(fields: [roundId], references: [id])
|
||||
members JuryGroupMember[]
|
||||
|
||||
@@index([competitionId])
|
||||
}
|
||||
```
|
||||
|
||||
### JuryGroupMember
|
||||
|
||||
```prisma
|
||||
model JuryGroupMember {
|
||||
id String @id @default(cuid())
|
||||
juryGroupId String
|
||||
userId String
|
||||
role JuryGroupMemberRole @default(MEMBER) // CHAIR, MEMBER, OBSERVER
|
||||
|
||||
// Per-member overrides (null = use group default)
|
||||
capMode CapMode? // override group default
|
||||
maxProjects Int? // override group default
|
||||
categoryBias Json? // override group default ratio
|
||||
|
||||
// Onboarding self-service values (set by judge during onboarding if allowed)
|
||||
selfServiceCap Int?
|
||||
selfServiceBias Json?
|
||||
|
||||
availabilityNotes String? // Free-text availability info
|
||||
|
||||
joinedAt DateTime @default(now())
|
||||
|
||||
juryGroup JuryGroup @relation(fields: [juryGroupId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@unique([juryGroupId, userId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
enum JuryGroupMemberRole {
|
||||
CHAIR // Jury lead — can manage session, see aggregate data, moderate deliberation
|
||||
MEMBER // Regular juror — votes, scores, provides feedback
|
||||
OBSERVER // View-only — can see evaluations/deliberations but cannot vote or score
|
||||
}
|
||||
```
|
||||
|
||||
**Role Permissions:**
|
||||
|
||||
| Permission | CHAIR | MEMBER | OBSERVER |
|
||||
|-----------|-------|--------|----------|
|
||||
| Submit evaluations | Yes | Yes | No |
|
||||
| Vote in deliberation | Yes | Yes | No |
|
||||
| See aggregate scores (during deliberation) | Yes | No* | No |
|
||||
| Moderate deliberation discussion | Yes | No | No |
|
||||
| View all assigned projects | Yes | Yes | Yes (read-only) |
|
||||
| Be counted toward quorum | Yes | Yes | No |
|
||||
|
||||
*Members see aggregates only if `showCollectiveRankings` is enabled in DeliberationConfig.
|
||||
|
||||
### Supporting Enums
|
||||
|
||||
```prisma
|
||||
enum CapMode {
|
||||
HARD // AI cannot assign more than maxProjects
|
||||
SOFT // AI tries to stay under maxProjects, can go up to maxProjects + softCapBuffer
|
||||
NONE // No cap (unlimited assignments)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cap Mode Behavior
|
||||
|
||||
| Cap Mode | AI Assignment Behavior | Manual Assignment |
|
||||
|----------|----------------------|-------------------|
|
||||
| **HARD** | AI will NOT assign more than `maxProjects`. Period. | Admin can still force-assign beyond cap (creates `AssignmentException`). |
|
||||
| **SOFT** | AI tries to stay at `maxProjects`. If excess projects remain after distributing to all judges at their cap, excess is distributed evenly among SOFT judges up to `maxProjects + softCapBuffer`. | Same as HARD — admin can override. |
|
||||
| **NONE** | No limit. AI distributes projects as needed. | No limit. |
|
||||
|
||||
### Effective Cap Calculation
|
||||
|
||||
The effective cap for a judge considers the 5-layer policy precedence:
|
||||
|
||||
```
|
||||
effectiveCap(member) =
|
||||
member.selfServiceCap // Layer 4: onboarding self-service (if allowed and set)
|
||||
?? member.maxProjects // Layer 4: admin per-member override
|
||||
?? member.juryGroup.defaultMaxProjects // Layer 3: jury group default
|
||||
?? program.defaultMaxProjects // Layer 2: program default
|
||||
?? 15 // Layer 1: system default
|
||||
```
|
||||
|
||||
Admin override (Layer 5) bypasses this entirely via `AssignmentException`.
|
||||
|
||||
### Soft Cap Overflow Algorithm
|
||||
|
||||
1. Assign projects to all judges up to their effective cap, respecting category bias
|
||||
2. Count remaining unassigned projects
|
||||
3. If remaining > 0 and SOFT-cap judges exist:
|
||||
- Distribute remaining projects evenly among SOFT judges
|
||||
- Stop when each SOFT judge reaches `effectiveCap + softCapBuffer`
|
||||
4. Any projects still unassigned go to the **Unassigned Queue**
|
||||
|
||||
---
|
||||
|
||||
## Category Quotas & Ratio Bias
|
||||
|
||||
### CategoryQuotas (per JuryGroup)
|
||||
|
||||
```typescript
|
||||
type CategoryQuotas = {
|
||||
STARTUP: { min: number; max: number }
|
||||
BUSINESS_CONCEPT: { min: number; max: number }
|
||||
}
|
||||
```
|
||||
|
||||
### Category Bias (per member)
|
||||
|
||||
Category bias is a **soft preference**, not a hard constraint. It influences the assignment algorithm's project selection but does not guarantee exact ratios.
|
||||
|
||||
```typescript
|
||||
type CategoryBias = {
|
||||
STARTUP: number // e.g., 0.6 = prefer 60% startups
|
||||
BUSINESS_CONCEPT: number // e.g., 0.4 = prefer 40% concepts
|
||||
}
|
||||
```
|
||||
|
||||
Special cases:
|
||||
- `{ STARTUP: 1.0, BUSINESS_CONCEPT: 0.0 }` — judge reviews only startups
|
||||
- `{ STARTUP: 0.0, BUSINESS_CONCEPT: 1.0 }` — judge reviews only concepts
|
||||
- `null` — no preference, algorithm decides
|
||||
|
||||
**Disclosure:** When bias is set, it is disclosed to judges on their dashboard (e.g., "You have been assigned primarily Startup projects based on your preference").
|
||||
|
||||
---
|
||||
|
||||
## 5-Layer Policy Precedence
|
||||
|
||||
| Layer | Scope | Who Sets It | Override Behavior |
|
||||
|-------|-------|-------------|-------------------|
|
||||
| 1 | System default | Platform config | Baseline for all programs |
|
||||
| 2 | Program default | Program admin | Overrides system default for this program |
|
||||
| 3 | Jury group default | Program admin | Overrides program default for this jury |
|
||||
| 4a | Per-member override | Program admin | Overrides jury group default for this judge |
|
||||
| 4b | Self-service override | Judge (during onboarding) | Only if `allowOnboardingSelfService` is true. Cannot exceed admin-set maximum. |
|
||||
| 5 | Admin override | Program admin / Super admin | Always wins. Creates `AssignmentException` with audit trail. |
|
||||
|
||||
Policy resolution proceeds from Layer 5 → Layer 1. The first non-null value wins.
|
||||
|
||||
---
|
||||
|
||||
## Judge Onboarding Self-Service
|
||||
|
||||
When `allowOnboardingSelfService` is enabled on the JuryGroup:
|
||||
|
||||
1. Judge receives invitation email with link to accept
|
||||
2. During onboarding, judge sees their assigned cap and category preference
|
||||
3. Judge can adjust:
|
||||
- **Max projects**: up or down within admin-defined bounds
|
||||
- **Category preference**: ratio of startups vs concepts
|
||||
4. Adjusted values stored in `JuryGroupMember.selfServiceCap` and `JuryGroupMember.selfServiceBias`
|
||||
5. Admin can review and override self-service values at any time
|
||||
|
||||
---
|
||||
|
||||
## Assignment Algorithm
|
||||
|
||||
### Input
|
||||
- List of eligible projects (with category)
|
||||
- List of JuryGroupMembers (with effective caps, biases, COI declarations)
|
||||
- CategoryQuotas for the JuryGroup
|
||||
|
||||
### Steps
|
||||
|
||||
1. **COI Check**: For each judge-project pair, check COI declarations. Skip if COI exists.
|
||||
2. **Category Allocation**: Divide projects into STARTUP and BUSINESS_CONCEPT pools.
|
||||
3. **Primary Assignment** (up to effective cap):
|
||||
- For each judge, select projects matching their category bias
|
||||
- Apply geo-diversity penalty and familiarity bonus (existing algorithm)
|
||||
- Stop when judge reaches effective cap
|
||||
4. **Soft Cap Overflow** (if unassigned projects remain):
|
||||
- Identify SOFT-cap judges
|
||||
- Distribute remaining projects evenly up to cap + buffer
|
||||
5. **Unassigned Queue**: Any remaining projects enter the queue with reason codes
|
||||
|
||||
### Reason Codes for Unassigned Queue
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `ALL_HARD_CAPPED` | All judges at hard cap |
|
||||
| `SOFT_BUFFER_EXHAUSTED` | All soft-cap judges at cap + buffer |
|
||||
| `COI_CONFLICT` | Project has COI with all available judges |
|
||||
| `CATEGORY_IMBALANCE` | No judges available for this category |
|
||||
| `MANUAL_ONLY` | Project flagged for manual assignment |
|
||||
|
||||
---
|
||||
|
||||
## AssignmentIntent (Pre-Assignment at Invite Time)
|
||||
|
||||
From the invite/onboarding integration: when a jury member is invited, the admin can optionally pre-assign specific projects.
|
||||
|
||||
```prisma
|
||||
model AssignmentIntent {
|
||||
id String @id @default(cuid())
|
||||
juryGroupMemberId String
|
||||
roundId String
|
||||
projectId String
|
||||
source AssignmentIntentSource // INVITE | ADMIN | SYSTEM
|
||||
status AssignmentIntentStatus @default(PENDING)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
juryGroupMember JuryGroupMember @relation(...)
|
||||
round Round @relation(...)
|
||||
project Project @relation(...)
|
||||
|
||||
@@unique([juryGroupMemberId, roundId, projectId])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
enum AssignmentIntentSource {
|
||||
INVITE // set during invitation
|
||||
ADMIN // set by admin after invite
|
||||
SYSTEM // set by AI suggestion
|
||||
}
|
||||
|
||||
enum AssignmentIntentStatus {
|
||||
PENDING // Created, awaiting assignment algorithm execution
|
||||
HONORED // Algorithm materialized this intent into an Assignment record
|
||||
OVERRIDDEN // Admin changed the assignment, superseding this intent
|
||||
EXPIRED // Round completed without this intent being honored
|
||||
CANCELLED // Explicitly cancelled by admin or system
|
||||
}
|
||||
```
|
||||
|
||||
### Intent Lifecycle State Machine
|
||||
|
||||
```
|
||||
PENDING ──── [algorithm runs, intent matched] ──── → HONORED
|
||||
│
|
||||
├──── [admin changes assignment] ──────────── → OVERRIDDEN
|
||||
├──── [round completes unmatched] ─────────── → EXPIRED
|
||||
└──── [admin/system cancels] ──────────────── → CANCELLED
|
||||
```
|
||||
|
||||
**Lifecycle rules:**
|
||||
- **PENDING → HONORED**: When the assignment algorithm runs and creates an `Assignment` record matching this intent's judge+project pair. The `AssignmentIntent.id` is stored on the `Assignment` as provenance.
|
||||
- **PENDING → OVERRIDDEN**: When an admin manually assigns the project to a different judge, or removes the intended judge from the JuryGroup. The override is logged to `DecisionAuditLog`.
|
||||
- **PENDING → EXPIRED**: When the round transitions to CLOSED and this intent was never materialized. Automatic batch update on round close.
|
||||
- **PENDING → CANCELLED**: When an admin explicitly removes the intent, or when the judge is removed from the JuryGroup. Source logged.
|
||||
- **Terminal states**: HONORED, OVERRIDDEN, EXPIRED, CANCELLED are all terminal. No transitions out.
|
||||
|
||||
**Algorithm integration:**
|
||||
1. Before algorithmic assignment, load all PENDING intents for the round
|
||||
2. For each intent, attempt to create the assignment (respecting COI, cap checks)
|
||||
3. If assignment succeeds → mark intent HONORED
|
||||
4. If assignment fails (COI, cap exceeded) → keep PENDING, add to unassigned queue with reason code `INTENT_BLOCKED`
|
||||
5. Admin can manually resolve blocked intents
|
||||
|
||||
---
|
||||
|
||||
## AssignmentException (Audited Over-Cap Assignment)
|
||||
|
||||
When an admin manually assigns a project to a judge who is at or beyond their cap:
|
||||
|
||||
```prisma
|
||||
model AssignmentException {
|
||||
id String @id @default(cuid())
|
||||
assignmentId String // the actual assignment record
|
||||
reason String // admin must provide justification
|
||||
overCapBy Int // how many over the effective cap
|
||||
approvedById String // admin who approved
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
approvedBy User @relation(...)
|
||||
}
|
||||
```
|
||||
|
||||
All exceptions are visible in the assignment audit view and included in compliance reports.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Jury Membership
|
||||
|
||||
Judges can belong to multiple JuryGroups simultaneously:
|
||||
|
||||
- Judge A is on Jury 1 AND Jury 2
|
||||
- Judge B is on Jury 2 AND the Innovation Award jury
|
||||
- Judge C is on Jury 1, Jury 2, AND Jury 3
|
||||
|
||||
Each membership has independent cap/bias configuration. A judge's Jury 1 cap of 20 doesn't affect their Jury 2 cap of 10.
|
||||
|
||||
---
|
||||
|
||||
## Admin "Juries" UI Section
|
||||
|
||||
A dedicated admin section for managing JuryGroups:
|
||||
|
||||
- **Create Jury Group**: name, description, linked round, default policies
|
||||
- **Manage Members**: add/remove members, set per-member overrides, view self-service adjustments
|
||||
- **View Assignments**: see all assignments for the group, identify unassigned projects
|
||||
- **Manual Assignment**: drag-and-drop or bulk-assign unassigned projects
|
||||
- **Assignment Audit**: view all exceptions, overrides, and intent fulfillment
|
||||
|
||||
This is separate from the round configuration — JuryGroups are entities that exist independently and are linked to rounds.
|
||||
|
||||
See [08-platform-integration-matrix.md](./08-platform-integration-matrix.md) for full page mapping.
|
||||
|
||||
---
|
||||
|
||||
## Manual Override
|
||||
|
||||
Admin can override ANY assignment decision at ANY time:
|
||||
|
||||
- Reassign a project from one judge to another
|
||||
- Assign a project to a judge beyond their cap (creates `AssignmentException`)
|
||||
- Remove an assignment
|
||||
- Change a judge's cap or bias mid-round
|
||||
- Override the algorithm's category allocation
|
||||
|
||||
All overrides are logged to `DecisionAuditLog` with the admin's identity, timestamp, and reason.
|
||||
248
docs/unified-architecture-redesign/05-special-awards.md
Normal file
248
docs/unified-architecture-redesign/05-special-awards.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Special Awards
|
||||
|
||||
## Overview
|
||||
|
||||
Special awards run alongside the main competition flow, typically activating during or after the Jury 2 evaluation round (R5). Each award has its own jury, document requirements, review window, and selection process. Awards do NOT use the deliberation model — they are decided by their own jury/judge mechanism.
|
||||
|
||||
---
|
||||
|
||||
## Award Routing Modes
|
||||
|
||||
Every special award operates in one of two configurable modes:
|
||||
|
||||
### Mode A: SEPARATE_POOL (Pull-Out)
|
||||
|
||||
Projects are filtered into a dedicated award pool. The award may:
|
||||
- **Pull projects out** of the main competition pool, OR
|
||||
- **Keep projects in both** the main pool and the award pool
|
||||
|
||||
Pull-out requires **admin confirmation** (`routingConfirmationMode: ADMIN_CONFIRMED`). The admin reviews which projects are pulled out and approves before the pull-out takes effect.
|
||||
|
||||
```
|
||||
Main Pool ──┬──→ continues in main competition
|
||||
└──→ [admin confirms] ──→ Award Pool ──→ Award Review ──→ Award Winner
|
||||
```
|
||||
|
||||
### Mode B: STAY_IN_MAIN (Dual Track)
|
||||
|
||||
Projects remain in the main competition but are flagged as "eligible for award." The award evaluation runs in parallel with the main flow.
|
||||
|
||||
```
|
||||
Main Pool ──→ continues in main competition
|
||||
│
|
||||
└──→ flagged "eligible" ──→ Award Review ──→ Award Winner
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Award Mini-Pipeline
|
||||
|
||||
Each special award has its own mini-pipeline:
|
||||
|
||||
1. **Filtering** → AI screens eligible projects based on award criteria
|
||||
2. **Review** → Award jury evaluates eligible projects
|
||||
3. **Selection** → Winner(s) selected by jury vote or single-judge decision
|
||||
|
||||
This mini-pipeline is independent of the main competition rounds. It has its own:
|
||||
- **Jury** (or reuses judges from main juries, with overlap allowed)
|
||||
- **Document requirements** (if the award needs specific docs beyond main submissions)
|
||||
- **Review window** (own start/end dates)
|
||||
- **Selection process** (jury vote or single-judge)
|
||||
|
||||
---
|
||||
|
||||
## Data Model
|
||||
|
||||
### SpecialAward
|
||||
|
||||
```prisma
|
||||
model SpecialAward {
|
||||
id String @id @default(cuid())
|
||||
competitionId String
|
||||
name String
|
||||
description String?
|
||||
|
||||
// Routing
|
||||
routingMode AwardRoutingMode // STAY_IN_MAIN | SEPARATE_POOL
|
||||
pullOutBehavior PullOutBehavior? // REMOVE_FROM_MAIN | KEEP_IN_BOTH (only for SEPARATE_POOL)
|
||||
routingConfirmationMode RoutingConfirmation @default(ADMIN_CONFIRMED)
|
||||
|
||||
// Eligibility
|
||||
eligibilityMode AwardEligibilityMode // AI_SUGGESTED | MANUAL | ALL_ELIGIBLE | ROUND_BASED
|
||||
eligibilityCriteria Json? // AI screening criteria (if AI_SUGGESTED)
|
||||
eligibilityRoundId String? // filter from this round's output (if ROUND_BASED)
|
||||
|
||||
// Jury
|
||||
juryGroupId String? // dedicated jury group (null = single judge)
|
||||
|
||||
// Single judge mode
|
||||
winnerDecisionMode WinnerDecisionMode @default(JURY_VOTE)
|
||||
singleJudgeUserId String? // only if winnerDecisionMode = SINGLE_JUDGE
|
||||
|
||||
// Doc requirements
|
||||
requiresAdditionalDocs Boolean @default(false)
|
||||
docRequirements Json? // additional file slot definitions
|
||||
|
||||
// Review window
|
||||
reviewWindowStart DateTime?
|
||||
reviewWindowEnd DateTime?
|
||||
|
||||
// Audience voting
|
||||
audienceVotingEnabled Boolean @default(false)
|
||||
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
competition Competition @relation(...)
|
||||
juryGroup JuryGroup? @relation(...)
|
||||
winners AwardWinner[]
|
||||
|
||||
@@index([competitionId])
|
||||
}
|
||||
```
|
||||
|
||||
### AwardWinner
|
||||
|
||||
```prisma
|
||||
model AwardWinner {
|
||||
id String @id @default(cuid())
|
||||
awardId String
|
||||
projectId String
|
||||
rank Int @default(1) // 1 = winner, 2+ = runner-up
|
||||
decidedById String // judge or admin who confirmed
|
||||
decisionMode WinnerDecisionMode
|
||||
reason String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
award SpecialAward @relation(...)
|
||||
project Project @relation(...)
|
||||
|
||||
@@unique([awardId, projectId])
|
||||
}
|
||||
```
|
||||
|
||||
### Supporting Enums
|
||||
|
||||
```prisma
|
||||
enum AwardRoutingMode {
|
||||
STAY_IN_MAIN // Mode B: projects stay in main pool
|
||||
SEPARATE_POOL // Mode A: projects enter dedicated pool
|
||||
}
|
||||
|
||||
enum PullOutBehavior {
|
||||
REMOVE_FROM_MAIN // pulled out of main competition
|
||||
KEEP_IN_BOTH // in both main and award pools
|
||||
}
|
||||
|
||||
enum RoutingConfirmation {
|
||||
ADMIN_CONFIRMED // admin must approve pull-out
|
||||
AUTOMATIC // pull-out happens automatically on eligibility
|
||||
}
|
||||
|
||||
enum AwardEligibilityMode {
|
||||
AI_SUGGESTED // AI screens projects against criteria
|
||||
MANUAL // admin manually selects eligible projects
|
||||
ALL_ELIGIBLE // all projects in the competition are eligible
|
||||
ROUND_BASED // projects that passed a specific round are eligible
|
||||
}
|
||||
|
||||
enum WinnerDecisionMode {
|
||||
JURY_VOTE // jury evaluates and votes
|
||||
SINGLE_JUDGE // one designated judge decides
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Eligibility & Filtering
|
||||
|
||||
Award eligibility is determined by the `eligibilityMode`:
|
||||
|
||||
| Mode | How Projects Become Eligible |
|
||||
|------|------------------------------|
|
||||
| `AI_SUGGESTED` | AI screens all projects against the award's `eligibilityCriteria`. Uses the existing AI screening system. |
|
||||
| `MANUAL` | Admin manually flags projects as eligible for this award. |
|
||||
| `ALL_ELIGIBLE` | Every project in the competition is automatically eligible. |
|
||||
| `ROUND_BASED` | Projects that passed a specific round (e.g., filtering, Jury 1) are eligible. |
|
||||
|
||||
For `AI_SUGGESTED`, the filtering uses the same AI screening infrastructure as the main R2 filtering round, just with award-specific criteria.
|
||||
|
||||
---
|
||||
|
||||
## Per-Award Jury
|
||||
|
||||
Each award can have its own `JuryGroup`:
|
||||
- Members can overlap with main juries (same judge on Jury 2 and Innovation Award jury)
|
||||
- Independent cap and assignment configuration
|
||||
- Own scoring rubric if needed
|
||||
|
||||
For simpler awards, `SINGLE_JUDGE` mode allows one designated judge to make the decision directly.
|
||||
|
||||
---
|
||||
|
||||
## Per-Award Document Requirements
|
||||
|
||||
If `requiresAdditionalDocs` is true, the award defines additional file slots that eligible projects must submit:
|
||||
|
||||
```typescript
|
||||
type AwardDocRequirement = {
|
||||
slotKey: string // e.g., "innovation_statement"
|
||||
label: string // "Innovation Impact Statement"
|
||||
required: boolean
|
||||
maxFileSize: number // bytes
|
||||
acceptedTypes: string[] // ["application/pdf", "video/mp4"]
|
||||
}
|
||||
```
|
||||
|
||||
These are separate from the main submission windows. Award docs are uploaded through the applicant's award-specific section.
|
||||
|
||||
---
|
||||
|
||||
## Award Review Window
|
||||
|
||||
Each award has its own review window (start/end dates) that is independent of the main competition schedule. The review window:
|
||||
- Can overlap with Jury 2 evaluation or run after it
|
||||
- Shows countdown on the award jury's dashboard
|
||||
- Triggers email reminders as the deadline approaches
|
||||
|
||||
---
|
||||
|
||||
## No Deliberation for Awards
|
||||
|
||||
Special awards are decided by their own jury/judge mechanism:
|
||||
- **JURY_VOTE**: Award jury members evaluate and vote. Simple majority or highest score wins.
|
||||
- **SINGLE_JUDGE**: Designated judge reviews and selects the winner(s).
|
||||
|
||||
There is no `DeliberationSession` for awards. The award winner is confirmed by the deciding jury/judge and recorded as an `AwardWinner`.
|
||||
|
||||
---
|
||||
|
||||
## Audience Voting for Awards
|
||||
|
||||
If `audienceVotingEnabled` is true on an award:
|
||||
- Audience can vote for their preferred project within the award pool
|
||||
- Audience vote can influence the award decision (configurable weight)
|
||||
- Audience vote results are visible to the award jury during their review
|
||||
|
||||
---
|
||||
|
||||
## Admin Controls
|
||||
|
||||
- **Create/edit awards**: name, mode, eligibility, jury, doc requirements, review window
|
||||
- **Confirm pull-out**: for SEPARATE_POOL mode, admin reviews and approves which projects are pulled
|
||||
- **Override eligibility**: admin can add/remove projects from award eligibility at any time
|
||||
- **Override winner**: admin can override the jury/judge decision with audit trail
|
||||
- **View award status**: see all awards, their eligible projects, jury progress, and winners
|
||||
|
||||
All admin actions on awards are logged to `DecisionAuditLog`.
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
- **R5 (Jury 2)**: Awards typically activate during or after Jury 2. Award filtering runs alongside Jury 2 evaluation.
|
||||
- **Live Finals (R7)**: Award winners may be announced during the live finals ceremony.
|
||||
- **Reports**: Award selections, jury decisions, and audit trails are included in competition reports.
|
||||
|
||||
See [03-competition-flow.md](./03-competition-flow.md) for how awards fit into the overall competition flow.
|
||||
@@ -0,0 +1,315 @@
|
||||
# Mentoring & Document Lifecycle
|
||||
|
||||
## Overview
|
||||
|
||||
This document covers two interconnected systems: (1) the multi-round document lifecycle that governs how submissions flow through the competition, and (2) the mentoring workspace that provides a collaboration layer for finalist teams with assigned mentors.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Document Lifecycle
|
||||
|
||||
### SubmissionWindow
|
||||
|
||||
Each round that requires document collection has an associated `SubmissionWindow`. A competition can have multiple windows (e.g., Round 1 application docs, Round 2 semifinal docs).
|
||||
|
||||
```prisma
|
||||
model SubmissionWindow {
|
||||
id String @id @default(cuid())
|
||||
competitionId String
|
||||
roundId String
|
||||
label String // "Round 1 Application Documents", "Round 2 Semifinal Documents"
|
||||
|
||||
opensAt DateTime
|
||||
closesAt DateTime
|
||||
deadlinePolicy DeadlinePolicy @default(HARD)
|
||||
gracePeriodMinutes Int? // only used with GRACE policy
|
||||
lockOnClose Boolean @default(true) // automatically lock submissions when window closes
|
||||
isLocked Boolean @default(false) // manual lock toggle (admin can lock before close)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
competition Competition @relation(...)
|
||||
round Round @relation(...)
|
||||
requirements SubmissionFileRequirement[]
|
||||
|
||||
@@index([competitionId])
|
||||
@@index([roundId])
|
||||
}
|
||||
```
|
||||
|
||||
### DeadlinePolicy
|
||||
|
||||
| Policy | Behavior |
|
||||
|--------|----------|
|
||||
| `HARD` | Submissions are cut off at `closesAt`. No uploads after the deadline. |
|
||||
| `FLAG` | Submissions after `closesAt` are accepted but marked as **Late**. Admin can see late status. |
|
||||
| `GRACE` | A grace period (`gracePeriodMinutes`) after `closesAt` during which submissions are still accepted without penalty. After grace, behaves as HARD. |
|
||||
|
||||
```prisma
|
||||
enum DeadlinePolicy {
|
||||
HARD
|
||||
FLAG
|
||||
GRACE
|
||||
}
|
||||
```
|
||||
|
||||
### SubmissionFileRequirement
|
||||
|
||||
Each window defines required file slots:
|
||||
|
||||
```prisma
|
||||
model SubmissionFileRequirement {
|
||||
id String @id @default(cuid())
|
||||
submissionWindowId String
|
||||
slotKey String // "executive_summary", "business_plan", "pitch_video"
|
||||
label String // "Executive Summary"
|
||||
description String? // guidance text for applicants
|
||||
required Boolean @default(true)
|
||||
maxFileSize Int @default(10485760) // 10MB default
|
||||
acceptedTypes String[] // ["application/pdf", "video/mp4"]
|
||||
sortOrder Int @default(0)
|
||||
|
||||
submissionWindow SubmissionWindow @relation(...)
|
||||
|
||||
@@unique([submissionWindowId, slotKey])
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Round Document Visibility
|
||||
|
||||
| Actor | Current Round Docs | Previous Round Docs |
|
||||
|-------|-------------------|-------------------|
|
||||
| **Applicant** | Can upload/edit until window closes | **Read-only** (view/download only) |
|
||||
| **Judge** | Sees current round docs in review | Sees previous round docs (clearly separated in UI) |
|
||||
| **Admin** | Full control (upload/remove/replace) | Full control (upload/remove/replace) |
|
||||
| **Mentor** | Sees within mentor workspace | Sees within mentor workspace |
|
||||
|
||||
When a submission window closes or a round advances:
|
||||
- Applicant's editing permissions for that window's docs are revoked
|
||||
- Docs remain visible for download
|
||||
- Judges in subsequent rounds can view all prior docs
|
||||
|
||||
### RoundSubmissionVisibility
|
||||
|
||||
Controls which prior round docs are visible to judges:
|
||||
|
||||
```prisma
|
||||
model RoundSubmissionVisibility {
|
||||
id String @id @default(cuid())
|
||||
roundId String // the round where the judge is reviewing
|
||||
visibleRoundId String // the round whose docs are visible
|
||||
separateInUi Boolean @default(true) // show in separate section
|
||||
|
||||
round Round @relation("reviewingRound", ...)
|
||||
visibleRound Round @relation("visibleRound", ...)
|
||||
|
||||
@@unique([roundId, visibleRoundId])
|
||||
}
|
||||
```
|
||||
|
||||
For example, Jury 2 (R5) would have visibility entries for R1 (Intake docs) and R4 (Semifinal docs), both with `separateInUi: true`.
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Mentoring Workspace
|
||||
|
||||
### Purpose
|
||||
|
||||
Mentoring is NOT a judging stage. It's a collaboration layer for finalist teams who have requested mentoring. Mentors help teams polish their submissions before live finals.
|
||||
|
||||
### Who Gets Mentoring
|
||||
|
||||
- Only finalist teams (or whatever stage admin configures) that have "requested mentor" enabled
|
||||
- Mentor assignment works similarly to judge assignment (from a mentor pool)
|
||||
- Each mentored team gets one assigned mentor
|
||||
|
||||
### Workspace Features
|
||||
|
||||
The mentor-team workspace provides three capabilities:
|
||||
|
||||
#### 1. Messaging / Chat
|
||||
|
||||
```prisma
|
||||
model MentorMessage {
|
||||
id String @id @default(cuid())
|
||||
projectId String // the mentored project
|
||||
mentorId String // the assigned mentor
|
||||
senderId String // who sent the message
|
||||
senderRole MentorMessageRole
|
||||
content String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
project Project @relation(...)
|
||||
mentor User @relation("mentorMessages", ...)
|
||||
sender User @relation("sentMentorMessages", ...)
|
||||
|
||||
@@index([projectId, mentorId])
|
||||
}
|
||||
|
||||
enum MentorMessageRole {
|
||||
MENTOR
|
||||
APPLICANT
|
||||
ADMIN
|
||||
}
|
||||
```
|
||||
|
||||
Messaging allows real-time communication between mentor and team. Admins can also send messages into the workspace.
|
||||
|
||||
#### 2. File Upload
|
||||
|
||||
```prisma
|
||||
model MentorFile {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
mentorId String
|
||||
uploadedById String
|
||||
uploaderRole MentorMessageRole
|
||||
fileName String
|
||||
fileKey String // MinIO storage key
|
||||
fileSize Int
|
||||
mimeType String
|
||||
description String?
|
||||
|
||||
// Promotion tracking
|
||||
promotedToSlot String? // slotKey if promoted to official submission
|
||||
promotedAt DateTime?
|
||||
promotedById String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
project Project @relation(...)
|
||||
comments MentorFileComment[]
|
||||
|
||||
@@index([projectId, mentorId])
|
||||
}
|
||||
```
|
||||
|
||||
Both mentor and team can upload files. Files are stored in MinIO and accessed via pre-signed URLs.
|
||||
|
||||
#### 3. Threaded File Comments
|
||||
|
||||
```prisma
|
||||
model MentorFileComment {
|
||||
id String @id @default(cuid())
|
||||
fileId String
|
||||
authorId String
|
||||
authorRole MentorMessageRole
|
||||
content String @db.Text
|
||||
parentId String? // for threading
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
file MentorFile @relation(...)
|
||||
author User @relation(...)
|
||||
parent MentorFileComment? @relation("commentThread", ...)
|
||||
replies MentorFileComment[] @relation("commentThread")
|
||||
|
||||
@@index([fileId])
|
||||
}
|
||||
```
|
||||
|
||||
Comments are threaded (parentId for replies). Each comment is tied to a specific file.
|
||||
|
||||
### Privacy
|
||||
|
||||
All mentoring workspace content is **private by default**:
|
||||
- Visible to: the assigned mentor, the team members, and admins
|
||||
- NOT visible to: other teams, other mentors, judges, audience
|
||||
|
||||
### File Promotion to Official Submission
|
||||
|
||||
A mentoring file can be "promoted" to become an official submission for a required document slot in the active submission round.
|
||||
|
||||
**Example flow:**
|
||||
1. Team uploads "Business Plan Draft v3.pdf" to mentor workspace
|
||||
2. Mentor reviews and approves
|
||||
3. Team (or admin) marks it as the official "Business Plan" submission for Round 2
|
||||
4. System creates a `SubmissionPromotionEvent` with immutable provenance
|
||||
|
||||
```prisma
|
||||
model SubmissionPromotionEvent {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
roundId String
|
||||
slotKey String // the doc slot being filled
|
||||
sourceType PromotionSourceType
|
||||
sourceFileId String // MentorFile.id or admin-uploaded file ID
|
||||
promotedById String // who triggered the promotion
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
project Project @relation(...)
|
||||
round Round @relation(...)
|
||||
promotedBy User @relation(...)
|
||||
|
||||
@@index([projectId, roundId])
|
||||
}
|
||||
|
||||
enum PromotionSourceType {
|
||||
MENTOR_FILE // promoted from mentor workspace
|
||||
ADMIN_REPLACEMENT // admin replaced the file directly
|
||||
}
|
||||
```
|
||||
|
||||
**Promotion authority:**
|
||||
- **Team lead**: can promote their own mentor workspace files
|
||||
- **Admin**: can promote any file or directly replace with admin-uploaded file
|
||||
- **Mentor**: can promote IF admin enables this per competition (configurable)
|
||||
|
||||
**Provenance:** Every promotion records who promoted, when, from which source file, and the source type. This audit trail is immutable.
|
||||
|
||||
### MentoringConfig (Round Type Configuration)
|
||||
|
||||
```typescript
|
||||
const MentoringConfig = z.object({
|
||||
eligibility: z.enum(['FINALISTS_ONLY', 'SEMIFINALISTS_AND_ABOVE', 'CONFIGURABLE']),
|
||||
requireMentorRequest: z.boolean().default(true), // team must opt-in
|
||||
assignmentMethod: z.enum(['MANUAL', 'AI_SUGGESTED', 'AI_AUTO', 'ALGORITHM']),
|
||||
workspaceFeatures: z.object({
|
||||
messagingEnabled: z.boolean().default(true),
|
||||
fileUploadEnabled: z.boolean().default(true),
|
||||
fileCommentsEnabled: z.boolean().default(true),
|
||||
}),
|
||||
promotionTarget: z.string().optional(), // roundId where promoted files go
|
||||
allowMentorPromotion: z.boolean().default(false), // can mentors promote files?
|
||||
deadlinePolicy: z.nativeEnum(DeadlinePolicy).default('FLAG'),
|
||||
})
|
||||
```
|
||||
|
||||
### Mentor Dashboard
|
||||
|
||||
Mentors have a dedicated dashboard showing:
|
||||
- **Assigned teams**: list of all teams they're mentoring
|
||||
- **Per-team workspace**: click through to messaging, file exchange, comments
|
||||
- **Deadline tracking**: upcoming deadlines for the mentored teams
|
||||
- **Progress indicators**: which teams have submitted their required docs, which are pending
|
||||
- **File review queue**: files uploaded by teams that need mentor review
|
||||
|
||||
See [08-platform-integration-matrix.md](./08-platform-integration-matrix.md) for the full mentor page mapping.
|
||||
|
||||
---
|
||||
|
||||
## Admin Document Controls
|
||||
|
||||
Admins have full control over documents across all rounds:
|
||||
|
||||
| Action | Scope | Audit |
|
||||
|--------|-------|-------|
|
||||
| View any submission | All rounds, all projects | Read-only, no audit needed |
|
||||
| Upload file for a project | Any round, any slot | Logged with `sourceType: ADMIN_REPLACEMENT` |
|
||||
| Remove/replace a file | Any round, any slot | Logged with previous file reference |
|
||||
| Lock a submission window early | Specific window | Logged |
|
||||
| Extend a deadline | Specific window | Logged |
|
||||
| Promote a mentor file | Any project's workspace | Creates `SubmissionPromotionEvent` |
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
- **R1 (Intake)**: First `SubmissionWindow` with R1 document requirements
|
||||
- **R4 (Semifinal Submission)**: Second `SubmissionWindow` with R2 requirements; R1 docs locked for applicants
|
||||
- **R5 (Jury 2)**: Judges see R1 + R2 docs clearly separated via `RoundSubmissionVisibility`
|
||||
- **R6 (Mentoring)**: Mentor workspace active, file promotion targets the active submission window
|
||||
- **R7 (Live Finals)**: Jury 3 sees all submitted docs from all rounds
|
||||
|
||||
See [03-competition-flow.md](./03-competition-flow.md) for the complete round-by-round specification.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,503 @@
|
||||
# 08. Platform Integration Matrix
|
||||
|
||||
## 1. Overview
|
||||
|
||||
This document maps the **blast radius** of the Competition/Round redesign across every page, router, and service in the MOPC platform. It serves as a reference for developers to understand:
|
||||
|
||||
- **Which pages** need UI updates to reflect new terminology and data models
|
||||
- **Which routers** require refactoring vs. minor updates vs. no changes
|
||||
- **Which services** must be renamed, rewritten, or extended
|
||||
- **Integration acceptance criteria** to verify completeness
|
||||
|
||||
Use this document alongside the data model ([02-data-model.md]) and competition flow ([03-competition-flow.md]) to plan implementation work.
|
||||
|
||||
### Terminology Translation
|
||||
|
||||
The redesign replaces Pipeline/Track/Stage terminology with Competition/Round:
|
||||
|
||||
| Old Term | New Term | Notes |
|
||||
|----------|----------|-------|
|
||||
| Pipeline | Competition | Top-level container for a complete program round |
|
||||
| Stage | Round | Individual phases within a competition (Intake, Jury 1, Jury 2, etc.) |
|
||||
| Track | *Eliminated* | Replaced by SpecialAward routing modes and JuryGroup memberships |
|
||||
| StageType | RoundType | 7 typed round kinds (Intake, Eligibility Filter, Jury Evaluation 1/2/3, Live Finals, Results) |
|
||||
| configJson | Typed configs | Each RoundType has its own Zod-validated config schema |
|
||||
|
||||
### Reading This Document
|
||||
|
||||
- **Priority levels**: Critical (blocks launch), High (required for full feature parity), Medium (enhances UX), Low (nice-to-have)
|
||||
- **Classifications**: Directly Governed (major refactor), Context-Dependent (moderate updates), Utility (minimal/no changes)
|
||||
- **File paths**: All paths relative to `G:\Repos\MOPC\`
|
||||
|
||||
---
|
||||
|
||||
## 2. Admin Pages Impact Matrix
|
||||
|
||||
Admin pages require the most extensive changes due to terminology shifts and new JuryGroup management.
|
||||
|
||||
| Page/Route | Current State | Required Changes | Priority | Related Docs |
|
||||
|------------|---------------|------------------|----------|--------------|
|
||||
| **Dashboard & Overview** |
|
||||
| `src/app/(admin)/admin/page.tsx` | Program overview, pipeline status | Update "Pipelines" → "Competitions", show Competition status tiles | High | [03-competition-flow.md] |
|
||||
| **Competition Management** (was Pipeline) |
|
||||
| `src/app/(admin)/admin/rounds/pipelines/page.tsx` | Pipeline list view | Rename to "Competitions", update filters/columns | Critical | [02-data-model.md] |
|
||||
| `src/app/(admin)/admin/rounds/pipeline/[id]/page.tsx` | Pipeline detail view | Rename to Competition detail, show Round timeline | Critical | [03-competition-flow.md] |
|
||||
| `src/app/(admin)/admin/rounds/new-pipeline/page.tsx` | Pipeline creation wizard | Rename to "New Competition", use wizard-template.ts | Critical | [05-competition-wizard.md] |
|
||||
| `src/app/(admin)/admin/rounds/pipeline/[id]/wizard/page.tsx` | Stage setup wizard | Rename to "Round Setup", enforce RoundType requirements, validate skeleton completeness | Critical | [05-competition-wizard.md] |
|
||||
| `src/app/(admin)/admin/rounds/pipeline/[id]/edit/page.tsx` | Basic pipeline editing | Rename to Competition editing, prevent invalid Round sequences | High | [05-competition-wizard.md] |
|
||||
| `src/app/(admin)/admin/rounds/pipeline/[id]/advanced/page.tsx` | Advanced config editing | Add policy editors for JuryGroup binding, assignment policies, submission windows, deliberation config | High | [04-jury-groups.md] |
|
||||
| **Round Configuration** (was Stage) |
|
||||
| `src/components/admin/pipeline/stage-config-editor.tsx` | Generic stage config | Render RoundType-aware controls only, use typed configs (IntakeConfig, FilterConfig, etc.) | Critical | [06-round-configs.md] |
|
||||
| `src/components/admin/pipeline/sections/assignment-section.tsx` | min/max load + overflow | Replace with hard/soft cap + buffer + category bias controls (labeled as suggestive, not deterministic) | High | [04-jury-groups.md] |
|
||||
| `src/components/admin/pipeline/sections/filtering-section.tsx` | Rules + AI criteria | Bind to Eligibility Filter RoundType, standardized outcomes | High | [07-deliberation.md] |
|
||||
| `src/components/admin/pipeline/sections/awards-section.tsx` | Award routing/scoring | Add participation mode (separate_pool/dual_track), pull-out behavior, single-judge config | High | [04-jury-groups.md] |
|
||||
| `src/components/admin/pipeline/sections/live-finals-section.tsx` | Live/audience settings | Add Jury 3 deliberation window, final deliberation linkage, cursor control config | High | [07-deliberation.md] |
|
||||
| **Juries Management (NEW)** |
|
||||
| *(NEW)* `src/app/(admin)/admin/juries/page.tsx` | None (create new) | List all JuryGroups (Jury 1, 2, 3, award juries), show member counts, caps, overlaps | Critical | [04-jury-groups.md] |
|
||||
| *(NEW)* `src/app/(admin)/admin/juries/[id]/page.tsx` | None (create new) | JuryGroup detail: members, cap config, category ratios, bias settings | Critical | [04-jury-groups.md] |
|
||||
| *(NEW)* `src/app/(admin)/admin/juries/[id]/members/page.tsx` | None (create new) | Add/remove jury members, configure individual caps/biases, track overlaps | Critical | [04-jury-groups.md] |
|
||||
| **Assignment Management** |
|
||||
| `src/app/(admin)/admin/projects/pool/page.tsx` | Project pool operations | Use AssignmentIntent for manual queuing, show reason tracking, policy compliance status | High | [04-jury-groups.md] |
|
||||
| `src/components/admin/assignment/*` | Stage-based assignment | Update to Round-based, show JuryGroup context, cap violations, buffer status | High | [04-jury-groups.md] |
|
||||
| **Filtering Configuration** |
|
||||
| `src/app/(admin)/admin/projects/page.tsx` | Project list by filters | Add Round-aware views, show submission bundle health, Round 1/2 document status | Medium | [06-round-configs.md] |
|
||||
| **Submission Window Management** |
|
||||
| *(Enhanced)* Round config pages | Embedded in stage config | Extract submission window config to dedicated UI: slots, grace periods, late policy, resubmit rules | High | [06-round-configs.md] |
|
||||
| **Special Awards Configuration** |
|
||||
| `src/app/(admin)/admin/awards/page.tsx` | Award list | Expose Mode A/B semantics, admin-confirmed pull-out workflow | High | [04-jury-groups.md] |
|
||||
| `src/app/(admin)/admin/awards/[id]/page.tsx` | Award detail | Show participation mode, JuryGroup bindings, single-judge config | High | [04-jury-groups.md] |
|
||||
| `src/app/(admin)/admin/awards/[id]/edit/page.tsx` | Award editing | Add strict validation for mode, Round bindings, jury requirements | High | [04-jury-groups.md] |
|
||||
| `src/app/(admin)/admin/awards/new/page.tsx` | Award creation | Wizard for Mode A/B selection, JuryGroup assignment | High | [04-jury-groups.md] |
|
||||
| **Live Finals Management** |
|
||||
| *(NEW)* `src/app/(admin)/admin/rounds/[id]/live/page.tsx` | None (create new) | Stage manager controls: cursor position, project advance, deliberation timer, audience vote toggle | Critical | [07-deliberation.md] |
|
||||
| **Deliberation Management (NEW)** |
|
||||
| *(NEW)* `src/app/(admin)/admin/rounds/[id]/deliberation/page.tsx` | None (create new) | Session management, lock controls, quorum status, admin override panel | Critical | [07-deliberation.md] |
|
||||
| **Results / Result Lock Management** |
|
||||
| `src/app/(admin)/admin/reports/stages/page.tsx` | Stage reporting | Include deliberation session status, result lock state, compliance metrics (cap violations, override counts) | High | [07-deliberation.md] |
|
||||
| *(NEW)* `src/app/(admin)/admin/rounds/[id]/results/lock/page.tsx` | None (create new) | Final result lock interface with snapshot preview, mandatory reason, audit trail | Critical | [07-deliberation.md] |
|
||||
| *(NEW)* `src/app/(admin)/admin/rounds/[id]/results/unlock/page.tsx` | None (super admin only) | Result unlock workflow with mandatory justification, relock capability | Critical | [07-deliberation.md] |
|
||||
| **User/Invite Management** |
|
||||
| `src/app/(admin)/admin/members/page.tsx` | User list | Add JuryGroup membership column, show pending AssignmentIntents | Medium | [04-jury-groups.md] |
|
||||
| `src/app/(admin)/admin/members/invite/page.tsx` | User invitation | Bind to JuryGroup selection, support AssignmentIntent mode vs. direct assignment | Critical | [04-jury-groups.md] |
|
||||
| `src/app/(admin)/admin/members/[id]/page.tsx` | User detail | Show all JuryGroup memberships, assignment history, cap status | Medium | [04-jury-groups.md] |
|
||||
| **Reports / Analytics** |
|
||||
| `src/app/(admin)/admin/reports/page.tsx` | Generic reports | Add Competition-aware KPIs: assignment saturation, override rate, policy compliance | Medium | [03-competition-flow.md] |
|
||||
| `src/app/(admin)/admin/audit/page.tsx` | Audit log search | Add filter facets: RoundType, JuryGroup ID, deliberation session | Medium | [07-deliberation.md] |
|
||||
| **Settings** |
|
||||
| `src/app/(admin)/admin/settings/page.tsx` | Platform settings | No terminology changes needed | Low | - |
|
||||
| `src/app/(admin)/admin/settings/tags/page.tsx` | Tag management | No changes | Low | - |
|
||||
| `src/app/(admin)/admin/settings/webhooks/page.tsx` | Webhook config | Update event names: pipeline.* → competition.*, stage.* → round.* | Medium | - |
|
||||
|
||||
---
|
||||
|
||||
## 3. Jury Pages Impact Matrix
|
||||
|
||||
Jury pages must reflect Round-based navigation and JuryGroup membership.
|
||||
|
||||
| Page/Route | Current State | Required Changes | Priority | Related Docs |
|
||||
|------------|---------------|------------------|----------|--------------|
|
||||
| **Jury Dashboard** |
|
||||
| `src/app/(jury)/jury/page.tsx` | Stage countdown, assigned projects | Show Round countdown, multi-JuryGroup memberships, completion obligations per group | High | [04-jury-groups.md] |
|
||||
| **Round Navigation** (was Stage) |
|
||||
| `src/app/(jury)/jury/stages/page.tsx` | Active stages list | Rename to "Rounds", show only Rounds bound to user's JuryGroups | Critical | [03-competition-flow.md] |
|
||||
| `src/app/(jury)/jury/stages/[stageId]/assignments/page.tsx` | Assignment list | Update to Round context, show cap status, category ratio compliance, buffer usage | High | [04-jury-groups.md] |
|
||||
| **Project Review** |
|
||||
| `src/app/(jury)/jury/stages/[stageId]/projects/[projectId]/page.tsx` | Project details | Separate Round 1 vs. Round 2 documents clearly, show submission window provenance | High | [06-round-configs.md] |
|
||||
| `src/app/(jury)/jury/stages/[stageId]/projects/[projectId]/evaluate/page.tsx` | Evaluation form | Enforce Round/JuryGroup policy, show graceful lock behavior, CoI declaration | Critical | [04-jury-groups.md] |
|
||||
| `src/app/(jury)/jury/stages/[stageId]/projects/[projectId]/evaluation/page.tsx` | View evaluation | Show evaluation history, AI summary integration | Medium | [06-round-configs.md] |
|
||||
| `src/app/(jury)/jury/stages/[stageId]/compare/page.tsx` | Compare projects | Update to Round context, show Round 1/2 docs side-by-side | Medium | - |
|
||||
| **Live Finals Voting** |
|
||||
| `src/app/(jury)/jury/stages/[stageId]/live/page.tsx` | Live finals interaction | Require Jury 3 membership, show current cursor position, enable voting only for current project | Critical | [07-deliberation.md] |
|
||||
| **Deliberation Voting (NEW)** |
|
||||
| *(NEW)* `src/app/(jury)/jury/stages/[stageId]/deliberation/page.tsx` | None (create new) | Deliberation interface: category scope, project shortlist, vote casting, quorum status | Critical | [07-deliberation.md] |
|
||||
| **Awards** |
|
||||
| `src/app/(jury)/jury/awards/page.tsx` | Award assignments | Bind to award JuryGroup memberships, hide single-judge awards | High | [04-jury-groups.md] |
|
||||
| `src/app/(jury)/jury/awards/[id]/page.tsx` | Award voting | Show participation mode, suppress jury voting for single-judge awards | High | [04-jury-groups.md] |
|
||||
| **Learning Resources** |
|
||||
| `src/app/(jury)/jury/learning/page.tsx` | Learning materials | No terminology changes | Low | - |
|
||||
|
||||
---
|
||||
|
||||
## 4. Applicant Pages Impact Matrix
|
||||
|
||||
Applicant pages must reflect Round-based journey and submission windows.
|
||||
|
||||
| Page/Route | Current State | Required Changes | Priority | Related Docs |
|
||||
|------------|---------------|------------------|----------|--------------|
|
||||
| **Applicant Dashboard** |
|
||||
| `src/app/(applicant)/applicant/page.tsx` | Generic dashboard | Show Round-based journey (R1 Intake → Eligibility → Jury 1 Result → R2 Submission → Jury 2 → Live Finals → Results) | High | [03-competition-flow.md] |
|
||||
| **Pipeline/Journey View** |
|
||||
| `src/app/(applicant)/applicant/pipeline/page.tsx` | Pipeline visibility | Rename to "Competition Journey", show RoundType-based progress, submission window status | Critical | [03-competition-flow.md] |
|
||||
| `src/app/(applicant)/applicant/pipeline/[stageId]/status/page.tsx` | Stage status | Update to Round status, show jury assignment count, evaluation progress | Medium | [06-round-configs.md] |
|
||||
| **Document Upload** |
|
||||
| `src/app/(applicant)/applicant/pipeline/[stageId]/documents/page.tsx` | Stage docs | Enforce Round-based submission windows, show slot state (write/read-only), late submission warnings | Critical | [06-round-configs.md] |
|
||||
| `src/app/(applicant)/applicant/documents/page.tsx` | Docs overview | Show Round 1 vs. Round 2 bundles, official slot timeline, promotion provenance | High | [06-round-configs.md] |
|
||||
| **Team Management** |
|
||||
| `src/app/(applicant)/applicant/team/page.tsx` | Team invites/members | Gate permissions via context resolver, ensure invite acceptance routes correctly | Medium | [02-data-model.md] |
|
||||
| **Mentoring Workspace** |
|
||||
| `src/app/(applicant)/applicant/mentor/page.tsx` | Mentor interaction | Show mentor messages, workspace files, promotion candidates, official slot mapping | High | [06-round-configs.md] |
|
||||
| **Public Submission Forms** |
|
||||
| `src/app/(public)/apply/[slug]/page.tsx` | Stage/public form | Enforce SubmissionWindow contract (slots, grace period, late policy) | Critical | [06-round-configs.md] |
|
||||
| `src/app/(public)/apply/edition/[programSlug]/page.tsx` | Edition mode intake | Map to Intake RoundType, enforce Round 1 submission policies | Critical | [06-round-configs.md] |
|
||||
| `src/app/(public)/my-submission/[id]/page.tsx` | Submission detail | Show Round context, submission window status, document bundle health | Medium | [06-round-configs.md] |
|
||||
| `src/app/(public)/my-submission/[id]/team/page.tsx` | Team management | Mirror applicant team flow, no divergent logic | Medium | [02-data-model.md] |
|
||||
|
||||
---
|
||||
|
||||
## 5. Mentor Pages Impact Matrix
|
||||
|
||||
Mentor pages need workspace enhancements and file promotion logic.
|
||||
|
||||
| Page/Route | Current State | Required Changes | Priority | Related Docs |
|
||||
|------------|---------------|------------------|----------|--------------|
|
||||
| **Mentor Dashboard** |
|
||||
| `src/app/(mentor)/mentor/page.tsx` | Assigned teams overview | Show Round progression, submission window deadlines, file promotion status | High | [06-round-configs.md] |
|
||||
| `src/app/(mentor)/mentor/projects/page.tsx` | Team list | Add Round context, show document bundle completeness | Medium | [06-round-configs.md] |
|
||||
| **Mentor Workspace** |
|
||||
| `src/app/(mentor)/mentor/projects/[id]/page.tsx` | Project detail | Add workspace file uploads, threaded comments, file promotion actions (team-lead/admin only) | Critical | [06-round-configs.md] |
|
||||
| *(NEW)* `src/app/(mentor)/mentor/projects/[id]/workspace/page.tsx` | None (create new) | Dedicated workspace tab: private files, comments, promotion queue, official slot preview | Critical | [06-round-configs.md] |
|
||||
| *(NEW)* `src/app/(mentor)/mentor/projects/[id]/milestones/page.tsx` | None (create new or enhance) | Milestone tracking, deadline alerts, progress reporting | Medium | - |
|
||||
| **Learning Resources** |
|
||||
| `src/app/(mentor)/mentor/resources/page.tsx` | Mentor materials | No terminology changes | Low | - |
|
||||
|
||||
---
|
||||
|
||||
## 6. Observer & Audience Pages Impact Matrix
|
||||
|
||||
Observer and audience pages have minimal changes but need consistent terminology.
|
||||
|
||||
| Page/Route | Current State | Required Changes | Priority | Related Docs |
|
||||
|------------|---------------|------------------|----------|--------------|
|
||||
| **Observer Dashboard** |
|
||||
| `src/app/(observer)/observer/page.tsx` | Read-only overview | Update "Pipelines" → "Competitions" | Low | - |
|
||||
| `src/app/(observer)/observer/reports/page.tsx` | Observer reports | Update terminology, no functional changes | Low | - |
|
||||
| **Audience Voting** |
|
||||
| `src/app/(public)/vote/[sessionId]/page.tsx` | Audience vote form | Enforce category/session mode, anti-duplicate policy | Medium | [07-deliberation.md] |
|
||||
| `src/app/(public)/vote/stage/[sessionId]/page.tsx` | Stage-specific voting | Update to Round context | Medium | [07-deliberation.md] |
|
||||
| **Live Scores** |
|
||||
| `src/app/(public)/live-scores/[sessionId]/page.tsx` | Live score display | Update terminology, no functional changes | Low | - |
|
||||
| `src/app/(public)/live-scores/stage/[sessionId]/page.tsx` | Stage-specific scores | Update to Round context | Low | - |
|
||||
|
||||
---
|
||||
|
||||
## 7. tRPC Router Classification
|
||||
|
||||
Every router is classified by its relationship to Competition/Round context.
|
||||
|
||||
| Router | Classification | Required Changes | Priority | Related Docs |
|
||||
|--------|----------------|------------------|----------|--------------|
|
||||
| **Directly Governed** (core competition logic, major refactoring required) |
|
||||
| `pipeline.ts` | Directly Governed | **Rename to `competition.ts`**. Replace Pipeline model with Competition, update all CRUD operations, wizard templates, context resolution | Critical | [02-data-model.md], [05-competition-wizard.md] |
|
||||
| `stage.ts` | Directly Governed | **Rename to `round.ts`**. Replace Stage model with Round, implement RoundType-based config validation, update transition logic | Critical | [02-data-model.md], [06-round-configs.md] |
|
||||
| `stageFiltering.ts` | Directly Governed | **Rename to `roundFiltering.ts`**. Update to use Round context, standardized eligibility outcomes | High | [06-round-configs.md] |
|
||||
| `stageAssignment.ts` | Directly Governed | **Rename to `roundAssignment.ts`**. Update to Round + JuryGroup context, implement new assignment preview/run logic | Critical | [04-jury-groups.md] |
|
||||
| *(NEW)* `juryGroup.ts` | Directly Governed | **Create new router**. CRUD for JuryGroups, member management, cap config, overlap validation | Critical | [04-jury-groups.md] |
|
||||
| *(NEW)* `deliberation.ts` | Directly Governed | **Create new router**. Session management, voting, quorum calculation, result locking, admin overrides | Critical | [07-deliberation.md] |
|
||||
| `live.ts` | Directly Governed | Update to require Jury 3 context, bind cursor to Live Finals RoundType, add deliberation handoff | High | [07-deliberation.md] |
|
||||
| `live-voting.ts` | Directly Governed | Tie session lifecycle to RoundType, add deliberation session support | High | [07-deliberation.md] |
|
||||
| `cohort.ts` | Directly Governed | Align with Live Finals and Selection policies, category boundaries | Medium | [03-competition-flow.md] |
|
||||
| `decision.ts` | Directly Governed | Extend to deliberation decisions, result lock events, admin override audit | High | [07-deliberation.md] |
|
||||
| `specialAward.ts` | Directly Governed | Add strict validation for participation mode, Round bindings, admin-confirmed pull-out, single-judge config | High | [04-jury-groups.md] |
|
||||
| `award.ts` | Directly Governed | Expose Mode A/B semantics, JuryGroup bindings | High | [04-jury-groups.md] |
|
||||
| **Context-Dependent** (consume competition context but have own logic, moderate updates) |
|
||||
| `assignment.ts` | Context-Dependent | Update to Round + JuryGroup context, centralize policy engine usage for all assignment paths | Critical | [04-jury-groups.md] |
|
||||
| `evaluation.ts` | Context-Dependent | Incorporate JuryGroup binding checks, deliberation contribution rights, Round-based evaluation windows | High | [04-jury-groups.md] |
|
||||
| `applicant.ts` | Context-Dependent | Add SubmissionBundleState, enforce slot state/round lock/promotion provenance/late policy | Critical | [06-round-configs.md] |
|
||||
| `application.ts` | Context-Dependent | Create SubmissionBundleState on submission, normalize category key mapping, enforce Round policies | Critical | [06-round-configs.md] |
|
||||
| `file.ts` | Context-Dependent | Use submission round + RoundType-aware visibility, enforce slot/promotion rules | High | [06-round-configs.md] |
|
||||
| `project.ts` | Context-Dependent | Add Round-aware views, submission bundle health, Round 1/2 document separation | High | [06-round-configs.md] |
|
||||
| `project-pool.ts` | Context-Dependent | Use AssignmentIntent for manual queue, reason tracking | High | [04-jury-groups.md] |
|
||||
| `mentor.ts` | Context-Dependent | Add workspace file/comment/promotion endpoints, enforce team-lead/admin promotion permissions | Critical | [06-round-configs.md] |
|
||||
| `user.ts` | Context-Dependent | Support dual invite behavior (AssignmentIntent vs. direct assignment), track JuryGroup memberships | High | [04-jury-groups.md] |
|
||||
| `notification.ts` | Context-Dependent | Expand event taxonomy: jury membership, promotion, deliberation lock, Round transitions | High | [03-competition-flow.md] |
|
||||
| `message.ts` | Context-Dependent | Ensure context tags reference Round/JuryGroup/project where applicable | Medium | - |
|
||||
| `dashboard.ts` | Context-Dependent | Update to show Competition status, Round progress, JuryGroup obligations | Medium | [03-competition-flow.md] |
|
||||
| `analytics.ts` | Context-Dependent | Add Competition KPIs: assignment saturation, override rate, policy compliance | Medium | [03-competition-flow.md] |
|
||||
| `export.ts` | Context-Dependent | Include result lock version, policy compliance evidence, Round context | Medium | [07-deliberation.md] |
|
||||
| `audit.ts` | Context-Dependent | Add filter facets: RoundType, JuryGroup ID, deliberation session | Medium | [07-deliberation.md] |
|
||||
| `filtering.ts` | Context-Dependent | Update to Round context, standardized eligibility outcomes | High | [06-round-configs.md] |
|
||||
| `gracePeriod.ts` | Context-Dependent | Integrate with SubmissionWindow grace period logic | Medium | [06-round-configs.md] |
|
||||
| **Utility** (minimal or no changes, context-independent) |
|
||||
| `avatar.ts` | Utility | No changes | None | - |
|
||||
| `logo.ts` | Utility | No changes | None | - |
|
||||
| `tag.ts` | Utility | No changes | None | - |
|
||||
| `partner.ts` | Utility | No changes | None | - |
|
||||
| `learningResource.ts` | Utility | No changes | None | - |
|
||||
| `settings.ts` | Utility | No changes | None | - |
|
||||
| `webhook.ts` | Utility | Update event names: pipeline.* → competition.*, stage.* → round.* | Low | - |
|
||||
| `typeform-import.ts` | Utility | No changes (legacy import) | None | - |
|
||||
| `notion-import.ts` | Utility | No changes (legacy import) | None | - |
|
||||
| `wizard-template.ts` | Utility | Update template structure: Pipeline → Competition, Stage → Round | High | [05-competition-wizard.md] |
|
||||
| `program.ts` | Utility | No changes (Program model unchanged) | None | - |
|
||||
| `_app.ts` | Utility | Update router exports (pipeline → competition, stage → round, add juryGroup, deliberation) | Critical | - |
|
||||
|
||||
---
|
||||
|
||||
## 8. Service Layer Classification
|
||||
|
||||
Services are the backend orchestration layer. Many require renaming and logic updates.
|
||||
|
||||
| Service | Required Changes | Depends On | Priority | Related Docs |
|
||||
|---------|------------------|------------|----------|--------------|
|
||||
| **Core Orchestration Services** (rename + refactor) |
|
||||
| `stage-engine.ts` | **Rename to `round-engine.ts`**. Implement RoundType-aware transition guards, validate Round sequence requirements | Round model, typed configs | Critical | [03-competition-flow.md], [06-round-configs.md] |
|
||||
| `stage-filtering.ts` | **Rename to `round-filtering.ts`**. Standardized eligibility outcomes with reason schema, AI filtering integration | Round model, AI services | High | [06-round-configs.md] |
|
||||
| `stage-assignment.ts` | **Rename to `round-assignment.ts`**. Full hard/soft cap policy engine, category bias scoring, JuryGroup binding | Round model, JuryGroup model, assignment policies | Critical | [04-jury-groups.md] |
|
||||
| `stage-notifications.ts` | **Rename to `round-notifications.ts`**. Expand event taxonomy: jury membership, Round transitions, deliberation, promotion, result lock | Round model, notification router | High | [03-competition-flow.md] |
|
||||
| **Live & Deliberation Services** |
|
||||
| `live-control.ts` | Update to bind cursor to Live Finals RoundType, require Jury 3 membership, add deliberation handoff logic | Round model, JuryGroup model | High | [07-deliberation.md] |
|
||||
| *(NEW)* `deliberation.ts` | **Create new service**. Session orchestration, quorum calculation, unanimous-with-fallback voting, result locking, admin override handling | Round model, JuryGroup model, decision router | Critical | [07-deliberation.md] |
|
||||
| *(NEW)* `result-lock.ts` | **Create new service**. Final result snapshot creation, lock/unlock workflow, audit trail, super-admin-only unlock | Round model, deliberation service | Critical | [07-deliberation.md] |
|
||||
| **AI Services** |
|
||||
| `ai-filtering.ts` | Update to use Round context, standardized eligibility schema | Round model, anonymization | High | [06-round-configs.md] |
|
||||
| `ai-assignment.ts` | Update to use JuryGroup context, Round-based project pool | Round model, JuryGroup model | High | [04-jury-groups.md] |
|
||||
| `ai-evaluation-summary.ts` | **Add AI shortlist generation**. Generate deliberation shortlists from evaluation scores, handle ties | Round model, evaluation router | Critical | [07-deliberation.md] |
|
||||
| `ai-tagging.ts` | No changes (project-level tagging, context-independent) | None | None | - |
|
||||
| `ai-award-eligibility.ts` | Update to use Round context, participation mode awareness | Round model, specialAward router | Medium | [04-jury-groups.md] |
|
||||
| `ai-errors.ts` | No changes (utility error handling) | None | None | - |
|
||||
| **Assignment & Matching Services** |
|
||||
| `smart-assignment.ts` | **Partial refactor**. Convert soft heuristics into policy-compliant scoring, treat category bias as transparent scoring (not strict quota), integrate with round-assignment.ts | Round model, JuryGroup model | High | [04-jury-groups.md] |
|
||||
| `mentor-matching.ts` | Update to gate matching by finalist status, mentorship request, Round policy | Round model, mentor router | Medium | [06-round-configs.md] |
|
||||
| **Background Jobs** |
|
||||
| `award-eligibility-job.ts` | Update to use Round context, persist decision reasoning under unified audit taxonomy | Round model, audit router | Medium | [04-jury-groups.md] |
|
||||
| `evaluation-reminders.ts` | Update to RoundType-aware reminder templates, JuryGroup-specific obligations, deliberation reminders | Round model, JuryGroup model | High | [04-jury-groups.md] |
|
||||
| `email-digest.ts` | Update terminology in email templates (Pipeline → Competition, Stage → Round) | Round model | Medium | - |
|
||||
| **Utility Services** |
|
||||
| `anonymization.ts` | No changes (AI privacy utility) | None | None | - |
|
||||
| `notification.ts` | Update event templates to use Competition/Round terminology | Round model | Medium | - |
|
||||
| `in-app-notification.ts` | Update notification rendering to use new terminology | Round model | Medium | - |
|
||||
| `webhook-dispatcher.ts` | Update event names: pipeline.* → competition.*, stage.* → round.* | Round model | Low | - |
|
||||
| **Workspace Services** (new or enhanced) |
|
||||
| *(NEW)* `mentor-workspace.ts` | **Create new or enhance mentor.ts service**. File upload, threaded comments, promotion queue, official slot mapping, team-lead/admin-only promotion | Round model, file router, mentor router | Critical | [06-round-configs.md] |
|
||||
|
||||
---
|
||||
|
||||
## 9. Integration Acceptance Criteria
|
||||
|
||||
The integration is considered complete when ALL of the following are verified:
|
||||
|
||||
### 9.1 Terminology & Data Model
|
||||
- [ ] All `Pipeline` references eliminated from codebase (code, comments, docs)
|
||||
- [ ] All `Stage` references eliminated from codebase (code, comments, docs)
|
||||
- [ ] All `Track` references eliminated from codebase (code, comments, docs)
|
||||
- [ ] `Competition` model fully implemented with typed configs
|
||||
- [ ] `Round` model fully implemented with 7 RoundType configs
|
||||
- [ ] `JuryGroup` model fully implemented with member management
|
||||
- [ ] All database migrations applied successfully
|
||||
- [ ] Prisma schema validated and generated
|
||||
|
||||
### 9.2 Core Functionality
|
||||
- [ ] Competition CRUD fully functional (create, read, update, delete)
|
||||
- [ ] Round CRUD fully functional (create, read, update, delete, reorder)
|
||||
- [ ] JuryGroup CRUD fully functional (create, assign members, configure caps/biases)
|
||||
- [ ] All 7 typed RoundType configs validated by Zod on save
|
||||
- [ ] Round transition engine working (intake → eligibility → jury 1 → jury 2 → jury 3 → live finals → results)
|
||||
- [ ] Assignment engine respects hard/soft caps, buffer, category bias
|
||||
- [ ] Filtering engine produces standardized eligibility outcomes
|
||||
- [ ] AI services integration working (filtering, assignment, evaluation summary, shortlist generation)
|
||||
|
||||
### 9.3 Deliberation & Results
|
||||
- [ ] Deliberation session management working (create, vote, quorum, lock)
|
||||
- [ ] Unanimous-with-quorum-fallback voting logic functional
|
||||
- [ ] AI shortlist generation from evaluation scores working
|
||||
- [ ] Admin override panel functional with audit trail
|
||||
- [ ] Result lock/unlock workflow functional (super-admin only)
|
||||
- [ ] Result lock snapshots persisted with version history
|
||||
- [ ] DecisionAuditLog captures all deliberation events
|
||||
|
||||
### 9.4 Admin UX
|
||||
- [ ] Admin dashboard shows Competition status (not Pipeline)
|
||||
- [ ] Competition wizard uses new Competition/Round terminology
|
||||
- [ ] Round configuration editor uses RoundType-specific controls
|
||||
- [ ] JuryGroup management pages functional (list, detail, members)
|
||||
- [ ] Assignment management shows JuryGroup context, cap violations
|
||||
- [ ] Live finals stage manager working (cursor, deliberation timer)
|
||||
- [ ] Deliberation admin panel working (session controls, overrides)
|
||||
- [ ] Result lock UI functional with preview and audit trail
|
||||
|
||||
### 9.5 Jury UX
|
||||
- [ ] Jury dashboard shows Round countdown (not Stage)
|
||||
- [ ] Jury sees only Rounds bound to their JuryGroups
|
||||
- [ ] Project review shows Round 1 vs. Round 2 documents separately
|
||||
- [ ] Evaluation form enforces Round/JuryGroup policy
|
||||
- [ ] Live finals voting requires Jury 3 membership
|
||||
- [ ] Deliberation voting interface functional (shortlist, vote, quorum)
|
||||
- [ ] Award voting respects JuryGroup bindings, hides single-judge awards
|
||||
|
||||
### 9.6 Applicant UX
|
||||
- [ ] Applicant pipeline renamed to "Competition Journey"
|
||||
- [ ] Journey shows RoundType-based progress (Intake → Eligibility → Jury 1 → ...)
|
||||
- [ ] Document upload enforces submission window slots
|
||||
- [ ] Round 1 vs. Round 2 bundles clearly separated
|
||||
- [ ] Late submission warnings displayed per policy
|
||||
- [ ] Mentor workspace shows file promotion candidates
|
||||
- [ ] Team management gates permissions correctly
|
||||
|
||||
### 9.7 Mentor UX
|
||||
- [ ] Mentor dashboard shows Round progression
|
||||
- [ ] Mentor workspace file upload working
|
||||
- [ ] Threaded comments functional
|
||||
- [ ] File promotion actions restricted to team-lead/admin
|
||||
- [ ] Official slot preview shows promoted files correctly
|
||||
|
||||
### 9.8 Audience & Observer UX
|
||||
- [ ] Audience voting enforces category/session mode
|
||||
- [ ] Anti-duplicate voting policy working
|
||||
- [ ] Observer pages use Competition/Round terminology
|
||||
- [ ] Live scores display updated terminology
|
||||
|
||||
### 9.9 Technical Requirements
|
||||
- [ ] All tRPC routers updated (pipeline → competition, stage → round, etc.)
|
||||
- [ ] All services renamed (stage-engine → round-engine, etc.)
|
||||
- [ ] All page routes updated (pipeline → competition terminology in UI)
|
||||
- [ ] TypeScript strict mode passing with no errors
|
||||
- [ ] All unit tests passing (update test factories for Competition/Round)
|
||||
- [ ] Integration tests covering Competition/Round/JuryGroup workflows
|
||||
- [ ] Database seed script updated to use new models
|
||||
- [ ] Docker entrypoint working with new migrations
|
||||
|
||||
### 9.10 Audit & Compliance
|
||||
- [ ] All admin actions audited via DecisionAuditLog
|
||||
- [ ] Deliberation decisions recorded with session context
|
||||
- [ ] Result lock/unlock events captured
|
||||
- [ ] Assignment policy compliance tracked
|
||||
- [ ] Override counts reportable per Competition
|
||||
- [ ] Cap violation metrics available in reports
|
||||
|
||||
### 9.11 Documentation & Training
|
||||
- [ ] All architecture docs updated (this document, [02], [03], [04], [05], [06], [07])
|
||||
- [ ] API documentation regenerated (tRPC router docs)
|
||||
- [ ] User guide updated (admin, jury, applicant, mentor)
|
||||
- [ ] Migration guide written (old → new terminology mapping)
|
||||
- [ ] Training materials prepared for admins
|
||||
|
||||
---
|
||||
|
||||
## 10. Procedure-Level Migration Tables
|
||||
|
||||
These tables detail the exact tRPC procedure renames and signature changes required for each portal.
|
||||
|
||||
### 10.1 Jury Portal Procedures
|
||||
|
||||
| Current Procedure | New Procedure | Signature Change | Notes |
|
||||
|-------------------|---------------|-----------------|-------|
|
||||
| `stageAssignment.myAssignments` | `roundAssignment.myAssignments` | `{ stageId }` → `{ roundId, juryGroupId? }` | Filter by JuryGroup membership |
|
||||
| `stageAssignment.getAssignmentDetail` | `roundAssignment.getAssignmentDetail` | `{ assignmentId }` → `{ assignmentId }` | Add JuryGroup context to response |
|
||||
| `evaluation.getStageForm` | `evaluation.getRoundForm` | `{ stageId }` → `{ roundId }` | Return round-type-aware form |
|
||||
| `evaluation.submitScore` | `evaluation.submitScore` | `{ stageId, projectId, ... }` → `{ roundId, projectId, ... }` | Add JuryGroup validation |
|
||||
| `evaluation.checkStageWindow` | `evaluation.checkRoundWindow` | `{ stageId }` → `{ roundId }` | Return SubmissionWindow status |
|
||||
| `evaluation.listStageEvaluations` | `evaluation.listRoundEvaluations` | `{ stageId }` → `{ roundId, juryGroupId? }` | Multi-jury filtering |
|
||||
| `evaluation.declareCOI` | `evaluation.declareCOI` | `{ assignmentId }` → `{ assignmentId }` | No change (COI is on Assignment) |
|
||||
| `file.listByProjectForStage` | `file.listByProjectForRound` | `{ projectId, stageId }` → `{ projectId, roundId }` | Return multi-window grouped files with labels |
|
||||
| `live.getCurrentProject` | `live.getCurrentProject` | `{ stageId }` → `{ roundId }` | Require Jury 3 JuryGroup membership |
|
||||
| `live.submitVote` | `live.submitVote` | `{ stageId, ... }` → `{ roundId, ... }` | Require Jury 3 membership |
|
||||
| `live.getVotingStatus` | `live.getVotingStatus` | `{ stageId }` → `{ roundId }` | Add deliberation session linkage |
|
||||
|
||||
### 10.2 Applicant Portal Procedures
|
||||
|
||||
| Current Procedure | New Procedure | Signature Change | Notes |
|
||||
|-------------------|---------------|-----------------|-------|
|
||||
| `pipeline.getApplicantView` | `competition.getApplicantView` | `{ pipelineId }` → `{ competitionId }` | Return rounds with status, progress % |
|
||||
| `stage.getApplicantTimeline` | `round.getApplicantTimeline` | `{ stageId }` → `{ roundId }` | Include SubmissionWindow deadlines |
|
||||
| `stage.getRequirements` | `submissionWindow.getRequirements` | `{ stageId }` → `{ submissionWindowId }` | Window-based, not round-based |
|
||||
| `applicant.getMyDashboard` | `applicant.getMyDashboard` | returns `openStages[]` → returns `openRounds[]` | Include SubmissionWindow status per round |
|
||||
| `application.submit` | `application.submit` | `{ stageId, ... }` → `{ roundId, submissionWindowId, ... }` | Window-scoped submission |
|
||||
| `file.uploadForStage` | `file.uploadForWindow` | `{ stageId, slotKey }` → `{ submissionWindowId, requirementId }` | Requirement-based validation |
|
||||
| `file.getMyFiles` | `file.getMyFiles` | `{ stageId? }` → `{ submissionWindowId? }` | Multi-window grouping |
|
||||
|
||||
### 10.3 Component Renames
|
||||
|
||||
| Current Component | New Component | Used By |
|
||||
|-------------------|---------------|---------|
|
||||
| `StageTimeline` | `RoundTimeline` | Applicant journey, jury overview |
|
||||
| `StageWindowBadge` | `RoundStatusBadge` | All portals — status indicators |
|
||||
| `RequirementUploadSlot` | `SubmissionSlot` | Applicant document upload |
|
||||
| `PipelineBreadcrumb` | `CompetitionBreadcrumb` | All portals — navigation |
|
||||
| `StageConfigEditor` | `RoundConfigEditor` | Admin round configuration |
|
||||
| `PipelineWizard` | `CompetitionWizard` | Admin competition creation |
|
||||
|
||||
### 10.4 Route Migrations
|
||||
|
||||
| Current Route | New Route | Portal |
|
||||
|---------------|-----------|--------|
|
||||
| `/admin/rounds/pipelines/*` | `/admin/competitions/*` | Admin |
|
||||
| `/admin/rounds/pipeline/[id]/*` | `/admin/competitions/[id]/*` | Admin |
|
||||
| `/jury/stages/[stageId]/*` | `/jury/rounds/[roundId]/*` | Jury |
|
||||
| `/jury/stages/[stageId]/live/*` | `/jury/rounds/[roundId]/live/*` | Jury |
|
||||
| `/jury/stages/[stageId]/deliberation/*` | `/jury/rounds/[roundId]/deliberation/*` | Jury (NEW) |
|
||||
| `/applicant/pipeline/*` | `/applicant/competition/*` | Applicant |
|
||||
| `/applicant/pipeline/[stageId]/documents/*` | `/applicant/competition/[roundId]/documents/*` | Applicant |
|
||||
| `/vote/stage/[sessionId]/*` | `/vote/round/[sessionId]/*` | Public |
|
||||
| `/live-scores/stage/[sessionId]/*` | `/live-scores/round/[sessionId]/*` | Public |
|
||||
|
||||
### 10.5 Background Job & Event Migrations
|
||||
|
||||
| Current Job/Event | New Job/Event | Changes |
|
||||
|-------------------|---------------|---------|
|
||||
| `evaluationReminder` cron | `evaluationReminder` cron | `stageId` → `roundId` in query and templates |
|
||||
| `stage.transitioned` event | `round.transitioned` event | Event name + payload rename |
|
||||
| `pipeline.created` event | `competition.created` event | Event name + payload rename |
|
||||
| `pipeline.statusChanged` event | `competition.statusChanged` event | Event name + payload rename |
|
||||
| `stage.assignment.completed` event | `round.assignment.completed` event | Event name + JuryGroup context in payload |
|
||||
| `stage.filtering.completed` event | `round.filtering.completed` event | Event name rename |
|
||||
| `stage-notifications` digest events | `round-notifications` digest events | All event keys rename |
|
||||
| `award-eligibility-job` background | `award-eligibility-job` background | `stageId` → `roundId` in context |
|
||||
|
||||
---
|
||||
|
||||
## 11. Phased Rollout Recommendation
|
||||
|
||||
Given the scope, recommend phased implementation:
|
||||
|
||||
### Phase 1: Data Model & Core Routers (Weeks 1-2)
|
||||
- Create Competition, Round, JuryGroup models
|
||||
- Rename pipeline.ts → competition.ts, stage.ts → round.ts
|
||||
- Create juryGroup.ts router
|
||||
- Update database schema and migrations
|
||||
- Update test factories
|
||||
|
||||
### Phase 2: Admin UX & Wizards (Weeks 3-4)
|
||||
- Update Competition wizard
|
||||
- Implement RoundType config editors
|
||||
- Create JuryGroup management pages
|
||||
- Update assignment management UI
|
||||
|
||||
### Phase 3: Services & Assignment Engine (Weeks 5-6)
|
||||
- Rename stage-engine.ts → round-engine.ts
|
||||
- Rename stage-assignment.ts → round-assignment.ts
|
||||
- Implement new assignment policy engine
|
||||
- Update AI services
|
||||
|
||||
### Phase 4: Deliberation & Results (Weeks 7-8)
|
||||
- Create deliberation.ts service and router
|
||||
- Implement result-lock.ts service
|
||||
- Build deliberation admin panel
|
||||
- Build deliberation jury interface
|
||||
|
||||
### Phase 5: Jury & Applicant UX (Weeks 9-10)
|
||||
- Update jury pages (Round navigation, deliberation voting)
|
||||
- Update applicant pages (Competition Journey, submission windows)
|
||||
- Update mentor workspace
|
||||
|
||||
### Phase 6: Testing & Validation (Weeks 11-12)
|
||||
- Integration testing
|
||||
- UAT with admin/jury/applicant test users
|
||||
- Performance testing
|
||||
- Documentation finalization
|
||||
|
||||
---
|
||||
|
||||
## 11. Cross-Reference Map
|
||||
|
||||
This document integrates concepts from:
|
||||
|
||||
- **[02-data-model.md]** — Competition, Round, JuryGroup schemas
|
||||
- **[03-competition-flow.md]** — 7-round flow, state transitions
|
||||
- **[04-jury-groups-and-assignment-policy.md]** — JuryGroup management, caps, biases, assignment rules
|
||||
- **[05-competition-wizard.md]** — Wizard UI for Competition setup
|
||||
- **[06-round-configs.md]** — 7 typed RoundType configs, submission windows
|
||||
- **[07-deliberation-and-result-lock.md]** — Deliberation sessions, quorum voting, result locking
|
||||
|
||||
---
|
||||
|
||||
**End of Platform Integration Matrix**
|
||||
247
docs/unified-architecture-redesign/09-implementation-roadmap.md
Normal file
247
docs/unified-architecture-redesign/09-implementation-roadmap.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Implementation Roadmap
|
||||
|
||||
## Overview
|
||||
|
||||
The redesign is implemented in 9 phases, progressing from contract definition through schema migration, feature implementation, and legacy decommission. Each phase has defined scope, dependencies, and rollback points.
|
||||
|
||||
---
|
||||
|
||||
## Phase Summary
|
||||
|
||||
| Phase | Scope | Duration | Dependencies |
|
||||
|-------|-------|----------|--------------|
|
||||
| 0 | **Contract Freeze** — type definitions, Zod schemas, feature flags, test factories | 1 week | None |
|
||||
| 1 | **Schema & Runtime Foundation** — Prisma migration, Competition/Round/JuryGroup CRUD | 2 weeks | Phase 0 |
|
||||
| 2 | **Policy Engine** — centralized context resolver, policy resolution, assignment policy evaluator | 1 week | Phase 1 |
|
||||
| 3 | **Invite/Onboarding Integration** — jury memberships on invite, assignment intent, onboarding routing | 1–2 weeks | Phase 1 |
|
||||
| 4 | **Backend Orchestration** — enhanced assignment, submission round manager, mentor workspace, deliberation service | 2 weeks | Phase 2, 3 |
|
||||
| 5 | **Admin Control Plane + Participant UX** — competition wizard, round management, jury/applicant/mentor dashboards | 2 weeks | Phase 4 |
|
||||
| 6 | **Special Awards + Live Finals + Deliberation** — award modes, stage manager, deliberation voting UI | 2 weeks | Phase 4, 5 |
|
||||
| 7 | **Platform-Wide Refit** — remove Pipeline/Track/Stage references, drop old tables | 2 weeks | Phase 5, 6 |
|
||||
| 8 | **Cutover & Legacy Decommission** — enable new contracts, deprecate legacy, burn-in period | 1 week | Phase 7 |
|
||||
|
||||
**Total estimated duration**: 14–15 weeks
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
Phase 0 ──→ Phase 1 ──→ Phase 2 ──→ Phase 4 ──→ Phase 5 ──→ Phase 7 ──→ Phase 8
|
||||
└──→ Phase 3 ──┘ └──→ Phase 6 ──┘
|
||||
```
|
||||
|
||||
- Phases 2 and 3 can run in parallel after Phase 1
|
||||
- Phase 4 requires both Phase 2 and 3
|
||||
- Phases 5 and 6 can partially overlap
|
||||
- Phase 7 requires both Phase 5 and 6
|
||||
|
||||
**Critical path**: 0 → 1 → 2 → 4 → 5 → 7 → 8 (12 weeks minimum)
|
||||
|
||||
---
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 0: Contract Freeze (1 week)
|
||||
|
||||
**Goal**: Lock down all type definitions, schemas, and interfaces before any implementation begins.
|
||||
|
||||
**Deliverables**:
|
||||
- All Prisma model definitions finalized (see [02-data-model.md](./02-data-model.md))
|
||||
- All 7 Zod config schemas finalized (IntakeConfig through DeliberationConfig)
|
||||
- All new enum definitions finalized
|
||||
- Feature flag infrastructure in place
|
||||
- Test factory functions for all new models
|
||||
- TypeScript type exports for all new models
|
||||
|
||||
**Rollback**: No production changes. Delete type files if needed.
|
||||
|
||||
**Gate**: All type definitions reviewed and approved. No changes to schemas after this phase without architecture sign-off (see "No Silent Contract Drift" in [13-open-questions-and-governance.md](./13-open-questions-and-governance.md)).
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: Schema & Runtime Foundation (2 weeks)
|
||||
|
||||
**Goal**: New database tables exist alongside old ones. Basic CRUD operations work.
|
||||
|
||||
**Deliverables**:
|
||||
- Prisma migration: add Competition, Round, JuryGroup, JuryGroupMember, SubmissionWindow, SubmissionFileRequirement, DeliberationSession, DeliberationVote, DeliberationResult, DeliberationParticipant, ResultLock, ResultUnlockEvent, MentorMessage, AssignmentIntent, AssignmentException, SubmissionPromotionEvent tables
|
||||
- New enums: RoundType, RoundStatus, CompetitionStatus, CapMode, DeadlinePolicy, DeliberationMode, DeliberationStatus, TieBreakMethod, etc.
|
||||
- Basic tRPC routers: `competition.create`, `competition.getById`, `round.create`, `juryGroup.create`, `juryGroup.addMember`
|
||||
- Admin can create a Competition with Rounds through the API (no UI yet)
|
||||
|
||||
**Rollback**: Drop new tables. No old tables modified.
|
||||
|
||||
**Gate**: All new tables exist with correct schemas. CRUD operations pass integration tests.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Policy Engine (1 week)
|
||||
|
||||
**Goal**: Centralized resolution of competition context and assignment policies.
|
||||
|
||||
**Deliverables**:
|
||||
- `CompetitionContextResolver` service: given a roundId, returns the full competition context (competition, round type, config, jury group, submission windows, etc.)
|
||||
- Policy resolution function: 5-layer precedence chain for assignment caps, ratios, and modes
|
||||
- Assignment policy evaluator: `canAssignMore(member)`, `getEffectiveCap(member)`, `getRemainingCapacity(member)`
|
||||
- Unit tests for all policy combinations
|
||||
|
||||
**Rollback**: Remove new services. No data changes.
|
||||
|
||||
**Gate**: Policy resolution returns correct values for all 5 layers. Edge cases (null overrides, conflicting settings) handled correctly.
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Invite/Onboarding Integration (1–2 weeks)
|
||||
|
||||
**Goal**: Jury memberships are created at invite time. Judges can self-service during onboarding.
|
||||
|
||||
**Deliverables**:
|
||||
- Invite flow creates `JuryGroupMember` record when judge is invited to a jury
|
||||
- `AssignmentIntent` records can be created during invite (pre-assignment)
|
||||
- Onboarding page shows cap/ratio preferences when `allowOnboardingSelfService` is true
|
||||
- Judge can adjust cap and category bias during onboarding
|
||||
- Admin can review and override self-service values
|
||||
|
||||
**Rollback**: Revert invite flow changes. JuryGroupMember records can be deleted.
|
||||
|
||||
**Gate**: End-to-end flow: invite judge → judge accepts → onboarding shows jury details → judge adjusts preferences → admin sees updated values.
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Backend Orchestration (2 weeks)
|
||||
|
||||
**Goal**: All backend services for the 8-round flow are operational.
|
||||
|
||||
**Deliverables**:
|
||||
- **RoundEngine** service: state machine for round transitions (replaces StageEngine)
|
||||
- **Enhanced Assignment** service: hard/soft cap algorithm, category bias, unassigned queue, COI check
|
||||
- **SubmissionRoundManager**: manages submission windows, file requirements, read-only enforcement
|
||||
- **MentorWorkspaceService**: messaging, file upload, file comments, file promotion
|
||||
- **DeliberationService**: session creation, vote submission, aggregation (Borda count / vote tally), tie-breaking, result lock
|
||||
- **AI Shortlist** service: generates ranked recommendations at end of evaluation rounds
|
||||
- **ResultLockService**: lock/unlock with audit trail
|
||||
|
||||
**Rollback**: Remove new services. Feature flags prevent activation.
|
||||
|
||||
**Gate**: All services pass unit tests. Integration test covers full R1→R8 flow with test data.
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Admin Control Plane + Participant UX (2 weeks)
|
||||
|
||||
**Goal**: Admin can configure and run a competition through the UI. Participants see the correct experience.
|
||||
|
||||
**Deliverables**:
|
||||
- **Competition Wizard**: create competition with rounds, configure each round type
|
||||
- **Round Management**: view/edit round configs, manage submission windows, set deadlines
|
||||
- **Juries Management (NEW section)**: create JuryGroups, add/remove members, configure caps/ratios, view assignments
|
||||
- **Assignment Dashboard**: view assignments, manage unassigned queue, manual override
|
||||
- **Jury Dashboard**: countdown, assigned projects, multi-round doc viewing, scoring/feedback
|
||||
- **Applicant Portal**: multi-round doc upload, read-only enforcement, submission status
|
||||
- **Mentor Dashboard**: assigned teams, messaging, file management, promotion
|
||||
|
||||
**Rollback**: Revert UI pages. Backend services still work via API.
|
||||
|
||||
**Gate**: Admin can create, configure, and manage a full competition through the UI. Jury, applicant, and mentor dashboards functional.
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: Special Awards + Live Finals + Deliberation (2 weeks)
|
||||
|
||||
**Goal**: The most complex runtime features are operational.
|
||||
|
||||
**Deliverables**:
|
||||
- **Special Awards**: create awards with routing modes, eligibility filtering, per-award jury, doc requirements, review windows, single-judge mode
|
||||
- **Live Finals Stage Manager**: admin controls for ceremony flow, project cursor, voting windows
|
||||
- **Jury 3 Live Interface**: real-time notes, all docs from prior rounds, prior jury data (if enabled), live scoring
|
||||
- **Audience Voting**: optional per-category audience vote, reveal timing configuration
|
||||
- **Deliberation UI**: session management, juror voting interface (both modes), result display, tie-breaking, admin lock
|
||||
- **Result Lock**: admin locks results, unlock requires super-admin with reason
|
||||
|
||||
**Rollback**: Feature flags disable awards, live finals, and deliberation. Fall back to basic flow.
|
||||
|
||||
**Gate**: Full live finals ceremony simulation. Deliberation voting in both modes. Result lock and unlock. Award creation and selection.
|
||||
|
||||
---
|
||||
|
||||
### Phase 7: Platform-Wide Refit (2 weeks)
|
||||
|
||||
**Goal**: Remove all references to Pipeline, Track, and Stage from the codebase. Estimated 120+ files, ~350 hours of work.
|
||||
|
||||
**Sub-phases** (ordered by dependency):
|
||||
|
||||
| Sub-Phase | Scope | Files | Est. Hours |
|
||||
|-----------|-------|-------|-----------|
|
||||
| **7a** | Database: rename FKs (stageId→roundId) in AudienceVote, COIDeclaration, ProjectStatusHistory, DigestLog, PartnerAccess | 1 (schema) + migration | 8 |
|
||||
| **7b** | Types & libraries: rewrite pipeline-wizard.ts, wizard-config.ts, pipeline-defaults.ts, pipeline-validation.ts, stage-config-schema.ts | 6 | 32 |
|
||||
| **7c** | Core routers: rename pipeline.ts→competition.ts, stage.ts→round.ts, track.ts→(delete), stageAssignment.ts→roundAssignment.ts | 4 | 24 |
|
||||
| **7d** | Dependent routers: update all 20 routers with stageId/pipelineId references (evaluation, file, live, invite, notification, etc.) | 20 | 80 |
|
||||
| **7e** | Services: rename stage-engine→round-engine, stage-filtering→round-filtering, stage-assignment→round-assignment, stage-notifications→round-notifications, live-control (update refs) | 11 | 50 |
|
||||
| **7f** | Admin pages: rename 35+ pages referencing pipeline/stage, update breadcrumbs, wizard, config editors | 35+ | 52 |
|
||||
| **7g** | Jury pages: update 16 jury pages + components (stage routes→round routes, assignment queries, evaluation forms) | 16 | 18 |
|
||||
| **7h** | Applicant pages: update 8 pages (pipeline visualization→competition progress, stage documents→round documents) | 8 | 12 |
|
||||
| **7i** | Infrastructure: update Docker entrypoint, cron jobs, webhook event names, notification templates, seed data | 4 | 11 |
|
||||
| **7j** | Tests: update 13+ test files + helpers.ts, rename factories, update all assertions | 13+ | 45 |
|
||||
|
||||
**Week 1**: Sub-phases 7a–7e (database, types, routers, services — backend foundation)
|
||||
**Week 2**: Sub-phases 7f–7j (UI pages, infrastructure, tests — frontend + verification)
|
||||
|
||||
**Deliverables**:
|
||||
- All `pipeline` references renamed to `competition` in routers, services, types, UI
|
||||
- All `stage` references renamed to `round`
|
||||
- All `track` references removed
|
||||
- All imports, type aliases, and variable names updated
|
||||
- Old database tables dropped (Pipeline, Track, Stage, ProjectStageState)
|
||||
- Seed data updated to use new models
|
||||
- All background jobs and webhook events using new names
|
||||
|
||||
**Rollback**: This is the point of no return for the old schema. Rollback requires restoring from backup.
|
||||
|
||||
**Gate**: `grep -r "pipeline\|Pipeline\|stage\|Stage\|track\|Track" src/` returns zero results (excluding legitimate uses like "stage manager" in live finals). All tests pass. Build succeeds.
|
||||
|
||||
---
|
||||
|
||||
### Phase 8: Cutover & Legacy Decommission (1 week)
|
||||
|
||||
**Goal**: Production system running entirely on the new architecture.
|
||||
|
||||
**Deliverables**:
|
||||
- Feature flags set to new system for all subsystems
|
||||
- Legacy API endpoints deprecated (return 410 Gone with migration guide)
|
||||
- Burn-in period: monitor for errors, performance regressions
|
||||
- Documentation updated (CLAUDE.md, README, API docs)
|
||||
- Seed data updated
|
||||
- Docker entrypoint updated for new migration flow
|
||||
|
||||
**Rollback**: Feature flags can revert to legacy for individual subsystems (only if old tables still exist, i.e., Phase 7 wasn't completed).
|
||||
|
||||
**Gate**: 72-hour burn-in with zero critical errors. All release gates A–F passed (see [12-observability-and-release-gates.md](./12-observability-and-release-gates.md)).
|
||||
|
||||
---
|
||||
|
||||
## Rollback Strategy
|
||||
|
||||
| Phase | Rollback Method | Data Loss Risk |
|
||||
|-------|----------------|----------------|
|
||||
| 0 | Delete type files | None |
|
||||
| 1 | Drop new tables | None (old tables untouched) |
|
||||
| 2 | Remove services | None |
|
||||
| 3 | Revert invite flow | JuryGroupMember records deleted |
|
||||
| 4 | Feature flag off | None |
|
||||
| 5 | Revert UI pages | None |
|
||||
| 6 | Feature flag off | None |
|
||||
| 7 | **Point of no return** — restore from backup | Requires backup |
|
||||
| 8 | Feature flags to legacy | Only if Phase 7 not completed |
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|-----------|
|
||||
| Schema migration breaks existing data | Phase 1 adds tables alongside old ones. No modifications to existing tables until Phase 7. |
|
||||
| Assignment algorithm incorrect | Extensive unit tests in Phase 2. Manual override always available. |
|
||||
| Deliberation edge cases | Phase 6 includes deliberation test matrix (see [11-testing-and-qa.md](./11-testing-and-qa.md)). |
|
||||
| Live finals concurrency | Load testing in Phase 6. See [12-observability-and-release-gates.md](./12-observability-and-release-gates.md). |
|
||||
| Late requirement changes | "No Silent Contract Drift" policy enforced after Phase 0. |
|
||||
565
docs/unified-architecture-redesign/10-migration-strategy.md
Normal file
565
docs/unified-architecture-redesign/10-migration-strategy.md
Normal file
@@ -0,0 +1,565 @@
|
||||
# Migration Strategy
|
||||
|
||||
## Overview
|
||||
|
||||
The migration from Pipeline/Track/Stage to Competition/Round follows a 4-phase approach designed to minimize risk. New tables are added alongside old ones, data is backfilled, code is migrated, and finally old tables are dropped. Feature flags allow granular control of the transition.
|
||||
|
||||
---
|
||||
|
||||
## Migration Phases
|
||||
|
||||
### Phase 1: Schema Additions (Non-Breaking)
|
||||
|
||||
Add all new tables and enums alongside existing tables. No existing tables are modified or dropped.
|
||||
|
||||
**New tables added:**
|
||||
- `Competition` (parallel to `Pipeline`)
|
||||
- `Round` (parallel to `Stage`)
|
||||
- `JuryGroup`, `JuryGroupMember`
|
||||
- `SubmissionWindow`, `SubmissionFileRequirement`
|
||||
- `RoundSubmissionVisibility`
|
||||
- `ProjectRoundState` (parallel to `ProjectStageState`)
|
||||
- `DeliberationSession`, `DeliberationVote`, `DeliberationResult`, `DeliberationParticipant`
|
||||
- `ResultLock`, `ResultUnlockEvent`
|
||||
- `MentorMessage`
|
||||
- `AssignmentIntent`, `AssignmentException`
|
||||
- `SubmissionPromotionEvent`
|
||||
- `AwardWinner`
|
||||
|
||||
**New enums added:**
|
||||
- `CompetitionStatus`: DRAFT, ACTIVE, COMPLETED, ARCHIVED
|
||||
- `RoundType`: INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
|
||||
- `RoundStatus`: DRAFT, READY, ACTIVE, COMPLETED, SKIPPED
|
||||
- `CapMode`: HARD, SOFT, NONE
|
||||
- `DeadlinePolicy`: HARD, FLAG, GRACE
|
||||
- `DeliberationMode`: SINGLE_WINNER_VOTE, FULL_RANKING
|
||||
- `DeliberationStatus`: OPEN, VOTING, TALLYING, RUNOFF, LOCKED
|
||||
- `TieBreakMethod`: RUNOFF, ADMIN_DECIDES, SCORE_FALLBACK
|
||||
- `DeliberationParticipantStatus`: REQUIRED, ABSENT_EXCUSED, REPLACED, REPLACEMENT_ACTIVE
|
||||
- `AwardRoutingMode`: STAY_IN_MAIN, SEPARATE_POOL
|
||||
- `AwardEligibilityMode`: AI_SUGGESTED, MANUAL, ALL_ELIGIBLE, ROUND_BASED
|
||||
- `WinnerDecisionMode`: JURY_VOTE, SINGLE_JUDGE
|
||||
- `MentorMessageRole`: MENTOR, APPLICANT, ADMIN
|
||||
- `PromotionSourceType`: MENTOR_FILE, ADMIN_REPLACEMENT
|
||||
- `AssignmentIntentSource`: INVITE, ADMIN, SYSTEM
|
||||
|
||||
**Rollback**: Drop new tables and enums. Zero impact on existing system.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Data Migration (Backfill)
|
||||
|
||||
Backfill new tables from existing data. Old tables remain populated and active.
|
||||
|
||||
**Mapping table:**
|
||||
|
||||
| Old Model | New Model | Mapping Logic |
|
||||
|-----------|-----------|---------------|
|
||||
| `Pipeline` | `Competition` | 1:1. Copy id, programId, name, status, settings → typed config split |
|
||||
| `Track` (MAIN) | *(eliminated)* | Main track's stages become Competition's rounds directly |
|
||||
| `Track` (AWARD) | `SpecialAward` | One SpecialAward per AWARD track |
|
||||
| `Stage` | `Round` | 1:1. Copy type mapping (see below), order, status |
|
||||
| `Stage.configJson` | `Round.configJson` | Parse and validate against typed schema for the round type |
|
||||
| `ProjectStageState` | `ProjectRoundState` | Map stageId → roundId, trackId dropped |
|
||||
| Judge assignments | `JuryGroupMember` + assignments | Create JuryGroups from distinct jury configurations |
|
||||
| `SpecialAward` | `SpecialAward` (enhanced) | Add routing mode, eligibility mode fields |
|
||||
|
||||
**Stage type to Round type mapping:**
|
||||
|
||||
| Old StageType | New RoundType | Notes |
|
||||
|---------------|---------------|-------|
|
||||
| `INTAKE` | `INTAKE` | Direct mapping |
|
||||
| `FILTER` | `FILTERING` | Renamed for clarity |
|
||||
| `EVALUATION` | `EVALUATION` | Direct mapping (used for both Jury 1 and Jury 2) |
|
||||
| `SELECTION` | *(absorbed)* | Selection logic moves into evaluation round config |
|
||||
| `LIVE_FINAL` | `LIVE_FINAL` | Direct mapping |
|
||||
| `RESULTS` | *(absorbed)* | Results are part of the deliberation round |
|
||||
| *(new)* | `SUBMISSION` | New round type for multi-round document collection |
|
||||
| *(new)* | `MENTORING` | New round type for mentor collaboration |
|
||||
| *(new)* | `DELIBERATION` | New round type replacing confirmation |
|
||||
|
||||
**Backfill script**: A TypeScript migration script reads all existing Pipelines/Tracks/Stages and creates corresponding Competition/Round records. The script is idempotent (can be run multiple times safely).
|
||||
|
||||
**Rollback**: Delete backfilled data from new tables. Old tables unchanged.
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Code Migration
|
||||
|
||||
Update all services, routers, and UI to use new models. Feature flags control which code path is active.
|
||||
|
||||
**Feature flag strategy:**
|
||||
|
||||
```typescript
|
||||
const FEATURE_FLAGS = {
|
||||
USE_COMPETITION_MODEL: false, // Phase 3: switch to Competition/Round queries
|
||||
USE_JURY_GROUPS: false, // Phase 3: switch to JuryGroup-based assignment
|
||||
USE_DELIBERATION: false, // Phase 6: switch to new deliberation model
|
||||
USE_MENTOR_WORKSPACE: false, // Phase 4: enable enhanced mentor features
|
||||
USE_SUBMISSION_WINDOWS: false, // Phase 4: enable multi-round submissions
|
||||
HIDE_LEGACY_PIPELINE_UI: false, // Phase 7: hide old Pipeline/Stage UI
|
||||
}
|
||||
```
|
||||
|
||||
**Dual-read pattern**: During transition, services can read from both old and new tables:
|
||||
|
||||
```typescript
|
||||
async function getCompetitionContext(id: string) {
|
||||
if (FEATURE_FLAGS.USE_COMPETITION_MODEL) {
|
||||
return getFromCompetitionModel(id)
|
||||
}
|
||||
return getFromPipelineModel(id) // legacy path
|
||||
}
|
||||
```
|
||||
|
||||
**Router migration order:**
|
||||
1. `pipeline.ts` → `competition.ts` (create new router, keep old active behind flag)
|
||||
2. `stage.ts` → `round.ts` (same approach)
|
||||
3. Remove `track.ts` (logic absorbed into competition + specialAward routers)
|
||||
4. Update `assignment.ts` to use JuryGroup model
|
||||
5. Update `evaluation.ts` to use Round model
|
||||
6. Add `deliberation.ts` (new router)
|
||||
7. Update `mentor.ts` with workspace features
|
||||
8. Update all UI pages to use new routers
|
||||
|
||||
**Rollback**: Flip feature flags back to legacy. Both code paths exist during this phase.
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Cleanup (Point of No Return)
|
||||
|
||||
Drop old tables and remove legacy code paths. This is irreversible.
|
||||
|
||||
**Tables dropped:**
|
||||
- `Pipeline`
|
||||
- `Track`
|
||||
- `Stage`
|
||||
- `ProjectStageState`
|
||||
- Legacy configJson schemas
|
||||
|
||||
**Enums dropped:**
|
||||
- `StageType` (replaced by `RoundType`)
|
||||
- `TrackKind` (MAIN/AWARD — eliminated)
|
||||
- `RoutingMode` (SHARED/EXCLUSIVE — replaced by AwardRoutingMode)
|
||||
- `WinnerProposalStatus` (replaced by `DeliberationStatus`)
|
||||
- `WinnerApprovalRole` (replaced by `DeliberationParticipantStatus`)
|
||||
|
||||
**Models dropped:**
|
||||
- `WinnerProposal` (replaced by `DeliberationSession`)
|
||||
- `WinnerApproval` (replaced by `DeliberationVote`)
|
||||
|
||||
**Code removed:**
|
||||
- All feature flag conditionals (only new path remains)
|
||||
- All dual-read logic
|
||||
- All legacy router files
|
||||
- All legacy service files
|
||||
|
||||
**Rollback**: Restore from database backup. This phase should only be executed after thorough testing in Phases 1–3 and the burn-in period in Phase 8 of the implementation roadmap.
|
||||
|
||||
---
|
||||
|
||||
## Data Mapping Reference
|
||||
|
||||
### Complete Field Mapping
|
||||
|
||||
| Old Field | New Field | Notes |
|
||||
|-----------|-----------|-------|
|
||||
| `Pipeline.id` | `Competition.id` | Preserve IDs for foreign key consistency |
|
||||
| `Pipeline.programId` | `Competition.programId` | Direct |
|
||||
| `Pipeline.name` | `Competition.name` | Direct |
|
||||
| `Pipeline.description` | `Competition.description` | Direct |
|
||||
| `Pipeline.status` | `Competition.status` | Map to CompetitionStatus enum |
|
||||
| `Pipeline.settingsJson` | `Competition.settingsJson` | Carry forward, validate |
|
||||
| `Stage.id` | `Round.id` | Preserve IDs |
|
||||
| `Stage.trackId` | *(dropped)* | No more track reference |
|
||||
| `Stage.pipelineId` | `Round.competitionId` | Rename |
|
||||
| `Stage.type` | `Round.type` | Map StageType → RoundType |
|
||||
| `Stage.order` | `Round.order` | Direct |
|
||||
| `Stage.status` | `Round.status` | Map to RoundStatus |
|
||||
| `Stage.configJson` | `Round.configJson` | Validate against typed schema |
|
||||
| `Stage.startsAt` | `Round.startsAt` | Direct |
|
||||
| `Stage.endsAt` | `Round.endsAt` | Direct |
|
||||
| `ProjectStageState.stageId` | `ProjectRoundState.roundId` | Rename |
|
||||
| `ProjectStageState.trackId` | *(dropped)* | No more track |
|
||||
| `ProjectStageState.projectId` | `ProjectRoundState.projectId` | Direct |
|
||||
| `ProjectStageState.state` | `ProjectRoundState.state` | Map values |
|
||||
|
||||
### Additional FK Migrations (stageId → roundId)
|
||||
|
||||
These existing models have `stageId` foreign keys that must be renamed to `roundId`. They are NOT being replaced by new models — just renamed:
|
||||
|
||||
| Model | Old FK | New FK | Notes |
|
||||
|-------|--------|--------|-------|
|
||||
| `AudienceVote` | `stageId` | `roundId` | Live voting records |
|
||||
| `COIDeclaration` | `stageId` | `roundId` | Conflict of interest declarations |
|
||||
| `ProjectStatusHistory` | `stageId` | `roundId` | Project state change log |
|
||||
| `DigestLog` | `stageId` | `roundId` | Notification digest tracking |
|
||||
| `MentorNote` | `stageId` | `roundId` | Mentor notes (legacy model, still used) |
|
||||
| `MentorMilestone` | `stageId` | `roundId` | Mentor milestone definitions |
|
||||
| `PartnerStageAccess` | `stageId` | `roundId` | Rename model to `PartnerRoundAccess` |
|
||||
| `LearningResource` | `stageId` | `roundId` | Learning resources linked to rounds |
|
||||
| `TaggingJob` | `stageId` | `roundId` | AI tagging job references |
|
||||
| `FilteringResult` | `stageId` | `roundId` | Filtering results per project |
|
||||
| `FilteringJob` | `stageId` | `roundId` | Filtering batch job records |
|
||||
| `EvaluationReminder` | `stageId` | `roundId` | Cron-triggered reminder records |
|
||||
| `GracePeriod` | `stageId` | `roundId` | Grace period extensions |
|
||||
| `LiveProgressCursor` | `stageId` | `roundId` | Live ceremony cursor |
|
||||
| `LiveVotingSession` | `stageId` | `roundId` | Live voting session |
|
||||
|
||||
**Migration approach**: These are simple column renames with FK constraint updates. Run as a single migration after the main table additions in Phase 1.
|
||||
|
||||
### New Field Additions (Non-Breaking)
|
||||
|
||||
These fields are added to existing new models during Phase 1. All have defaults, so they are non-breaking:
|
||||
|
||||
| Model | Field | Type | Default | Purpose |
|
||||
|-------|-------|------|---------|---------|
|
||||
| `Round` | `purposeKey` | `String?` | null | Optional analytics tag |
|
||||
| `JuryGroupMember` | `role` | `JuryGroupMemberRole` | `MEMBER` | Replaces `isLead: Boolean` |
|
||||
| `SubmissionWindow` | `isLocked` | `Boolean` | `false` | Manual lock independent of window close |
|
||||
| `AssignmentIntent` | `status` | `AssignmentIntentStatus` | `PENDING` | Proper lifecycle enum |
|
||||
|
||||
**Data migration for `isLead → role`**: For existing data where `isLead = true`, set `role = CHAIR`. For `isLead = false`, set `role = MEMBER`. Drop `isLead` column after backfill.
|
||||
|
||||
### New Enums (Phase 1)
|
||||
|
||||
| Enum | Values | Used By |
|
||||
|------|--------|---------|
|
||||
| `JuryGroupMemberRole` | CHAIR, MEMBER, OBSERVER | `JuryGroupMember.role` |
|
||||
| `AssignmentIntentStatus` | PENDING, HONORED, OVERRIDDEN, EXPIRED, CANCELLED | `AssignmentIntent.status` |
|
||||
|
||||
---
|
||||
|
||||
## Pre-Migration Checklist
|
||||
|
||||
Before starting Phase 1:
|
||||
- [ ] All type definitions finalized (Phase 0 of roadmap)
|
||||
- [ ] Database backup taken
|
||||
- [ ] Migration script written and tested on staging
|
||||
- [ ] Feature flags infrastructure in place
|
||||
- [ ] Rollback procedures documented and tested
|
||||
- [ ] All team members briefed on migration plan
|
||||
|
||||
Before starting Phase 4 (cleanup):
|
||||
- [ ] All feature flags pointing to new code paths
|
||||
- [ ] 72-hour burn-in period completed with zero critical errors
|
||||
- [ ] All release gates A–F passed (see [12-observability-and-release-gates.md](./12-observability-and-release-gates.md))
|
||||
- [ ] Database backup taken (point-of-no-return backup)
|
||||
- [ ] Rollback from backup tested on staging
|
||||
|
||||
---
|
||||
|
||||
## Timeline Alignment
|
||||
|
||||
| Migration Phase | Roadmap Phase | When |
|
||||
|----------------|---------------|------|
|
||||
| Phase 1: Schema additions | Implementation Phase 1 | Weeks 2–3 |
|
||||
| Phase 2: Data backfill | Implementation Phase 1 | Weeks 3–4 |
|
||||
| Phase 3: Code migration | Implementation Phases 3–6 | Weeks 4–12 |
|
||||
| Phase 4: Cleanup | Implementation Phase 7 | Week 12–13 |
|
||||
|
||||
See [09-implementation-roadmap.md](./09-implementation-roadmap.md) for the full implementation timeline.
|
||||
|
||||
---
|
||||
|
||||
## System Inventory Appendix
|
||||
|
||||
Complete file-by-file migration effort estimate. Risk: L = Low (rename only), M = Medium (logic changes), H = High (rewrite).
|
||||
|
||||
### Database & Schema (3 files, ~28 hours)
|
||||
|
||||
| File | Changes | Risk |
|
||||
|------|---------|------|
|
||||
| `prisma/schema.prisma` | 40+ model/enum updates, 17+ FK renames | H |
|
||||
| `prisma/seed.ts` | 300+ lines referencing Pipeline/Stage | M |
|
||||
| `prisma/migrations/*` | New migration files for all schema changes | M |
|
||||
|
||||
### tRPC Routers (24 files, ~104 hours)
|
||||
|
||||
| File | Changes | Risk |
|
||||
|------|---------|------|
|
||||
| `src/server/routers/pipeline.ts` | Rename to `competition.ts`, update all queries | H |
|
||||
| `src/server/routers/stage.ts` | Rename to `round.ts`, update all queries | H |
|
||||
| `src/server/routers/track.ts` | Remove entirely, absorb into competition + specialAward | H |
|
||||
| `src/server/routers/evaluation.ts` | stageId→roundId, add JuryGroup awareness | M |
|
||||
| `src/server/routers/assignment.ts` | JuryGroup model, intent lifecycle | H |
|
||||
| `src/server/routers/file.ts` | submissionWindowId, multi-round grouping | M |
|
||||
| `src/server/routers/mentor.ts` | Workspace features, file promotion | M |
|
||||
| `src/server/routers/award.ts` | competitionId, evaluationRoundId | M |
|
||||
| `src/server/routers/audience-vote.ts` | stageId→roundId | L |
|
||||
| `src/server/routers/coi.ts` | roundId awareness | L |
|
||||
| `src/server/routers/notification.ts` | Stage event→round event mapping | M |
|
||||
| `src/server/routers/cron.ts` | stageId→roundId in all scheduled jobs | M |
|
||||
| `src/server/routers/deliberation.ts` | **NEW** — full deliberation router | H |
|
||||
| `src/server/routers/result-lock.ts` | **NEW** — result lock/unlock procedures | M |
|
||||
| `src/server/routers/jury-group.ts` | **NEW** — jury management procedures | H |
|
||||
| `src/server/routers/submission-window.ts` | **NEW** — submission window CRUD | M |
|
||||
| Other 8 routers | Indirect references (imports, type refs) | L |
|
||||
|
||||
### Services (11 files, ~50 hours)
|
||||
|
||||
| File | Changes | Risk |
|
||||
|------|---------|------|
|
||||
| `src/server/services/stage-engine.ts` | Rename to `round-engine.ts`, full rewrite | H |
|
||||
| `src/server/services/stage-filtering.ts` | Rename to `round-filtering.ts`, roundId refs | M |
|
||||
| `src/server/services/stage-assignment.ts` | Rename to `round-assignment.ts`, JuryGroup model | H |
|
||||
| `src/server/services/stage-notifications.ts` | Rename to `round-notifications.ts`, event types | M |
|
||||
| `src/server/services/live-control.ts` | stageId→roundId | M |
|
||||
| `src/server/services/ai-filtering.ts` | stageId→roundId in AI context | L |
|
||||
| `src/server/services/ai-assignment.ts` | JuryGroup awareness | M |
|
||||
| `src/server/services/ai-evaluation-summary.ts` | roundId refs | L |
|
||||
| `src/server/services/ai-tagging.ts` | stageId→roundId | L |
|
||||
| `src/server/services/ai-award-eligibility.ts` | competitionId awareness | L |
|
||||
| `src/server/services/anonymization.ts` | No changes expected | L |
|
||||
|
||||
### Types & Libraries (6 files, ~32 hours)
|
||||
|
||||
| File | Changes | Risk |
|
||||
|------|---------|------|
|
||||
| `src/types/pipeline-wizard.ts` | Complete rewrite → `competition-wizard.ts` | H |
|
||||
| `src/types/wizard-config.ts` | Complete rewrite for round-based wizard | H |
|
||||
| `src/lib/pipeline-defaults.ts` | Rename to `competition-defaults.ts` | M |
|
||||
| `src/lib/pipeline-validation.ts` | Rename to `competition-validation.ts` | M |
|
||||
| `src/lib/pipeline-conversions.ts` | Rename to `competition-conversions.ts` | M |
|
||||
| `src/lib/stage-config-schema.ts` | Rename to `round-config-schema.ts`, Zod schemas | M |
|
||||
|
||||
### Admin Pages (35+ files, ~52 hours)
|
||||
|
||||
| File Pattern | Count | Changes | Risk |
|
||||
|-------------|-------|---------|------|
|
||||
| `src/app/(admin)/admin/pipelines/*` | 8 pages | Rename to `competitions/*` | H |
|
||||
| `src/app/(admin)/admin/stages/*` | 5 pages | Rename to `rounds/*` | H |
|
||||
| Pipeline wizard components | 6 | Full rewrite for competition model | H |
|
||||
| Stage config components | 8 | Update to round-type configs | M |
|
||||
| Other admin components | 10 | Indirect references | L |
|
||||
|
||||
### Jury Pages (16 files, ~18 hours)
|
||||
|
||||
| File Pattern | Count | Changes | Risk |
|
||||
|-------------|-------|---------|------|
|
||||
| `src/app/(jury)/jury/stages/[stageId]/*` | 6 pages | Rename to `rounds/[roundId]/*` | M |
|
||||
| Jury evaluation components | 4 | roundId, JuryGroup filtering | M |
|
||||
| Jury shared components | 6 | Breadcrumbs, timeline, badges | L |
|
||||
|
||||
### Applicant Pages (8 files, ~12 hours)
|
||||
|
||||
| File Pattern | Count | Changes | Risk |
|
||||
|-------------|-------|---------|------|
|
||||
| `src/app/(applicant)/applicant/pipeline/*` | 4 pages | Rename to `competition/*` | H |
|
||||
| Applicant components | 4 | StageTimeline→RoundTimeline | M |
|
||||
|
||||
### Tests (13+ files, ~45 hours)
|
||||
|
||||
| File | Changes | Risk |
|
||||
|------|---------|------|
|
||||
| `tests/helpers.ts` | Update all factories | M |
|
||||
| `tests/unit/stage-engine.test.ts` | Rename + update references | M |
|
||||
| `tests/unit/stage-filtering.test.ts` | Rename + update | M |
|
||||
| `tests/unit/stage-assignment.test.ts` | Rename + add JuryGroup tests | M |
|
||||
| `tests/integration/pipeline-crud.test.ts` | Rename to competition-crud | M |
|
||||
| `tests/integration/evaluation-flow.test.ts` | Round model, multi-round docs | M |
|
||||
| New test files (8 files) | See [11-testing-and-qa.md](./11-testing-and-qa.md) | H |
|
||||
|
||||
### Infrastructure (4 files, ~11 hours)
|
||||
|
||||
| File | Changes | Risk |
|
||||
|------|---------|------|
|
||||
| `docker/docker-entrypoint.sh` | Migration flow update | L |
|
||||
| Cron job configs | stageId→roundId in scheduled tasks | M |
|
||||
| Webhook event definitions | Stage→Round event names | M |
|
||||
| Email templates | Stage references in copy | L |
|
||||
|
||||
### Totals
|
||||
|
||||
| Category | Files | Est. Hours |
|
||||
|----------|-------|------------|
|
||||
| Database & Schema | 3 | 28 |
|
||||
| tRPC Routers | 24 | 104 |
|
||||
| Services | 11 | 50 |
|
||||
| Types & Libraries | 6 | 32 |
|
||||
| Admin UI | 35+ | 52 |
|
||||
| Jury UI | 16 | 18 |
|
||||
| Applicant UI | 8 | 12 |
|
||||
| Tests | 13+ | 45 |
|
||||
| Infrastructure | 4 | 11 |
|
||||
| **Total** | **120+** | **~352** |
|
||||
|
||||
> **Note**: Conservative estimates for one developer. Parallel frontend/backend work reduces calendar time. Critical path: Schema → Services → Routers → UI.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: System Inventory — File-by-File Migration Map
|
||||
|
||||
Complete inventory of all files requiring changes, grouped by category with effort estimates.
|
||||
|
||||
### Prisma & Database (1 file, HIGH effort)
|
||||
|
||||
| File | Changes Required | Effort |
|
||||
|------|-----------------|--------|
|
||||
| `prisma/schema.prisma` | 40+ model/enum updates, 17+ FK renames (stageId→roundId), add new enums, add new fields | HIGH |
|
||||
|
||||
### tRPC Routers (24 files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `src/server/routers/pipeline.ts` | **Rename** → `competition.ts` + all procedures | HIGH |
|
||||
| `src/server/routers/stage.ts` | **Rename** → `round.ts` + all procedures | HIGH |
|
||||
| `src/server/routers/track.ts` | **Remove** — logic absorbed into competition + specialAward | HIGH |
|
||||
| `src/server/routers/evaluation.ts` | Update to use Round model, roundId params, JuryGroup queries | HIGH |
|
||||
| `src/server/routers/assignment.ts` | Update to use JuryGroup model, roundId params | HIGH |
|
||||
| `src/server/routers/file.ts` | Update `listByProjectForStage` → `listByProjectForRound`, multi-round grouping | MEDIUM |
|
||||
| `src/server/routers/mentor.ts` | Add workspace features, file promotion procedures | MEDIUM |
|
||||
| `src/server/routers/applicant.ts` | Update `getMyDashboard` (openStages→openRounds), pipeline view | MEDIUM |
|
||||
| `src/server/routers/application.ts` | Update stage mode config → round-type-aware config | MEDIUM |
|
||||
| `src/server/routers/live-control.ts` | Update stageId → roundId in cursor management | MEDIUM |
|
||||
| `src/server/routers/award.ts` | Update to use competitionId, enhanced routing modes | MEDIUM |
|
||||
| `src/server/routers/notification.ts` | Update event keys from stageId → roundId | LOW |
|
||||
| `src/server/routers/analytics.ts` | Update stage references → round references | LOW |
|
||||
| `src/server/routers/digest.ts` | Update stageId references | LOW |
|
||||
| `src/server/routers/coi.ts` | Update stageId → roundId in COI declarations | LOW |
|
||||
| `src/server/routers/audit.ts` | Update event type names (stage.* → round.*) | LOW |
|
||||
| **New:** `src/server/routers/deliberation.ts` | New router for deliberation lifecycle | HIGH |
|
||||
| **New:** `src/server/routers/result-lock.ts` | New router for result locking/unlocking | MEDIUM |
|
||||
| **New:** `src/server/routers/jury-group.ts` | New router for jury group management | MEDIUM |
|
||||
| **New:** `src/server/routers/submission-window.ts` | New router for submission window management | MEDIUM |
|
||||
|
||||
### Services (11 files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `src/server/services/stage-engine.ts` | **Rename** → `round-engine.ts`, update all type references | HIGH |
|
||||
| `src/server/services/stage-filtering.ts` | **Rename** → `round-filtering.ts`, update to RoundType.FILTERING | HIGH |
|
||||
| `src/server/services/stage-assignment.ts` | **Rename** → `round-assignment.ts`, add JuryGroup-based assignment | HIGH |
|
||||
| `src/server/services/stage-notifications.ts` | **Rename** → `round-notifications.ts`, update event keys | MEDIUM |
|
||||
| `src/server/services/live-control.ts` | Update stageId → roundId in cursor management | MEDIUM |
|
||||
| `src/server/services/ai-filtering.ts` | Update stage references → round references | LOW |
|
||||
| `src/server/services/ai-assignment.ts` | Update to use JuryGroup model | MEDIUM |
|
||||
| `src/server/services/ai-evaluation-summary.ts` | Update stage references | LOW |
|
||||
| **New:** `src/server/services/deliberation-engine.ts` | Deliberation lifecycle, vote aggregation, Borda count | HIGH |
|
||||
| **New:** `src/server/services/result-lock.ts` | Lock/unlock with audit trail | MEDIUM |
|
||||
| **New:** `src/server/services/mentor-workspace.ts` | Messaging, file management, promotion | MEDIUM |
|
||||
|
||||
### Types & Libraries (6 files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `src/types/pipeline-wizard.ts` | **Rewrite** → `competition-wizard.ts` with Competition/Round types | HIGH |
|
||||
| `src/types/wizard-config.ts` | **Rewrite** → update all Stage→Round type references | HIGH |
|
||||
| `src/lib/pipeline-defaults.ts` | **Rename** → `competition-defaults.ts`, update all defaults | MEDIUM |
|
||||
| `src/lib/pipeline-validation.ts` | **Rename** → `competition-validation.ts` | MEDIUM |
|
||||
| `src/lib/pipeline-conversions.ts` | **Rename** → `competition-conversions.ts` | MEDIUM |
|
||||
| `src/lib/stage-config-schema.ts` | **Rename** → `round-config-schema.ts`, update to 7 typed Zod schemas | HIGH |
|
||||
|
||||
### Admin Pages (13+ files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `src/app/(admin)/pipelines/*` | **Rename** → `competitions/*`, update all router calls | HIGH |
|
||||
| `src/app/(admin)/pipelines/[id]/stages/*` | **Rename** → `competitions/[id]/rounds/*` | HIGH |
|
||||
| `src/app/(admin)/pipelines/[id]/tracks/*` | **Remove** — track UI eliminated | MEDIUM |
|
||||
| **New:** `src/app/(admin)/competitions/[id]/juries/*` | New jury management section (3 pages) | HIGH |
|
||||
| **New:** `src/app/(admin)/competitions/[id]/deliberation/*` | New deliberation management | HIGH |
|
||||
| **New:** `src/app/(admin)/competitions/[id]/results/*` | New results & locks page | MEDIUM |
|
||||
| **New:** `src/app/(admin)/competitions/[id]/submission-windows/*` | New submission window management | MEDIUM |
|
||||
|
||||
### Jury Pages (6 files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `src/app/(jury)/stages/[stageId]/*` | **Rename** → `rounds/[roundId]/*` (6 routes) | HIGH |
|
||||
| `src/app/(jury)/stages/[stageId]/evaluate/*` | Update to multi-round doc viewing, JuryGroup context | HIGH |
|
||||
| `src/app/(jury)/stages/[stageId]/compare/*` | Update cross-round project comparison | MEDIUM |
|
||||
| **New:** `src/app/(jury)/rounds/[roundId]/deliberation/*` | Juror deliberation voting interface | HIGH |
|
||||
|
||||
### Applicant Pages (8 files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `src/app/(applicant)/pipeline/*` | **Rename** → `competition/*`, redesign progress visualization | HIGH |
|
||||
| `src/app/(applicant)/pipeline/[stageId]/documents/*` | **Rename** → multi-round aware doc upload with read-only enforcement | HIGH |
|
||||
| `src/app/(applicant)/pipeline/[stageId]/status/*` | **Rename** → round-based status with cross-round visibility | MEDIUM |
|
||||
|
||||
### Shared Components (24+ files)
|
||||
|
||||
| Component | Change | Effort |
|
||||
|-----------|--------|--------|
|
||||
| `StageTimeline` | **Rename** → `RoundTimeline`, update props from stageId to roundId | MEDIUM |
|
||||
| `StageWindowBadge` | **Rename** → `RoundStatusBadge` | LOW |
|
||||
| `RequirementUploadSlot` | Update `stageId` prop → `roundId` + `submissionWindowId` | LOW |
|
||||
| `PipelineWizard` | **Rename** → `CompetitionWizard`, rewrite step flow | HIGH |
|
||||
| `StageSidebar` | **Rename** → `RoundSidebar` | LOW |
|
||||
| `StageConfigEditor` | **Rename** → `RoundConfigEditor`, update to typed Zod schemas | HIGH |
|
||||
| `TrackSelector` | **Remove** | LOW |
|
||||
| Breadcrumb components | Update Pipeline→Track→Stage to Competition→Round | LOW |
|
||||
|
||||
### Infrastructure & Config (5 files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `prisma/seed.ts` | Update all Pipeline/Stage creation to Competition/Round (300+ lines) | HIGH |
|
||||
| `docker/docker-entrypoint.sh` | Update migration flow for new tables | LOW |
|
||||
| `tests/helpers.ts` | Add new factory functions for Competition/Round/JuryGroup/etc. | MEDIUM |
|
||||
| `.env.example` | Add any new feature flag env vars | LOW |
|
||||
| `CLAUDE.md` | Update architecture documentation | LOW |
|
||||
|
||||
### Background Jobs / Cron (3 files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `src/server/cron/evaluation-reminders.ts` | Update stageId → roundId in deadline checks | LOW |
|
||||
| `src/server/cron/digest-sender.ts` | Update stageId references in digest generation | LOW |
|
||||
| `src/server/cron/ai-tagging.ts` | Update stageId → roundId in tagging jobs | LOW |
|
||||
|
||||
### Test Files (12 files)
|
||||
|
||||
| File | Change Type | Effort |
|
||||
|------|------------|--------|
|
||||
| `tests/unit/stage-engine.test.ts` | **Rename** → `round-engine.test.ts`, update all references | MEDIUM |
|
||||
| `tests/unit/stage-filtering.test.ts` | **Rename** → `round-filtering.test.ts` | MEDIUM |
|
||||
| `tests/unit/stage-assignment.test.ts` | **Rename**, add JuryGroup-based tests, cap mode tests | MEDIUM |
|
||||
| `tests/integration/pipeline-crud.test.ts` | **Rename** → `competition-crud.test.ts` | MEDIUM |
|
||||
| `tests/integration/evaluation-flow.test.ts` | Update to Round model, multi-round doc visibility | MEDIUM |
|
||||
| **New:** `tests/unit/policy-resolution.test.ts` | 5-layer policy precedence tests | HIGH |
|
||||
| **New:** `tests/unit/borda-count.test.ts` | Borda count aggregation tests | LOW |
|
||||
| **New:** `tests/unit/deliberation-tally.test.ts` | Vote tallying for both modes | MEDIUM |
|
||||
| **New:** `tests/unit/config-validation.test.ts` | All 7 Zod schema validation tests | MEDIUM |
|
||||
| **New:** `tests/integration/deliberation-flow.test.ts` | Full deliberation lifecycle | HIGH |
|
||||
| **New:** `tests/integration/mentor-workspace.test.ts` | Messaging, files, comments, promotion | MEDIUM |
|
||||
| **New:** `tests/e2e/monaco-full-flow.test.ts` | Complete 8-round simulation | HIGH |
|
||||
|
||||
### Webhook & Event Mapping
|
||||
|
||||
| Old Event Type | New Event Type |
|
||||
|----------------|----------------|
|
||||
| `stage.transitioned` | `round.transitioned` |
|
||||
| `stage.opened` | `round.opened` |
|
||||
| `stage.closed` | `round.closed` |
|
||||
| `pipeline.created` | `competition.created` |
|
||||
| `pipeline.statusChanged` | `competition.statusChanged` |
|
||||
| `stage.assignmentCompleted` | `round.assignmentCompleted` |
|
||||
| `stage.evaluationCompleted` | `round.evaluationCompleted` |
|
||||
| `stage.filteringCompleted` | `round.filteringCompleted` |
|
||||
| *(new)* | `deliberation.sessionCreated` |
|
||||
| *(new)* | `deliberation.voteSubmitted` |
|
||||
| *(new)* | `deliberation.resultLocked` |
|
||||
| *(new)* | `deliberation.resultUnlocked` |
|
||||
| *(new)* | `mentoring.filePromoted` |
|
||||
| *(new)* | `submission.windowOpened` |
|
||||
| *(new)* | `submission.windowClosed` |
|
||||
|
||||
### Effort Summary
|
||||
|
||||
| Category | Files | New | Modified | Removed | Estimated Hours |
|
||||
|----------|-------|-----|----------|---------|----------------|
|
||||
| Prisma & DB | 1 | 0 | 1 | 0 | 16–24 |
|
||||
| tRPC Routers | 24 | 4 | 16 | 1 | 40–60 |
|
||||
| Services | 11 | 3 | 8 | 0 | 30–40 |
|
||||
| Types & Libs | 6 | 0 | 6 | 0 | 16–24 |
|
||||
| Admin Pages | 13+ | 4 | 9 | 1 | 30–40 |
|
||||
| Jury Pages | 6+ | 1 | 5 | 0 | 20–30 |
|
||||
| Applicant Pages | 8 | 0 | 8 | 0 | 16–24 |
|
||||
| Components | 24+ | 0 | 20+ | 2+ | 20–30 |
|
||||
| Infrastructure | 5 | 0 | 5 | 0 | 8–12 |
|
||||
| Background Jobs | 3 | 0 | 3 | 0 | 4–6 |
|
||||
| Tests | 12 | 7 | 5 | 0 | 30–40 |
|
||||
| **Total** | **113+** | **19** | **86+** | **4+** | **230–330** |
|
||||
303
docs/unified-architecture-redesign/11-testing-and-qa.md
Normal file
303
docs/unified-architecture-redesign/11-testing-and-qa.md
Normal file
@@ -0,0 +1,303 @@
|
||||
# Testing & QA
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the test strategy for the redesigned competition system. It covers the test pyramid, specific test matrices for the Monaco competition flow and deliberation system, regression coverage, audit verification, and performance scenarios.
|
||||
|
||||
---
|
||||
|
||||
## Test Pyramid
|
||||
|
||||
```
|
||||
╱╲
|
||||
╱ E2E ╲ ~10 tests — Full Monaco flow simulation
|
||||
╱────────╲
|
||||
╱Integration╲ ~50 tests — Service + database interaction
|
||||
╱──────────────╲
|
||||
╱ Unit Tests ╲ ~200+ tests — Pure logic, no I/O
|
||||
╱────────────────────╲
|
||||
```
|
||||
|
||||
| Level | What | Tools | Speed |
|
||||
|-------|------|-------|-------|
|
||||
| **Unit** | Pure functions: policy resolution, cap calculation, Borda count, tie-breaking logic, config validation | Vitest | < 1s each |
|
||||
| **Integration** | Service + Prisma: assignment algorithm with real DB, round transitions, deliberation vote aggregation | Vitest + test DB | < 5s each |
|
||||
| **E2E** | Full flow: create competition → run all 8 rounds → lock results | Vitest + test DB + tRPC callers | < 30s each |
|
||||
|
||||
---
|
||||
|
||||
## Monaco Flow Test Matrix
|
||||
|
||||
End-to-end tests that simulate the complete Monaco 2026 competition:
|
||||
|
||||
### R1: Intake
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| Submit project before deadline | Submission accepted, project created with category |
|
||||
| Submit project after deadline (HARD policy) | Submission rejected |
|
||||
| Submit project after deadline (FLAG policy) | Submission accepted, marked as Late |
|
||||
| Submit with missing required doc | Validation error returned |
|
||||
| Admin uploads doc for applicant | File associated, provenance recorded |
|
||||
| Submission window not yet open | Submission rejected |
|
||||
|
||||
### R2: AI Filtering
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| AI marks project eligible | ProjectRoundState = ELIGIBLE |
|
||||
| AI marks project ineligible | ProjectRoundState = INELIGIBLE, reason stored |
|
||||
| AI marks project for manual review | ProjectRoundState = MANUAL_REVIEW |
|
||||
| Admin overrides ineligible → eligible | Override recorded in audit log |
|
||||
| Admin overrides eligible → ineligible | Override recorded in audit log |
|
||||
| Filtering with no AI (deterministic rules only) | Rules-based filtering works without AI |
|
||||
|
||||
### R3: Jury 1 Evaluation
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| Assignment respects hard cap | Judge gets ≤ maxProjects |
|
||||
| Assignment distributes overflow to soft cap judges | Overflow distributed evenly, up to cap + buffer |
|
||||
| Unassigned projects enter queue | Remaining projects have reason codes |
|
||||
| COI declared → project skipped | COI judge doesn't get COI project |
|
||||
| Judge submits score within window | Score recorded |
|
||||
| Judge submits score outside window | Rejected |
|
||||
| AI shortlist generated at round end | Ranked recommendations exist per category |
|
||||
| Admin overrides shortlist selection | Override recorded, selected projects advance |
|
||||
|
||||
### R4: Semifinal Submission
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| Applicant uploads R2 docs | Files accepted for correct slots |
|
||||
| Applicant tries to edit R1 docs | Rejected (read-only) |
|
||||
| Admin replaces R1 doc | Replacement accepted with provenance |
|
||||
| Applicant downloads R1 doc | Download succeeds |
|
||||
|
||||
### R5: Jury 2 Evaluation + Special Awards
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| Judge sees R1 + R2 docs clearly separated | Both sets of docs returned with round labels |
|
||||
| Award Mode A: projects filtered into award pool | Eligible projects appear in award pool |
|
||||
| Award Mode A: admin confirms pull-out | Pull-out confirmed, project moved |
|
||||
| Award Mode B: projects flagged but stay in main | Project in both main and award evaluation |
|
||||
| Award single-judge decision | Single judge can select winner |
|
||||
| Top N finalists selected (N configurable) | Exactly N projects advance per category |
|
||||
|
||||
### R6: Mentoring
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| Mentor sends message to team | Message delivered and visible |
|
||||
| Team uploads file to workspace | File visible to mentor + team + admin |
|
||||
| Mentor comments on file | Comment created with thread support |
|
||||
| File promoted to official submission | SubmissionPromotionEvent created with provenance |
|
||||
| Non-mentored finalist has no workspace | No workspace features visible |
|
||||
|
||||
### R7: Live Finals
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| Admin advances project cursor | Current project updates for all Jury 3 members |
|
||||
| Jury 3 member submits live score | Score recorded within voting window |
|
||||
| Audience vote submitted | Vote counted |
|
||||
| Audience vote outside window | Rejected |
|
||||
| Jury 3 sees prior jury data (when enabled) | Prior jury scores/feedback visible |
|
||||
| Jury 3 doesn't see prior jury data (when disabled) | Prior jury data hidden |
|
||||
|
||||
### R8: Deliberation
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| SINGLE_WINNER_VOTE: juror picks winner | Vote recorded |
|
||||
| SINGLE_WINNER_VOTE: tally produces ranking | Most votes = rank 1, others by vote count |
|
||||
| FULL_RANKING: juror submits ordinal ranks | All ranks recorded |
|
||||
| FULL_RANKING: Borda count aggregation | Correct Borda scores computed |
|
||||
| Tie detected → runoff initiated | Session status = RUNOFF, new vote round |
|
||||
| Tie detected → admin breaks tie | Admin decision recorded |
|
||||
| Admin overrides entire result | Override result stored, flag set |
|
||||
| Admin locks result | ResultLock created with snapshot |
|
||||
| Super-admin unlocks result | ResultUnlockEvent created with reason |
|
||||
| Non-super-admin tries to unlock | Rejected |
|
||||
|
||||
---
|
||||
|
||||
## Deliberation-Specific Test Matrix
|
||||
|
||||
Detailed tests for the deliberation subsystem:
|
||||
|
||||
### SINGLE_WINNER_VOTE Mode
|
||||
|
||||
| Scenario | Expected Outcome |
|
||||
|----------|-----------------|
|
||||
| 3 jurors, unanimous pick | Winner = picked project, rank 1 |
|
||||
| 3 jurors, split vote (2-1) | Winner = project with 2 votes |
|
||||
| 3 jurors, three-way tie (1-1-1) | Tie-break method triggered |
|
||||
| Juror submits vote, then changes | Latest vote overwrites (within window) |
|
||||
| Voting window closes | No more votes accepted |
|
||||
|
||||
### FULL_RANKING Mode
|
||||
|
||||
| Scenario | Expected Outcome |
|
||||
|----------|-----------------|
|
||||
| 3 jurors rank 5 projects | Borda count: 1st=5pts, 2nd=4pts, ... 5th=1pt |
|
||||
| Clear winner (highest Borda) | Rank 1 = highest Borda score |
|
||||
| Two projects tied on Borda | Tie-break method triggered |
|
||||
| Juror ranks only top 3 (partial) | Unranked projects get 0 points from this juror |
|
||||
|
||||
### Tie-Breaking
|
||||
|
||||
| Method | Test Case | Expected |
|
||||
|--------|-----------|----------|
|
||||
| RUNOFF | Two projects tied → runoff vote | New voting round with only tied projects |
|
||||
| RUNOFF | Runoff still tied → second runoff | Session supports multiple runoff rounds |
|
||||
| ADMIN_DECIDES | Tie detected → admin picks winner | Admin decision recorded, tie resolved |
|
||||
| SCORE_FALLBACK | Tie → fall back to Jury 3 scores | Higher Jury 3 score wins |
|
||||
|
||||
### Participant Management
|
||||
|
||||
| Scenario | Expected |
|
||||
|----------|----------|
|
||||
| Juror marked ABSENT_EXCUSED | Doesn't count toward quorum |
|
||||
| Juror REPLACED | Replacement can vote, original cannot |
|
||||
| Quorum not met | Session cannot proceed to VOTING |
|
||||
|
||||
---
|
||||
|
||||
## Invite/Onboarding Test Matrix
|
||||
|
||||
| Test Case | Validates |
|
||||
|-----------|-----------|
|
||||
| Admin invites judge to Jury 1 | JuryGroupMember created |
|
||||
| Judge accepts invite and onboards | Member status active, self-service values set |
|
||||
| Judge adjusts cap during onboarding | selfServiceCap updated |
|
||||
| Judge adjusts ratio during onboarding | selfServiceBias updated |
|
||||
| Self-service disabled → fields hidden | No self-service options shown |
|
||||
| Admin overrides self-service values | Override takes precedence |
|
||||
| Invite with pre-assignment intent | AssignmentIntent record created |
|
||||
| Invite with pre-assignment + COI conflict | Intent stays PENDING, reason `INTENT_BLOCKED` |
|
||||
| Judge removed from group with pending intent | Intent status → CANCELLED |
|
||||
| Round completes with unmatched intent | Intent status → EXPIRED |
|
||||
|
||||
---
|
||||
|
||||
## Config Schema Validation Test Matrix
|
||||
|
||||
All 7 round-type Zod schemas must validate both correct and malformed input:
|
||||
|
||||
| Schema | Valid Input Test | Invalid Input Test |
|
||||
|--------|-----------------|-------------------|
|
||||
| `IntakeConfigSchema` | Valid with `deadlinePolicy: "FLAG"`, `maxFileSizeMB: 50` | Rejects negative `maxFileSizeMB`, unknown `deadlinePolicy` |
|
||||
| `FilteringConfigSchema` | Valid with `aiEnabled: true`, `autoAdvanceEligible: false` | Rejects `autoAdvanceEligible` without `aiEnabled` set |
|
||||
| `EvaluationConfigSchema` | Valid with rubric, `requireFeedback: true`, `feedbackMinLength: 50` | Rejects `feedbackMinLength < 0`, missing rubric |
|
||||
| `SubmissionConfigSchema` | Valid with doc slot requirements, `deadlinePolicy: "HARD"` | Rejects empty `requiredDocSlots`, invalid MIME types |
|
||||
| `MentoringConfigSchema` | Valid with `eligibility: "all_advancing"`, `filePromotionEnabled: true` | Rejects unknown `eligibility` value |
|
||||
| `LiveFinalConfigSchema` | Valid with `audienceVoteWeight: 0.3`, `presentationDurationMinutes: 15` | Rejects `audienceVoteWeight > 1`, negative duration |
|
||||
| `DeliberationConfigSchema` | Valid with `mode: "FULL_RANKING"`, `topN: 3`, `tieBreakMethod: "RUNOFF"` | Rejects `topN < 1`, unknown `mode` |
|
||||
|
||||
**Edge cases (all schemas):**
|
||||
|
||||
| Test Case | Expected |
|
||||
|-----------|----------|
|
||||
| Empty object | Defaults applied, valid |
|
||||
| Extra unknown fields | Stripped (Zod `.strip()`) |
|
||||
| `null` config | Rejected — config is required |
|
||||
| Partial config (some fields) | Missing fields get defaults |
|
||||
|
||||
---
|
||||
|
||||
## Assignment Intent Lifecycle Tests
|
||||
|
||||
| Test Case | Expected |
|
||||
|-----------|----------|
|
||||
| Create intent at invite time | `AssignmentIntent` created with status `PENDING`, source `INVITE` |
|
||||
| Algorithm runs, intent matchable | Intent transitions to `HONORED`, `Assignment` record created |
|
||||
| Algorithm runs, intent blocked by COI | Intent stays `PENDING`, unassigned queue entry with `INTENT_BLOCKED` |
|
||||
| Algorithm runs, intent blocked by cap | Intent stays `PENDING`, unassigned queue entry with `INTENT_BLOCKED` |
|
||||
| Admin reassigns project to different judge | Intent transitions to `OVERRIDDEN`, audit logged |
|
||||
| Judge removed from JuryGroup | Intent transitions to `CANCELLED` |
|
||||
| Round closes with pending intent | Intent transitions to `EXPIRED` (batch update) |
|
||||
| Admin explicitly cancels intent | Intent transitions to `CANCELLED` |
|
||||
| Intent already `HONORED` → no further transitions | Terminal state enforced |
|
||||
| Intent already `EXPIRED` → no further transitions | Terminal state enforced |
|
||||
|
||||
---
|
||||
|
||||
## Regression Coverage
|
||||
|
||||
Existing test files that need updates for the redesign:
|
||||
|
||||
| Test File | Required Updates |
|
||||
|-----------|-----------------|
|
||||
| `tests/unit/stage-engine.test.ts` | Rename to round-engine, update all type references |
|
||||
| `tests/unit/stage-filtering.test.ts` | Update to use RoundType.FILTERING, Competition model |
|
||||
| `tests/unit/stage-assignment.test.ts` | Add JuryGroup-based assignment tests, cap mode tests |
|
||||
| `tests/integration/pipeline-crud.test.ts` | Rename to competition-crud, test Competition/Round CRUD |
|
||||
| `tests/integration/evaluation-flow.test.ts` | Update to use Round model, multi-round doc visibility |
|
||||
|
||||
New test files to create:
|
||||
|
||||
| Test File | Coverage |
|
||||
|-----------|----------|
|
||||
| `tests/unit/policy-resolution.test.ts` | 5-layer policy precedence |
|
||||
| `tests/unit/borda-count.test.ts` | Borda count aggregation |
|
||||
| `tests/unit/deliberation-tally.test.ts` | Vote tallying for both modes |
|
||||
| `tests/unit/config-validation.test.ts` | All 7 Zod schema validations |
|
||||
| `tests/integration/deliberation-flow.test.ts` | Full deliberation lifecycle |
|
||||
| `tests/integration/mentor-workspace.test.ts` | Messaging, files, comments, promotion |
|
||||
| `tests/integration/submission-windows.test.ts` | Multi-round doc lifecycle |
|
||||
| `tests/e2e/monaco-full-flow.test.ts` | Complete 8-round simulation |
|
||||
|
||||
---
|
||||
|
||||
## Audit Completeness Checks
|
||||
|
||||
Every critical operation must emit an audit record to `DecisionAuditLog`:
|
||||
|
||||
| Operation | Must Audit |
|
||||
|-----------|-----------|
|
||||
| Admin overrides eligibility | Yes — who, when, project, old→new status |
|
||||
| Admin overrides assignment | Yes — who, when, judge, project, reason |
|
||||
| Admin overrides shortlist selection | Yes — who, when, selected projects |
|
||||
| Admin overrides deliberation result | Yes — who, when, reason, original→override |
|
||||
| Result lock | Yes — who, when, snapshot |
|
||||
| Result unlock | Yes — who, when, reason (super-admin only) |
|
||||
| File promotion | Yes — who, when, source file, target slot |
|
||||
| Award pull-out confirmation | Yes — who, when, projects pulled |
|
||||
| Judge cap/ratio override | Yes — who, when, old→new values |
|
||||
|
||||
Test: For each operation above, verify an audit record exists with all required fields.
|
||||
|
||||
---
|
||||
|
||||
## Performance & Capacity Scenarios
|
||||
|
||||
| Scenario | Target | Test Method |
|
||||
|----------|--------|-------------|
|
||||
| Bulk intake: 500 projects submitted in 1 hour | All submissions processed, no timeouts | Load test with concurrent submissions |
|
||||
| Assignment: 500 projects across 30 judges | Assignment completes < 30s | Integration test with timing |
|
||||
| Live voting: 50 audience members voting simultaneously | All votes recorded, no conflicts | Concurrent request test |
|
||||
| Deliberation: 15 jurors submitting rankings | Aggregation completes < 5s | Integration test with timing |
|
||||
| Multi-round doc query: judge views 3 rounds of docs | Response < 2s | Query performance test |
|
||||
|
||||
---
|
||||
|
||||
## Test Data Factories
|
||||
|
||||
All tests use factory functions (see `tests/helpers.ts`):
|
||||
|
||||
```typescript
|
||||
// New factories needed
|
||||
createTestCompetition(overrides?)
|
||||
createTestRound(competitionId, type, overrides?)
|
||||
createTestJuryGroup(competitionId, overrides?)
|
||||
createTestJuryGroupMember(juryGroupId, userId, overrides?)
|
||||
createTestSubmissionWindow(competitionId, roundId, overrides?)
|
||||
createTestDeliberationSession(competitionId, roundId, overrides?)
|
||||
createTestDeliberationVote(sessionId, juryMemberId, overrides?)
|
||||
createTestSpecialAward(competitionId, overrides?)
|
||||
createTestMentorFile(projectId, mentorId, overrides?)
|
||||
```
|
||||
|
||||
See [09-implementation-roadmap.md](./09-implementation-roadmap.md) Phase 0 for factory creation timeline.
|
||||
@@ -0,0 +1,322 @@
|
||||
# Observability & Release Gates
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the metrics, logging, alerting, and release gate criteria for the redesigned competition system. Every phase of the implementation must pass its corresponding release gate before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## Competition Lifecycle Metrics
|
||||
|
||||
### Submission Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `projects_submitted_total` | Counter | Total projects submitted, by category and round |
|
||||
| `projects_submitted_late` | Counter | Projects submitted after deadline (FLAG policy) |
|
||||
| `submission_window_utilization` | Gauge | % of deadline elapsed vs submissions received |
|
||||
| `file_upload_duration_seconds` | Histogram | Time to upload a submission file |
|
||||
| `file_upload_size_bytes` | Histogram | Size distribution of uploaded files |
|
||||
|
||||
### Filtering Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `eligibility_pass_rate` | Gauge | % of projects passing AI filtering |
|
||||
| `eligibility_manual_review_count` | Counter | Projects flagged for manual review |
|
||||
| `eligibility_override_count` | Counter | Admin overrides of AI decisions |
|
||||
| `ai_filtering_duration_seconds` | Histogram | Time for AI to process one project |
|
||||
|
||||
### Assignment Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `assignment_coverage_percent` | Gauge | % of projects assigned to at least one judge |
|
||||
| `assignment_unassigned_queue_size` | Gauge | Projects in unassigned queue |
|
||||
| `assignment_cap_utilization` | Gauge | Average % of cap used per judge |
|
||||
| `assignment_exception_count` | Counter | Over-cap manual assignments |
|
||||
| `assignment_coi_skip_count` | Counter | Assignments skipped due to COI |
|
||||
| `assignment_duration_seconds` | Histogram | Time to run assignment algorithm |
|
||||
|
||||
### Evaluation Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `evaluations_completed_total` | Counter | Scores submitted, by jury group |
|
||||
| `evaluation_completion_rate` | Gauge | % of assigned evaluations completed |
|
||||
| `ai_shortlist_generated` | Counter | AI shortlists generated per round |
|
||||
| `admin_shortlist_override_count` | Counter | Admin overrides of AI shortlist |
|
||||
|
||||
### Deliberation Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `deliberation_session_count` | Counter | Sessions created, by mode |
|
||||
| `deliberation_duration_seconds` | Histogram | Time from VOTING to LOCKED |
|
||||
| `deliberation_runoff_count` | Counter | Runoff rounds triggered |
|
||||
| `deliberation_admin_override_count` | Counter | Admin overrides of deliberation result |
|
||||
| `result_lock_count` | Counter | Results locked |
|
||||
| `result_unlock_count` | Counter | Results unlocked (should be rare) |
|
||||
|
||||
### Live Finals Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `live_voting_concurrent_users` | Gauge | Concurrent jury + audience users during voting |
|
||||
| `live_vote_submission_duration_ms` | Histogram | Time to submit a live vote |
|
||||
| `audience_vote_total` | Counter | Total audience votes submitted |
|
||||
| `stage_manager_cursor_advances` | Counter | Project cursor advances by admin |
|
||||
|
||||
### Mentoring Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `mentor_messages_sent` | Counter | Messages sent in mentor workspaces |
|
||||
| `mentor_files_uploaded` | Counter | Files uploaded to mentor workspaces |
|
||||
| `mentor_file_promotions` | Counter | Files promoted to official submissions |
|
||||
| `mentor_assignment_coverage` | Gauge | % of mentoring-requesting teams with assigned mentors |
|
||||
|
||||
---
|
||||
|
||||
## Reliability Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `api_request_duration_seconds` | Histogram | tRPC procedure latency, by router and procedure |
|
||||
| `api_error_rate` | Gauge | % of requests returning errors, by router |
|
||||
| `job_success_total` | Counter | Background job completions |
|
||||
| `job_failure_total` | Counter | Background job failures |
|
||||
| `email_send_total` | Counter | Emails sent (reminders, invitations, notifications) |
|
||||
| `email_send_failure_total` | Counter | Failed email sends |
|
||||
| `db_query_duration_seconds` | Histogram | Prisma query latency |
|
||||
|
||||
---
|
||||
|
||||
## Audit Event Metrics
|
||||
|
||||
All admin override actions emit `DecisionAuditLog` records. These metrics track audit completeness and quality:
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `audit_events_total` | Counter | Total audit log entries, by `actionType` |
|
||||
| `audit_events_with_diff` | Counter | Audit entries that include both `beforeState` and `afterState` in `detailsJson` |
|
||||
| `audit_coverage_percent` | Gauge | % of override actions (eligibility, assignment, shortlist, deliberation, lock/unlock, promotion, cap change) that have a corresponding audit record |
|
||||
| `audit_missing_diff_count` | Counter | Override actions where `detailsJson` is missing before/after state (should be 0) |
|
||||
| `audit_correlation_coverage` | Gauge | % of audit entries that include full correlation context (`competitionId`, `roundId`, `projectId`) |
|
||||
|
||||
### Audit Completeness Validation
|
||||
|
||||
During each phase gate review, run an automated check that verifies:
|
||||
|
||||
1. **Every admin override has an audit record** — query `DecisionAuditLog` for each override type and verify count matches the operation count
|
||||
2. **Before/after state present** — for all OVERRIDE-type actions, `detailsJson` must contain `{ before: {...}, after: {...} }` structure
|
||||
3. **Correlation completeness** — every audit entry references at minimum `userId`, `competitionId`, and `timestamp`
|
||||
4. **No orphaned operations** — cross-reference assignment exceptions, result unlocks, eligibility overrides, and file promotions against audit log
|
||||
|
||||
### Audit Event Correlation
|
||||
|
||||
For tracing admin actions across the system, audit log entries should include these correlation fields when available:
|
||||
|
||||
```typescript
|
||||
type AuditCorrelation = {
|
||||
competitionId: string
|
||||
roundId?: string
|
||||
roundType?: RoundType
|
||||
juryGroupId?: string
|
||||
projectId?: string
|
||||
sessionId?: string // deliberation session
|
||||
assignmentIntentId?: string // when overriding an intent
|
||||
}
|
||||
```
|
||||
|
||||
This enables queries like: "Show all admin overrides for Competition X, Round 3, sorted by time" or "Show all actions taken on Project Y across all rounds."
|
||||
|
||||
---
|
||||
|
||||
## Structured Logging
|
||||
|
||||
All log entries must include correlation IDs for tracing:
|
||||
|
||||
```typescript
|
||||
type LogContext = {
|
||||
// Always present
|
||||
requestId: string
|
||||
userId?: string
|
||||
userRole?: string
|
||||
|
||||
// Competition context (when available)
|
||||
programId?: string
|
||||
competitionId?: string
|
||||
roundId?: string
|
||||
roundType?: RoundType
|
||||
|
||||
// Entity context (when relevant)
|
||||
juryGroupId?: string
|
||||
projectId?: string
|
||||
awardId?: string
|
||||
sessionId?: string // deliberation session
|
||||
|
||||
// Operation context
|
||||
operation: string // e.g., "assignment.run", "deliberation.submitVote"
|
||||
result: 'success' | 'failure' | 'skipped'
|
||||
details?: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
### Key Log Events
|
||||
|
||||
| Event | Level | When |
|
||||
|-------|-------|------|
|
||||
| `competition.created` | INFO | New competition created |
|
||||
| `round.transitioned` | INFO | Round status changed |
|
||||
| `assignment.completed` | INFO | Assignment algorithm finished |
|
||||
| `assignment.unassigned` | WARN | Projects remain unassigned |
|
||||
| `assignment.exception` | WARN | Over-cap assignment made |
|
||||
| `evaluation.submitted` | INFO | Judge submitted a score |
|
||||
| `deliberation.voteSubmitted` | INFO | Juror submitted deliberation vote |
|
||||
| `deliberation.tieDetected` | WARN | Tie detected in deliberation |
|
||||
| `deliberation.adminOverride` | WARN | Admin overrode deliberation result |
|
||||
| `resultLock.locked` | INFO | Result locked |
|
||||
| `resultLock.unlocked` | WARN | Result unlocked (should be rare) |
|
||||
| `eligibility.override` | WARN | Admin overrode AI eligibility decision |
|
||||
| `file.promoted` | INFO | Mentor file promoted to submission |
|
||||
| `liveVoting.windowClosed` | INFO | Live voting window closed |
|
||||
|
||||
---
|
||||
|
||||
## Alert Definitions
|
||||
|
||||
| Alert | Condition | Severity | Action |
|
||||
|-------|-----------|----------|--------|
|
||||
| **Unresolved manual queue** | `assignment_unassigned_queue_size > 0` for > 24h | WARNING | Notify admin to manually assign |
|
||||
| **Quorum mismatch** | Deliberation participants < required quorum | CRITICAL | Block voting, notify admin |
|
||||
| **Result lock failure** | ResultLock creation fails | CRITICAL | Retry, notify super-admin |
|
||||
| **Unlock by non-super-admin** | ResultUnlock attempted by non-super-admin | CRITICAL | Block, log security event |
|
||||
| **AI filtering timeout** | `ai_filtering_duration_seconds > 60` | WARNING | Check OpenAI API, retry |
|
||||
| **Evaluation completion low** | `evaluation_completion_rate < 50%` with < 24h remaining | WARNING | Send reminder emails |
|
||||
| **Live voting overload** | `live_voting_concurrent_users > 200` | WARNING | Monitor for performance degradation |
|
||||
| **Email delivery failure spike** | `email_send_failure_total` increases > 10 in 1h | WARNING | Check SMTP connection |
|
||||
| **API error rate spike** | `api_error_rate > 5%` for any router | CRITICAL | Investigate, potential rollback |
|
||||
|
||||
---
|
||||
|
||||
## Release Gates
|
||||
|
||||
Each release gate must be passed before the corresponding implementation phase can proceed to production.
|
||||
|
||||
### Gate A: Schema + Backfill Readiness
|
||||
|
||||
**When**: Before Phase 1 code reaches production
|
||||
|
||||
**Criteria**:
|
||||
- [ ] All Prisma migrations apply cleanly on staging database
|
||||
- [ ] Backfill script runs successfully on staging data
|
||||
- [ ] No existing table modified (new tables only)
|
||||
- [ ] Rollback tested: drop new tables, verify existing system works
|
||||
- [ ] Migration takes < 5 minutes on production-sized dataset
|
||||
|
||||
---
|
||||
|
||||
### Gate B: Policy Engine Correctness
|
||||
|
||||
**When**: Before Phase 2 code reaches production
|
||||
|
||||
**Criteria**:
|
||||
- [ ] 5-layer policy precedence returns correct values for all combinations
|
||||
- [ ] Cap mode behavior correct: HARD stops at cap, SOFT allows buffer, NONE unlimited
|
||||
- [ ] Category bias applied correctly in assignment
|
||||
- [ ] COI check prevents assignment to declared conflicts
|
||||
- [ ] Edge cases: null overrides, conflicting settings, zero cap
|
||||
- [ ] All policy unit tests pass (100% coverage on resolution logic)
|
||||
|
||||
---
|
||||
|
||||
### Gate C: End-to-End Monaco Flow Simulation
|
||||
|
||||
**When**: Before Phase 5 code reaches production
|
||||
|
||||
**Criteria**:
|
||||
- [ ] Full 8-round flow completes on staging with realistic data
|
||||
- [ ] All round transitions work correctly
|
||||
- [ ] Multi-round document visibility correct for all roles
|
||||
- [ ] AI shortlist generated at end of evaluation rounds
|
||||
- [ ] Assignment algorithm handles 500+ projects across 30 judges
|
||||
- [ ] Result: finalists selected, no data inconsistencies
|
||||
|
||||
---
|
||||
|
||||
### Gate D: Invite/Onboarding Stability Under Load
|
||||
|
||||
**When**: Before Phase 3 code reaches production
|
||||
|
||||
**Criteria**:
|
||||
- [ ] 50 concurrent invite acceptances process correctly
|
||||
- [ ] JuryGroupMember records created for all accepted invites
|
||||
- [ ] Onboarding self-service values saved correctly
|
||||
- [ ] AssignmentIntent records created for pre-assigned judges
|
||||
- [ ] No race conditions in concurrent onboarding
|
||||
|
||||
---
|
||||
|
||||
### Gate E: Live Finals + Deliberation Integrity
|
||||
|
||||
**When**: Before Phase 6 code reaches production
|
||||
|
||||
**Criteria**:
|
||||
- [ ] Stage manager cursor advances correctly for all Jury 3 members
|
||||
- [ ] Live voting window enforcement (no votes outside window)
|
||||
- [ ] 50 concurrent audience votes processed without conflicts
|
||||
- [ ] Deliberation SINGLE_WINNER_VOTE tallying correct
|
||||
- [ ] Deliberation FULL_RANKING Borda count correct
|
||||
- [ ] Tie-breaking: RUNOFF creates new vote round
|
||||
- [ ] Tie-breaking: ADMIN_DECIDES records decision
|
||||
- [ ] ResultLock creates immutable snapshot
|
||||
- [ ] ResultUnlock blocked for non-super-admin
|
||||
- [ ] Audience vote totals correctly shown to Jury 3
|
||||
|
||||
---
|
||||
|
||||
### Gate F: Operational Readiness
|
||||
|
||||
**When**: Before Phase 8 (cutover) begins
|
||||
|
||||
**Criteria**:
|
||||
- [ ] All metrics listed above are being collected and visible in dashboard
|
||||
- [ ] All alerts listed above are configured and tested
|
||||
- [ ] Structured logging includes all correlation IDs
|
||||
- [ ] Runbook exists for common operational scenarios
|
||||
- [ ] On-call team briefed on new system components
|
||||
- [ ] Backup and restore tested for new tables
|
||||
- [ ] Performance baseline established (API latency, DB query times)
|
||||
|
||||
---
|
||||
|
||||
## Audit Event Metrics
|
||||
|
||||
Track audit completeness and integrity across the redesigned system:
|
||||
|
||||
| Metric | Target | Alert Threshold |
|
||||
|--------|--------|-----------------|
|
||||
| Audit coverage (admin overrides with audit record) | 100% | Any override without audit record |
|
||||
| Before/after state completeness | 100% of override actions | Missing `before` or `after` in details JSON |
|
||||
| Assignment intent fulfillment rate | > 80% | < 50% intents HONORED per round |
|
||||
| Result lock integrity | 0 unauthorized unlocks | Any unlock without super-admin role |
|
||||
| File replacement provenance chain | 100% linked | Any replacement without `replacedById` |
|
||||
|
||||
**Audit Correlation Tracking:**
|
||||
- Every admin override must include `correlationId` linking the audit entry to the triggering action (e.g., which assignment algorithm run triggered an intent override)
|
||||
- Round transition events must correlate with the `DecisionAuditLog` entries for that round
|
||||
- Result lock/unlock events must reference the `DeliberationSession.id` they apply to
|
||||
|
||||
---
|
||||
|
||||
## Sign-Off Criteria
|
||||
|
||||
Final sign-off requires:
|
||||
|
||||
1. **All 6 release gates passed** (A through F)
|
||||
2. **72-hour burn-in period** with zero critical errors
|
||||
3. **All test matrices passed** (see [11-testing-and-qa.md](./11-testing-and-qa.md))
|
||||
4. **Documentation updated** (CLAUDE.md, API docs, admin guide)
|
||||
5. **Architecture owner sign-off** on schema finality
|
||||
6. **Product owner sign-off** on feature completeness
|
||||
@@ -0,0 +1,360 @@
|
||||
# Open Questions & Governance
|
||||
|
||||
## Overview
|
||||
|
||||
This document tracks all design decisions — resolved and remaining — and defines the governance process for the redesign. Resolved decisions are numbered for reference. Remaining questions are prioritized (P1 = must resolve before implementation, P2 = can resolve during implementation).
|
||||
|
||||
---
|
||||
|
||||
## Resolved Decisions
|
||||
|
||||
| # | Decision | Resolution | Resolved By |
|
||||
|---|----------|------------|-------------|
|
||||
| 1 | **Winner count per category** | Top N (configurable, default 3). All projects ranked within category. Podium UI for top 3. Cross-category comparison view. | User Q&A |
|
||||
| 2 | **Score model for final winners** | Jury 3 live scores only for final winner determination. All-jury composite rankings available in reports/analytics. Configurable between "Jury 3 only" and "Jury 3 + audience blend" per competition. | User Q&A |
|
||||
| 3 | **Cross-jury visibility** | Fully independent during active evaluation — no cross-jury score visibility. During Live Finals (R7) and Deliberation (R8), prior jury scores/feedback/notes visible to Jury 3 **if admin enables** (`showPriorJuryData` toggle). All cross-jury data available in reports section for internal analytics. | User Q&A |
|
||||
| 4 | **Confirmation model** | Deliberation IS the confirmation — no separate WinnerProposal → jury sign-off → admin approval. Deliberation voting serves as jury agreement. Replaces "all jury agree + admin approval" from original flow spec. | User Q&A |
|
||||
| 5 | **Deliberation modes** | Two admin-configurable modes: SINGLE_WINNER_VOTE (each juror picks one winner) and FULL_RANKING (ordinal 1st, 2nd, 3rd... aggregated via Borda count). | User Q&A |
|
||||
| 6 | **Deliberation scope** | Separate per category (Startup and Concept deliberations are independent sessions). No deliberation for special awards — awards decided by their own jury/judge mechanism. | User Q&A |
|
||||
| 7 | **Tie-breaking** | Multiple methods supported: runoff vote (new vote with tied projects only), admin tie-break (admin decides between tied), admin override (override entire result). All configurable per deliberation session. | User Q&A |
|
||||
| 8 | **Post-deliberation flow** | Admin reviews final deliberation result → locks → ResultLock snapshot created. Unlock requires super-admin with mandatory reason. | User Q&A |
|
||||
| 9 | **Soft-cap buffer** | Default +10 over the soft cap. Configurable per JuryGroup via `softCapBuffer` field. | User Q&A |
|
||||
| 10 | **Startup/concept ratio** | Suggestive bias only (not deterministic). Disclosed to judges. Judges see a note like "You have been assigned primarily Startup projects based on your preference." | User Q&A |
|
||||
| 11 | **Mode A pull-out** | Admin-confirmed (`routingConfirmationMode: ADMIN_CONFIRMED`). Admin must review and approve which projects are pulled from the main pool into the award pool. | User Q&A |
|
||||
| 12 | **File promotion authority** | Team lead and admin can promote mentor workspace files to official submissions. Mentor can promote IF admin enables it per competition. | User Q&A |
|
||||
| 13 | **Invite pre-assignment** | Both modes supported: AssignmentIntent (intent at invite time, honored by algorithm) and direct admin assignment. | User Q&A |
|
||||
| 14 | **Jury absence handling** | Quorum fallback with participant status types: REQUIRED, ABSENT_EXCUSED, REPLACED, REPLACEMENT_ACTIVE. Absent-excused don't count toward quorum. Admin can replace juror or mark as excused. | User Q&A |
|
||||
| 15 | **Result unlock** | Super-admin only, mandatory reason. Creates ResultUnlockEvent with audit trail. | User Q&A |
|
||||
| 16 | **Jury naming** | Custom labels per program. JuryGroup has a `label` field (e.g., "Jury 1", "Selection Panel", "Live Finals Jury"). | User Q&A |
|
||||
| 17 | **Judge onboarding self-service** | Judges CAN adjust their cap and category ratio preference during onboarding. Admin-configurable toggle (`allowOnboardingSelfService`) to enable/disable per JuryGroup. | Flow spec + Q&A |
|
||||
| 18 | **AI ranked shortlist** | AI generates recommended ranked shortlist per category at the end of EVERY evaluation round (Jury 1, Jury 2, and any award evaluation). Admin can always override. | Flow spec + Q&A |
|
||||
| 19 | **Audience vote totals** | Shown to Jury 3 during deliberation phase. Configurable reveal timing (real-time, after jury vote, at deliberation). | Flow spec + Q&A |
|
||||
| 20 | **Assignment intent lifecycle** | Full lifecycle tracking: PENDING → HONORED (algorithm matched) / OVERRIDDEN (admin changed) / EXPIRED (round closed) / CANCELLED (removed). All terminal states immutable. See [04-jury-groups-and-assignment-policy.md](./04-jury-groups-and-assignment-policy.md). | Gap analysis |
|
||||
| 21 | **Submission bundle state** | **REJECTED** — Per-file tracking with SubmissionWindow enforcement is simpler than a formal SubmissionBundle entity. Completeness is derived from slot requirements vs. uploaded files. See Rejected Alternatives below. | Gap analysis |
|
||||
| 22 | **Purpose keys for analytics** | Optional `Round.purposeKey: String?` for analytics grouping (e.g., "jury1_selection", "semifinal_docs"). NOT a new enum — purely semantic. RoundType + label is sufficient for routing; purposeKey is for cross-competition reporting only. | Gap analysis |
|
||||
| 23 | **Enhanced audit with before/after state** | `DecisionAuditLog.details` JSON field already supports before/after state. Convention: include `{ before: {...}, after: {...} }` structure for all override actions. No new DB fields needed — documented as convention. | Gap analysis |
|
||||
|
||||
---
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
Design concepts from the Codex architecture plan that were evaluated and rejected in favor of simpler approaches:
|
||||
|
||||
| # | Concept | Source | Rejection Rationale |
|
||||
|---|---------|--------|-------------------|
|
||||
| R1 | **SubmissionBundle entity** | Codex docs | Added complexity without benefit. Per-file tracking with `SubmissionWindow` + slot requirements achieves the same completeness tracking. Deriving bundle state from `required slots - uploaded files` is simpler than maintaining a separate state machine. |
|
||||
| R2 | **FinalConfirmation as separate step** | Codex docs | Merged into Deliberation per Decision #4. Deliberation voting IS the confirmation — a separate WinnerProposal → jury sign-off → admin approval flow added unnecessary ceremony. Admin reviews and locks the deliberation result directly. |
|
||||
| R3 | **Purpose Keys as enum** | Codex docs | Made optional `String?` instead of enum (Decision #22). A fixed enum would require schema migration for each new analytics category. Free-text purposeKey with conventional values is more flexible. |
|
||||
| R4 | **Jury-Stage binding** | Codex docs | JuryGroups are independent entities linked to rounds via `roundId`, not bound to stages. This allows a jury to be reused across competitions and rounds without schema changes. |
|
||||
|
||||
---
|
||||
|
||||
## Ambiguity Log
|
||||
|
||||
Design decisions where both approaches were viable. Documenting the trade-off for future reference:
|
||||
|
||||
| # | Ambiguity | Decision | Trade-Off |
|
||||
|---|-----------|----------|-----------|
|
||||
| A1 | **Cap enforcement: strict vs. flexible** | 3-mode system (HARD/SOFT/NONE) | More complex than binary on/off, but covers all real-world scenarios. SOFT mode with buffer is the common case. |
|
||||
| A2 | **Cross-jury data visibility: always vs. configurable** | Configurable per round via `showPriorJuryData` | Default OFF prevents bias, but some programs want continuity. Toggle gives admin control. |
|
||||
| A3 | **Deliberation mode: single vs. dual** | Both modes supported (SINGLE_WINNER_VOTE / FULL_RANKING) | Two code paths to maintain, but programs have genuinely different needs. Borda count for detailed ranking, simple vote for quick decisions. |
|
||||
| A4 | **Document mutability: immutable vs. admin-replaceable** | Admin can replace with full provenance tracking | Trades simplicity for flexibility. Provenance chain (`replacedById`, audit log) ensures accountability. |
|
||||
|
||||
---
|
||||
|
||||
## Remaining P1 Questions (Before UI/Router Refactor)
|
||||
|
||||
These must be resolved before starting Phase 5 (Admin Control Plane + Participant UX):
|
||||
|
||||
### P1-1: Mentoring Scope
|
||||
|
||||
**Question**: Is mentoring available only to finalists, or configurable to include semi-finalists?
|
||||
|
||||
**Options**:
|
||||
- A) Finalists only (simplest, matches current flow)
|
||||
- B) Configurable via `MentoringConfig.eligibility` enum: FINALISTS_ONLY | SEMIFINALISTS_AND_ABOVE | CONFIGURABLE
|
||||
|
||||
**Impact**: Affects who sees the "Request Mentor" toggle and when mentor assignment runs.
|
||||
|
||||
**Current leaning**: The `MentoringConfig` already has an `eligibility` field with options. Implement B for flexibility.
|
||||
|
||||
---
|
||||
|
||||
### P1-2: Document Mutability After Lock
|
||||
|
||||
**Question**: Can admins replace locked (read-only) submissions from prior rounds?
|
||||
|
||||
**Options**:
|
||||
- A) No — once a round's docs are locked, they're immutable for everyone
|
||||
- B) Yes — admin can replace with full provenance (`sourceType: ADMIN_REPLACEMENT`)
|
||||
|
||||
**Impact**: Affects document lifecycle rules and audit requirements.
|
||||
|
||||
**Current leaning**: B — admin override everywhere is a guiding principle. Provenance tracking ensures accountability.
|
||||
|
||||
---
|
||||
|
||||
### P1-3: Audience Reveal Timing
|
||||
|
||||
**Question**: Are audience vote totals visible to Jury 3 in real time during the live event, or only when deliberation begins?
|
||||
|
||||
**Options**:
|
||||
- A) Real-time (jury sees audience votes as they come in)
|
||||
- B) After jury vote (jury submits their scores first, then audience results revealed)
|
||||
- C) At deliberation start (audience results shown when deliberation session opens)
|
||||
- D) Configurable via `LiveFinalConfig.audienceRevealTiming`
|
||||
|
||||
**Impact**: Affects whether audience votes can bias Jury 3 scoring.
|
||||
|
||||
**Current leaning**: D — make it configurable. Default to C (at deliberation start) to prevent audience bias on jury scores.
|
||||
|
||||
---
|
||||
|
||||
## Remaining P2 Questions (Can Resolve During Implementation)
|
||||
|
||||
### P2-1: Reporting Visibility
|
||||
|
||||
**Question**: Are per-jury compliance reports (assignment coverage, evaluation completion, etc.) visible to all program admins or super-admin only?
|
||||
|
||||
**Impact**: Affects report page access control.
|
||||
|
||||
---
|
||||
|
||||
### P2-2: Override Transparency
|
||||
|
||||
**Question**: Which override details are visible to jury users? For example, if admin overrides a judge's assignment, does the judge see "Admin assigned this project to you" or just the project appearing?
|
||||
|
||||
**Impact**: Affects jury dashboard messaging.
|
||||
|
||||
---
|
||||
|
||||
### P2-3: Notification Triggers
|
||||
|
||||
**Question**: What exact round transitions trigger applicant email notifications? (e.g., "You've advanced to the semi-finals", "Your submission window is open", "You've been selected as a finalist")
|
||||
|
||||
**Impact**: Affects notification service configuration.
|
||||
|
||||
---
|
||||
|
||||
### P2-4: CSV Bulk Invite
|
||||
|
||||
**Question**: What columns should the CSV bulk invite format include? Current thinking: name, email, role, juryGroupLabel, roundLabel, preAssignmentMode (INTENT | DIRECT), maxProjects, categoryBias.
|
||||
|
||||
**Impact**: Affects invite import service.
|
||||
|
||||
---
|
||||
|
||||
## Delivery Governance
|
||||
|
||||
### Weekly Architecture Sync
|
||||
|
||||
- **Who**: Backend lead, frontend lead, product owner, ops
|
||||
- **When**: Weekly, 30 minutes
|
||||
- **Agenda**: Phase progress, blockers, upcoming decisions, contract drift review
|
||||
|
||||
### Phase Gate Reviews
|
||||
|
||||
Each phase completion triggers a review:
|
||||
1. Deliverables checklist reviewed
|
||||
2. Release gate criteria verified (see [12-observability-and-release-gates.md](./12-observability-and-release-gates.md))
|
||||
3. Test results reviewed
|
||||
4. Any outstanding P1 questions flagged
|
||||
5. Sign-off from architecture owner and product owner
|
||||
|
||||
### No Silent Contract Drift
|
||||
|
||||
After Phase 0 (Contract Freeze), any change to the following requires explicit review:
|
||||
|
||||
| Change Type | Approval Required |
|
||||
|-------------|-------------------|
|
||||
| Prisma model addition or field change | Architecture owner |
|
||||
| Zod config schema modification | Architecture owner |
|
||||
| RoundType enum change | Architecture owner + product owner |
|
||||
| tRPC procedure signature change | Architecture owner |
|
||||
| Assignment policy behavior change | Architecture owner + product owner |
|
||||
| New feature flag addition | Architecture owner |
|
||||
|
||||
Changes are NOT blocked — they require documentation (what changed, why, impact) and sign-off.
|
||||
|
||||
### Evidence Package Template
|
||||
|
||||
Each phase gate review includes:
|
||||
|
||||
```
|
||||
Phase [X] Evidence Package
|
||||
- Date: YYYY-MM-DD
|
||||
- Phase: [name]
|
||||
- Status: PASS / CONDITIONAL PASS / FAIL
|
||||
|
||||
Deliverables:
|
||||
- [ ] [deliverable 1] — status, link to PR/commit
|
||||
- [ ] [deliverable 2] — status
|
||||
|
||||
Test Results:
|
||||
- Unit: X/X passing
|
||||
- Integration: X/X passing
|
||||
- E2E: X/X passing
|
||||
|
||||
Release Gate: [A/B/C/D/E/F]
|
||||
- [ ] Criterion 1 — status
|
||||
- [ ] Criterion 2 — status
|
||||
|
||||
Open Items:
|
||||
- [any blockers or deferred items]
|
||||
|
||||
Sign-off:
|
||||
- Architecture: [name] [date]
|
||||
- Product: [name] [date]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference Monaco 2026 Configuration
|
||||
|
||||
A concrete example showing how the Monaco 2026 competition would be configured in the redesigned system:
|
||||
|
||||
```yaml
|
||||
competition:
|
||||
name: "Monaco Ocean Protection Challenge 2026"
|
||||
programId: "monaco-opc-2026"
|
||||
status: DRAFT
|
||||
|
||||
rounds:
|
||||
- order: 1
|
||||
label: "Application Window"
|
||||
type: INTAKE
|
||||
config:
|
||||
deadlinePolicy: FLAG # accept late with flag
|
||||
requiredDocSlots: ["executive_summary", "business_plan", "team_profile"]
|
||||
|
||||
- order: 2
|
||||
label: "AI Eligibility Screening"
|
||||
type: FILTERING
|
||||
config:
|
||||
aiEnabled: true
|
||||
autoAdvanceEligible: false # admin reviews before advancing
|
||||
|
||||
- order: 3
|
||||
label: "Jury 1 — Semi-Finalist Selection"
|
||||
type: EVALUATION
|
||||
juryGroupId: jury-1
|
||||
config:
|
||||
scoringRubric: { criteria: [...], maxScore: 100 }
|
||||
requireFeedback: true
|
||||
generateAiShortlist: true
|
||||
|
||||
- order: 4
|
||||
label: "Semi-Finalist Documents"
|
||||
type: SUBMISSION
|
||||
config:
|
||||
requiredDocSlots: ["updated_business_plan", "financial_projections", "impact_report"]
|
||||
lockPriorRoundDocs: true
|
||||
deadlinePolicy: HARD
|
||||
|
||||
- order: 5
|
||||
label: "Jury 2 — Finalist Selection"
|
||||
type: EVALUATION
|
||||
juryGroupId: jury-2
|
||||
config:
|
||||
scoringRubric: { criteria: [...], maxScore: 100 }
|
||||
requireFeedback: true
|
||||
generateAiShortlist: true
|
||||
showPriorJuryData: false # independent evaluation
|
||||
|
||||
- order: 6
|
||||
label: "Finalist Mentoring"
|
||||
type: MENTORING
|
||||
config:
|
||||
eligibility: FINALISTS_ONLY
|
||||
requireMentorRequest: true
|
||||
assignmentMethod: MANUAL
|
||||
allowMentorPromotion: false
|
||||
|
||||
- order: 7
|
||||
label: "Live Finals — Jury 3"
|
||||
type: LIVE_FINAL
|
||||
juryGroupId: jury-3
|
||||
config:
|
||||
audienceVotingEnabled: true
|
||||
audienceRevealTiming: AT_DELIBERATION
|
||||
audienceBlendWeight: 0 # jury only for scoring
|
||||
showPriorJuryData: true # Jury 3 sees prior jury history
|
||||
scoringMode: CRITERIA_BASED
|
||||
presentationOrder: MANUAL
|
||||
|
||||
- order: 8
|
||||
label: "Final Deliberation"
|
||||
type: DELIBERATION
|
||||
juryGroupId: jury-3
|
||||
config:
|
||||
mode: FULL_RANKING
|
||||
showCollectiveRankings: true
|
||||
tieBreakMethod: ADMIN_DECIDES
|
||||
topN: 3
|
||||
allowAdminOverride: true
|
||||
|
||||
juryGroups:
|
||||
- label: "Jury 1"
|
||||
defaultCapMode: SOFT
|
||||
defaultMaxProjects: 15
|
||||
softCapBuffer: 10
|
||||
allowOnboardingSelfService: true
|
||||
defaultCategoryBias: { STARTUP: 0.5, BUSINESS_CONCEPT: 0.5 }
|
||||
|
||||
- label: "Jury 2"
|
||||
defaultCapMode: SOFT
|
||||
defaultMaxProjects: 10
|
||||
softCapBuffer: 10
|
||||
allowOnboardingSelfService: true
|
||||
|
||||
- label: "Jury 3 — Live Finals"
|
||||
defaultCapMode: NONE # all finalists reviewed
|
||||
allowOnboardingSelfService: false
|
||||
|
||||
specialAwards:
|
||||
- name: "Innovation Award"
|
||||
routingMode: STAY_IN_MAIN
|
||||
eligibilityMode: AI_SUGGESTED
|
||||
winnerDecisionMode: JURY_VOTE
|
||||
juryGroupLabel: "Innovation Award Panel"
|
||||
|
||||
- name: "Ocean Impact Award"
|
||||
routingMode: SEPARATE_POOL
|
||||
pullOutBehavior: KEEP_IN_BOTH
|
||||
routingConfirmationMode: ADMIN_CONFIRMED
|
||||
eligibilityMode: AI_SUGGESTED
|
||||
winnerDecisionMode: SINGLE_JUDGE
|
||||
singleJudgeLabel: "Impact Award Chair"
|
||||
|
||||
submissionWindows:
|
||||
- label: "Round 1 Application Documents"
|
||||
roundOrder: 1
|
||||
requirements:
|
||||
- slotKey: "executive_summary"
|
||||
label: "Executive Summary"
|
||||
required: true
|
||||
acceptedTypes: ["application/pdf"]
|
||||
- slotKey: "business_plan"
|
||||
label: "Business Plan"
|
||||
required: true
|
||||
acceptedTypes: ["application/pdf"]
|
||||
- slotKey: "team_profile"
|
||||
label: "Team Profile"
|
||||
required: true
|
||||
acceptedTypes: ["application/pdf"]
|
||||
|
||||
- label: "Round 2 Semi-Finalist Documents"
|
||||
roundOrder: 4
|
||||
requirements:
|
||||
- slotKey: "updated_business_plan"
|
||||
label: "Updated Business Plan"
|
||||
required: true
|
||||
- slotKey: "financial_projections"
|
||||
label: "Financial Projections"
|
||||
required: true
|
||||
- slotKey: "impact_report"
|
||||
label: "Environmental Impact Report"
|
||||
required: true
|
||||
```
|
||||
|
||||
This configuration, when loaded into the system, would create the full Monaco 2026 competition with all 8 rounds, 3 main juries, 2 special awards, and 2 submission windows — ready for admin to set dates and invite jury members.
|
||||
Reference in New Issue
Block a user