Competition/Round architecture: full platform rewrite (Phases 1-9)
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:
parent
9ab4717f96
commit
6ca39c976b
507
CLAUDE.md
507
CLAUDE.md
|
|
@ -1,388 +1,171 @@
|
||||||
# MOPC Platform - Claude Code Context
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**MOPC (Monaco Ocean Protection Challenge)** is a secure jury online voting platform for managing project selection rounds. The platform enables jury members to evaluate submitted ocean conservation projects, with Phase 1 supporting two selection rounds:
|
**MOPC (Monaco Ocean Protection Challenge)** — a secure jury voting platform for managing multi-round project selection. Jury members evaluate ocean conservation projects through configurable competitions with rounds (intake, filtering, evaluation, submission, mentoring, live finals, deliberation). Domain: `monaco-opc.com`.
|
||||||
|
|
||||||
- **Round 1**: ~130 projects → ~60 semi-finalists
|
|
||||||
- **Round 2**: ~60 projects → 6 finalists
|
|
||||||
|
|
||||||
**Domain**: `monaco-opc.com`
|
|
||||||
|
|
||||||
The platform is designed for future expansion into a comprehensive program management system including learning hub, communication workflows, and partner modules.
|
|
||||||
|
|
||||||
## Key Decisions
|
|
||||||
|
|
||||||
| Decision | Choice |
|
|
||||||
|----------|--------|
|
|
||||||
| Evaluation Criteria | Fully configurable per round (admin defines) |
|
|
||||||
| CSV Import | Flexible column mapping (admin maps columns) |
|
|
||||||
| Max File Size | 500MB (for videos) |
|
|
||||||
| Observer Role | Included in Phase 1 |
|
|
||||||
| First Admin | Database seed script |
|
|
||||||
| Past Evaluations | Visible read-only after submit |
|
|
||||||
| Grace Period | Admin-configurable per juror/project |
|
|
||||||
| Smart Assignment | AI-powered (GPT) + Smart Algorithm fallback + geo-diversity, familiarity, COI scoring |
|
|
||||||
| AI Data Privacy | All data anonymized before sending to GPT |
|
|
||||||
| Evaluation Criteria Types | `numeric`, `text`, `boolean`, `section_header` (backward-compatible) |
|
|
||||||
| COI Workflow | Mandatory declaration before evaluation, admin review |
|
|
||||||
| Evaluation Reminders | Cron-based email reminders with countdown urgency |
|
|
||||||
|
|
||||||
## Brand Identity
|
|
||||||
|
|
||||||
| Name | Hex | Usage |
|
|
||||||
|------|-----|-------|
|
|
||||||
| Primary Red | `#de0f1e` | CTAs, alerts |
|
|
||||||
| Dark Blue | `#053d57` | Headers, sidebar |
|
|
||||||
| White | `#fefefe` | Backgrounds |
|
|
||||||
| Teal | `#557f8c` | Links, secondary |
|
|
||||||
|
|
||||||
**Typography**: Montserrat (600/700 for headings, 300/400 for body)
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
| Layer | Technology | Version |
|
|
||||||
|-------|-----------|---------|
|
|
||||||
| **Framework** | Next.js (App Router) | 15.x |
|
|
||||||
| **Language** | TypeScript | 5.x |
|
|
||||||
| **UI Components** | shadcn/ui | latest |
|
|
||||||
| **Styling** | Tailwind CSS | 3.x |
|
|
||||||
| **API Layer** | tRPC | 11.x |
|
|
||||||
| **Database** | PostgreSQL | 16.x |
|
|
||||||
| **ORM** | Prisma | 6.x |
|
|
||||||
| **Authentication** | NextAuth.js (Auth.js) | 5.x |
|
|
||||||
| **AI** | OpenAI GPT | 4.x SDK |
|
|
||||||
| **Animation** | Motion (Framer Motion) | 11.x |
|
|
||||||
| **Notifications** | Sonner | 1.x |
|
|
||||||
| **Command Palette** | cmdk | 1.x |
|
|
||||||
| **File Storage** | MinIO (S3-compatible) | External |
|
|
||||||
| **Email** | Nodemailer + Poste.io | External |
|
|
||||||
| **Containerization** | Docker Compose | 2.x |
|
|
||||||
| **Reverse Proxy** | Nginx | External |
|
|
||||||
|
|
||||||
## Architecture Principles
|
|
||||||
|
|
||||||
1. **Type Safety First**: End-to-end TypeScript from database to UI via Prisma → tRPC → React
|
|
||||||
2. **Mobile-First Responsive**: All components designed for mobile, enhanced for desktop
|
|
||||||
3. **Full Control**: No black-box services; every component is understood and maintainable
|
|
||||||
4. **Extensible Data Model**: JSON fields for future attributes without schema migrations
|
|
||||||
5. **Security by Default**: RBAC, audit logging, secure file access with pre-signed URLs
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
mopc-platform/
|
|
||||||
├── CLAUDE.md # This file - project context
|
|
||||||
├── docs/
|
|
||||||
│ └── architecture/ # Architecture documentation
|
|
||||||
│ ├── README.md # System overview
|
|
||||||
│ ├── database.md # Database design
|
|
||||||
│ ├── api.md # API design
|
|
||||||
│ ├── infrastructure.md # Deployment docs
|
|
||||||
│ └── ui.md # UI/UX patterns
|
|
||||||
├── src/
|
|
||||||
│ ├── app/ # Next.js App Router pages
|
|
||||||
│ │ ├── (auth)/ # Public auth routes (login, verify)
|
|
||||||
│ │ ├── (admin)/ # Admin dashboard (protected)
|
|
||||||
│ │ ├── (jury)/ # Jury interface (protected)
|
|
||||||
│ │ ├── api/ # API routes
|
|
||||||
│ │ │ ├── trpc/ # tRPC endpoint
|
|
||||||
│ │ │ └── cron/
|
|
||||||
│ │ │ └── reminders/ # Cron endpoint for evaluation reminders (F4)
|
|
||||||
│ │ ├── layout.tsx # Root layout
|
|
||||||
│ │ └── page.tsx # Home/landing
|
|
||||||
│ ├── components/
|
|
||||||
│ │ ├── ui/ # shadcn/ui components
|
|
||||||
│ │ ├── admin/ # Admin-specific components
|
|
||||||
│ │ │ └── evaluation-summary-card.tsx # AI summary display
|
|
||||||
│ │ ├── forms/ # Form components
|
|
||||||
│ │ │ ├── evaluation-form.tsx # With progress indicator (F1)
|
|
||||||
│ │ │ ├── coi-declaration-dialog.tsx # COI blocking dialog (F5)
|
|
||||||
│ │ │ └── evaluation-form-with-coi.tsx # COI-gated wrapper (F5)
|
|
||||||
│ │ ├── layouts/ # Layout components (sidebar, nav)
|
|
||||||
│ │ └── shared/ # Shared components
|
|
||||||
│ │ └── countdown-timer.tsx # Live countdown with urgency (F4)
|
|
||||||
│ ├── lib/
|
|
||||||
│ │ ├── auth.ts # NextAuth configuration
|
|
||||||
│ │ ├── prisma.ts # Prisma client singleton
|
|
||||||
│ │ ├── trpc/ # tRPC client & server setup
|
|
||||||
│ │ ├── minio.ts # MinIO client
|
|
||||||
│ │ └── email.ts # Email utilities
|
|
||||||
│ ├── server/
|
|
||||||
│ │ ├── routers/ # tRPC routers by domain
|
|
||||||
│ │ │ ├── program.ts
|
|
||||||
│ │ │ ├── round.ts
|
|
||||||
│ │ │ ├── project.ts
|
|
||||||
│ │ │ ├── user.ts
|
|
||||||
│ │ │ ├── assignment.ts
|
|
||||||
│ │ │ ├── evaluation.ts
|
|
||||||
│ │ │ ├── audit.ts
|
|
||||||
│ │ │ ├── settings.ts
|
|
||||||
│ │ │ ├── gracePeriod.ts
|
|
||||||
│ │ │ ├── export.ts # CSV export incl. filtering results (F2)
|
|
||||||
│ │ │ ├── analytics.ts # Reports/analytics (observer access, F3)
|
|
||||||
│ │ │ └── mentor.ts # Mentor messaging endpoints (F10)
|
|
||||||
│ │ ├── services/ # Business logic services
|
|
||||||
│ │ │ ├── smart-assignment.ts # With geo/familiarity/COI scoring (F8)
|
|
||||||
│ │ │ ├── evaluation-reminders.ts # Email reminder service (F4)
|
|
||||||
│ │ │ └── ai-evaluation-summary.ts # GPT summary generation (F7)
|
|
||||||
│ │ └── middleware/ # RBAC & auth middleware
|
|
||||||
│ ├── hooks/ # React hooks
|
|
||||||
│ ├── types/ # Shared TypeScript types
|
|
||||||
│ └── utils/ # Utility functions
|
|
||||||
├── prisma/
|
|
||||||
│ ├── schema.prisma # Database schema
|
|
||||||
│ ├── migrations/ # Migration files
|
|
||||||
│ └── seed.ts # Seed data
|
|
||||||
├── public/ # Static assets
|
|
||||||
├── docker/
|
|
||||||
│ ├── Dockerfile # Production build
|
|
||||||
│ ├── docker-compose.yml # Production stack
|
|
||||||
│ └── docker-compose.dev.yml # Development stack
|
|
||||||
├── tests/
|
|
||||||
│ ├── unit/ # Unit tests
|
|
||||||
│ └── e2e/ # End-to-end tests
|
|
||||||
└── config files... # package.json, tsconfig, etc.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Coding Standards
|
|
||||||
|
|
||||||
### TypeScript
|
|
||||||
- Strict mode enabled
|
|
||||||
- Explicit return types for functions
|
|
||||||
- Use `type` over `interface` for consistency (unless extending)
|
|
||||||
- Prefer `unknown` over `any`
|
|
||||||
|
|
||||||
### React/Next.js
|
|
||||||
- Use Server Components by default
|
|
||||||
- `'use client'` only when needed (interactivity, hooks)
|
|
||||||
- Collocate components with their routes when specific to that route
|
|
||||||
- Use React Query (via tRPC) for server state
|
|
||||||
|
|
||||||
### Naming Conventions
|
|
||||||
- **Files**: kebab-case (`user-profile.tsx`)
|
|
||||||
- **Components**: PascalCase (`UserProfile`)
|
|
||||||
- **Functions/Variables**: camelCase (`getUserById`)
|
|
||||||
- **Constants**: SCREAMING_SNAKE_CASE (`MAX_FILE_SIZE`)
|
|
||||||
- **Database Tables**: PascalCase in Prisma (`User`, `Project`)
|
|
||||||
- **Database Columns**: camelCase in Prisma (`createdAt`)
|
|
||||||
|
|
||||||
### Styling
|
|
||||||
- Tailwind CSS utility classes
|
|
||||||
- Mobile-first: base styles for mobile, `md:` for tablet, `lg:` for desktop
|
|
||||||
- Use shadcn/ui components as base, customize via CSS variables
|
|
||||||
- No inline styles; no separate CSS files unless absolutely necessary
|
|
||||||
|
|
||||||
### API Design (tRPC)
|
|
||||||
- Group by domain: `trpc.program.create()`, `trpc.round.list()`
|
|
||||||
- Use Zod for input validation
|
|
||||||
- Return consistent response shapes
|
|
||||||
- Throw `TRPCError` with appropriate codes
|
|
||||||
|
|
||||||
## Common Commands
|
## Common Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Development
|
# Development
|
||||||
npm run dev # Start Next.js dev server
|
npm run dev # Next.js dev (Turbopack)
|
||||||
npm run db:studio # Open Prisma Studio
|
npm run build # Production build (always test before push)
|
||||||
npm run db:push # Push schema changes (dev only)
|
npm run typecheck # tsc --noEmit
|
||||||
npm run db:migrate # Run migrations
|
|
||||||
npm run db:seed # Seed database
|
|
||||||
|
|
||||||
# Testing
|
# Database
|
||||||
npm run test # Run unit tests
|
npx prisma generate # Regenerate client after schema changes
|
||||||
npm run test:e2e # Run E2E tests
|
npx prisma migrate dev # Create + apply migration
|
||||||
npm run test:coverage # Test with coverage
|
npx prisma studio # GUI database browser
|
||||||
|
npm run db:seed # Run seed (tsx prisma/seed.ts)
|
||||||
|
|
||||||
# Build & Deploy
|
# Testing (vitest, not jest)
|
||||||
npm run build # Production build
|
npx vitest # Run all tests (watch mode)
|
||||||
npm run start # Start production server
|
npx vitest run # Run all tests once
|
||||||
docker compose up -d # Start Docker stack
|
npx vitest run tests/unit/round-engine.test.ts # Single file
|
||||||
docker compose logs -f app # View app logs
|
npx vitest run -t 'test name' # Single test by name
|
||||||
|
|
||||||
# Code Quality
|
# Code Quality
|
||||||
npm run lint # ESLint
|
npm run lint # ESLint
|
||||||
npm run format # Prettier
|
npm run format # Prettier
|
||||||
npm run typecheck # TypeScript check
|
|
||||||
|
# Docker (production)
|
||||||
|
docker compose -f docker/docker-compose.yml up -d
|
||||||
|
docker compose -f docker/docker-compose.dev.yml up -d # Dev stack
|
||||||
```
|
```
|
||||||
|
|
||||||
## Windows Development Notes
|
## Tech Stack
|
||||||
|
|
||||||
**IMPORTANT**: On Windows, all Docker commands AND all npm/node commands must be run using PowerShell (`powershell -ExecutionPolicy Bypass -Command "..."`), not bash/cmd. This is required for proper Docker Desktop integration and Node.js execution.
|
Next.js 15 (App Router) | TypeScript 5 (strict) | Tailwind CSS 4 | shadcn/ui | tRPC 11 (superjson) | Prisma 6 | PostgreSQL 16 | NextAuth 5 (Auth.js) | Vitest 4 | OpenAI SDK 6 | MinIO (S3) | Nodemailer + Poste.io
|
||||||
|
|
||||||
**IMPORTANT**: When invoking PowerShell from bash, always use `-ExecutionPolicy Bypass` to skip the user profile script which is blocked by execution policy:
|
## Architecture
|
||||||
```bash
|
|
||||||
powershell -ExecutionPolicy Bypass -Command "..."
|
### Data Flow: Prisma → tRPC → React
|
||||||
|
|
||||||
|
End-to-end type safety: `prisma/schema.prisma` defines models → `src/server/routers/*.ts` expose tRPC procedures with Zod validation → `src/lib/trpc/client.ts` provides typed React hooks → components call `trpc.domain.procedure.useQuery()`.
|
||||||
|
|
||||||
|
### tRPC Middleware Hierarchy (`src/server/trpc.ts`)
|
||||||
|
|
||||||
|
All role-based access is enforced via procedure types:
|
||||||
|
|
||||||
|
| Procedure | Roles Allowed |
|
||||||
|
|-----------|---------------|
|
||||||
|
| `publicProcedure` | Anyone (no auth) |
|
||||||
|
| `protectedProcedure` | Any authenticated user |
|
||||||
|
| `adminProcedure` | SUPER_ADMIN, PROGRAM_ADMIN |
|
||||||
|
| `superAdminProcedure` | SUPER_ADMIN only |
|
||||||
|
| `juryProcedure` | JURY_MEMBER only |
|
||||||
|
| `mentorProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, MENTOR |
|
||||||
|
| `observerProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, OBSERVER |
|
||||||
|
| `awardMasterProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, AWARD_MASTER |
|
||||||
|
| `audienceProcedure` | Any authenticated user |
|
||||||
|
|
||||||
|
### Route Groups (Next.js App Router)
|
||||||
|
|
||||||
|
- `src/app/(auth)/` — Public auth pages (login, verify, accept-invite, onboarding)
|
||||||
|
- `src/app/(admin)/` — Admin dashboard, competition management, round config
|
||||||
|
- `src/app/(jury)/` — Jury evaluation interface, round assignments, live voting
|
||||||
|
- `src/app/(applicant)/` — Applicant dashboard, competition progress, document uploads
|
||||||
|
- `src/app/(mentor)/` — Mentor workspace
|
||||||
|
|
||||||
|
### Competition System
|
||||||
|
|
||||||
|
The core domain model. A **Competition** represents a complete evaluation cycle:
|
||||||
|
|
||||||
|
```
|
||||||
|
Competition → has many Rounds (ordered)
|
||||||
```
|
```
|
||||||
|
|
||||||
Examples:
|
- **Competition** (`src/server/routers/competition.ts`): Top-level competition config with program link, status, and settings
|
||||||
```bash
|
- **Round** (`RoundType: INTAKE | FILTERING | EVALUATION | SUBMISSION | MENTORING | LIVE_FINAL | DELIBERATION`): Each competition has ordered rounds with type-specific config in `configJson`
|
||||||
# npm commands
|
|
||||||
powershell -ExecutionPolicy Bypass -Command "npm install"
|
|
||||||
powershell -ExecutionPolicy Bypass -Command "npm run build"
|
|
||||||
powershell -ExecutionPolicy Bypass -Command "npx prisma generate"
|
|
||||||
|
|
||||||
# Docker commands
|
Key models:
|
||||||
powershell -ExecutionPolicy Bypass -Command "docker compose -f docker/docker-compose.dev.yml up -d"
|
- `JuryGroup` — Named jury panel with chair/member/observer roles
|
||||||
```
|
- `AdvancementRule` — Auto-advance, score threshold, top-N, admin selection between rounds
|
||||||
|
- `SubmissionWindow` — File submission deadlines per round
|
||||||
|
- `AssignmentPolicy` / `AssignmentIntent` — Governance layer for jury assignment
|
||||||
|
- `ProjectRoundState` — Per-project state within each round
|
||||||
|
- `DeliberationSession` / `DeliberationVote` / `ResultLock` — Structured deliberation and result finalization
|
||||||
|
|
||||||
```powershell
|
Key services:
|
||||||
# Docker commands on Windows (use PowerShell)
|
- `src/server/services/round-engine.ts` — State machine for round transitions
|
||||||
docker compose -f docker/docker-compose.dev.yml up -d
|
- `src/server/services/round-assignment.ts` — Jury assignment generation with policy enforcement
|
||||||
docker compose -f docker/docker-compose.dev.yml build --no-cache app
|
- `src/server/services/submission-manager.ts` — File submission + filtering with duplicate detection
|
||||||
docker compose -f docker/docker-compose.dev.yml logs -f app
|
- `src/server/services/deliberation.ts` — Deliberation session management and vote tallying
|
||||||
docker compose -f docker/docker-compose.dev.yml down
|
- `src/server/services/result-lock.ts` — Result finalization and unlock governance
|
||||||
```
|
- `src/server/services/live-control.ts` — Live ceremony cursor management
|
||||||
|
- `src/server/services/competition-context.ts` — Cross-cutting competition context resolver
|
||||||
|
|
||||||
|
Competition types: `src/types/competition.ts`, `src/types/competition-configs.ts`
|
||||||
|
|
||||||
|
### AI Services (`src/server/services/ai-*.ts`)
|
||||||
|
|
||||||
|
All AI calls anonymize data before sending to OpenAI. Services:
|
||||||
|
- `ai-filtering.ts` — AI-powered project screening with rubric
|
||||||
|
- `ai-assignment.ts` — GPT-suggested jury-project matching
|
||||||
|
- `ai-evaluation-summary.ts` — Strengths/weaknesses synthesis from evaluations
|
||||||
|
- `ai-tagging.ts` — Auto-tagging projects
|
||||||
|
- `ai-award-eligibility.ts` — Award eligibility assessment
|
||||||
|
- `ai-shortlist.ts` — AI-powered shortlist recommendations
|
||||||
|
- `anonymization.ts` — Strips PII before AI calls
|
||||||
|
|
||||||
|
### Auth System
|
||||||
|
|
||||||
|
NextAuth v5 with two providers: **Email** (magic links) and **Credentials** (password + invite token). Failed login tracking with 5-attempt lockout (15 min). Session includes `user.role` for RBAC.
|
||||||
|
|
||||||
|
### Docker Entrypoint (`docker/docker-entrypoint.sh`)
|
||||||
|
|
||||||
|
On container start: retry `prisma migrate deploy` → `prisma generate` → auto-seed if User table is empty → `node server.js`. A `docker compose down -v && docker compose up -d` will run all migrations from scratch and seed.
|
||||||
|
|
||||||
|
## Testing Infrastructure
|
||||||
|
|
||||||
|
- **Framework**: Vitest 4 with `fileParallelism: false` and `pool: 'forks'` (tests run sequentially)
|
||||||
|
- **Setup**: `tests/setup.ts` provides `prisma` client (uses `DATABASE_URL_TEST` or `DATABASE_URL`), `createTestContext(user)`, `createCaller(router, user)`
|
||||||
|
- **Factories**: `tests/helpers.ts` has `createTestUser()`, `createTestProgram()`, `createTestCompetition()`, `createTestRound()`, `uid()` helper, and `cleanupTestData()`
|
||||||
|
- **Pattern**: Create data with factories → build caller with `createCaller(routerModule, user)` → call procedures → assert → cleanup in `afterAll`
|
||||||
|
|
||||||
|
## User Roles
|
||||||
|
|
||||||
|
`SUPER_ADMIN` | `PROGRAM_ADMIN` | `JURY_MEMBER` | `OBSERVER` | `MENTOR` | `APPLICANT` | `AWARD_MASTER` | `AUDIENCE`
|
||||||
|
|
||||||
|
## Coding Standards
|
||||||
|
|
||||||
|
- **TypeScript**: Strict mode, `type` over `interface`, prefer `unknown` over `any`
|
||||||
|
- **Files**: kebab-case. **Components**: PascalCase. **DB models**: PascalCase in Prisma
|
||||||
|
- **React**: Server Components by default, `'use client'` only when needed
|
||||||
|
- **Styling**: Tailwind utility classes, mobile-first (`md:`, `lg:` breakpoints), shadcn/ui as base
|
||||||
|
- **tRPC**: Group by domain (`trpc.competition.create()`), Zod input validation, `TRPCError` for errors
|
||||||
|
- **Brand colors**: Primary Red `#de0f1e`, Dark Blue `#053d57`, White `#fefefe`, Teal `#557f8c`
|
||||||
|
- **Typography**: Montserrat (600/700 headings, 300/400 body)
|
||||||
|
|
||||||
|
## Key Constraints
|
||||||
|
|
||||||
|
1. Jury can only see assigned projects (enforced at query level)
|
||||||
|
2. Voting windows are strict — submissions blocked outside active window
|
||||||
|
3. All admin actions are audited via `DecisionAuditLog`
|
||||||
|
4. Files accessed via MinIO pre-signed URLs only (no public bucket)
|
||||||
|
5. COI declaration required before evaluation (blocking dialog)
|
||||||
|
6. Smart assignment skips COI-declared jurors, applies geo-diversity penalty and familiarity bonus
|
||||||
|
7. Cron endpoints protected by `CRON_SECRET` header
|
||||||
|
8. Round notifications never throw — all errors caught and logged
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- CSRF: tRPC uses `application/json` (triggers CORS preflight). Do NOT add permissive CORS headers.
|
||||||
|
- Rate limiting: 100 req/min tRPC, 10 req/min auth, 5-attempt lockout
|
||||||
|
- AI privacy: All data anonymized before OpenAI calls
|
||||||
|
|
||||||
|
## Seed Data
|
||||||
|
|
||||||
|
`prisma/seed.ts` imports from `docs/Candidatures 2026 *.csv` with special handling for non-breaking spaces (U+00A0) around French guillemets. Includes ALL CSV rows (no filtering/dedup) — duplicate detection happens in `submission-manager.ts` at runtime.
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
```env
|
Required: `DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET`, `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `EMAIL_FROM`, `OPENAI_API_KEY`, `CRON_SECRET`
|
||||||
# Database
|
|
||||||
DATABASE_URL="postgresql://user:pass@localhost:5432/mopc"
|
|
||||||
|
|
||||||
# NextAuth
|
External services (pre-existing on VPS): MinIO `:9000`, Poste.io `:587`, Nginx reverse proxy with SSL.
|
||||||
NEXTAUTH_URL="https://monaco-opc.com"
|
|
||||||
NEXTAUTH_SECRET="your-secret-key"
|
|
||||||
|
|
||||||
# MinIO (existing separate stack)
|
## Git
|
||||||
MINIO_ENDPOINT="http://localhost:9000"
|
|
||||||
MINIO_ACCESS_KEY="your-access-key"
|
|
||||||
MINIO_SECRET_KEY="your-secret-key"
|
|
||||||
MINIO_BUCKET="mopc-files"
|
|
||||||
|
|
||||||
# Email (Poste.io - existing)
|
Remote: `code.monaco-opc.com/MOPC/MOPC-Portal`. Branch: `main`. Always `npm run build` before pushing.
|
||||||
SMTP_HOST="localhost"
|
|
||||||
SMTP_PORT="587"
|
|
||||||
SMTP_USER="noreply@monaco-opc.com"
|
|
||||||
SMTP_PASS="your-smtp-password"
|
|
||||||
EMAIL_FROM="MOPC Platform <noreply@monaco-opc.com>"
|
|
||||||
|
|
||||||
# OpenAI (for smart assignment and AI evaluation summaries)
|
|
||||||
OPENAI_API_KEY="your-openai-api-key"
|
|
||||||
|
|
||||||
# Cron (for scheduled evaluation reminders)
|
|
||||||
CRON_SECRET="your-cron-secret-key"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Architectural Decisions
|
|
||||||
|
|
||||||
### 1. Next.js App Router over Pages Router
|
|
||||||
**Rationale**: Server Components reduce client bundle, better data fetching patterns, layouts system
|
|
||||||
|
|
||||||
### 2. tRPC over REST
|
|
||||||
**Rationale**: End-to-end type safety without code generation, excellent DX with autocomplete
|
|
||||||
|
|
||||||
### 3. Prisma over raw SQL
|
|
||||||
**Rationale**: Type-safe queries, migration system, works seamlessly with TypeScript
|
|
||||||
|
|
||||||
### 4. NextAuth.js over custom auth
|
|
||||||
**Rationale**: Battle-tested, supports magic links, session management built-in
|
|
||||||
|
|
||||||
### 5. MinIO (external) over local file storage
|
|
||||||
**Rationale**: S3-compatible, pre-signed URLs for security, scalable, already deployed
|
|
||||||
|
|
||||||
### 6. JSON fields for extensibility
|
|
||||||
**Rationale**: `metadata_json`, `settings_json` allow adding attributes without migrations
|
|
||||||
|
|
||||||
### 7. Soft deletes with status fields
|
|
||||||
**Rationale**: Audit trail preservation, recovery capability, referential integrity
|
|
||||||
|
|
||||||
## User Roles (RBAC)
|
|
||||||
|
|
||||||
| Role | Permissions |
|
|
||||||
|------|------------|
|
|
||||||
| **SUPER_ADMIN** | Full system access, all programs, user management |
|
|
||||||
| **PROGRAM_ADMIN** | Manage specific programs, rounds, projects, jury |
|
|
||||||
| **JURY_MEMBER** | View assigned projects only, submit evaluations, declare COI |
|
|
||||||
| **OBSERVER** | Read-only access to dashboards, all analytics/reports |
|
|
||||||
| **MENTOR** | View assigned projects, message applicants via `mentorProcedure` |
|
|
||||||
| **APPLICANT** | View own project status, upload documents per round, message mentor |
|
|
||||||
|
|
||||||
## Important Constraints
|
|
||||||
|
|
||||||
1. **Jury can only see assigned projects** - enforced at query level
|
|
||||||
2. **Voting windows are strict** - submissions blocked outside active window
|
|
||||||
3. **Evaluations are versioned** - edits create new versions
|
|
||||||
4. **All admin actions are audited** - immutable audit log
|
|
||||||
5. **Files accessed via pre-signed URLs** - no public bucket access
|
|
||||||
6. **Mobile responsiveness is mandatory** - every view must work on phones
|
|
||||||
7. **File downloads require project authorization** - jury/mentor must be assigned to the project
|
|
||||||
8. **Mentor endpoints require MENTOR role** - uses `mentorProcedure` middleware
|
|
||||||
9. **COI declaration required before evaluation** - blocking dialog gates evaluation form; admin reviews COI declarations
|
|
||||||
10. **Evaluation form supports multiple criterion types** - `numeric`, `text`, `boolean`, `section_header`; defaults to `numeric` for backward compatibility
|
|
||||||
11. **Smart assignment respects COI** - jurors with declared conflicts are skipped entirely; geo-diversity penalty and prior-round familiarity bonus applied
|
|
||||||
12. **Cron endpoints protected by CRON_SECRET** - `/api/cron/reminders` validates secret header
|
|
||||||
13. **Project status changes tracked** - every status update creates a `ProjectStatusHistory` record
|
|
||||||
14. **Per-round document management** - `ProjectFile` supports `roundId` scoping and `isLate` deadline tracking
|
|
||||||
|
|
||||||
## Security Notes
|
|
||||||
|
|
||||||
### CSRF Protection
|
|
||||||
tRPC mutations are protected against CSRF attacks because:
|
|
||||||
- tRPC uses `application/json` content type, which triggers CORS preflight on cross-origin requests
|
|
||||||
- Browsers block cross-origin JSON POSTs by default (Same-Origin Policy)
|
|
||||||
- NextAuth's own routes (`/api/auth/*`) have built-in CSRF token protection
|
|
||||||
- No custom CORS headers are configured to allow external origins
|
|
||||||
|
|
||||||
**Do NOT add permissive CORS headers** (e.g., `Access-Control-Allow-Origin: *`) without also implementing explicit CSRF token validation on all mutation endpoints.
|
|
||||||
|
|
||||||
### Rate Limiting
|
|
||||||
- tRPC API: 100 requests/minute per IP
|
|
||||||
- Auth endpoints: 10 POST requests/minute per IP
|
|
||||||
- Account lockout: 5 failed password attempts triggers 15-minute lockout
|
|
||||||
|
|
||||||
## External Services (Pre-existing)
|
|
||||||
|
|
||||||
These services are already running on the VPS in separate Docker Compose stacks:
|
|
||||||
|
|
||||||
- **MinIO**: `http://localhost:9000` - S3-compatible storage
|
|
||||||
- **Poste.io**: `localhost:587` - SMTP server for emails
|
|
||||||
- **Nginx**: Host-level reverse proxy with SSL (certbot)
|
|
||||||
|
|
||||||
The MOPC platform connects to these via environment variables.
|
|
||||||
|
|
||||||
## Phase 1 Scope
|
|
||||||
|
|
||||||
### In Scope
|
|
||||||
- Round management (create, configure, activate/close)
|
|
||||||
- Project import (CSV) and file uploads
|
|
||||||
- Jury invitation (magic link)
|
|
||||||
- Manual project assignment (single + bulk)
|
|
||||||
- Evaluation form (configurable criteria)
|
|
||||||
- Autosave + final submit
|
|
||||||
- Voting window enforcement
|
|
||||||
- Progress dashboards
|
|
||||||
- CSV export
|
|
||||||
- Audit logging
|
|
||||||
- **F1: Evaluation progress indicator** - sticky status bar with percentage tracking across criteria, global score, decision, feedback
|
|
||||||
- **F2: Export filtering results as CSV** - dynamic AI column flattening from `aiScreeningJson`
|
|
||||||
- **F3: Observer access to reports/analytics** - all 8 analytics procedures use `observerProcedure`; observer reports page with round selector, tabs, charts
|
|
||||||
- **F4: Countdown timer + email reminders** - live countdown with urgency colors; `EvaluationRemindersService` with cron endpoint (`/api/cron/reminders`)
|
|
||||||
- **F5: Conflict of Interest declaration** - `ConflictOfInterest` model; blocking dialog before evaluation; admin COI review page
|
|
||||||
- **F6: Bulk status update UI** - checkbox selection, floating toolbar, `ProjectStatusHistory` tracking
|
|
||||||
- **F7: AI-powered evaluation summary** - `EvaluationSummary` model; GPT-generated strengths/weaknesses, themes, scoring stats
|
|
||||||
- **F8: Smart assignment improvements** - `geoDiversityPenalty`, `previousRoundFamiliarity`, `coiPenalty` scoring factors
|
|
||||||
- **F9: Evaluation form flexibility** - extended criterion types (`numeric`, `text`, `boolean`, `section_header`); conditional visibility, section grouping
|
|
||||||
- **F10: Applicant portal enhancements** - `ProjectStatusHistory` timeline; per-round document management (`roundId` + `isLate` on `ProjectFile`); `MentorMessage` model for mentor-applicant chat
|
|
||||||
|
|
||||||
### Out of Scope (Phase 2+)
|
|
||||||
- Typeform/Notion integrations
|
|
||||||
- WhatsApp notifications
|
|
||||||
- Learning hub
|
|
||||||
- Partner modules
|
|
||||||
- Public website
|
|
||||||
|
|
||||||
## Testing Strategy
|
|
||||||
|
|
||||||
- **Unit Tests**: Business logic, utilities, validators
|
|
||||||
- **Integration Tests**: tRPC routers with test database
|
|
||||||
- **E2E Tests**: Critical user flows (Playwright)
|
|
||||||
- **Manual Testing**: Responsive design on real devices
|
|
||||||
|
|
||||||
## Documentation Links
|
|
||||||
|
|
||||||
- [Architecture Overview](./docs/architecture/README.md)
|
|
||||||
- [Database Design](./docs/architecture/database.md)
|
|
||||||
- [API Design](./docs/architecture/api.md)
|
|
||||||
- [Infrastructure](./docs/architecture/infrastructure.md)
|
|
||||||
- [UI/UX Patterns](./docs/architecture/ui.md)
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
# Executive Summary: MOPC Architecture Redesign
|
||||||
|
|
||||||
|
## Why This Redesign
|
||||||
|
|
||||||
|
The MOPC platform currently uses a **Pipeline -> Track -> Stage** model with generic JSON configs to orchestrate the competition. While technically sound, this architecture introduces unnecessary abstraction for what is fundamentally a **linear sequential competition flow**.
|
||||||
|
|
||||||
|
### Current Problems
|
||||||
|
|
||||||
|
| Problem | Impact |
|
||||||
|
|---------|--------|
|
||||||
|
| **3-level nesting** (Pipeline->Track->Stage) | Cognitive overhead for admins configuring rounds |
|
||||||
|
| **Generic `configJson` blobs** per stage type | "Vague" — 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 a thing |
|
||||||
|
| **Single submission round** | No way to open a second submission window for semi-finalists |
|
||||||
|
| **Track layer for main flow** | MAIN track adds indirection without value for a linear flow |
|
||||||
|
| **No mentoring workspace** | Mentor file exchange exists but no comments, no promotion to submission |
|
||||||
|
| **No winner confirmation** | No multi-party agreement step to cement winners |
|
||||||
|
| **Missing round types** | Can't model a "Semi-finalist Submission" or "Mentoring" or "Confirmation" step |
|
||||||
|
|
||||||
|
### Design Principles
|
||||||
|
|
||||||
|
1. **Domain over abstraction** — Models map directly to competition concepts (Jury 1, Round 2, etc.)
|
||||||
|
2. **Linear by default** — The main flow is sequential. Branching is only for special awards.
|
||||||
|
3. **Typed configs over JSON blobs** — Each round type has explicit, documented fields.
|
||||||
|
4. **Explicit entities** — Juries, submission windows, and confirmation steps are first-class models.
|
||||||
|
5. **Deep integration** — Every feature connects. Jury groups link to rounds, rounds link to submissions, submissions link to evaluations.
|
||||||
|
6. **Admin override everywhere** — Any automated decision can be manually overridden with audit trail.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Before & After: Architecture Comparison
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
│ ├── Stage: "Evaluation" (EVALUATION)
|
||||||
|
│ └── Stage: "Results" (RESULTS)
|
||||||
|
└── Track: "Award 2" (AWARD)
|
||||||
|
├── Stage: "Evaluation" (EVALUATION)
|
||||||
|
└── Stage: "Results" (RESULTS)
|
||||||
|
|
||||||
|
Juries: implicit (assignments per stage, no named entity)
|
||||||
|
Submissions: single round (one INTAKE stage)
|
||||||
|
Mentoring: basic (messages + notes, no workspace)
|
||||||
|
Winner confirmation: none
|
||||||
|
```
|
||||||
|
|
||||||
|
### AFTER (Redesigned System)
|
||||||
|
|
||||||
|
```
|
||||||
|
Program
|
||||||
|
└── Competition (purpose-built, replaces Pipeline)
|
||||||
|
├── Rounds (linear sequence, replaces Track+Stage):
|
||||||
|
│ ├── Round 1: "Application Window" ─────── (INTAKE)
|
||||||
|
│ ├── Round 2: "AI Screening" ──────────── (FILTERING)
|
||||||
|
│ ├── Round 3: "Jury 1 - Semi-finalist" ── (EVALUATION) ── juryGroupId: jury-1
|
||||||
|
│ ├── Round 4: "Semi-finalist Docs" ─────── (SUBMISSION) ── submissionWindowId: sw-2
|
||||||
|
│ ├── Round 5: "Jury 2 - Finalist" ──────── (EVALUATION) ── juryGroupId: jury-2
|
||||||
|
│ ├── Round 6: "Finalist Mentoring" ─────── (MENTORING)
|
||||||
|
│ ├── Round 7: "Live Finals" ────────────── (LIVE_FINAL) ── juryGroupId: jury-3
|
||||||
|
│ └── Round 8: "Confirm Winners" ─────────── (CONFIRMATION)
|
||||||
|
│
|
||||||
|
├── Jury Groups (explicit, named):
|
||||||
|
│ ├── "Jury 1" ── members: [judge-a, judge-b, ...] ── linked to Round 3
|
||||||
|
│ ├── "Jury 2" ── members: [judge-c, judge-d, ...] ── linked to Round 5
|
||||||
|
│ └── "Jury 3" ── members: [judge-e, judge-f, ...] ── linked to Round 7
|
||||||
|
│
|
||||||
|
├── 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):
|
||||||
|
├── "Innovation Award" ── mode: STAY_IN_MAIN, juryGroup: jury-2-award
|
||||||
|
└── "Impact Award" ── mode: SEPARATE_POOL, juryGroup: dedicated-jury
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Decisions
|
||||||
|
|
||||||
|
### 1. Eliminate the Track Layer
|
||||||
|
|
||||||
|
**Decision:** Remove the `Track` model entirely. The main competition is a linear sequence of Rounds. Special awards become standalone entities.
|
||||||
|
|
||||||
|
**Rationale:** The MOPC competition has one main flow (Intake -> Filtering -> Jury 1 -> Submission 2 -> Jury 2 -> Mentoring -> Finals -> Confirmation). The `Track` concept (MAIN/AWARD/SHOWCASE with RoutingMode and DecisionMode) was designed for branching flows that don't exist in this competition. Awards don't need their own track — they're parallel evaluation/voting processes that reference the same projects.
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- `Track` model deleted
|
||||||
|
- `TrackKind`, `RoutingMode` enums deleted
|
||||||
|
- `ProjectStageState.trackId` removed (becomes `ProjectRoundState` with just `projectId` + `roundId`)
|
||||||
|
- Award tracks replaced with enhanced `SpecialAward` model
|
||||||
|
- ~200 lines of Track CRUD code eliminated
|
||||||
|
|
||||||
|
### 2. Rename Pipeline -> Competition, Stage -> Round
|
||||||
|
|
||||||
|
**Decision:** Use domain-specific names that map to the competition vocabulary.
|
||||||
|
|
||||||
|
**Rationale:** Admins think in terms of "Competition 2026" and "Round 3: Jury 1 Evaluation", not "Pipeline" and "Stage". The rename costs nothing but improves comprehension.
|
||||||
|
|
||||||
|
### 3. Expand RoundType Enum
|
||||||
|
|
||||||
|
**Decision:** Add SUBMISSION, MENTORING, and CONFIRMATION to the existing types.
|
||||||
|
|
||||||
|
**Current:** `INTAKE | FILTER | EVALUATION | SELECTION | LIVE_FINAL | RESULTS`
|
||||||
|
|
||||||
|
**New:** `INTAKE | FILTERING | EVALUATION | SUBMISSION | MENTORING | LIVE_FINAL | CONFIRMATION`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- `FILTER` -> `FILTERING` (clearer naming)
|
||||||
|
- `SELECTION` removed (merged into EVALUATION's advancement config)
|
||||||
|
- `RESULTS` removed (results are a view, not a round — handled by the CONFIRMATION round output)
|
||||||
|
- `SUBMISSION` added (new doc requirements for advancing teams)
|
||||||
|
- `MENTORING` added (mentor-team workspace activation)
|
||||||
|
- `CONFIRMATION` added (multi-party winner agreement)
|
||||||
|
|
||||||
|
### 4. Explicit JuryGroup Model
|
||||||
|
|
||||||
|
**Decision:** Juries are first-class entities with names, members, and per-juror configuration.
|
||||||
|
|
||||||
|
**Before:** Assignments were per-stage with no grouping concept. "Jury 1" only existed in the admin's head.
|
||||||
|
|
||||||
|
**After:** `JuryGroup` model with members, linked to specific evaluation/live-final rounds. A juror can belong to multiple groups.
|
||||||
|
|
||||||
|
### 5. Multi-Round Submissions via SubmissionWindow
|
||||||
|
|
||||||
|
**Decision:** A new `SubmissionWindow` model handles document requirements per round, with automatic locking of previous windows.
|
||||||
|
|
||||||
|
**Before:** One INTAKE stage with one set of `FileRequirement` records.
|
||||||
|
|
||||||
|
**After:** Each submission window has its own requirements. When a new window opens, previous ones lock for applicants. Jury rounds can see docs from specific windows.
|
||||||
|
|
||||||
|
### 6. Typed Configs Replace JSON Blobs
|
||||||
|
|
||||||
|
**Decision:** Replace generic `configJson: Json?` with round-type-specific config models or strongly-typed JSON with Zod validation.
|
||||||
|
|
||||||
|
**Before:** `Stage.configJson` could be anything — you'd have to read the code to know what fields exist for each StageType.
|
||||||
|
|
||||||
|
**After:** Each round type has a documented, validated config shape. The wizard presents only the fields relevant to each type.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scope Summary
|
||||||
|
|
||||||
|
| Area | Action | Complexity |
|
||||||
|
|------|--------|------------|
|
||||||
|
| **Schema** | Major changes (new models, renamed models, deleted Track) | High |
|
||||||
|
| **Stage engine** | Rename to round engine, simplify (no Track references) | Medium |
|
||||||
|
| **Assignment service** | Enhance with jury groups, hard/soft caps, category ratios | Medium |
|
||||||
|
| **Filtering service** | Minimal changes (rename stageId -> roundId) | Low |
|
||||||
|
| **Live control** | Enhanced stage manager UI, same core logic | Medium |
|
||||||
|
| **Mentor system** | Major enhancement (workspace, files, comments, promotion) | High |
|
||||||
|
| **Winner confirmation** | New system (proposal, approvals, freezing) | High |
|
||||||
|
| **Special awards** | Enhanced (standalone, two modes, own jury groups) | Medium |
|
||||||
|
| **Notification system** | Enhanced (deadline countdowns, reminder triggers) | Medium |
|
||||||
|
| **Admin UI** | Full redesign (competition wizard, round management) | High |
|
||||||
|
| **Jury UI** | Enhanced (multi-jury dashboard, cross-round docs) | Medium |
|
||||||
|
| **Applicant UI** | Enhanced (multi-round submissions, mentoring workspace) | Medium |
|
||||||
|
| **Mentor UI** | New (dedicated mentor dashboard and workspace) | High |
|
||||||
|
| **API routers** | Major refactor (rename, new endpoints, removed endpoints) | High |
|
||||||
|
| **Migration** | Data migration from old schema to new | Medium |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Document Index
|
||||||
|
|
||||||
|
| # | Document | Purpose |
|
||||||
|
|---|----------|---------|
|
||||||
|
| 00 | This document | Executive summary and key decisions |
|
||||||
|
| 01 | Current System Audit | What exists today — models, services, routers, UI |
|
||||||
|
| 02 | Gap Analysis | Current vs required, feature-by-feature comparison |
|
||||||
|
| 03 | Data Model | Complete Prisma schema redesign with migration SQL |
|
||||||
|
| 04 | Round: Intake | Application window, forms, deadlines, drafts |
|
||||||
|
| 05 | Round: Filtering | AI screening, eligibility, admin overrides |
|
||||||
|
| 06 | Round: Evaluation | Multi-jury, caps, ratios, scoring, advancement |
|
||||||
|
| 07 | Round: Submission | Multi-round docs, locking, jury visibility |
|
||||||
|
| 08 | Round: Mentoring | Private workspace, file comments, promotion |
|
||||||
|
| 09 | Round: Live Finals | Stage manager, live voting, deliberation |
|
||||||
|
| 10 | Round: Confirmation | Jury signatures, admin override, result freezing |
|
||||||
|
| 11 | Special Awards | Two modes, award juries, integration |
|
||||||
|
| 12 | Jury Groups | Multi-jury architecture, members, overrides |
|
||||||
|
| 13 | Notifications & Deadlines | Countdowns, reminders, window management |
|
||||||
|
| 14 | AI Services | Filtering, assignment, summaries, eligibility |
|
||||||
|
| 15 | Admin UI Redesign | Dashboard, wizard, round management |
|
||||||
|
| 16 | Jury UI Redesign | Dashboard, evaluation, live voting |
|
||||||
|
| 17 | Applicant UI Redesign | Dashboard, multi-round uploads, mentoring |
|
||||||
|
| 18 | Mentor UI Redesign | Dashboard, workspace, file review |
|
||||||
|
| 19 | API Router Reference | tRPC changes — new, modified, removed |
|
||||||
|
| 20 | Service Layer Changes | Engine, assignment, new services |
|
||||||
|
| 21 | Migration Strategy | Schema migration, data migration, rollback |
|
||||||
|
| 22 | Integration Map | Cross-reference of all feature connections |
|
||||||
|
| 23 | Implementation Sequence | Phased order with dependencies |
|
||||||
|
|
@ -0,0 +1,591 @@
|
||||||
|
# Current System Audit: MOPC Platform
|
||||||
|
|
||||||
|
**Document Version:** 1.0
|
||||||
|
**Date:** 2026-02-15
|
||||||
|
**Status:** Complete
|
||||||
|
**Purpose:** Comprehensive inventory of all data models, services, routers, pages, and capabilities in the MOPC platform as of February 2026.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Data Models](#1-data-models)
|
||||||
|
2. [Enums](#2-enums)
|
||||||
|
3. [Services](#3-services)
|
||||||
|
4. [tRPC Routers](#4-trpc-routers)
|
||||||
|
5. [UI Pages](#5-ui-pages)
|
||||||
|
6. [Strengths](#6-strengths-of-current-system)
|
||||||
|
7. [Weaknesses](#7-weaknesses-of-current-system)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Data Models
|
||||||
|
|
||||||
|
### 1.1 Competition Structure Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **Pipeline** | Top-level competition round container | `programId`, `name`, `slug`, `status`, `settingsJson` | → Program, → Track[] |
|
||||||
|
| **Track** | Competition lane (MAIN or AWARD) | `pipelineId`, `name`, `kind`, `routingMode`, `decisionMode`, `sortOrder`, `settingsJson` | → Pipeline, → Stage[], → ProjectStageState[], ← SpecialAward? |
|
||||||
|
| **Stage** | Individual competition phase within a track | `trackId`, `stageType`, `name`, `slug`, `status`, `sortOrder`, `configJson`, `windowOpenAt`, `windowCloseAt` | → Track, → ProjectStageState[], → StageTransition[], → Cohort[], → LiveProgressCursor?, → LiveVotingSession? |
|
||||||
|
| **StageTransition** | Defines valid stage-to-stage movements | `fromStageId`, `toStageId`, `isDefault`, `guardJson` | → Stage (from), → Stage (to) |
|
||||||
|
| **ProjectStageState** | Tracks project position in pipeline | `projectId`, `trackId`, `stageId`, `state`, `enteredAt`, `exitedAt`, `metadataJson` | → Project, → Track, → Stage |
|
||||||
|
| **Cohort** | Groups projects for live voting | `stageId`, `name`, `votingMode`, `isOpen`, `windowOpenAt`, `windowCloseAt` | → Stage, → CohortProject[] |
|
||||||
|
| **CohortProject** | Project membership in a cohort | `cohortId`, `projectId`, `sortOrder` | → Cohort, → Project |
|
||||||
|
|
||||||
|
### 1.2 Project & Submission Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **Project** | Core project/application entity | `programId`, `title`, `teamName`, `description`, `competitionCategory`, `oceanIssue`, `country`, `geographicZone`, `institution`, `wantsMentorship`, `foundedAt`, `status`, `submissionSource`, `submittedByEmail`, `submittedAt`, `tags`, `metadataJson`, `isDraft` | → Program, → ProjectFile[], → Assignment[], → TeamMember[], → MentorAssignment?, → FilteringResult[], → AwardEligibility[], → ProjectStageState[], → CohortProject[] |
|
||||||
|
| **ProjectFile** | File uploads attached to projects | `projectId`, `requirementId`, `fileType`, `fileName`, `mimeType`, `size`, `bucket`, `objectKey`, `version`, `replacedById`, `isLate` | → Project, → FileRequirement?, → ProjectFile (versioning) |
|
||||||
|
| **FileRequirement** | Defines required file uploads per stage | `stageId`, `name`, `description`, `acceptedMimeTypes`, `maxSizeMB`, `isRequired`, `sortOrder` | → Stage, ← ProjectFile[] |
|
||||||
|
| **TeamMember** | Team composition for projects | `projectId`, `userId`, `role`, `title` | → Project, → User |
|
||||||
|
|
||||||
|
### 1.3 Jury & Evaluation Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **Assignment** | Jury member assigned to evaluate a project | `userId`, `projectId`, `stageId`, `method`, `isRequired`, `isCompleted`, `aiConfidenceScore`, `expertiseMatchScore`, `aiReasoning` | → User, → Project, → Stage, → Evaluation?, → ConflictOfInterest? |
|
||||||
|
| **Evaluation** | Jury member's assessment of a project | `assignmentId`, `formId`, `status`, `criterionScoresJson`, `globalScore`, `binaryDecision`, `feedbackText`, `version`, `submittedAt` | → Assignment, → EvaluationForm |
|
||||||
|
| **EvaluationForm** | Configurable evaluation criteria per stage | `stageId`, `version`, `criteriaJson`, `scalesJson`, `isActive` | → Stage, ← Evaluation[] |
|
||||||
|
| **ConflictOfInterest** | COI declarations by jury members | `assignmentId`, `userId`, `projectId`, `hasConflict`, `conflictType`, `description`, `declaredAt`, `reviewedById`, `reviewAction` | → Assignment, → User, → User (reviewer) |
|
||||||
|
| **GracePeriod** | Extended deadlines for specific jury members | `stageId`, `userId`, `projectId`, `extendedUntil`, `reason`, `grantedById` | → Stage, → User, → User (granter) |
|
||||||
|
| **EvaluationSummary** | AI-generated synthesis of evaluations | `projectId`, `stageId`, `summaryJson`, `generatedAt`, `generatedById`, `model`, `tokensUsed` | → Project, → Stage, → User |
|
||||||
|
| **EvaluationDiscussion** | Discussion thread for deliberation | `projectId`, `stageId`, `status`, `createdAt`, `closedAt`, `closedById` | → Project, → Stage, → User, → DiscussionComment[] |
|
||||||
|
| **DiscussionComment** | Individual comment in discussion | `discussionId`, `userId`, `content`, `createdAt` | → EvaluationDiscussion, → User |
|
||||||
|
|
||||||
|
### 1.4 Live Voting Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **LiveVotingSession** | Live final event configuration | `stageId`, `status`, `currentProjectIndex`, `currentProjectId`, `votingStartedAt`, `votingEndsAt`, `projectOrderJson`, `votingMode`, `criteriaJson`, `allowAudienceVotes`, `audienceVoteWeight`, `tieBreakerMethod` | → Stage, → LiveVote[], → AudienceVoter[] |
|
||||||
|
| **LiveVote** | Individual vote during live event | `sessionId`, `projectId`, `userId`, `score`, `isAudienceVote`, `votedAt`, `criterionScoresJson`, `audienceVoterId` | → LiveVotingSession, → User?, → AudienceVoter? |
|
||||||
|
| **AudienceVoter** | Anonymous audience participant | `sessionId`, `token`, `identifier`, `identifierType`, `ipAddress`, `userAgent` | → LiveVotingSession, → LiveVote[] |
|
||||||
|
| **LiveProgressCursor** | Real-time cursor for live presentation | `stageId`, `sessionId`, `activeProjectId`, `activeOrderIndex`, `isPaused` | → Stage |
|
||||||
|
|
||||||
|
### 1.5 Awards Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **SpecialAward** | Special prize/recognition category | `programId`, `trackId`, `name`, `description`, `status`, `criteriaText`, `autoTagRulesJson`, `useAiEligibility`, `scoringMode`, `maxRankedPicks`, `votingStartAt`, `votingEndAt`, `winnerProjectId`, `winnerOverridden`, `eligibilityJobStatus` | → Program, → Track?, → Project (winner), → AwardEligibility[], → AwardJuror[], → AwardVote[] |
|
||||||
|
| **AwardEligibility** | AI-determined award eligibility | `awardId`, `projectId`, `method`, `eligible`, `aiReasoningJson`, `overriddenBy`, `overriddenAt` | → SpecialAward, → Project, → User? |
|
||||||
|
| **AwardJuror** | Jury panel for special award | `awardId`, `userId` | → SpecialAward, → User |
|
||||||
|
| **AwardVote** | Vote for special award winner | `awardId`, `userId`, `projectId`, `rank`, `votedAt` | → SpecialAward, → User, → Project |
|
||||||
|
|
||||||
|
### 1.6 Mentoring Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **MentorAssignment** | Mentor-project pairing | `projectId`, `mentorId`, `method`, `assignedAt`, `assignedBy`, `aiConfidenceScore`, `expertiseMatchScore`, `completionStatus` | → Project (unique), → User (mentor), → MentorNote[], → MentorMilestoneCompletion[] |
|
||||||
|
| **MentorMessage** | Chat messages between mentor and team | `projectId`, `senderId`, `message`, `isRead` | → Project, → User |
|
||||||
|
| **MentorNote** | Private notes by mentor/admin | `mentorAssignmentId`, `authorId`, `content`, `isVisibleToAdmin` | → MentorAssignment, → User |
|
||||||
|
| **MentorMilestone** | Program-wide mentorship checkpoints | `programId`, `name`, `description`, `isRequired`, `deadlineOffsetDays`, `sortOrder` | → Program, → MentorMilestoneCompletion[] |
|
||||||
|
| **MentorMilestoneCompletion** | Completion record for milestones | `milestoneId`, `mentorAssignmentId`, `completedById`, `completedAt` | → MentorMilestone, → MentorAssignment, → User |
|
||||||
|
|
||||||
|
### 1.7 Filtering Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **FilteringRule** | Automated screening rule | `stageId`, `name`, `ruleType`, `configJson`, `priority`, `isActive` | → Stage |
|
||||||
|
| **FilteringResult** | Per-project filtering outcome | `stageId`, `projectId`, `outcome`, `ruleResultsJson`, `aiScreeningJson`, `overriddenBy`, `overriddenAt`, `overrideReason`, `finalOutcome` | → Stage, → Project, → User? |
|
||||||
|
| **FilteringJob** | Progress tracking for filtering runs | `stageId`, `status`, `totalProjects`, `processedCount`, `passedCount`, `filteredCount`, `flaggedCount`, `errorMessage`, `startedAt`, `completedAt` | → Stage |
|
||||||
|
| **AssignmentJob** | Progress tracking for assignment generation | `stageId`, `status`, `totalProjects`, `processedCount`, `suggestionsCount`, `suggestionsJson`, `errorMessage`, `fallbackUsed` | → Stage |
|
||||||
|
| **TaggingJob** | Progress tracking for AI tagging | `programId`, `status`, `totalProjects`, `processedCount`, `taggedCount`, `skippedCount`, `failedCount`, `errorsJson` | → Program? |
|
||||||
|
|
||||||
|
### 1.8 Users & Auth Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **User** | Platform user account | `email`, `name`, `role`, `status`, `expertiseTags`, `maxAssignments`, `country`, `bio`, `phoneNumber`, `notificationPreference`, `digestFrequency`, `preferredWorkload`, `passwordHash`, `inviteToken`, `onboardingCompletedAt` | → Assignment[], → GracePeriod[], → LiveVote[], → TeamMember[], → MentorAssignment[], → AwardJuror[], → ConflictOfInterest[], → InAppNotification[] |
|
||||||
|
| **Account** | NextAuth provider accounts | `userId`, `provider`, `providerAccountId`, `access_token`, `refresh_token` | → User |
|
||||||
|
| **Session** | NextAuth active sessions | `userId`, `sessionToken`, `expires` | → User |
|
||||||
|
| **VerificationToken** | NextAuth magic link tokens | `identifier`, `token`, `expires` | (standalone) |
|
||||||
|
|
||||||
|
### 1.9 Audit & Logging Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **AuditLog** | General platform activity log | `userId`, `action`, `entityType`, `entityId`, `detailsJson`, `previousDataJson`, `ipAddress`, `userAgent`, `sessionId`, `timestamp` | → User? |
|
||||||
|
| **DecisionAuditLog** | Pipeline decision tracking | `eventType`, `entityType`, `entityId`, `actorId`, `detailsJson`, `snapshotJson`, `createdAt` | (no FK relations) |
|
||||||
|
| **OverrideAction** | Manual admin overrides log | `entityType`, `entityId`, `previousValue`, `newValueJson`, `reasonCode`, `reasonText`, `actorId`, `createdAt` | (no FK relations) |
|
||||||
|
| **AIUsageLog** | AI API consumption tracking | `userId`, `action`, `entityType`, `entityId`, `model`, `promptTokens`, `completionTokens`, `estimatedCostUsd`, `status`, `errorMessage` | (no FK relations) |
|
||||||
|
| **NotificationLog** | Email/SMS delivery tracking | `userId`, `channel`, `provider`, `type`, `status`, `externalId`, `errorMsg` | → User |
|
||||||
|
|
||||||
|
### 1.10 Program & Resources Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **Program** | Competition edition/year | `name`, `slug`, `year`, `status`, `description`, `settingsJson` | → Pipeline[], → Project[], → LearningResource[], → Partner[], → SpecialAward[] |
|
||||||
|
| **LearningResource** | Educational content for teams | `programId`, `title`, `description`, `contentJson`, `resourceType`, `cohortLevel`, `fileName`, `mimeType`, `bucket`, `objectKey`, `externalUrl`, `isPublished` | → Program?, → User (creator), → ResourceAccess[] |
|
||||||
|
| **ResourceAccess** | Access log for learning materials | `resourceId`, `userId`, `accessedAt`, `ipAddress` | → LearningResource, → User |
|
||||||
|
| **Partner** | Sponsor/partner organization | `programId`, `name`, `description`, `website`, `partnerType`, `visibility`, `logoFileName`, `sortOrder`, `isActive` | → Program? |
|
||||||
|
| **WizardTemplate** | Saved pipeline configuration templates | `name`, `description`, `config`, `isGlobal`, `programId`, `createdBy` | → Program?, → User |
|
||||||
|
|
||||||
|
### 1.11 Communication Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **InAppNotification** | Bell icon notifications | `userId`, `type`, `priority`, `icon`, `title`, `message`, `linkUrl`, `linkLabel`, `metadata`, `groupKey`, `isRead`, `expiresAt` | → User |
|
||||||
|
| **NotificationEmailSetting** | Email notification toggles per type | `notificationType`, `category`, `label`, `sendEmail`, `emailSubject`, `emailTemplate` | (standalone) |
|
||||||
|
| **NotificationPolicy** | Event-driven notification config | `eventType`, `channel`, `templateId`, `isActive`, `configJson` | (no FK relations) |
|
||||||
|
| **Message** | Bulk messaging system | `senderId`, `recipientType`, `recipientFilter`, `stageId`, `templateId`, `subject`, `body`, `deliveryChannels`, `scheduledAt`, `sentAt` | → User (sender), → Stage?, → MessageTemplate?, → MessageRecipient[] |
|
||||||
|
| **MessageRecipient** | Individual message delivery | `messageId`, `userId`, `channel`, `isRead`, `readAt`, `deliveredAt` | → Message, → User |
|
||||||
|
| **MessageTemplate** | Reusable email templates | `name`, `category`, `subject`, `body`, `variables`, `isActive`, `createdBy` | → User, ← Message[] |
|
||||||
|
|
||||||
|
### 1.12 Webhooks & Integrations
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **Webhook** | Outbound event webhooks | `name`, `url`, `secret`, `events`, `headers`, `maxRetries`, `isActive`, `createdById` | → User, → WebhookDelivery[] |
|
||||||
|
| **WebhookDelivery** | Webhook delivery log | `webhookId`, `event`, `payload`, `status`, `responseStatus`, `responseBody`, `attempts`, `lastAttemptAt` | → Webhook |
|
||||||
|
|
||||||
|
### 1.13 Miscellaneous Models
|
||||||
|
|
||||||
|
| Model | Purpose | Key Fields | Relations |
|
||||||
|
|-------|---------|------------|-----------|
|
||||||
|
| **SystemSettings** | Platform-wide config KV store | `key`, `value`, `type`, `category`, `description`, `isSecret` | (standalone) |
|
||||||
|
| **ExpertiseTag** | Tag taxonomy for matching | `name`, `description`, `category`, `color`, `isActive`, `sortOrder` | → ProjectTag[] |
|
||||||
|
| **ProjectTag** | Project-tag association | `projectId`, `tagId`, `confidence`, `source` | → Project, → ExpertiseTag |
|
||||||
|
| **ProjectStatusHistory** | Historical status changes | `projectId`, `status`, `changedAt`, `changedBy` | → Project |
|
||||||
|
| **ReminderLog** | Evaluation deadline reminders | `stageId`, `userId`, `type`, `sentAt` | → Stage, → User |
|
||||||
|
| **DigestLog** | Email digest delivery log | `userId`, `digestType`, `contentJson`, `sentAt` | → User |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Enums
|
||||||
|
|
||||||
|
### 2.1 User & Auth Enums
|
||||||
|
|
||||||
|
| Enum | Values | Usage |
|
||||||
|
|------|--------|-------|
|
||||||
|
| **UserRole** | `SUPER_ADMIN`, `PROGRAM_ADMIN`, `JURY_MEMBER`, `MENTOR`, `OBSERVER`, `APPLICANT`, `AWARD_MASTER`, `AUDIENCE` | User permissions hierarchy |
|
||||||
|
| **UserStatus** | `NONE`, `INVITED`, `ACTIVE`, `SUSPENDED` | User account state |
|
||||||
|
|
||||||
|
### 2.2 Project & Competition Enums
|
||||||
|
|
||||||
|
| Enum | Values | Usage |
|
||||||
|
|------|--------|-------|
|
||||||
|
| **ProjectStatus** | `SUBMITTED`, `ELIGIBLE`, `ASSIGNED`, `SEMIFINALIST`, `FINALIST`, `REJECTED` | Legacy project state (superseded by ProjectStageState) |
|
||||||
|
| **CompetitionCategory** | `STARTUP`, `BUSINESS_CONCEPT` | Project type (existing company vs. student idea) |
|
||||||
|
| **OceanIssue** | `POLLUTION_REDUCTION`, `CLIMATE_MITIGATION`, `TECHNOLOGY_INNOVATION`, `SUSTAINABLE_SHIPPING`, `BLUE_CARBON`, `HABITAT_RESTORATION`, `COMMUNITY_CAPACITY`, `SUSTAINABLE_FISHING`, `CONSUMER_AWARENESS`, `OCEAN_ACIDIFICATION`, `OTHER` | Project focus area |
|
||||||
|
|
||||||
|
### 2.3 Pipeline Enums
|
||||||
|
|
||||||
|
| Enum | Values | Usage |
|
||||||
|
|------|--------|-------|
|
||||||
|
| **StageType** | `INTAKE`, `FILTER`, `EVALUATION`, `SELECTION`, `LIVE_FINAL`, `RESULTS` | Stage functional type |
|
||||||
|
| **TrackKind** | `MAIN`, `AWARD`, `SHOWCASE` | Track purpose |
|
||||||
|
| **RoutingMode** | `SHARED`, `EXCLUSIVE` | Project routing behavior (can projects be in multiple tracks?) |
|
||||||
|
| **StageStatus** | `STAGE_DRAFT`, `STAGE_ACTIVE`, `STAGE_CLOSED`, `STAGE_ARCHIVED` | Stage lifecycle state |
|
||||||
|
| **ProjectStageStateValue** | `PENDING`, `IN_PROGRESS`, `PASSED`, `REJECTED`, `ROUTED`, `COMPLETED`, `WITHDRAWN` | Project state within a stage |
|
||||||
|
| **DecisionMode** | `JURY_VOTE`, `AWARD_MASTER_DECISION`, `ADMIN_DECISION` | How winners are determined in a track |
|
||||||
|
|
||||||
|
### 2.4 Evaluation & Assignment Enums
|
||||||
|
|
||||||
|
| Enum | Values | Usage |
|
||||||
|
|------|--------|-------|
|
||||||
|
| **EvaluationStatus** | `NOT_STARTED`, `DRAFT`, `SUBMITTED`, `LOCKED` | Evaluation completion state |
|
||||||
|
| **AssignmentMethod** | `MANUAL`, `BULK`, `AI_SUGGESTED`, `AI_AUTO`, `ALGORITHM` | How assignment was created |
|
||||||
|
| **MentorAssignmentMethod** | `MANUAL`, `AI_SUGGESTED`, `AI_AUTO`, `ALGORITHM` | How mentor was paired |
|
||||||
|
|
||||||
|
### 2.5 Filtering Enums
|
||||||
|
|
||||||
|
| Enum | Values | Usage |
|
||||||
|
|------|--------|-------|
|
||||||
|
| **FilteringOutcome** | `PASSED`, `FILTERED_OUT`, `FLAGGED` | Filtering result |
|
||||||
|
| **FilteringRuleType** | `FIELD_BASED`, `DOCUMENT_CHECK`, `AI_SCREENING` | Type of filtering rule |
|
||||||
|
| **FilteringJobStatus** | `PENDING`, `RUNNING`, `COMPLETED`, `FAILED` | Job progress state |
|
||||||
|
| **AssignmentJobStatus** | `PENDING`, `RUNNING`, `COMPLETED`, `FAILED` | Job progress state |
|
||||||
|
| **TaggingJobStatus** | `PENDING`, `RUNNING`, `COMPLETED`, `FAILED` | Job progress state |
|
||||||
|
|
||||||
|
### 2.6 Awards Enums
|
||||||
|
|
||||||
|
| Enum | Values | Usage |
|
||||||
|
|------|--------|-------|
|
||||||
|
| **AwardScoringMode** | `PICK_WINNER`, `RANKED`, `SCORED` | Award voting method |
|
||||||
|
| **AwardStatus** | `DRAFT`, `NOMINATIONS_OPEN`, `VOTING_OPEN`, `CLOSED`, `ARCHIVED` | Award lifecycle |
|
||||||
|
| **EligibilityMethod** | `AUTO`, `MANUAL` | How eligibility was determined |
|
||||||
|
|
||||||
|
### 2.7 Miscellaneous Enums
|
||||||
|
|
||||||
|
| Enum | Values | Usage |
|
||||||
|
|------|--------|-------|
|
||||||
|
| **FileType** | `EXEC_SUMMARY`, `PRESENTATION`, `VIDEO`, `OTHER`, `BUSINESS_PLAN`, `VIDEO_PITCH`, `SUPPORTING_DOC` | Project file categorization |
|
||||||
|
| **SubmissionSource** | `MANUAL`, `CSV`, `NOTION`, `TYPEFORM`, `PUBLIC_FORM` | How project was submitted |
|
||||||
|
| **NotificationChannel** | `EMAIL`, `WHATSAPP`, `BOTH`, `NONE` | Notification delivery method |
|
||||||
|
| **ResourceType** | `PDF`, `VIDEO`, `DOCUMENT`, `LINK`, `OTHER` | Learning resource type |
|
||||||
|
| **CohortLevel** | `ALL`, `SEMIFINALIST`, `FINALIST` | Access level for resources |
|
||||||
|
| **PartnerVisibility** | `ADMIN_ONLY`, `JURY_VISIBLE`, `PUBLIC` | Who can see partner |
|
||||||
|
| **PartnerType** | `SPONSOR`, `PARTNER`, `SUPPORTER`, `MEDIA`, `OTHER` | Partner categorization |
|
||||||
|
| **TeamMemberRole** | `LEAD`, `MEMBER`, `ADVISOR` | Team composition |
|
||||||
|
| **OverrideReasonCode** | `DATA_CORRECTION`, `POLICY_EXCEPTION`, `JURY_CONFLICT`, `SPONSOR_DECISION`, `ADMIN_DISCRETION` | Why decision was overridden |
|
||||||
|
| **ProgramStatus** | `DRAFT`, `ACTIVE`, `ARCHIVED` | Program lifecycle |
|
||||||
|
| **SettingType** | `STRING`, `NUMBER`, `BOOLEAN`, `JSON`, `SECRET` | System setting data type |
|
||||||
|
| **SettingCategory** | `AI`, `BRANDING`, `EMAIL`, `STORAGE`, `SECURITY`, `DEFAULTS`, `WHATSAPP`, `AUDIT_CONFIG`, `LOCALIZATION`, `DIGEST`, `ANALYTICS`, `INTEGRATIONS`, `COMMUNICATION` | Setting organization |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Services
|
||||||
|
|
||||||
|
All services located in `src/server/services/*.ts`.
|
||||||
|
|
||||||
|
| Service | Purpose | Key Functions |
|
||||||
|
|---------|---------|---------------|
|
||||||
|
| **stage-engine.ts** | State machine for project transitions | `validateTransition()`, `executeTransition()`, `executeBatchTransition()` - handles guard evaluation, atomic PSS updates, audit logging |
|
||||||
|
| **stage-filtering.ts** | Runs filtering pipeline scoped to stage | `runStageFiltering()`, `resolveManualDecision()`, `getManualQueue()` - executes field-based, document, and AI rules; duplicate detection built-in |
|
||||||
|
| **stage-assignment.ts** | Smart jury assignment generation | `previewStageAssignment()`, `executeStageAssignment()`, `getCoverageReport()`, `rebalance()` - tag matching, workload balancing, COI handling |
|
||||||
|
| **stage-notifications.ts** | Event-driven notification producer | `emitStageEvent()`, `onStageTransitioned()`, `onFilteringCompleted()`, `onAssignmentGenerated()`, `onCursorUpdated()` - never throws, creates DecisionAuditLog + in-app + email |
|
||||||
|
| **live-control.ts** | Real-time live ceremony control | `startSession()`, `setActiveProject()`, `jumpToProject()`, `reorderQueue()`, `pauseResume()`, `openCohortWindow()`, `closeCohortWindow()` - manages LiveProgressCursor |
|
||||||
|
| **ai-filtering.ts** | AI-powered project screening | Anonymizes data, calls OpenAI API, confidence banding, spam detection (delegates to stage-filtering.ts for execution) |
|
||||||
|
| **ai-assignment.ts** | AI-suggested jury matching | GPT-based assignment generation with expertise matching (100 lines) |
|
||||||
|
| **ai-evaluation-summary.ts** | GPT synthesis of evaluations | Generates strengths/weaknesses summary from jury feedback |
|
||||||
|
| **ai-tagging.ts** | Automatic project categorization | Tags projects with expertise areas using GPT |
|
||||||
|
| **ai-award-eligibility.ts** | Award eligibility assessment | GPT determines if project meets award criteria |
|
||||||
|
| **anonymization.ts** | GDPR-compliant data stripping | Removes PII before AI calls (name, email, institution, etc.) |
|
||||||
|
| **ai-errors.ts** | Centralized AI error handling | Classifies errors (rate limit, token limit, API down), provides retry logic |
|
||||||
|
| **award-eligibility-job.ts** | Batch award eligibility processing | Runs AI eligibility checks across all projects for an award |
|
||||||
|
| **smart-assignment.ts** | Scoring algorithm for matching | Tag overlap, bio match, workload balance, geo diversity, COI blocking, availability checking |
|
||||||
|
| **mentor-matching.ts** | Mentor-project pairing logic | Similar to smart-assignment but for mentorship |
|
||||||
|
| **evaluation-reminders.ts** | Cron job for deadline reminders | Sends 3-day, 24h, 1h reminders to jury with incomplete evaluations |
|
||||||
|
| **email-digest.ts** | Daily/weekly email summaries | Aggregates pending tasks for users |
|
||||||
|
| **in-app-notification.ts** | In-app notification helpers | Creates bell-icon notifications with linking |
|
||||||
|
| **notification.ts** | Email sending service | Wraps Nodemailer, supports templates |
|
||||||
|
| **webhook-dispatcher.ts** | Webhook delivery service | Sends events to registered webhook URLs with retry logic |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. tRPC Routers
|
||||||
|
|
||||||
|
All routers located in `src/server/routers/*.ts`. Total: 38 routers.
|
||||||
|
|
||||||
|
| Router | Procedure Count | Key Procedures | Purpose |
|
||||||
|
|--------|-----------------|----------------|---------|
|
||||||
|
| **pipeline.ts** | ~15 | `create`, `update`, `delete`, `list`, `getById`, `archive` | Pipeline CRUD, linking to Program |
|
||||||
|
| **stage.ts** | ~20 | `create`, `updateConfig`, `updateStatus`, `delete`, `getByTrack`, `reorderStages`, `createTransition` | Stage CRUD, window management, transition setup |
|
||||||
|
| **stageFiltering.ts** | ~10 | `createRule`, `runFiltering`, `getManualQueue`, `resolveManualDecision`, `getJobStatus` | Filtering rule management + execution |
|
||||||
|
| **stageAssignment.ts** | ~8 | `previewAssignment`, `executeAssignment`, `getCoverage`, `rebalance`, `bulkDelete` | Assignment generation, coverage analysis |
|
||||||
|
| **project.ts** | ~25 | `create`, `update`, `delete`, `getById`, `list`, `import`, `advanceToRound`, `updateStatus` | Project CRUD, CSV import, status changes |
|
||||||
|
| **assignment.ts** | ~12 | `create`, `bulkCreate`, `delete`, `getByUser`, `getByProject`, `markComplete` | Manual assignment management |
|
||||||
|
| **evaluation.ts** | ~15 | `create`, `update`, `submit`, `lock`, `unlock`, `getByAssignment`, `generateSummary` | Evaluation submission, locking |
|
||||||
|
| **gracePeriod.ts** | ~6 | `create`, `delete`, `getByStage`, `getByUser`, `checkActive` | Grace period management |
|
||||||
|
| **user.ts** | ~20 | `create`, `update`, `delete`, `invite`, `resendInvite`, `list`, `updateProfile`, `uploadAvatar` | User management, invites |
|
||||||
|
| **specialAward.ts** | ~15 | `create`, `update`, `delete`, `runEligibility`, `vote`, `getResults`, `overrideWinner` | Award creation, voting, eligibility |
|
||||||
|
| **live-voting.ts** | ~12 | `createSession`, `vote`, `getResults`, `closeSession`, `updateCriteria` | Live voting session management (legacy LiveVotingSession model) |
|
||||||
|
| **live.ts** | ~10 | `startSession`, `setActiveProject`, `jumpToProject`, `pauseResume`, `openCohort`, `closeCohort` | Live control (new LiveProgressCursor model) |
|
||||||
|
| **cohort.ts** | ~8 | `create`, `update`, `delete`, `addProjects`, `removeProjects`, `reorder` | Cohort management for live finals |
|
||||||
|
| **mentor.ts** | ~12 | `assignMentor`, `removeMentor`, `sendMessage`, `addNote`, `completeMilestone`, `getMentorDashboard` | Mentorship workflow |
|
||||||
|
| **learningResource.ts** | ~10 | `create`, `update`, `delete`, `list`, `upload`, `markAccessed` | Learning hub content |
|
||||||
|
| **partner.ts** | ~8 | `create`, `update`, `delete`, `list`, `uploadLogo` | Partner management |
|
||||||
|
| **tag.ts** | ~10 | `create`, `update`, `delete`, `list`, `runTagging`, `getTaggingJobStatus` | Expertise tag management |
|
||||||
|
| **notification.ts** | ~8 | `getInApp`, `markRead`, `markAllRead`, `getUnreadCount`, `updateEmailSettings` | Notification center |
|
||||||
|
| **message.ts** | ~10 | `send`, `schedule`, `list`, `getRecipients`, `createTemplate`, `listTemplates` | Bulk messaging |
|
||||||
|
| **webhook.ts** | ~8 | `create`, `update`, `delete`, `test`, `getDeliveries`, `retry` | Webhook management |
|
||||||
|
| **audit.ts** | ~6 | `getAuditLog`, `getDecisionLog`, `getOverrides`, `export` | Audit trail viewing |
|
||||||
|
| **analytics.ts** | ~12 | `getDashboardStats`, `getProjectStats`, `getJuryStats`, `getAwardStats`, `getEngagementMetrics` | Reporting and analytics |
|
||||||
|
| **dashboard.ts** | ~8 | `getAdminDashboard`, `getJuryDashboard`, `getApplicantDashboard`, `getMentorDashboard` | Role-specific dashboards |
|
||||||
|
| **export.ts** | ~8 | `exportProjects`, `exportEvaluations`, `exportVotes`, `exportAuditLog` | CSV/Excel exports |
|
||||||
|
| **file.ts** | ~8 | `uploadFile`, `getPresignedUrl`, `deleteFile`, `listFiles`, `createRequirement` | MinIO file management |
|
||||||
|
| **filtering.ts** | ~6 | Legacy filtering endpoints (superseded by stageFiltering) | Deprecated |
|
||||||
|
| **avatar.ts** | ~4 | `upload`, `delete`, `getUrl` | User profile images |
|
||||||
|
| **logo.ts** | ~4 | `upload`, `delete`, `getUrl` | Project logos |
|
||||||
|
| **decision.ts** | ~6 | `overrideFilteringResult`, `overrideAwardEligibility`, `overridePSS`, `getOverrideHistory` | Admin override controls |
|
||||||
|
| **program.ts** | ~10 | `create`, `update`, `delete`, `list`, `getById`, `archive` | Program CRUD |
|
||||||
|
| **application.ts** | ~8 | `submitApplication`, `saveDraft`, `getDraft`, `deleteDraft` | Public application form |
|
||||||
|
| **applicant.ts** | ~10 | `getMyProjects`, `updateTeam`, `uploadDocument`, `requestMentorship` | Applicant portal |
|
||||||
|
| **notion-import.ts** | ~4 | `sync`, `import`, `getStatus` | Notion integration |
|
||||||
|
| **typeform-import.ts** | ~4 | `sync`, `import`, `getStatus` | Typeform integration |
|
||||||
|
| **settings.ts** | ~8 | `get`, `set`, `getBulk`, `setBulk`, `getByCategory` | System settings KV store |
|
||||||
|
| **project-pool.ts** | ~6 | `getUnassignedProjects`, `getProjectsByStage`, `getProjectsByStatus` | Project queries for assignment |
|
||||||
|
| **wizard-template.ts** | ~8 | `create`, `update`, `delete`, `list`, `clone`, `applyTemplate` | Pipeline wizard templates |
|
||||||
|
|
||||||
|
**Total Procedures:** ~400+
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. UI Pages
|
||||||
|
|
||||||
|
### 5.1 Admin Pages (`src/app/(admin)/admin/`)
|
||||||
|
|
||||||
|
| Route | Purpose | Key Features |
|
||||||
|
|-------|---------|--------------|
|
||||||
|
| `/admin` | Admin dashboard | Overview metrics, recent activity, quick actions |
|
||||||
|
| `/admin/members` | User management list | User table with filters, role assignment, status changes |
|
||||||
|
| `/admin/members/[id]` | User detail/edit | Profile editing, role changes, assignment history |
|
||||||
|
| `/admin/members/invite` | Invite new users | Bulk invite form with role selection |
|
||||||
|
| `/admin/programs` | Program list | Program cards, create/archive/edit |
|
||||||
|
| `/admin/programs/[id]` | Program detail | Program overview, linked pipelines, projects |
|
||||||
|
| `/admin/programs/[id]/edit` | Program settings editor | Name, year, status, settingsJson editor |
|
||||||
|
| `/admin/programs/[id]/apply-settings` | Application form config | Public submission form customization |
|
||||||
|
| `/admin/programs/[id]/mentorship` | Mentorship milestones | Milestone creation, completion tracking |
|
||||||
|
| `/admin/projects` | Project list | Searchable/filterable project table |
|
||||||
|
| `/admin/projects/[id]` | Project detail | Full project view with evaluations, history |
|
||||||
|
| `/admin/projects/[id]/edit` | Project editor | Edit project metadata, team, tags |
|
||||||
|
| `/admin/projects/[id]/mentor` | Mentor assignment | Assign/remove mentor, view messages |
|
||||||
|
| `/admin/projects/new` | Manual project creation | Add project without public form |
|
||||||
|
| `/admin/projects/import` | CSV/Typeform/Notion import | Bulk import wizard |
|
||||||
|
| `/admin/projects/pool` | Unassigned project pool | Projects awaiting assignment |
|
||||||
|
| `/admin/rounds/pipelines` | Pipeline list | All pipelines across programs |
|
||||||
|
| `/admin/rounds/pipeline/[id]` | Pipeline detail | Track/stage tree, project flow diagram |
|
||||||
|
| `/admin/rounds/pipeline/[id]/edit` | Pipeline settings | Name, slug, status, settingsJson |
|
||||||
|
| `/admin/rounds/pipeline/[id]/wizard` | Pipeline wizard | Step-by-step configuration UI (tracks, stages, transitions) |
|
||||||
|
| `/admin/rounds/pipeline/[id]/advanced` | Advanced pipeline editor | JSON config editor, raw transitions |
|
||||||
|
| `/admin/rounds/new-pipeline` | Pipeline creation wizard | Multi-step pipeline setup |
|
||||||
|
| `/admin/awards` | Special awards list | Award cards with status |
|
||||||
|
| `/admin/awards/[id]` | Award detail | Eligibility, votes, results |
|
||||||
|
| `/admin/awards/[id]/edit` | Award editor | Criteria, voting config, jury panel |
|
||||||
|
| `/admin/awards/new` | Create award | Award creation form |
|
||||||
|
| `/admin/mentors` | Mentor list | All users with MENTOR role |
|
||||||
|
| `/admin/mentors/[id]` | Mentor detail | Assigned projects, notes, milestones |
|
||||||
|
| `/admin/learning` | Learning hub management | Resource list, upload, publish |
|
||||||
|
| `/admin/learning/[id]` | Resource detail/edit | Content editor (BlockNote), access logs |
|
||||||
|
| `/admin/learning/new` | Create resource | Upload or link external content |
|
||||||
|
| `/admin/partners` | Partner management | Partner list, logos, visibility |
|
||||||
|
| `/admin/partners/[id]` | Partner detail/edit | Edit partner info, upload logo |
|
||||||
|
| `/admin/partners/new` | Add partner | Partner creation form |
|
||||||
|
| `/admin/messages` | Messaging dashboard | Send bulk messages, view sent messages |
|
||||||
|
| `/admin/messages/templates` | Message templates | Template CRUD |
|
||||||
|
| `/admin/settings` | System settings | Category tabs, KV editor |
|
||||||
|
| `/admin/settings/tags` | Expertise tags | Tag taxonomy management |
|
||||||
|
| `/admin/settings/webhooks` | Webhook management | Webhook CRUD, delivery logs |
|
||||||
|
| `/admin/audit` | Audit log viewer | Searchable audit trail |
|
||||||
|
| `/admin/reports` | Analytics reports | Charts, exports, metrics |
|
||||||
|
| `/admin/reports/stages` | Stage-level reports | Per-stage assignment coverage, completion rates |
|
||||||
|
|
||||||
|
### 5.2 Jury Pages (`src/app/(jury)/jury/`)
|
||||||
|
|
||||||
|
| Route | Purpose | Key Features |
|
||||||
|
|-------|---------|--------------|
|
||||||
|
| `/jury` | Jury dashboard | Assigned stages, pending evaluations, deadlines |
|
||||||
|
| `/jury/stages` | Jury stage list | Stages where user has assignments |
|
||||||
|
| `/jury/stages/[stageId]/assignments` | Assignment list for stage | Projects assigned to this user |
|
||||||
|
| `/jury/stages/[stageId]/projects/[projectId]` | Project detail view | Full project info, files, team |
|
||||||
|
| `/jury/stages/[stageId]/projects/[projectId]/evaluate` | Evaluation form | Criterion scoring, feedback, submit |
|
||||||
|
| `/jury/stages/[stageId]/projects/[projectId]/evaluation` | View submitted evaluation | Read-only evaluation, edit if not locked |
|
||||||
|
| `/jury/stages/[stageId]/compare` | Side-by-side comparison | Compare multiple projects, scoring matrix |
|
||||||
|
| `/jury/stages/[stageId]/live` | Live voting interface | Real-time voting during live ceremony |
|
||||||
|
| `/jury/awards` | Special awards list | Awards where user is juror |
|
||||||
|
| `/jury/awards/[id]` | Award voting | View eligible projects, cast votes |
|
||||||
|
| `/jury/learning` | Learning hub (jury access) | Resources for jury members |
|
||||||
|
|
||||||
|
### 5.3 Applicant Pages (`src/app/(applicant)/applicant/`)
|
||||||
|
|
||||||
|
| Route | Purpose | Key Features |
|
||||||
|
|-------|---------|--------------|
|
||||||
|
| `/applicant` | Applicant dashboard | Application status, next steps |
|
||||||
|
| `/applicant/pipeline` | Pipeline progress view | Visual pipeline with current stage |
|
||||||
|
| `/applicant/pipeline/[stageId]/status` | Stage detail view | Stage status, requirements, deadlines |
|
||||||
|
| `/applicant/pipeline/[stageId]/documents` | Document upload | Upload required files for stage |
|
||||||
|
| `/applicant/documents` | All documents | Document library, versions |
|
||||||
|
| `/applicant/team` | Team management | Add/remove team members, roles |
|
||||||
|
| `/applicant/mentor` | Mentorship dashboard | Chat with mentor, milestones |
|
||||||
|
|
||||||
|
### 5.4 Auth Pages (`src/app/(auth)/`)
|
||||||
|
|
||||||
|
| Route | Purpose | Key Features |
|
||||||
|
|-------|---------|--------------|
|
||||||
|
| `/login` | Login page | Email magic link + password login |
|
||||||
|
| `/verify` | Magic link verification | Token verification, auto-login |
|
||||||
|
| `/verify-email` | Email verification | Verify email after signup |
|
||||||
|
| `/accept-invite` | Invitation acceptance | One-click invite token handling |
|
||||||
|
| `/set-password` | Password setup | First-time password creation |
|
||||||
|
| `/onboarding` | User onboarding wizard | Profile completion, expertise tags |
|
||||||
|
| `/error` | Auth error page | Error display with retry |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Strengths of Current System
|
||||||
|
|
||||||
|
### 6.1 Architecture Strengths
|
||||||
|
|
||||||
|
| Strength | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| **Full Type Safety** | End-to-end TypeScript from DB → tRPC → React. Prisma generates types, tRPC enforces them, components consume them safely. |
|
||||||
|
| **Atomic Transactions** | All critical operations (stage transitions, filtering, assignments) use `$transaction` with proper rollback. |
|
||||||
|
| **Comprehensive Audit** | Dual audit system: `AuditLog` for general activity, `DecisionAuditLog` for pipeline decisions. Full traceability. |
|
||||||
|
| **RBAC Enforcement** | tRPC middleware hierarchy (`adminProcedure`, `juryProcedure`, etc.) enforces role-based access at API level. |
|
||||||
|
| **GDPR Compliance** | All AI calls strip PII via `anonymization.ts`. No personal data sent to OpenAI. |
|
||||||
|
| **Event-Driven Design** | `stage-notifications.ts` emits events on every pipeline action. Notifications never block core operations (catch all errors). |
|
||||||
|
| **Graceful AI Error Handling** | `ai-errors.ts` classifies errors (rate limit, token limit, API down) and provides retry guidance. AI failures never crash the system. |
|
||||||
|
| **Duplicate Detection** | Built-in duplicate submission detection in `stage-filtering.ts` (by email). Always flags for manual review, never auto-rejects. |
|
||||||
|
|
||||||
|
### 6.2 Data Model Strengths
|
||||||
|
|
||||||
|
| Strength | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| **Flexible Pipeline Model** | Pipeline → Track → Stage → ProjectStageState allows arbitrary round structures. Main track + multiple award tracks supported. |
|
||||||
|
| **Guard-Based Transitions** | StageTransition `guardJson` field allows complex conditional routing (e.g., "only advance if avgScore >= 7"). |
|
||||||
|
| **Stage Config Polymorphism** | `Stage.configJson` adapts to `stageType`. FILTER stages have filtering config, EVALUATION stages have evaluation config, etc. |
|
||||||
|
| **Versioned Evaluations** | `Evaluation.version` field allows rollback (though not currently used). |
|
||||||
|
| **Override Audit Trail** | `OverrideAction` model logs all admin overrides with reason codes. Immutable audit. |
|
||||||
|
|
||||||
|
### 6.3 Service Layer Strengths
|
||||||
|
|
||||||
|
| Strength | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| **State Machine Isolation** | `stage-engine.ts` is the ONLY service that modifies `ProjectStageState`. All transitions go through it. Single source of truth. |
|
||||||
|
| **Service Purity** | Services are pure functions that accept Prisma client as parameter. Testable without mocking globals. |
|
||||||
|
| **Progress Tracking** | Long-running operations (filtering, assignment, tagging) use Job models (`FilteringJob`, `AssignmentJob`, `TaggingJob`) for progress tracking. |
|
||||||
|
| **AI Batching** | All AI services batch projects (20-50 per call) to reduce API cost and latency. |
|
||||||
|
|
||||||
|
### 6.4 UX Strengths
|
||||||
|
|
||||||
|
| Strength | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| **Wizard-Driven Setup** | Pipeline wizard (`/admin/rounds/pipeline/[id]/wizard`) guides admins through complex configuration. |
|
||||||
|
| **Real-Time Live Control** | `/jury/stages/[stageId]/live` provides live voting with cursor sync via `LiveProgressCursor`. |
|
||||||
|
| **Notification Center** | In-app notification bell with grouping, priorities, expiration. |
|
||||||
|
| **Grace Period UX** | Admins can grant individual deadline extensions with reason tracking. |
|
||||||
|
| **Filtering Manual Queue** | Flagged projects go to dedicated review queue (`/admin/rounds/pipeline/[id]/filtering/manual`) for admin decision. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Weaknesses of Current System
|
||||||
|
|
||||||
|
### 7.1 Data Model Issues
|
||||||
|
|
||||||
|
| Issue | Description | Impact |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| **Legacy `roundId` Fields** | 50+ models have `roundId String?` (marked "Legacy — kept for historical data"). Adds noise, not enforced. | Confusing for new developers. No FK constraints. |
|
||||||
|
| **Unclear Pipeline Lifecycle** | Pipeline has `status` enum (`DRAFT`, `ACTIVE`, `ARCHIVED`), but no enforcement. Active pipelines can have draft stages. | Inconsistent state possible. |
|
||||||
|
| **Overlapping Voting Models** | `LiveVotingSession` (old) and `Cohort` + `LiveProgressCursor` (new) both exist. Unclear which to use. | Duplicate functionality, confusion. |
|
||||||
|
| **No PSS Validation Constraints** | `ProjectStageState` allows multiple active (non-exited) records for same project/track/stage combo. Should be unique. | Data integrity risk. |
|
||||||
|
| **Track-Award Linkage Vague** | `SpecialAward.trackId` is optional. Unclear if awards MUST have a track or can exist independently. | Ambiguous design. |
|
||||||
|
|
||||||
|
### 7.2 Service Layer Issues
|
||||||
|
|
||||||
|
| Issue | Description | Impact |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| **Mixed Abstraction Levels** | `stage-filtering.ts` contains both high-level orchestration AND low-level rule evaluation. Hard to test individually. | Tight coupling. |
|
||||||
|
| **Notification Side Effects** | Services call `stage-notifications.ts` directly. If notification fails (e.g., email down), error is swallowed. | Lost notifications, no visibility. |
|
||||||
|
| **AI Service Duplication** | `ai-filtering.ts`, `ai-assignment.ts`, `ai-tagging.ts` all have similar batching/retry logic. Should be abstracted. | Code duplication. |
|
||||||
|
| **No Explicit Workflow Engine** | Stage transitions are ad-hoc. No central workflow definition. Must read code to understand flow. | Hard to visualize, modify. |
|
||||||
|
|
||||||
|
### 7.3 tRPC Router Issues
|
||||||
|
|
||||||
|
| Issue | Description | Impact |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| **Router Bloat** | `project.ts` has 25+ procedures. `user.ts` has 20+. Hard to navigate. | Monolithic routers. |
|
||||||
|
| **Inconsistent Naming** | `stage.ts` has `updateConfig`, `stage-filtering.ts` router has `updateRule`. Naming conventions vary. | Confusing API. |
|
||||||
|
| **No Batch Procedures** | Most CRUD operations are one-at-a-time. No bulk create/update/delete (except assignments). | N+1 queries in UI. |
|
||||||
|
| **Missing Pagination** | List procedures (`project.list`, `user.list`) return all records. No cursor or offset pagination. | Performance issue at scale. |
|
||||||
|
|
||||||
|
### 7.4 UI/UX Issues
|
||||||
|
|
||||||
|
| Issue | Description | Impact |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| **No Pipeline Visualization** | Pipeline detail page shows table of stages, not a flowchart. Hard to see transitions. | Poor admin UX. |
|
||||||
|
| **Filtering Manual Queue Hidden** | Flagged projects not prominently surfaced. Admin must navigate deep into pipeline detail. | Flagged items forgotten. |
|
||||||
|
| **No Bulk Actions** | Can't bulk-assign projects, bulk-approve evaluations, bulk-transition projects. Must click one-by-one. | Tedious admin work. |
|
||||||
|
| **Live Voting Lacks Feedback** | Jury votes during live event but doesn't see if vote was counted. No confirmation toast. | Uncertainty. |
|
||||||
|
| **No Undo** | All admin actions (delete pipeline, archive stage, reject project) are immediate. No soft delete or undo. | Risky operations. |
|
||||||
|
|
||||||
|
### 7.5 Missing Features
|
||||||
|
|
||||||
|
| Missing Feature | Description | Impact |
|
||||||
|
|-----------------|-------------|--------|
|
||||||
|
| **Stage Dependency Graph** | No visual representation of stage transitions and guards. Admin must infer from transitions table. | Hard to debug routing. |
|
||||||
|
| **Evaluation Calibration** | No juror calibration (e.g., flag jurors who score 10x higher/lower than peers). | Scoring bias undetected. |
|
||||||
|
| **Award Winner Tie-Breaking** | `SpecialAward.tieBreakerMethod` exists in `LiveVotingSession` but not in `SpecialAward`. No tie resolution for ranked awards. | Undefined behavior on ties. |
|
||||||
|
| **Project Search Ranking** | Project search is basic string match. No relevance ranking, fuzzy matching, or faceted filters. | Poor search UX. |
|
||||||
|
| **Stage Templates** | No template system for common stage configs (e.g., "Standard 3-juror evaluation stage"). | Repetitive setup. |
|
||||||
|
| **Notification Preferences** | Users can toggle email on/off globally but not per event type. No granular control. | All-or-nothing notifications. |
|
||||||
|
| **Pipeline Cloning** | No way to duplicate a pipeline for a new year/program. Must recreate manually. | Time-consuming setup. |
|
||||||
|
| **Evaluation Rubric Library** | Each stage creates evaluation forms from scratch. No reusable rubrics. | Reinventing the wheel. |
|
||||||
|
|
||||||
|
### 7.6 Code Quality Issues
|
||||||
|
|
||||||
|
| Issue | Description | Impact |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| **Inconsistent Error Messages** | Some procedures throw `TRPCError` with clear messages, others just throw generic Error. | Debugging harder. |
|
||||||
|
| **No Input Sanitization** | Zod validates types but doesn't trim strings, lowercase emails, etc. | Data inconsistency. |
|
||||||
|
| **Magic Numbers** | Hardcoded constants (e.g., `AI_CONFIDENCE_THRESHOLD_PASS = 0.75`) scattered across services. | Hard to tune. |
|
||||||
|
| **Limited Test Coverage** | Only `stage-engine.test.ts` exists. No tests for filtering, assignment, AI services. | Regression risk. |
|
||||||
|
| **No API Versioning** | tRPC routers have no version prefix. Breaking changes would break old clients. | API fragility. |
|
||||||
|
|
||||||
|
### 7.7 Performance Issues
|
||||||
|
|
||||||
|
| Issue | Description | Impact |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| **N+1 Queries** | Project list page loads projects, then fetches assignments for each in a loop. | Slow page load. |
|
||||||
|
| **No Caching** | Every tRPC call hits database. No Redis, no in-memory cache. | High DB load. |
|
||||||
|
| **Unindexed Joins** | Some `ProjectStageState` queries join on `(projectId, trackId)` without composite index. | Slow at scale. |
|
||||||
|
| **AI Batching Non-Optimal** | AI services batch by count (20 projects) not by token size. Large projects can exceed token limits. | API errors. |
|
||||||
|
|
||||||
|
### 7.8 Documentation Issues
|
||||||
|
|
||||||
|
| Issue | Description | Impact |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| **No Architecture Docs** | No high-level system overview. New developers must read code. | Steep onboarding. |
|
||||||
|
| **Minimal JSDoc** | Most services have file-level comments but not function-level. | Hard to use without reading implementation. |
|
||||||
|
| **No API Reference** | tRPC procedures not documented in OpenAPI or similar. | Client integration difficult. |
|
||||||
|
| **No Runbook** | No operational docs for common tasks (e.g., "How to fix a stuck pipeline"). | Manual troubleshooting. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary Statistics
|
||||||
|
|
||||||
|
| Category | Count |
|
||||||
|
|----------|-------|
|
||||||
|
| **Database Models** | 73 |
|
||||||
|
| **Enums** | 31 |
|
||||||
|
| **Service Files** | 20 |
|
||||||
|
| **tRPC Routers** | 38 |
|
||||||
|
| **tRPC Procedures** | ~400 |
|
||||||
|
| **Admin Pages** | 45 |
|
||||||
|
| **Jury Pages** | 11 |
|
||||||
|
| **Applicant Pages** | 7 |
|
||||||
|
| **Auth Pages** | 7 |
|
||||||
|
| **Total Distinct Routes** | ~70 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: Service Function Inventory
|
||||||
|
|
||||||
|
### stage-engine.ts
|
||||||
|
- `evaluateGuardCondition()` - Evaluates a single guard condition
|
||||||
|
- `evaluateGuard()` - Evaluates guard config with AND/OR logic
|
||||||
|
- `validateTransition()` - Checks if transition is allowed (PSS exists, transition defined, stage active, window open, guards pass)
|
||||||
|
- `executeTransition()` - Atomically transitions a project between stages (exits source PSS, creates/updates dest PSS, logs in DecisionAuditLog + AuditLog)
|
||||||
|
- `executeBatchTransition()` - Batch wrapper around executeTransition (processes 50 at a time)
|
||||||
|
|
||||||
|
### stage-filtering.ts
|
||||||
|
- `evaluateFieldCondition()` - Evaluates a single field-based rule condition
|
||||||
|
- `evaluateFieldRule()` - Evaluates field-based rule with AND/OR logic
|
||||||
|
- `evaluateDocumentCheck()` - Checks if project has required files
|
||||||
|
- `bandByConfidence()` - AI confidence thresholding (0.75+ = PASSED, 0.25- = FILTERED_OUT, else FLAGGED)
|
||||||
|
- `runStageFiltering()` - Main orchestration: loads projects, rules, runs deterministic then AI, saves FilteringResults, creates FilteringJob
|
||||||
|
- `resolveManualDecision()` - Admin resolves a FLAGGED result to PASSED or FILTERED_OUT, logs override
|
||||||
|
- `getManualQueue()` - Returns all FLAGGED results for a stage
|
||||||
|
|
||||||
|
### stage-assignment.ts
|
||||||
|
- `calculateTagOverlapScore()` - Counts matching tags between juror and project (max 40 points)
|
||||||
|
- `calculateWorkloadScore()` - Scores juror based on current load vs preferred (max 25 points)
|
||||||
|
- `previewStageAssignment()` - Dry run: scores all juror-project pairs, returns top N per project
|
||||||
|
- `executeStageAssignment()` - Creates Assignment records, logs in AssignmentJob
|
||||||
|
- `getCoverageReport()` - Returns per-project review counts, per-juror assignment counts
|
||||||
|
- `rebalance()` - Identifies overloaded/underloaded jurors, suggests reassignments
|
||||||
|
|
||||||
|
### stage-notifications.ts
|
||||||
|
- `emitStageEvent()` - Core event producer: creates DecisionAuditLog, checks NotificationPolicy, creates InAppNotification, sends email (never throws)
|
||||||
|
- `resolveRecipients()` - Determines who gets notified based on event type (admins, jury, etc.)
|
||||||
|
- `buildNotificationMessage()` - Builds human-readable message from event details
|
||||||
|
- `onStageTransitioned()` - Convenience wrapper for stage.transitioned event
|
||||||
|
- `onFilteringCompleted()` - Convenience wrapper for filtering.completed event
|
||||||
|
- `onAssignmentGenerated()` - Convenience wrapper for assignment.generated event
|
||||||
|
- `onCursorUpdated()` - Convenience wrapper for live.cursor_updated event
|
||||||
|
- `onDecisionOverridden()` - Convenience wrapper for decision.overridden event
|
||||||
|
|
||||||
|
### live-control.ts
|
||||||
|
- `generateSessionId()` - Creates unique session ID (timestamp + random)
|
||||||
|
- `startSession()` - Creates/resets LiveProgressCursor, sets first project active
|
||||||
|
- `setActiveProject()` - Updates cursor to point to a specific project (validates project is in cohort)
|
||||||
|
- `jumpToProject()` - Jumps to project by order index
|
||||||
|
- `reorderQueue()` - Updates CohortProject sortOrder values in batch
|
||||||
|
- `pauseResume()` - Toggles cursor pause state
|
||||||
|
- `openCohortWindow()` - Opens voting window for a cohort (sets isOpen=true, windowOpenAt=now)
|
||||||
|
- `closeCohortWindow()` - Closes voting window for a cohort (sets isOpen=false, windowCloseAt=now)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**End of Document**
|
||||||
|
|
@ -0,0 +1,786 @@
|
||||||
|
# Gap Analysis: Current System vs. Target 8-Step Competition Flow
|
||||||
|
|
||||||
|
**Document Version:** 1.0
|
||||||
|
**Date:** 2026-02-15
|
||||||
|
**Author:** Architecture Review (Claude)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This gap analysis compares the **current MOPC platform** (pipeline-based, stage-engine architecture) against the **target 8-step competition flow** required for the 2026 Monaco Ocean Protection Challenge.
|
||||||
|
|
||||||
|
**Key Findings:**
|
||||||
|
- **Foundation is Strong**: Pipeline/Track/Stage architecture, stage-engine transitions, AI filtering, jury assignment, and live voting infrastructure are all in place.
|
||||||
|
- **Critical Gaps**: Multi-jury support (named jury groups with overlap), multi-round submission windows with read-only enforcement, per-juror capacity constraints (hard cap vs soft cap + buffer), category ratio preferences, countdown timers, and mentoring workspace features are **missing or incomplete**.
|
||||||
|
- **Integration Gaps**: The current system treats each stage independently; the target flow requires **cross-stage coordination** (e.g., Round 1 docs become read-only in Round 2, jury sees cumulative files).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Feature-by-Feature Comparison Table](#1-feature-by-feature-comparison-table)
|
||||||
|
2. [Per-Step Deep Analysis](#2-per-step-deep-analysis)
|
||||||
|
3. [Cross-Cutting Gap Analysis](#3-cross-cutting-gap-analysis)
|
||||||
|
4. [Integration Gaps](#4-integration-gaps)
|
||||||
|
5. [Priority Matrix](#5-priority-matrix)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Feature-by-Feature Comparison Table
|
||||||
|
|
||||||
|
| Feature | Required by Flow | Current Status | Gap Level | Notes | File References |
|
||||||
|
|---------|-----------------|----------------|-----------|-------|-----------------|
|
||||||
|
| **Intake (Submission Round 1)** |
|
||||||
|
| Public submission form | Applicants upload Round 1 docs, deadline enforcement | ✅ Exists | **None** | `applicantRouter.saveSubmission()` handles create/update, deadline checked via `Stage.windowCloseAt` | `src/server/routers/applicant.ts:126` |
|
||||||
|
| Configurable deadline behavior | Grace periods, late submission flags | ✅ Exists | **None** | `GracePeriod` model, `isLate` flag on `ProjectFile` | `prisma/schema.prisma:703-728`, `ProjectFile.isLate:606` |
|
||||||
|
| File requirements per stage | Specify required file types, max size, mime types | ✅ Exists | **None** | `FileRequirement` model linked to stages | `prisma/schema.prisma:569-588` |
|
||||||
|
| Draft support | Save progress without submitting | ✅ Exists | **None** | `isDraft`, `draftDataJson`, `draftExpiresAt` on `Project` | `prisma/schema.prisma:528-530` |
|
||||||
|
| **AI Filtering** |
|
||||||
|
| Automated eligibility screening | Run deterministic + AI rules, band by confidence | ✅ Exists | **None** | `stage-filtering.ts` with banding logic, `FilteringResult` outcome | `src/server/services/stage-filtering.ts:173-191` |
|
||||||
|
| Admin override capability | Manually resolve flagged projects | ✅ Exists | **None** | `resolveManualDecision()` updates `finalOutcome`, logs override in `OverrideAction` | `src/server/services/stage-filtering.ts:529-611` |
|
||||||
|
| Duplicate detection | Flag duplicate submissions (same email) | ✅ Exists | **None** | Built-in duplicate check by `submittedByEmail`, always flags (never auto-rejects) | `src/server/services/stage-filtering.ts:267-289` |
|
||||||
|
| **Jury 1 (Evaluation Round 1)** |
|
||||||
|
| Semi-finalist selection | Jury evaluates and votes Yes/No | ✅ Exists | **None** | `Evaluation.binaryDecision` field, evaluation submission flow | `src/server/routers/evaluation.ts:130-200` |
|
||||||
|
| Hard cap per juror | Max N projects per juror (enforced) | ⚠️ **Partial** | **Partial** | `User.maxAssignments` exists but used as global limit, not stage-specific hard cap | `prisma/schema.prisma:249` |
|
||||||
|
| Soft cap + buffer | Target N, allow up to N+buffer with warning | ❌ **Missing** | **Missing** | No concept of soft cap vs hard cap, no buffer configuration | — |
|
||||||
|
| Category ratio preferences per juror | Juror wants X% Startup / Y% Concept | ❌ **Missing** | **Missing** | No `User.preferredCategoryRatio` or equivalent | — |
|
||||||
|
| Explicit Jury 1 group | Named jury entity with members | ❌ **Missing** | **Missing** | All JURY_MEMBER users are global pool, no stage-scoped jury groups | — |
|
||||||
|
| **Semi-finalist Submission (Submission Round 2)** |
|
||||||
|
| New doc requirements | Round 2 has different file requirements | ✅ Exists | **None** | Each stage can have its own `FileRequirement` list | `prisma/schema.prisma:569-588` |
|
||||||
|
| Round 1 docs become read-only | Applicants can't edit/delete Round 1 files | ❌ **Missing** | **Missing** | No `ProjectFile.isReadOnly` or `FileRequirement.allowEdits` field | — |
|
||||||
|
| Jury sees both rounds | Jury can access Round 1 + Round 2 files | ⚠️ **Partial** | **Partial** | File access checks in `fileRouter.getDownloadUrl()` allow prior stages but complex logic, no explicit "cumulative view" | `src/server/routers/file.ts:66-108` |
|
||||||
|
| Multi-round submission windows | Distinct open/close dates for Round 1 vs Round 2 | ✅ Exists | **None** | Each stage has `windowOpenAt` / `windowCloseAt` | `prisma/schema.prisma:1888-1889` |
|
||||||
|
| **Jury 2 (Evaluation Round 2)** |
|
||||||
|
| Finalist selection | Jury evaluates semifinalists, selects finalists | ✅ Exists | **None** | Same evaluation flow, can configure different form per stage | `prisma/schema.prisma:450-472` |
|
||||||
|
| Special awards alongside | Run award eligibility + voting in parallel | ✅ Exists | **None** | `SpecialAward` system with `AwardEligibility`, `AwardJuror`, `AwardVote` | `prisma/schema.prisma:1363-1481` |
|
||||||
|
| Explicit Jury 2 group | Named jury entity, possibly overlapping with Jury 1 | ❌ **Missing** | **Missing** | Same global jury pool issue | — |
|
||||||
|
| Same cap/ratio features | Per-juror hard cap, soft cap, category ratios | ❌ **Missing** | **Missing** | (Same as Jury 1) | — |
|
||||||
|
| **Mentoring** |
|
||||||
|
| Private mentor-team workspace | Chat, file upload, threaded discussions | ⚠️ **Partial** | **Partial** | `MentorMessage` exists but no threading, no file comments, no promotion mechanism | `prisma/schema.prisma:1577-1590` |
|
||||||
|
| Mentor file upload | Mentor can upload files to project | ❌ **Missing** | **Missing** | No `ProjectFile.uploadedByMentorId` or mentor file upload router endpoint | — |
|
||||||
|
| Threaded file comments | Comment on specific files with replies | ❌ **Missing** | **Missing** | No `FileComment` model | — |
|
||||||
|
| File promotion to official submission | Mentor-uploaded file becomes part of official docs | ❌ **Missing** | **Missing** | No promotion workflow or `ProjectFile.promotedFromMentorFileId` | — |
|
||||||
|
| **Jury 3 Live Finals** |
|
||||||
|
| Stage manager admin controls | Cursor navigation, pause/resume, queue reorder | ✅ Exists | **None** | `live-control.ts` service with `LiveProgressCursor`, `Cohort` | `src/server/services/live-control.ts:1-619` |
|
||||||
|
| Jury live voting with notes | Vote during presentation, add notes | ⚠️ **Partial** | **Partial** | `LiveVote` exists but no `notes` field for per-vote commentary | `prisma/schema.prisma:1073-1099` |
|
||||||
|
| Audience voting | Audience can vote with configurable weight | ✅ Exists | **None** | `AudienceVoter`, `allowAudienceVotes`, `audienceVoteWeight` | `prisma/schema.prisma:1051-1060, 1101-1117` |
|
||||||
|
| Deliberation period | Time for jury discussion before final vote | ❌ **Missing** | **Missing** | No stage-specific `deliberationDurationMinutes` or deliberation status | — |
|
||||||
|
| Explicit Jury 3 group | Named jury entity for live finals | ❌ **Missing** | **Missing** | (Same global pool issue) | — |
|
||||||
|
| **Winner Confirmation** |
|
||||||
|
| Individual jury member confirmation | Each juror digitally signs off on results | ❌ **Missing** | **Missing** | No `JuryConfirmation` model or per-user signature workflow | — |
|
||||||
|
| Admin override to force majority | Admin can override and pick winner | ⚠️ **Partial** | **Partial** | `SpecialAward.winnerOverridden` exists, `OverrideAction` logs admin actions, but no explicit "force majority" vs "choose winner" distinction | `prisma/schema.prisma:1388-1389, 2024-2040` |
|
||||||
|
| Results frozen with audit trail | Immutable record of final decision | ⚠️ **Partial** | **Partial** | `DecisionAuditLog` exists, `OverrideAction` tracks changes, but no `ResultsSnapshot` or explicit freeze mechanism | `prisma/schema.prisma:2042-2057` |
|
||||||
|
| **Cross-Cutting Features** |
|
||||||
|
| Multi-jury support (named entities) | Jury 1, Jury 2, Jury 3 with overlapping members | ❌ **Missing** | **Missing** | No `JuryGroup` or `JuryMembership` model | — |
|
||||||
|
| Countdown timers on dashboards | Show time remaining until deadline | ❌ **Missing** | **Missing** | Backend has `windowCloseAt` but no tRPC endpoint for countdown state | — |
|
||||||
|
| Email reminders as deadlines approach | Automated reminders at 72h, 24h, 1h | ⚠️ **Partial** | **Partial** | `processEvaluationReminders()` exists for jury, `ReminderLog` tracks sent reminders, but no applicant deadline reminders | `prisma/schema.prisma:1487-1501` |
|
||||||
|
| Full audit trail for all decisions | Every action logged, immutable | ✅ Exists | **None** | `DecisionAuditLog`, `OverrideAction`, `AuditLog` comprehensive | `prisma/schema.prisma:754-783, 2024-2057` |
|
||||||
|
|
||||||
|
**Legend:**
|
||||||
|
- ✅ **Exists** = Feature fully implemented
|
||||||
|
- ⚠️ **Partial** = Feature partially implemented, needs extension
|
||||||
|
- ❌ **Missing** = Feature does not exist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Per-Step Deep Analysis
|
||||||
|
|
||||||
|
### Step 1: Intake (Submission Round 1)
|
||||||
|
|
||||||
|
**What the Flow Requires:**
|
||||||
|
- Applicants submit initial docs (executive summary, pitch deck, video)
|
||||||
|
- Public submission form with deadline enforcement
|
||||||
|
- Configurable grace periods for late submissions
|
||||||
|
- Draft support to save progress without submitting
|
||||||
|
- File type/size validation per requirement
|
||||||
|
|
||||||
|
**What Currently Exists:**
|
||||||
|
- ✅ **Public submission form**: `applicantRouter.saveSubmission()` creates/updates projects, `isDraft` flag allows partial saves
|
||||||
|
- ✅ **Deadline enforcement**: `Stage.windowOpenAt` / `windowCloseAt` enforced in `evaluationRouter.submit()` and applicant submission logic
|
||||||
|
- ✅ **Grace periods**: `GracePeriod` model per stage/user, `extendedUntil` overrides default deadline
|
||||||
|
- ✅ **File requirements**: `FileRequirement` linked to stages, defines `acceptedMimeTypes`, `maxSizeMB`, `isRequired`
|
||||||
|
- ✅ **Late submission tracking**: `ProjectFile.isLate` flag set if uploaded after deadline
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
- (None — intake is fully functional)
|
||||||
|
|
||||||
|
**What Needs Modification:**
|
||||||
|
- (None — intake meets requirements)
|
||||||
|
|
||||||
|
**File References:**
|
||||||
|
- `src/server/routers/applicant.ts:126-200` (saveSubmission)
|
||||||
|
- `prisma/schema.prisma:569-588` (FileRequirement)
|
||||||
|
- `prisma/schema.prisma:703-728` (GracePeriod)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 2: AI Filtering
|
||||||
|
|
||||||
|
**What the Flow Requires:**
|
||||||
|
- Automated eligibility screening using deterministic rules (field checks, doc checks) + AI rubric
|
||||||
|
- Confidence banding: high confidence auto-pass, low confidence auto-reject, medium confidence flagged for manual review
|
||||||
|
- Admin override capability to resolve flagged projects
|
||||||
|
- Duplicate submission detection (never auto-reject, always flag)
|
||||||
|
|
||||||
|
**What Currently Exists:**
|
||||||
|
- ✅ **Filtering service**: `stage-filtering.ts` runs deterministic rules first, then AI screening if deterministic passes
|
||||||
|
- ✅ **Confidence banding**: `bandByConfidence()` function with thresholds 0.75 (pass) / 0.25 (reject), middle = flagged
|
||||||
|
- ✅ **Manual queue**: `getManualQueue()` returns flagged projects, `resolveManualDecision()` sets `finalOutcome`
|
||||||
|
- ✅ **Duplicate detection**: Built-in check by `submittedByEmail`, groups duplicates, always flags (never auto-rejects)
|
||||||
|
- ✅ **FilteringResult model**: Stores `outcome` (PASSED/FILTERED_OUT/FLAGGED), `ruleResultsJson`, `aiScreeningJson`, `finalOutcome` after override
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
- (None — filtering is fully functional)
|
||||||
|
|
||||||
|
**What Needs Modification:**
|
||||||
|
- (None — filtering meets requirements)
|
||||||
|
|
||||||
|
**File References:**
|
||||||
|
- `src/server/services/stage-filtering.ts:1-647` (full filtering pipeline)
|
||||||
|
- `prisma/schema.prisma:1190-1237` (FilteringRule, FilteringResult)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 3: Jury 1 (Evaluation Round 1)
|
||||||
|
|
||||||
|
**What the Flow Requires:**
|
||||||
|
- Semi-finalist selection with hard/soft caps per juror
|
||||||
|
- Per-juror hard cap (e.g., max 20 projects, enforced)
|
||||||
|
- Per-juror soft cap + buffer (e.g., target 15, allow up to 18 with warning)
|
||||||
|
- Per-juror category ratio preferences (e.g., "I want 60% Startup / 40% Concept")
|
||||||
|
- Explicit Jury 1 group (named entity, distinct from Jury 2/Jury 3)
|
||||||
|
|
||||||
|
**What Currently Exists:**
|
||||||
|
- ✅ **Evaluation flow**: `evaluationRouter.submit()` accepts `binaryDecision` for yes/no semifinalist vote
|
||||||
|
- ✅ **Assignment system**: `stage-assignment.ts` generates assignments with workload balancing
|
||||||
|
- ⚠️ **Per-juror max**: `User.maxAssignments` exists but treated as global limit across all stages, not stage-specific hard cap
|
||||||
|
- ⚠️ **Workload scoring**: `calculateWorkloadScore()` in `stage-assignment.ts` uses `preferredWorkload` but not distinct soft vs hard cap
|
||||||
|
- ❌ **Soft cap + buffer**: No configuration for soft cap + buffer (e.g., target 15, allow up to 18)
|
||||||
|
- ❌ **Category ratio preferences**: No `User.preferredCategoryRatioJson` or similar field
|
||||||
|
- ❌ **Named jury groups**: All `JURY_MEMBER` users are a global pool, no `JuryGroup` model to create Jury 1, Jury 2, Jury 3 as separate entities
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
1. **Soft cap + buffer**: Need `User.targetAssignments` (soft cap) and `User.maxAssignments` (hard cap), with UI warning when juror is in buffer zone
|
||||||
|
2. **Category ratio preferences**: Need `User.preferredCategoryRatioJson: { STARTUP: 0.6, BUSINESS_CONCEPT: 0.4 }` and assignment scoring that respects ratios
|
||||||
|
3. **Named jury groups**: Need `JuryGroup` model with `name`, `stageId`, `members[]`, so assignment can be scoped to "Jury 1" vs "Jury 2"
|
||||||
|
|
||||||
|
**What Needs Modification:**
|
||||||
|
- **Assignment service**: Update `stage-assignment.ts` to:
|
||||||
|
- Filter jury pool by `JuryGroup.members` for the stage
|
||||||
|
- Check both soft cap (warning) and hard cap (reject) when assigning
|
||||||
|
- Score assignments based on `preferredCategoryRatioJson` to balance category distribution per juror
|
||||||
|
- **Schema**: Add `JuryGroup`, `JuryMembership`, modify `User` to have `targetAssignments` and `preferredCategoryRatioJson`
|
||||||
|
- **Admin UI**: Jury group management, per-juror cap/ratio configuration
|
||||||
|
|
||||||
|
**File References:**
|
||||||
|
- `src/server/services/stage-assignment.ts:1-777` (assignment algorithm)
|
||||||
|
- `src/server/routers/evaluation.ts:130-200` (evaluation submission)
|
||||||
|
- `prisma/schema.prisma:241-357` (User model)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 4: Semi-finalist Submission (Submission Round 2)
|
||||||
|
|
||||||
|
**What the Flow Requires:**
|
||||||
|
- New doc requirements (e.g., detailed business plan, updated pitch deck)
|
||||||
|
- Round 1 docs become **read-only** for applicants (no edit/delete)
|
||||||
|
- Jury sees **both rounds** (cumulative file view)
|
||||||
|
- Multi-round submission windows (Round 2 opens after Jury 1 closes)
|
||||||
|
|
||||||
|
**What Currently Exists:**
|
||||||
|
- ✅ **Multi-round file requirements**: Each stage can define its own `FileRequirement` list
|
||||||
|
- ✅ **Multi-round windows**: `Stage.windowOpenAt` / `windowCloseAt` per stage
|
||||||
|
- ⚠️ **Jury file access**: `fileRouter.getDownloadUrl()` checks if juror has assignment to project, allows access to files from prior stages in same track (lines 66-108), but logic is implicit and complex
|
||||||
|
- ❌ **Read-only enforcement**: No `ProjectFile.isReadOnly` or `FileRequirement.allowEdits` field
|
||||||
|
- ❌ **Cumulative view**: No explicit "show all files from all prior stages" flag on stages
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
1. **Read-only flag**: Need `ProjectFile.isReadOnlyForApplicant: Boolean` set when stage transitions, or `FileRequirement.allowEdits: Boolean` to control mutability
|
||||||
|
2. **Cumulative view**: Need `Stage.showPriorStageFiles: Boolean` or `Stage.cumulativeFileView: Boolean` to make jury file access explicit
|
||||||
|
3. **File versioning**: Current `replacedById` allows versioning but doesn't enforce read-only from prior rounds
|
||||||
|
|
||||||
|
**What Needs Modification:**
|
||||||
|
- **Applicant file upload**: Check `isReadOnlyForApplicant` before allowing delete/replace
|
||||||
|
- **File router**: Simplify jury file access by checking `Stage.cumulativeFileView` instead of complex prior-stage logic
|
||||||
|
- **Stage transition**: When project moves from Round 1 to Round 2, mark all Round 1 files as `isReadOnlyForApplicant: true`
|
||||||
|
- **Schema**: Add `ProjectFile.isReadOnlyForApplicant`, `Stage.cumulativeFileView`
|
||||||
|
|
||||||
|
**File References:**
|
||||||
|
- `src/server/routers/file.ts:12-125` (file download authorization)
|
||||||
|
- `prisma/schema.prisma:590-624` (ProjectFile)
|
||||||
|
- `prisma/schema.prisma:1879-1922` (Stage)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 5: Jury 2 (Evaluation Round 2)
|
||||||
|
|
||||||
|
**What the Flow Requires:**
|
||||||
|
- Finalist selection (same evaluation mechanics as Jury 1)
|
||||||
|
- Special awards eligibility + voting alongside main track
|
||||||
|
- Explicit Jury 2 group (named entity, may overlap with Jury 1)
|
||||||
|
- Same per-juror caps and category ratio features as Jury 1
|
||||||
|
|
||||||
|
**What Currently Exists:**
|
||||||
|
- ✅ **Evaluation flow**: Identical to Jury 1, `binaryDecision` for finalist vote
|
||||||
|
- ✅ **Special awards**: Full system with `SpecialAward`, `AwardEligibility`, `AwardJuror`, `AwardVote`, AI eligibility screening
|
||||||
|
- ✅ **Award tracks**: `Track.kind: AWARD` allows award-specific stages to run in parallel
|
||||||
|
- ❌ **Named Jury 2 group**: Same global jury pool issue as Jury 1
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
- (Same as Jury 1: named jury groups, soft cap + buffer, category ratio preferences)
|
||||||
|
|
||||||
|
**What Needs Modification:**
|
||||||
|
- (Same as Jury 1: jury group scoping, cap/ratio logic in assignment service)
|
||||||
|
|
||||||
|
**File References:**
|
||||||
|
- `src/server/routers/specialAward.ts:1-150` (award management)
|
||||||
|
- `prisma/schema.prisma:1363-1481` (award models)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 6: Mentoring
|
||||||
|
|
||||||
|
**What the Flow Requires:**
|
||||||
|
- Private mentor-team workspace with:
|
||||||
|
- Chat/messaging (already exists)
|
||||||
|
- Mentor file upload (mentor uploads docs for team to review)
|
||||||
|
- Threaded file comments (comment on specific files with replies)
|
||||||
|
- File promotion (mentor-uploaded file becomes part of official submission)
|
||||||
|
|
||||||
|
**What Currently Exists:**
|
||||||
|
- ✅ **Mentor assignment**: `MentorAssignment` model, AI-suggested matching, manual assignment
|
||||||
|
- ✅ **Mentor messages**: `MentorMessage` model for chat messages between mentor and team
|
||||||
|
- ❌ **Mentor file upload**: No `ProjectFile.uploadedByMentorId` or mentor file upload endpoint
|
||||||
|
- ❌ **Threaded file comments**: No `FileComment` model with `parentCommentId` for threading
|
||||||
|
- ❌ **File promotion**: No workflow to promote mentor-uploaded file to official project submission
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
1. **Mentor file upload**: Need `ProjectFile.uploadedByMentorId: String?`, extend `fileRouter.getUploadUrl()` to allow mentors to upload
|
||||||
|
2. **File comments**: Need `FileComment` model:
|
||||||
|
```prisma
|
||||||
|
model FileComment {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
fileId String
|
||||||
|
file ProjectFile @relation(...)
|
||||||
|
authorId String
|
||||||
|
author User @relation(...)
|
||||||
|
content String @db.Text
|
||||||
|
parentCommentId String?
|
||||||
|
parentComment FileComment? @relation("CommentReplies", ...)
|
||||||
|
replies FileComment[] @relation("CommentReplies")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. **File promotion**: Need `ProjectFile.promotedFromMentorFileId: String?` and a promotion workflow (admin/team approves mentor file as official doc)
|
||||||
|
|
||||||
|
**What Needs Modification:**
|
||||||
|
- **File router**: Add `mentorUploadFile` mutation, authorization check for mentor role
|
||||||
|
- **Mentor router**: Add `addFileComment`, `promoteFileToOfficial` mutations
|
||||||
|
- **Schema**: Add `FileComment`, modify `ProjectFile` to link mentor uploads and promotions
|
||||||
|
|
||||||
|
**File References:**
|
||||||
|
- `src/server/routers/mentor.ts:1-200` (mentor operations)
|
||||||
|
- `prisma/schema.prisma:1145-1172` (MentorAssignment)
|
||||||
|
- `prisma/schema.prisma:1577-1590` (MentorMessage)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 7: Jury 3 Live Finals
|
||||||
|
|
||||||
|
**What the Flow Requires:**
|
||||||
|
- Stage manager admin controls (cursor navigation, pause/resume, queue reorder) — **ALREADY EXISTS**
|
||||||
|
- Jury live voting with notes (vote + add commentary per vote)
|
||||||
|
- Audience voting — **ALREADY EXISTS**
|
||||||
|
- Deliberation period (pause for jury discussion before final vote)
|
||||||
|
- Explicit Jury 3 group (named entity for live finals)
|
||||||
|
|
||||||
|
**What Currently Exists:**
|
||||||
|
- ✅ **Live control service**: `live-control.ts` with `LiveProgressCursor`, session management, cursor navigation, queue reordering
|
||||||
|
- ✅ **Live voting**: `LiveVote` model, jury/audience voting, criteria-based scoring
|
||||||
|
- ✅ **Cohort management**: `Cohort` groups projects for voting windows
|
||||||
|
- ⚠️ **Vote notes**: `LiveVote` has no `notes` or `commentary` field for per-vote notes
|
||||||
|
- ❌ **Deliberation period**: No `Cohort.deliberationDurationMinutes` or deliberation status
|
||||||
|
- ❌ **Named Jury 3 group**: Same global jury pool issue
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
1. **Vote notes**: Add `LiveVote.notes: String?` for jury commentary during voting
|
||||||
|
2. **Deliberation period**: Add `Cohort.deliberationDurationMinutes: Int?`, `Cohort.deliberationStartedAt: DateTime?`, `Cohort.deliberationEndedAt: DateTime?`
|
||||||
|
3. **Named Jury 3 group**: (Same as Jury 1/Jury 2)
|
||||||
|
|
||||||
|
**What Needs Modification:**
|
||||||
|
- **LiveVote model**: Add `notes` field
|
||||||
|
- **Cohort model**: Add deliberation fields
|
||||||
|
- **Live voting router**: Add `startDeliberation()`, `endDeliberation()` procedures
|
||||||
|
- **Live control service**: Add deliberation status checks to prevent voting during deliberation
|
||||||
|
|
||||||
|
**File References:**
|
||||||
|
- `src/server/services/live-control.ts:1-619` (live session management)
|
||||||
|
- `src/server/routers/live-voting.ts:1-150` (live voting procedures)
|
||||||
|
- `prisma/schema.prisma:1035-1071, 1969-2006` (LiveVotingSession, Cohort)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 8: Winner Confirmation
|
||||||
|
|
||||||
|
**What the Flow Requires:**
|
||||||
|
- Individual jury member confirmation (each juror digitally signs off on results)
|
||||||
|
- Admin override to force majority or choose winner
|
||||||
|
- Results frozen with immutable audit trail
|
||||||
|
|
||||||
|
**What Currently Exists:**
|
||||||
|
- ⚠️ **Admin override**: `SpecialAward.winnerOverridden` flag, `OverrideAction` logs admin actions, but no explicit "force majority" vs "choose winner" distinction
|
||||||
|
- ⚠️ **Audit trail**: `DecisionAuditLog`, `OverrideAction` comprehensive, but no explicit `ResultsSnapshot` or freeze mechanism
|
||||||
|
- ❌ **Individual jury confirmation**: No `JuryConfirmation` model for per-user digital signatures
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
1. **Jury confirmation**: Need `JuryConfirmation` model:
|
||||||
|
```prisma
|
||||||
|
model JuryConfirmation {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
stageId String
|
||||||
|
stage Stage @relation(...)
|
||||||
|
userId String
|
||||||
|
user User @relation(...)
|
||||||
|
confirmedAt DateTime @default(now())
|
||||||
|
signature String // Digital signature or consent hash
|
||||||
|
ipAddress String?
|
||||||
|
userAgent String?
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. **Results freeze**: Need `Stage.resultsFrozenAt: DateTime?` to mark results as immutable
|
||||||
|
3. **Override modes**: Add `OverrideAction.overrideMode: Enum(FORCE_MAJORITY, CHOOSE_WINNER)` for clarity
|
||||||
|
|
||||||
|
**What Needs Modification:**
|
||||||
|
- **Live voting router**: Add `confirmResults()` procedure for jury members to sign off
|
||||||
|
- **Admin router**: Add `freezeResults()` procedure, check `resultsFrozenAt` before allowing further changes
|
||||||
|
- **Override service**: Update `OverrideAction` creation to include `overrideMode`
|
||||||
|
|
||||||
|
**File References:**
|
||||||
|
- `prisma/schema.prisma:1363-1418` (SpecialAward with winner override)
|
||||||
|
- `prisma/schema.prisma:2024-2040` (OverrideAction)
|
||||||
|
- `prisma/schema.prisma:2042-2057` (DecisionAuditLog)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Cross-Cutting Gap Analysis
|
||||||
|
|
||||||
|
### Multi-Jury Support (Named Jury Entities with Overlap)
|
||||||
|
|
||||||
|
**Requirement:**
|
||||||
|
- Create named jury groups (Jury 1, Jury 2, Jury 3) with explicit membership lists
|
||||||
|
- Allow jurors to be members of multiple groups (e.g., Juror A is in Jury 1 and Jury 3 but not Jury 2)
|
||||||
|
- Scope assignments, evaluations, and live voting to specific jury groups
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- All users with `role: JURY_MEMBER` are treated as a global pool
|
||||||
|
- No scoping of jury to specific stages or rounds
|
||||||
|
- `stage-assignment.ts` queries all active jury members without filtering by group
|
||||||
|
|
||||||
|
**Gap:**
|
||||||
|
- ❌ No `JuryGroup` model
|
||||||
|
- ❌ No `JuryMembership` model to link users to groups
|
||||||
|
- ❌ No stage-level configuration to specify which jury group evaluates that stage
|
||||||
|
|
||||||
|
**Required Schema Changes:**
|
||||||
|
```prisma
|
||||||
|
model JuryGroup {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
programId String
|
||||||
|
program Program @relation(...)
|
||||||
|
name String // "Jury 1", "Jury 2", "Jury 3"
|
||||||
|
description String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
memberships JuryMembership[]
|
||||||
|
stages Stage[] // One-to-many: stages can specify which jury group evaluates them
|
||||||
|
}
|
||||||
|
|
||||||
|
model JuryMembership {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
juryGroupId String
|
||||||
|
juryGroup JuryGroup @relation(...)
|
||||||
|
userId String
|
||||||
|
user User @relation(...)
|
||||||
|
joinedAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@unique([juryGroupId, userId])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend Stage model:
|
||||||
|
model Stage {
|
||||||
|
// ... existing fields
|
||||||
|
juryGroupId String?
|
||||||
|
juryGroup JuryGroup? @relation(...)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **High** — Affects assignment generation, evaluation authorization, live voting eligibility
|
||||||
|
- **Requires**: New admin UI for jury group management, updates to all jury-related queries/mutations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Multi-Round Submission Windows
|
||||||
|
|
||||||
|
**Requirement:**
|
||||||
|
- Distinct submission windows for Round 1 (Intake), Round 2 (Semi-finalist submission)
|
||||||
|
- Round 1 files become read-only after Round 1 closes
|
||||||
|
- Jury sees cumulative files from all prior rounds
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- ✅ Each stage has `windowOpenAt` / `windowCloseAt` (multi-round windows exist)
|
||||||
|
- ⚠️ File access is complex and implicit (checks prior stages in track but no clear flag)
|
||||||
|
- ❌ No read-only enforcement for applicants after stage transition
|
||||||
|
|
||||||
|
**Gap:**
|
||||||
|
- ❌ No `ProjectFile.isReadOnlyForApplicant` field
|
||||||
|
- ❌ No `Stage.cumulativeFileView` flag for jury access
|
||||||
|
- ❌ No automated mechanism to mark files as read-only on stage transition
|
||||||
|
|
||||||
|
**Required Schema Changes:**
|
||||||
|
```prisma
|
||||||
|
model ProjectFile {
|
||||||
|
// ... existing fields
|
||||||
|
isReadOnlyForApplicant Boolean @default(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
model Stage {
|
||||||
|
// ... existing fields
|
||||||
|
cumulativeFileView Boolean @default(false) // If true, jury sees files from all prior stages in track
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **Medium** — Affects file upload/delete authorization, jury file listing queries
|
||||||
|
- **Requires**: Stage transition hook to mark files as read-only, applicant file UI updates, jury file view updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Per-Juror Hard Cap vs Soft Cap + Buffer
|
||||||
|
|
||||||
|
**Requirement:**
|
||||||
|
- **Hard cap**: Max N projects (e.g., 20), enforced, cannot exceed
|
||||||
|
- **Soft cap**: Target N projects (e.g., 15), preferred, can exceed with warning
|
||||||
|
- **Buffer**: Soft cap to hard cap range (e.g., 15-18), shows warning in UI
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- ⚠️ `User.maxAssignments` exists but treated as global hard cap
|
||||||
|
- ⚠️ `User.preferredWorkload` used in assignment scoring but not enforced as soft cap
|
||||||
|
- ❌ No buffer concept, no UI warning when juror is over target
|
||||||
|
|
||||||
|
**Gap:**
|
||||||
|
- ❌ No distinction between soft cap and hard cap
|
||||||
|
- ❌ No buffer configuration or warning mechanism
|
||||||
|
|
||||||
|
**Required Schema Changes:**
|
||||||
|
```prisma
|
||||||
|
model User {
|
||||||
|
// ... existing fields
|
||||||
|
targetAssignments Int? // Soft cap (preferred target)
|
||||||
|
maxAssignments Int? // Hard cap (absolute max, enforced)
|
||||||
|
// preferredWorkload is deprecated in favor of targetAssignments
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Assignment Logic Changes:**
|
||||||
|
- Update `stage-assignment.ts`:
|
||||||
|
- Filter candidates to exclude jurors at `maxAssignments`
|
||||||
|
- Score jurors higher if below `targetAssignments`, lower if between `targetAssignments` and `maxAssignments` (buffer zone)
|
||||||
|
- UI shows warning icon for jurors in buffer zone (target < current < max)
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **Medium** — Affects assignment generation and admin UI for jury workload
|
||||||
|
- **Requires**: Update assignment service, admin assignment UI to show soft/hard cap status
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Per-Juror Category Ratio Preferences
|
||||||
|
|
||||||
|
**Requirement:**
|
||||||
|
- Juror specifies preferred category distribution (e.g., "I want 60% Startup / 40% Business Concept")
|
||||||
|
- Assignment algorithm respects these preferences when assigning projects
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- ❌ No category ratio configuration per juror
|
||||||
|
- ⚠️ Assignment scoring uses tag overlap and workload but not category distribution
|
||||||
|
|
||||||
|
**Gap:**
|
||||||
|
- ❌ No `User.preferredCategoryRatioJson` field
|
||||||
|
- ❌ Assignment algorithm doesn't score based on category distribution
|
||||||
|
|
||||||
|
**Required Schema Changes:**
|
||||||
|
```prisma
|
||||||
|
model User {
|
||||||
|
// ... existing fields
|
||||||
|
preferredCategoryRatioJson Json? @db.JsonB // { "STARTUP": 0.6, "BUSINESS_CONCEPT": 0.4 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Assignment Logic Changes:**
|
||||||
|
- Update `stage-assignment.ts`:
|
||||||
|
- For each juror, calculate current category distribution of assigned projects
|
||||||
|
- Score candidates higher if assigning this project would bring juror's distribution closer to `preferredCategoryRatioJson`
|
||||||
|
- Example: Juror wants 60/40 Startup/Concept, currently has 70/30, algorithm prefers assigning Concept projects to rebalance
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **Medium** — Affects assignment generation quality, requires juror onboarding to set preferences
|
||||||
|
- **Requires**: Update assignment algorithm, admin UI for juror profile editing, onboarding flow
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Countdown Timers on Dashboards
|
||||||
|
|
||||||
|
**Requirement:**
|
||||||
|
- Applicant dashboard shows countdown to submission deadline
|
||||||
|
- Jury dashboard shows countdown to evaluation deadline
|
||||||
|
- Admin dashboard shows countdown to stage window close
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- ✅ Backend has `Stage.windowCloseAt` timestamp
|
||||||
|
- ❌ No tRPC endpoint to fetch countdown state (time remaining, status: open/closing soon/closed)
|
||||||
|
- ❌ Frontend has no countdown component
|
||||||
|
|
||||||
|
**Gap:**
|
||||||
|
- ❌ No `stageRouter.getCountdown()` or similar procedure
|
||||||
|
- ❌ No frontend countdown component
|
||||||
|
|
||||||
|
**Required Changes:**
|
||||||
|
- Add tRPC procedure:
|
||||||
|
```typescript
|
||||||
|
stageRouter.getCountdown: protectedProcedure
|
||||||
|
.input(z.object({ stageId: z.string() }))
|
||||||
|
.query(async ({ ctx, input }) => {
|
||||||
|
const stage = await ctx.prisma.stage.findUniqueOrThrow({ where: { id: input.stageId } })
|
||||||
|
const now = new Date()
|
||||||
|
const closeAt = stage.windowCloseAt
|
||||||
|
if (!closeAt) return { status: 'no_deadline', timeRemaining: null }
|
||||||
|
const remaining = closeAt.getTime() - now.getTime()
|
||||||
|
if (remaining <= 0) return { status: 'closed', timeRemaining: 0 }
|
||||||
|
return {
|
||||||
|
status: remaining < 3600000 ? 'closing_soon' : 'open', // < 1 hour = closing soon
|
||||||
|
timeRemaining: remaining,
|
||||||
|
closeAt,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
- Frontend: Countdown component that polls `getCountdown()` and displays "X days Y hours Z minutes remaining"
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **Low** — UX improvement, no data model changes
|
||||||
|
- **Requires**: New tRPC procedure, frontend countdown component, dashboard integration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Email Reminders as Deadlines Approach
|
||||||
|
|
||||||
|
**Requirement:**
|
||||||
|
- Automated email reminders at 72 hours, 24 hours, 1 hour before deadline
|
||||||
|
- For applicants (submission deadlines) and jury (evaluation deadlines)
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- ⚠️ `processEvaluationReminders()` exists for jury reminders
|
||||||
|
- ⚠️ `ReminderLog` tracks sent reminders to prevent duplicates
|
||||||
|
- ❌ No applicant deadline reminder cron job
|
||||||
|
- ❌ No configurable reminder intervals (hardcoded to 3 days, 24h, 1h in evaluation reminders)
|
||||||
|
|
||||||
|
**Gap:**
|
||||||
|
- ❌ No applicant reminder service
|
||||||
|
- ❌ No configurable reminder intervals per stage
|
||||||
|
|
||||||
|
**Required Changes:**
|
||||||
|
- Add `Stage.reminderIntervalsJson: Json?` // `[72, 24, 1]` (hours before deadline)
|
||||||
|
- Add `src/server/services/applicant-reminders.ts`:
|
||||||
|
```typescript
|
||||||
|
export async function processApplicantReminders(prisma: PrismaClient) {
|
||||||
|
const now = new Date()
|
||||||
|
const stages = await prisma.stage.findMany({
|
||||||
|
where: { status: 'STAGE_ACTIVE', windowCloseAt: { gte: now } },
|
||||||
|
})
|
||||||
|
for (const stage of stages) {
|
||||||
|
const intervals = (stage.reminderIntervalsJson as number[]) ?? [72, 24, 1]
|
||||||
|
for (const hoursBeforeDeadline of intervals) {
|
||||||
|
const reminderTime = new Date(stage.windowCloseAt!.getTime() - hoursBeforeDeadline * 3600000)
|
||||||
|
if (now >= reminderTime && now < new Date(reminderTime.getTime() + 3600000)) {
|
||||||
|
// Send reminders to all applicants with draft projects in this stage
|
||||||
|
// Check ReminderLog to avoid duplicates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Add cron job in `src/app/api/cron/applicant-reminders/route.ts`
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **Medium** — Improves applicant engagement, reduces late submissions
|
||||||
|
- **Requires**: New service, new cron endpoint, extend `ReminderLog` model if needed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Admin Override Capability at Every Step
|
||||||
|
|
||||||
|
**Requirement:**
|
||||||
|
- Admin can override any automated decision (filtering, assignment, voting results)
|
||||||
|
- Override is logged with reason code and reason text in `OverrideAction`
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- ✅ Filtering: `resolveManualDecision()` overrides flagged projects
|
||||||
|
- ✅ Assignment: Manual assignment creation bypasses AI
|
||||||
|
- ⚠️ Live voting: `SpecialAward.winnerOverridden` flag exists but no explicit override flow for live voting results
|
||||||
|
- ⚠️ Stage transitions: No override capability to force projects between stages
|
||||||
|
|
||||||
|
**Gap:**
|
||||||
|
- ❌ No admin UI to override stage transitions (force project to next stage even if guard fails)
|
||||||
|
- ❌ No admin override for live voting results (admin can pick winner but not documented as override)
|
||||||
|
|
||||||
|
**Required Changes:**
|
||||||
|
- Add `stageRouter.overrideTransition()` procedure:
|
||||||
|
```typescript
|
||||||
|
overrideTransition: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
projectId: z.string(),
|
||||||
|
fromStageId: z.string(),
|
||||||
|
toStageId: z.string(),
|
||||||
|
reasonCode: z.nativeEnum(OverrideReasonCode),
|
||||||
|
reasonText: z.string(),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
// Force executeTransition() without validation
|
||||||
|
// Log in OverrideAction
|
||||||
|
})
|
||||||
|
```
|
||||||
|
- Add `liveVotingRouter.overrideWinner()` procedure (similar flow)
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **Low** — Fills gaps in admin control, already mostly exists
|
||||||
|
- **Requires**: New admin procedures, UI buttons for override actions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Integration Gaps
|
||||||
|
|
||||||
|
### Cross-Stage File Visibility
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
- Current file access is stage-scoped. Jury assigned to Round 2 can technically access Round 1 files (via complex `fileRouter.getDownloadUrl()` logic checking prior stages), but this is implicit and fragile.
|
||||||
|
- No clear flag to say "Round 2 jury should see Round 1 + Round 2 files" vs "Round 2 jury should only see Round 2 files".
|
||||||
|
|
||||||
|
**Required:**
|
||||||
|
- Add `Stage.cumulativeFileView: Boolean` — if true, jury sees files from all prior stages in the track.
|
||||||
|
- Simplify `fileRouter.getDownloadUrl()` authorization logic to check this flag instead of manual prior-stage traversal.
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **Medium** — Simplifies file access logic, makes jury file view behavior explicit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Round 1 to Round 2 Transition (File Read-Only Enforcement)
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
- When a project transitions from Round 1 (Intake) to Round 2 (Semi-finalist submission), Round 1 files should become read-only for applicants.
|
||||||
|
- Currently, no mechanism enforces this. Applicants could theoretically delete/replace Round 1 files during Round 2.
|
||||||
|
|
||||||
|
**Required:**
|
||||||
|
- Stage transition hook in `stage-engine.ts` `executeTransition()`:
|
||||||
|
```typescript
|
||||||
|
// After creating destination PSS:
|
||||||
|
if (fromStage.stageType === 'INTAKE' && toStage.stageType === 'INTAKE') {
|
||||||
|
// Mark all project files uploaded in fromStage as read-only for applicant
|
||||||
|
await tx.projectFile.updateMany({
|
||||||
|
where: { projectId, roundId: fromStageRoundId },
|
||||||
|
data: { isReadOnlyForApplicant: true },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Applicant file upload/delete checks: Reject if `ProjectFile.isReadOnlyForApplicant: true`.
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **High** — Ensures data integrity, prevents applicants from tampering with prior round submissions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Jury Group Scoping Across All Jury-Related Operations
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
- Assignments, evaluations, live voting all currently use global jury pool.
|
||||||
|
- Once `JuryGroup` is introduced, must update every jury-related query/mutation to filter by `Stage.juryGroupId`.
|
||||||
|
|
||||||
|
**Affected Areas:**
|
||||||
|
1. **Assignment generation**: `stage-assignment.ts` `previewStageAssignment()` must filter `prisma.user.findMany({ where: { role: 'JURY_MEMBER', ... } })` to `prisma.juryMembership.findMany({ where: { juryGroupId: stage.juryGroupId } })`.
|
||||||
|
2. **Evaluation authorization**: `evaluationRouter.submit()` must verify `assignment.userId` is a member of `stage.juryGroupId`.
|
||||||
|
3. **Live voting authorization**: `liveVotingRouter.submitVote()` must verify juror is in `stage.juryGroupId`.
|
||||||
|
4. **Admin assignment UI**: Dropdown to select jurors must filter by jury group.
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **High** — Pervasive change across all jury-related features.
|
||||||
|
- **Requires**: Careful migration plan, extensive testing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Countdown Timer Backend Support
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
- Dashboards need real-time countdown to deadlines, but no backend service provides this.
|
||||||
|
- Frontend would need to poll `Stage.windowCloseAt` directly and calculate client-side, or use a tRPC subscription.
|
||||||
|
|
||||||
|
**Required:**
|
||||||
|
- Add `stageRouter.getCountdown()` procedure (described in Cross-Cutting section).
|
||||||
|
- Frontend uses `trpc.stage.getCountdown.useQuery()` with `refetchInterval: 60000` (1 minute polling).
|
||||||
|
- Optionally: WebSocket subscription for real-time updates (out of scope for now, polling is sufficient).
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- **Low** — Backend is simple, frontend polling handles real-time updates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Priority Matrix
|
||||||
|
|
||||||
|
Features ranked by **Business Impact** (High/Medium/Low) x **Implementation Effort** (High/Medium/Low).
|
||||||
|
|
||||||
|
| Feature | Business Impact | Implementation Effort | Priority Quadrant | Notes |
|
||||||
|
|---------|----------------|----------------------|-------------------|-------|
|
||||||
|
| **Multi-jury support (named groups)** | **High** | **High** | **Critical** | Required for all 3 jury rounds, affects assignments/evaluations/voting |
|
||||||
|
| **Round 1 docs read-only enforcement** | **High** | **Low** | **Quick Win** | Data integrity essential, simple flag + hook |
|
||||||
|
| **Per-juror hard cap vs soft cap + buffer** | **High** | **Medium** | **Critical** | Ensures balanced workload, prevents burnout |
|
||||||
|
| **Per-juror category ratio preferences** | **Medium** | **Medium** | **Important** | Improves assignment quality, enhances juror satisfaction |
|
||||||
|
| **Jury vote notes (live finals)** | **Medium** | **Low** | **Quick Win** | Enhances deliberation, simple schema change |
|
||||||
|
| **Deliberation period (live finals)** | **Medium** | **Low** | **Quick Win** | Required for live finals flow, simple cohort fields |
|
||||||
|
| **Individual jury confirmation** | **High** | **Medium** | **Critical** | Legal/compliance requirement for final results |
|
||||||
|
| **Results freeze mechanism** | **High** | **Low** | **Quick Win** | Immutable audit trail, simple timestamp flag |
|
||||||
|
| **Cumulative file view flag** | **Medium** | **Low** | **Quick Win** | Simplifies jury file access logic |
|
||||||
|
| **Mentor file upload** | **Medium** | **Medium** | **Important** | Enhances mentoring, requires file router extension |
|
||||||
|
| **Threaded file comments** | **Low** | **Medium** | **Nice to Have** | Improves collaboration, but not blocking |
|
||||||
|
| **File promotion workflow** | **Low** | **Medium** | **Nice to Have** | Advanced feature, can defer to later phase |
|
||||||
|
| **Countdown timers (UI)** | **Low** | **Low** | **Nice to Have** | UX improvement, no data model changes |
|
||||||
|
| **Applicant deadline reminders** | **Medium** | **Low** | **Quick Win** | Reduces late submissions, simple cron job |
|
||||||
|
| **Admin override for stage transitions** | **Low** | **Low** | **Nice to Have** | Edge case, manual workaround exists |
|
||||||
|
|
||||||
|
**Priority Quadrants:**
|
||||||
|
- **Critical (High Impact / High Effort)**: Multi-jury support, jury confirmation — **must do**, high planning required
|
||||||
|
- **Quick Wins (High Impact / Low Effort)**: Read-only enforcement, results freeze, deliberation period — **do first**
|
||||||
|
- **Important (Medium Impact / Medium Effort)**: Caps/ratios, mentor file upload — **do after quick wins**
|
||||||
|
- **Nice to Have (Low Impact / Any Effort)**: File comments threading, countdown timers — **defer or phase 2**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The current MOPC platform has a **solid foundation** with the pipeline/track/stage architecture, stage-engine transitions, AI filtering, jury assignment, and live voting infrastructure fully implemented. The **critical gaps** are:
|
||||||
|
|
||||||
|
1. **Multi-jury support** (named jury entities with overlap) — **highest priority**, affects all jury-related features
|
||||||
|
2. **Per-juror caps and category ratio preferences** — **essential for workload balancing**
|
||||||
|
3. **Round 1 read-only enforcement + cumulative file view** — **data integrity and jury UX**
|
||||||
|
4. **Individual jury confirmation + results freeze** — **compliance and audit requirements**
|
||||||
|
5. **Mentoring workspace features** (file upload, comments, promotion) — **enhances mentoring but lower priority**
|
||||||
|
|
||||||
|
**Recommended Approach:**
|
||||||
|
- **Phase 1 (Quick Wins)**: Read-only enforcement, results freeze, deliberation period, vote notes, applicant reminders — **2-3 weeks**
|
||||||
|
- **Phase 2 (Critical)**: Multi-jury support, jury confirmation — **4-6 weeks** (complex, pervasive changes)
|
||||||
|
- **Phase 3 (Important)**: Caps/ratios, mentor file upload — **3-4 weeks**
|
||||||
|
- **Phase 4 (Nice to Have)**: Threaded comments, file promotion, countdown timers — **defer to post-MVP**
|
||||||
|
|
||||||
|
Total estimated effort for Phases 1-3: **9-13 weeks** (assumes single developer, includes testing).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**End of Gap Analysis Document**
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,698 @@
|
||||||
|
# Round: Evaluation (Jury 1 & Jury 2)
|
||||||
|
|
||||||
|
## 1. Purpose & Position in Flow
|
||||||
|
|
||||||
|
The EVALUATION round is the core judging mechanism of the competition. It appears **twice** in the standard flow:
|
||||||
|
|
||||||
|
| Instance | Name | Position | Jury | Purpose | Output |
|
||||||
|
|----------|------|----------|------|---------|--------|
|
||||||
|
| Round 3 | "Jury 1 — Semi-finalist Selection" | After FILTERING | Jury 1 | Score projects, select semi-finalists | Semi-finalists per category |
|
||||||
|
| Round 5 | "Jury 2 — Finalist Selection" | After SUBMISSION Round 2 | Jury 2 | Score semi-finalists, select finalists + awards | Finalists per category |
|
||||||
|
|
||||||
|
Both instances use the same `RoundType.EVALUATION` but are configured independently with:
|
||||||
|
- Different jury groups (Jury 1 vs Jury 2)
|
||||||
|
- Different evaluation forms/rubrics
|
||||||
|
- Different visible submission windows (Jury 1 sees Window 1 only; Jury 2 sees Windows 1+2)
|
||||||
|
- Different advancement counts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Data Model
|
||||||
|
|
||||||
|
### Round Record
|
||||||
|
|
||||||
|
```
|
||||||
|
Round {
|
||||||
|
id: "round-jury-1"
|
||||||
|
competitionId: "comp-2026"
|
||||||
|
name: "Jury 1 — Semi-finalist Selection"
|
||||||
|
roundType: EVALUATION
|
||||||
|
status: ROUND_DRAFT → ROUND_ACTIVE → ROUND_CLOSED
|
||||||
|
sortOrder: 2
|
||||||
|
windowOpenAt: "2026-04-01" // Evaluation window start
|
||||||
|
windowCloseAt: "2026-04-30" // Evaluation window end
|
||||||
|
juryGroupId: "jury-group-1" // Links to Jury 1
|
||||||
|
submissionWindowId: null // EVALUATION rounds don't collect submissions
|
||||||
|
configJson: { ...EvaluationConfig }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### EvaluationConfig
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type EvaluationConfig = {
|
||||||
|
// --- Assignment Settings ---
|
||||||
|
requiredReviewsPerProject: number // How many jurors review each project (default: 3)
|
||||||
|
|
||||||
|
// --- Scoring Mode ---
|
||||||
|
scoringMode: "criteria" | "global" | "binary"
|
||||||
|
// criteria: Score per criterion + weighted total
|
||||||
|
// global: Single 1-10 score
|
||||||
|
// binary: Yes/No decision (semi-finalist worthy?)
|
||||||
|
requireFeedback: boolean // Must provide text feedback (default: true)
|
||||||
|
|
||||||
|
// --- COI ---
|
||||||
|
coiRequired: boolean // Must declare COI before evaluating (default: true)
|
||||||
|
|
||||||
|
// --- Peer Review ---
|
||||||
|
peerReviewEnabled: boolean // Jurors can see anonymized peer evaluations after submission
|
||||||
|
anonymizationLevel: "fully_anonymous" | "show_initials" | "named"
|
||||||
|
|
||||||
|
// --- AI Features ---
|
||||||
|
aiSummaryEnabled: boolean // Generate AI-powered evaluation summaries
|
||||||
|
aiAssignmentEnabled: boolean // Allow AI-suggested jury-project matching
|
||||||
|
|
||||||
|
// --- Advancement ---
|
||||||
|
advancementMode: "auto_top_n" | "admin_selection" | "ai_recommended"
|
||||||
|
advancementConfig: {
|
||||||
|
perCategory: boolean // Separate counts per STARTUP / BUSINESS_CONCEPT
|
||||||
|
startupCount: number // How many startups advance (default: 10 for Jury 1, 3 for Jury 2)
|
||||||
|
conceptCount: number // How many concepts advance
|
||||||
|
tieBreaker: "admin_decides" | "highest_individual" | "revote"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Related Models
|
||||||
|
|
||||||
|
| Model | Role |
|
||||||
|
|-------|------|
|
||||||
|
| `JuryGroup` | Named jury entity linked to this round |
|
||||||
|
| `JuryGroupMember` | Members of the jury with per-juror overrides |
|
||||||
|
| `Assignment` | Juror-project pairing for this round, linked to JuryGroup |
|
||||||
|
| `Evaluation` | Score/feedback submitted by a juror for one project |
|
||||||
|
| `EvaluationForm` | Rubric/criteria definition for this round |
|
||||||
|
| `ConflictOfInterest` | COI declaration per assignment |
|
||||||
|
| `GracePeriod` | Per-juror deadline extension |
|
||||||
|
| `EvaluationSummary` | AI-generated insights per project per round |
|
||||||
|
| `EvaluationDiscussion` | Peer review discussion threads |
|
||||||
|
| `RoundSubmissionVisibility` | Which submission windows' docs jury can see |
|
||||||
|
| `AdvancementRule` | How projects advance after evaluation |
|
||||||
|
| `ProjectRoundState` | Per-project state in this round |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Setup Phase (Before Window Opens)
|
||||||
|
|
||||||
|
### 3.1 Admin Creates the Evaluation Round
|
||||||
|
|
||||||
|
Admin uses the competition wizard or round management UI to:
|
||||||
|
|
||||||
|
1. **Create the Round** with type EVALUATION
|
||||||
|
2. **Link a JuryGroup** — select "Jury 1" (or create a new jury group)
|
||||||
|
3. **Set the evaluation window** — start and end dates
|
||||||
|
4. **Configure the evaluation form** — scoring criteria, weights, scales
|
||||||
|
5. **Set visibility** — which submission windows jury can see (via RoundSubmissionVisibility)
|
||||||
|
6. **Configure advancement rules** — how many advance per category
|
||||||
|
|
||||||
|
### 3.2 Jury Group Configuration
|
||||||
|
|
||||||
|
The linked JuryGroup has:
|
||||||
|
|
||||||
|
```
|
||||||
|
JuryGroup {
|
||||||
|
name: "Jury 1"
|
||||||
|
defaultMaxAssignments: 20 // Default cap per juror
|
||||||
|
defaultCapMode: SOFT // HARD | SOFT | NONE
|
||||||
|
softCapBuffer: 2 // Can exceed by 2 for load balancing
|
||||||
|
categoryQuotasEnabled: true
|
||||||
|
defaultCategoryQuotas: {
|
||||||
|
"STARTUP": { "min": 3, "max": 15 },
|
||||||
|
"BUSINESS_CONCEPT": { "min": 3, "max": 15 }
|
||||||
|
}
|
||||||
|
allowJurorCapAdjustment: true // Jurors can adjust their cap during onboarding
|
||||||
|
allowJurorRatioAdjustment: true // Jurors can adjust their category preference
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Per-Juror Overrides
|
||||||
|
|
||||||
|
Each `JuryGroupMember` can override group defaults:
|
||||||
|
|
||||||
|
```
|
||||||
|
JuryGroupMember {
|
||||||
|
juryGroupId: "jury-group-1"
|
||||||
|
userId: "judge-alice"
|
||||||
|
maxAssignmentsOverride: 25 // Alice wants more projects
|
||||||
|
capModeOverride: HARD // Alice: hard cap, no exceptions
|
||||||
|
categoryQuotasOverride: {
|
||||||
|
"STARTUP": { "min": 5, "max": 20 }, // Alice prefers startups
|
||||||
|
"BUSINESS_CONCEPT": { "min": 0, "max": 5 }
|
||||||
|
}
|
||||||
|
preferredStartupRatio: 0.8 // 80% startups
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Juror Onboarding (Optional)
|
||||||
|
|
||||||
|
If `allowJurorCapAdjustment` or `allowJurorRatioAdjustment` is true:
|
||||||
|
|
||||||
|
1. When a juror first opens their jury dashboard after being added to the group
|
||||||
|
2. A one-time onboarding dialog appears:
|
||||||
|
- "Your default maximum is 20 projects. Would you like to adjust?" (slider)
|
||||||
|
- "Your default startup/concept ratio is 50/50. Would you like to adjust?" (slider)
|
||||||
|
3. Juror saves preferences → stored in `JuryGroupMember.maxAssignmentsOverride` and `preferredStartupRatio`
|
||||||
|
4. Dialog doesn't appear again (tracked via `JuryGroupMember.updatedAt` or a flag)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Assignment System (Enhanced)
|
||||||
|
|
||||||
|
### 4.1 Assignment Algorithm — Jury-Group-Aware
|
||||||
|
|
||||||
|
The current `stage-assignment.ts` algorithm is enhanced to:
|
||||||
|
|
||||||
|
1. **Filter jury pool by JuryGroup** — only members of the linked jury group are considered
|
||||||
|
2. **Apply hard/soft cap logic** per juror
|
||||||
|
3. **Apply category quotas** per juror
|
||||||
|
4. **Score candidates** using existing expertise matching + workload balancing + geo-diversity
|
||||||
|
|
||||||
|
#### Effective Limits Resolution
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getEffectiveLimits(member: JuryGroupMember, group: JuryGroup): EffectiveLimits {
|
||||||
|
return {
|
||||||
|
maxAssignments: member.maxAssignmentsOverride ?? group.defaultMaxAssignments,
|
||||||
|
capMode: member.capModeOverride ?? group.defaultCapMode,
|
||||||
|
softCapBuffer: group.softCapBuffer, // Group-level only (not per-juror)
|
||||||
|
categoryQuotas: member.categoryQuotasOverride ?? group.defaultCategoryQuotas,
|
||||||
|
categoryQuotasEnabled: group.categoryQuotasEnabled,
|
||||||
|
preferredStartupRatio: member.preferredStartupRatio,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Cap Enforcement Logic
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function canAssignMore(
|
||||||
|
jurorId: string,
|
||||||
|
projectCategory: CompetitionCategory,
|
||||||
|
currentLoad: LoadTracker,
|
||||||
|
limits: EffectiveLimits
|
||||||
|
): { allowed: boolean; penalty: number; reason?: string } {
|
||||||
|
const total = currentLoad.total(jurorId)
|
||||||
|
const catLoad = currentLoad.byCategory(jurorId, projectCategory)
|
||||||
|
|
||||||
|
// 1. HARD cap check
|
||||||
|
if (limits.capMode === "HARD" && total >= limits.maxAssignments) {
|
||||||
|
return { allowed: false, penalty: 0, reason: "Hard cap reached" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. SOFT cap check (can exceed by buffer)
|
||||||
|
let overflowPenalty = 0
|
||||||
|
if (limits.capMode === "SOFT") {
|
||||||
|
if (total >= limits.maxAssignments + limits.softCapBuffer) {
|
||||||
|
return { allowed: false, penalty: 0, reason: "Soft cap + buffer exceeded" }
|
||||||
|
}
|
||||||
|
if (total >= limits.maxAssignments) {
|
||||||
|
// In buffer zone — apply increasing penalty
|
||||||
|
overflowPenalty = (total - limits.maxAssignments + 1) * 15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Category quota check
|
||||||
|
if (limits.categoryQuotasEnabled && limits.categoryQuotas) {
|
||||||
|
const quota = limits.categoryQuotas[projectCategory]
|
||||||
|
if (quota) {
|
||||||
|
if (catLoad >= quota.max) {
|
||||||
|
return { allowed: false, penalty: 0, reason: `Category ${projectCategory} max reached (${quota.max})` }
|
||||||
|
}
|
||||||
|
// Bonus for under-min
|
||||||
|
if (catLoad < quota.min) {
|
||||||
|
overflowPenalty -= 15 // Negative penalty = bonus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Ratio preference alignment
|
||||||
|
if (limits.preferredStartupRatio != null && total > 0) {
|
||||||
|
const currentStartupRatio = currentLoad.byCategory(jurorId, "STARTUP") / total
|
||||||
|
const isStartup = projectCategory === "STARTUP"
|
||||||
|
const wantMore = isStartup
|
||||||
|
? currentStartupRatio < limits.preferredStartupRatio
|
||||||
|
: currentStartupRatio > limits.preferredStartupRatio
|
||||||
|
if (wantMore) overflowPenalty -= 10 // Bonus for aligning with preference
|
||||||
|
else overflowPenalty += 10 // Penalty for diverging
|
||||||
|
}
|
||||||
|
|
||||||
|
return { allowed: true, penalty: overflowPenalty }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Assignment Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Admin opens Assignment panel for Round 3 (Jury 1)
|
||||||
|
2. System loads:
|
||||||
|
- Projects with ProjectRoundState PENDING/IN_PROGRESS in this round
|
||||||
|
- JuryGroup members (with effective limits)
|
||||||
|
- Existing assignments (to avoid duplicates)
|
||||||
|
- COI records (to skip conflicted pairs)
|
||||||
|
3. Admin clicks "Generate Suggestions"
|
||||||
|
4. Algorithm runs:
|
||||||
|
a. For each project (sorted by fewest current assignments):
|
||||||
|
- Score each eligible juror (tag matching + workload + geo + cap/quota penalties)
|
||||||
|
- Select top N jurors (N = requiredReviewsPerProject - existing reviews)
|
||||||
|
- Track load in jurorLoadMap
|
||||||
|
b. Report unassigned projects (jurors at capacity)
|
||||||
|
5. Admin reviews preview:
|
||||||
|
- Assignment matrix (juror × project grid)
|
||||||
|
- Load distribution chart
|
||||||
|
- Unassigned projects list
|
||||||
|
- Category distribution per juror
|
||||||
|
6. Admin can:
|
||||||
|
- Accept all suggestions
|
||||||
|
- Modify individual assignments (drag-drop or manual add/remove)
|
||||||
|
- Re-run with different parameters
|
||||||
|
7. Admin clicks "Apply Assignments"
|
||||||
|
8. System creates Assignment records with juryGroupId set
|
||||||
|
9. Notifications sent to jurors
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 AI-Powered Assignment (Optional)
|
||||||
|
|
||||||
|
If `aiAssignmentEnabled` is true in config:
|
||||||
|
|
||||||
|
1. Admin clicks "AI Assignment Suggestions"
|
||||||
|
2. System calls `ai-assignment.ts`:
|
||||||
|
- Anonymizes juror profiles and project descriptions
|
||||||
|
- Sends to GPT with matching instructions
|
||||||
|
- Returns confidence scores and reasoning
|
||||||
|
3. AI suggestions shown alongside algorithm suggestions
|
||||||
|
4. Admin picks which to use or mixes both
|
||||||
|
|
||||||
|
### 4.4 Handling Unassigned Projects
|
||||||
|
|
||||||
|
When all jurors with SOFT cap reach cap+buffer:
|
||||||
|
1. Remaining projects become "unassigned"
|
||||||
|
2. Admin dashboard highlights these prominently
|
||||||
|
3. Admin can:
|
||||||
|
- Manually assign to specific jurors (bypasses cap — manual override)
|
||||||
|
- Increase a juror's cap
|
||||||
|
- Add more jurors to the jury group
|
||||||
|
- Reduce `requiredReviewsPerProject` for remaining projects
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Jury Evaluation Experience
|
||||||
|
|
||||||
|
### 5.1 Jury Dashboard
|
||||||
|
|
||||||
|
When a Jury 1 member opens their dashboard:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ JURY 1 — Semi-finalist Selection │
|
||||||
|
│ ─────────────────────────────────────────────────── │
|
||||||
|
│ Evaluation Window: April 1 – April 30 │
|
||||||
|
│ ⏱ 12 days remaining │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
|
||||||
|
│ │ 15 │ │ 8 │ │ 2 │ │ 5 │ │
|
||||||
|
│ │ Total │ │ Complete │ │ In Draft │ │ Pending│ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [Continue Next Evaluation →] │
|
||||||
|
│ │
|
||||||
|
│ Recent Assignments │
|
||||||
|
│ ┌──────────────────────────────────────────────┐ │
|
||||||
|
│ │ OceanClean AI │ Startup │ ✅ Done │ View │ │
|
||||||
|
│ │ Blue Carbon Hub │ Concept │ ⏳ Draft │ Cont │ │
|
||||||
|
│ │ SeaWatch Monitor │ Startup │ ⬜ Pending│ Start│ │
|
||||||
|
│ │ ... │ │
|
||||||
|
│ └──────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Key elements:
|
||||||
|
- **Deadline countdown** — prominent timer showing days/hours remaining
|
||||||
|
- **Progress stats** — total, completed, in-draft, pending
|
||||||
|
- **Quick action CTA** — jump to next unevaluated project
|
||||||
|
- **Assignment list** — sorted by status (pending first, then drafts, then done)
|
||||||
|
|
||||||
|
### 5.2 COI Declaration (Blocking)
|
||||||
|
|
||||||
|
Before evaluating any project, the juror MUST declare COI:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌───────────────────────────────────────────┐
|
||||||
|
│ Conflict of Interest Declaration │
|
||||||
|
│ │
|
||||||
|
│ Do you have a conflict of interest with │
|
||||||
|
│ "OceanClean AI" (Startup)? │
|
||||||
|
│ │
|
||||||
|
│ ○ No conflict — I can evaluate fairly │
|
||||||
|
│ ○ Yes, I have a conflict: │
|
||||||
|
│ Type: [Financial ▾] │
|
||||||
|
│ Description: [________________] │
|
||||||
|
│ │
|
||||||
|
│ [Submit Declaration] │
|
||||||
|
└───────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- If **No conflict**: Proceed to evaluation form
|
||||||
|
- If **Yes**: Assignment flagged, admin notified, juror may be reassigned
|
||||||
|
- COI declaration is logged in `ConflictOfInterest` model
|
||||||
|
- Admin can review and take action (cleared / reassigned / noted)
|
||||||
|
|
||||||
|
### 5.3 Evaluation Form
|
||||||
|
|
||||||
|
The form adapts to the `scoringMode`:
|
||||||
|
|
||||||
|
#### Criteria Mode (default for Jury 1 and Jury 2)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌───────────────────────────────────────────────────┐
|
||||||
|
│ Evaluating: OceanClean AI (Startup) │
|
||||||
|
│ ──────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ [📄 Documents] [📊 Scoring] [💬 Feedback] │
|
||||||
|
│ │
|
||||||
|
│ ── DOCUMENTS TAB ── │
|
||||||
|
│ ┌─ Round 1 Application Docs ─────────────────┐ │
|
||||||
|
│ │ 📄 Executive Summary.pdf [Download] │ │
|
||||||
|
│ │ 📄 Business Plan.pdf [Download] │ │
|
||||||
|
│ └─────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ (Jury 2 also sees:) │
|
||||||
|
│ ┌─ Round 2 Semi-finalist Docs ────────────────┐ │
|
||||||
|
│ │ 📄 Updated Business Plan.pdf [Download] │ │
|
||||||
|
│ │ 🎥 Video Pitch.mp4 [Play] │ │
|
||||||
|
│ └─────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ── SCORING TAB ── │
|
||||||
|
│ Innovation & Impact [1] [2] [3] [4] [5] (w:30%)│
|
||||||
|
│ Feasibility [1] [2] [3] [4] [5] (w:25%)│
|
||||||
|
│ Team & Execution [1] [2] [3] [4] [5] (w:25%)│
|
||||||
|
│ Ocean Relevance [1] [2] [3] [4] [5] (w:20%)│
|
||||||
|
│ │
|
||||||
|
│ Overall Score: 3.8 / 5.0 (auto-calculated) │
|
||||||
|
│ │
|
||||||
|
│ ── FEEDBACK TAB ── │
|
||||||
|
│ Feedback: [________________________________] │
|
||||||
|
│ │
|
||||||
|
│ [💾 Save Draft] [✅ Submit Evaluation] │
|
||||||
|
│ (Auto-saves every 30s) │
|
||||||
|
└───────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Binary Mode (optional for quick screening)
|
||||||
|
|
||||||
|
```
|
||||||
|
Should this project advance to the semi-finals?
|
||||||
|
[✅ Yes] [❌ No]
|
||||||
|
|
||||||
|
Justification (required): [________________]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Global Score Mode
|
||||||
|
|
||||||
|
```
|
||||||
|
Overall Score: [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]
|
||||||
|
|
||||||
|
Feedback (required): [________________]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Document Visibility (Cross-Round)
|
||||||
|
|
||||||
|
Controlled by `RoundSubmissionVisibility`:
|
||||||
|
|
||||||
|
| Round | Sees Window 1 ("Application Docs") | Sees Window 2 ("Semi-finalist Docs") |
|
||||||
|
|-------|------------------------------------|-----------------------------------------|
|
||||||
|
| Jury 1 (Round 3) | Yes | No (doesn't exist yet) |
|
||||||
|
| Jury 2 (Round 5) | Yes | Yes |
|
||||||
|
| Jury 3 (Round 7) | Yes | Yes |
|
||||||
|
|
||||||
|
In the evaluation UI:
|
||||||
|
- Documents are grouped by submission window
|
||||||
|
- Each group has a label (from `RoundSubmissionVisibility.displayLabel`)
|
||||||
|
- Clear visual separation (tabs, accordion sections, or side panels)
|
||||||
|
|
||||||
|
### 5.5 Auto-Save and Submission
|
||||||
|
|
||||||
|
- **Auto-save**: Client debounces and calls `evaluation.autosave` every 30 seconds while draft is open
|
||||||
|
- **Draft status**: Evaluation starts as NOT_STARTED → DRAFT on first save → SUBMITTED on explicit submit
|
||||||
|
- **Submission validation**:
|
||||||
|
- All required criteria scored (if criteria mode)
|
||||||
|
- Global score provided (if global mode)
|
||||||
|
- Binary decision selected (if binary mode)
|
||||||
|
- Feedback text provided (if `requireFeedback`)
|
||||||
|
- Window is open (or juror has grace period)
|
||||||
|
- **After submission**: Evaluation becomes read-only for juror (status = SUBMITTED)
|
||||||
|
- **Admin can lock**: Set status to LOCKED to prevent any further changes
|
||||||
|
|
||||||
|
### 5.6 Grace Periods
|
||||||
|
|
||||||
|
```
|
||||||
|
GracePeriod {
|
||||||
|
roundId: "round-jury-1"
|
||||||
|
userId: "judge-alice"
|
||||||
|
projectId: null // Applies to ALL Alice's assignments in this round
|
||||||
|
extendedUntil: "2026-05-02" // 2 days after official close
|
||||||
|
reason: "Travel conflict"
|
||||||
|
grantedById: "admin-1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Admin can grant per-juror or per-juror-per-project grace periods
|
||||||
|
- Evaluation submission checks grace period before rejecting past-window submissions
|
||||||
|
- Dashboard shows "(Grace period: 2 extra days)" badge for affected jurors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. End of Evaluation — Results & Advancement
|
||||||
|
|
||||||
|
### 6.1 Results Visualization
|
||||||
|
|
||||||
|
When the evaluation window closes, the admin sees:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ Jury 1 Results │
|
||||||
|
│ ─────────────────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ Completion: 142/150 evaluations submitted (94.7%) │
|
||||||
|
│ Outstanding: 8 (3 jurors have pending evaluations) │
|
||||||
|
│ │
|
||||||
|
│ ┌─ STARTUPS (Top 10) ──────────────────────────────────────┐│
|
||||||
|
│ │ # Project Avg Score Consensus Reviews Status ││
|
||||||
|
│ │ 1 OceanClean AI 4.6/5 0.92 3/3 ✅ ││
|
||||||
|
│ │ 2 SeaWatch 4.3/5 0.85 3/3 ✅ ││
|
||||||
|
│ │ 3 BlueCarbon 4.1/5 0.78 3/3 ✅ ││
|
||||||
|
│ │ ... ││
|
||||||
|
│ │ 10 TidalEnergy 3.2/5 0.65 3/3 ✅ ││
|
||||||
|
│ │ ── cutoff line ────────────────────────────────────────── ││
|
||||||
|
│ │ 11 WavePower 3.1/5 0.71 3/3 ⬜ ││
|
||||||
|
│ │ 12 CoralGuard 2.9/5 0.55 2/3 ⚠️ ││
|
||||||
|
│ └──────────────────────────────────────────────────────────┘│
|
||||||
|
│ │
|
||||||
|
│ ┌─ CONCEPTS (Top 10) ──────────────────────────────────────┐│
|
||||||
|
│ │ (same layout) ││
|
||||||
|
│ └──────────────────────────────────────────────────────────┘│
|
||||||
|
│ │
|
||||||
|
│ [🤖 AI Recommendation] [📊 Score Distribution] [Export] │
|
||||||
|
│ │
|
||||||
|
│ [✅ Approve Shortlist] [✏️ Edit Shortlist] │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Metrics shown:**
|
||||||
|
- Average global score (or weighted criteria average)
|
||||||
|
- Consensus score (1 - normalized stddev, where 1.0 = full agreement)
|
||||||
|
- Review count / required
|
||||||
|
- Per-criterion averages (expandable)
|
||||||
|
|
||||||
|
### 6.2 AI Recommendation
|
||||||
|
|
||||||
|
When admin clicks "AI Recommendation":
|
||||||
|
|
||||||
|
1. System calls `ai-evaluation-summary.ts` for each project in bulk
|
||||||
|
2. AI generates:
|
||||||
|
- Ranked shortlist per category based on scores + feedback analysis
|
||||||
|
- Strengths, weaknesses, themes per project
|
||||||
|
- Recommendation: "Advance" / "Borderline" / "Do not advance"
|
||||||
|
3. Admin sees AI recommendation alongside actual scores
|
||||||
|
4. AI recommendations are suggestions only — admin has final say
|
||||||
|
|
||||||
|
### 6.3 Advancement Decision
|
||||||
|
|
||||||
|
```
|
||||||
|
Advancement Mode: admin_selection (with AI recommendation)
|
||||||
|
|
||||||
|
1. System shows ranked list per category
|
||||||
|
2. AI highlights recommended top N per category
|
||||||
|
3. Admin can:
|
||||||
|
- Accept AI recommendation
|
||||||
|
- Drag projects to reorder
|
||||||
|
- Add/remove projects from advancement list
|
||||||
|
- Set custom cutoff line
|
||||||
|
4. Admin clicks "Confirm Advancement"
|
||||||
|
5. System:
|
||||||
|
a. Sets ProjectRoundState to PASSED for advancing projects
|
||||||
|
b. Sets ProjectRoundState to REJECTED for non-advancing projects
|
||||||
|
c. Updates Project.status to SEMIFINALIST (Jury 1) or FINALIST (Jury 2)
|
||||||
|
d. Logs all decisions in DecisionAuditLog
|
||||||
|
e. Sends notifications to all teams (advanced / not selected)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 Advancement Modes
|
||||||
|
|
||||||
|
| Mode | Behavior |
|
||||||
|
|------|----------|
|
||||||
|
| `auto_top_n` | Top N per category automatically advance when window closes |
|
||||||
|
| `admin_selection` | Admin manually selects who advances (with AI/score guidance) |
|
||||||
|
| `ai_recommended` | AI proposes list, admin must approve/modify |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Special Awards Integration (Jury 2 Only)
|
||||||
|
|
||||||
|
During the Jury 2 evaluation round, special awards can run alongside:
|
||||||
|
|
||||||
|
### 7.1 How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
Round 5: "Jury 2 — Finalist Selection"
|
||||||
|
├── Main evaluation (all semi-finalists scored by Jury 2)
|
||||||
|
└── Special Awards (run in parallel):
|
||||||
|
├── "Innovation Award" — STAY_IN_MAIN mode
|
||||||
|
│ Projects remain in main eval, flagged as eligible
|
||||||
|
│ Award jury (subset of Jury 2 or separate) votes
|
||||||
|
└── "Impact Award" — SEPARATE_POOL mode
|
||||||
|
AI filters eligible projects into award pool
|
||||||
|
Dedicated jury evaluates and votes
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 SpecialAward.evaluationRoundId
|
||||||
|
|
||||||
|
Each award links to the evaluation round it runs alongside:
|
||||||
|
```
|
||||||
|
SpecialAward {
|
||||||
|
evaluationRoundId: "round-jury-2" // Runs during Jury 2
|
||||||
|
eligibilityMode: STAY_IN_MAIN
|
||||||
|
juryGroupId: "jury-group-innovation" // Can be same or different jury
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Award Evaluation Flow
|
||||||
|
|
||||||
|
1. Before Jury 2 window opens: Admin runs award eligibility (AI or manual)
|
||||||
|
2. During Jury 2 window: Award jury members see their award assignments alongside regular evaluations
|
||||||
|
3. Award jury submits award votes (PICK_WINNER, RANKED, or SCORED)
|
||||||
|
4. After Jury 2 closes: Award results finalized alongside main results
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Differences Between Jury 1 and Jury 2
|
||||||
|
|
||||||
|
| Aspect | Jury 1 (Round 3) | Jury 2 (Round 5) |
|
||||||
|
|--------|-------------------|-------------------|
|
||||||
|
| Input projects | All eligible (post-filtering) | Semi-finalists only |
|
||||||
|
| Visible docs | Window 1 only | Window 1 + Window 2 |
|
||||||
|
| Output | Semi-finalists | Finalists |
|
||||||
|
| Project.status update | → SEMIFINALIST | → FINALIST |
|
||||||
|
| Special awards | No | Yes (alongside) |
|
||||||
|
| Jury group | Jury 1 | Jury 2 (different members, possible overlap) |
|
||||||
|
| Typical project count | 50-100+ | 10-20 |
|
||||||
|
| Required reviews | 3 (more projects, less depth) | 3-5 (fewer projects, more depth) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. API Changes
|
||||||
|
|
||||||
|
### Preserved Procedures (renamed stageId → roundId)
|
||||||
|
|
||||||
|
| Procedure | Change |
|
||||||
|
|-----------|--------|
|
||||||
|
| `evaluation.get` | roundId via assignment |
|
||||||
|
| `evaluation.start` | No change |
|
||||||
|
| `evaluation.autosave` | No change |
|
||||||
|
| `evaluation.submit` | Window check uses round.windowCloseAt + grace periods |
|
||||||
|
| `evaluation.declareCOI` | No change |
|
||||||
|
| `evaluation.getCOIStatus` | No change |
|
||||||
|
| `evaluation.getProjectStats` | No change |
|
||||||
|
| `evaluation.listByRound` | Renamed from listByStage |
|
||||||
|
| `evaluation.generateSummary` | roundId instead of stageId |
|
||||||
|
| `evaluation.generateBulkSummaries` | roundId instead of stageId |
|
||||||
|
|
||||||
|
### New Procedures
|
||||||
|
|
||||||
|
| Procedure | Purpose |
|
||||||
|
|-----------|---------|
|
||||||
|
| `assignment.previewWithJuryGroup` | Preview assignments filtered by jury group with cap/quota logic |
|
||||||
|
| `assignment.getJuryGroupStats` | Per-member stats: load, category distribution, cap utilization |
|
||||||
|
| `evaluation.getResultsOverview` | Rankings, scores, consensus, AI recommendations per category |
|
||||||
|
| `evaluation.confirmAdvancement` | Admin confirms which projects advance |
|
||||||
|
| `evaluation.getAdvancementPreview` | Preview advancement impact before confirming |
|
||||||
|
|
||||||
|
### Modified Procedures
|
||||||
|
|
||||||
|
| Procedure | Modification |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `assignment.getSuggestions` | Now filters by JuryGroup, applies hard/soft caps, category quotas |
|
||||||
|
| `assignment.create` | Now sets `juryGroupId` on Assignment |
|
||||||
|
| `assignment.bulkCreate` | Now validates against jury group caps |
|
||||||
|
| `file.listByProjectForRound` | Uses RoundSubmissionVisibility to filter docs |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Service Layer Changes
|
||||||
|
|
||||||
|
### `stage-assignment.ts` → `round-assignment.ts`
|
||||||
|
|
||||||
|
Key changes to `previewStageAssignment` → `previewRoundAssignment`:
|
||||||
|
|
||||||
|
1. **Load jury pool from JuryGroup** instead of all JURY_MEMBER users:
|
||||||
|
```typescript
|
||||||
|
const juryGroup = await prisma.juryGroup.findUnique({
|
||||||
|
where: { id: round.juryGroupId },
|
||||||
|
include: { members: { include: { user: true } } }
|
||||||
|
})
|
||||||
|
const jurors = juryGroup.members.map(m => ({
|
||||||
|
...m.user,
|
||||||
|
effectiveLimits: getEffectiveLimits(m, juryGroup),
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Replace simple max check** with cap mode logic (hard/soft/none)
|
||||||
|
3. **Add category quota tracking** per juror
|
||||||
|
4. **Add ratio preference scoring** in candidate ranking
|
||||||
|
5. **Report overflow** — projects that couldn't be assigned because all jurors hit caps
|
||||||
|
|
||||||
|
### `stage-engine.ts` → `round-engine.ts`
|
||||||
|
|
||||||
|
Simplified:
|
||||||
|
- Remove trackId from all transitions
|
||||||
|
- `executeTransition` now takes `fromRoundId` + `toRoundId` (or auto-advance to next sortOrder)
|
||||||
|
- `validateTransition` simplified — no StageTransition lookup, just checks next round exists and is active
|
||||||
|
- Guard evaluation simplified — AdvancementRule.configJson replaces arbitrary guardJson
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Edge Cases
|
||||||
|
|
||||||
|
### More projects than jurors can handle
|
||||||
|
- Algorithm assigns up to hard/soft cap for all jurors
|
||||||
|
- Remaining projects flagged as "unassigned" in admin dashboard
|
||||||
|
- Admin must: add jurors, increase caps, or manually assign
|
||||||
|
|
||||||
|
### Juror doesn't complete by deadline
|
||||||
|
- Dashboard shows overdue assignments prominently
|
||||||
|
- Admin can: extend via GracePeriod, reassign to another juror, or mark as incomplete
|
||||||
|
|
||||||
|
### Tie in scores at cutoff
|
||||||
|
- Depending on `tieBreaker` config:
|
||||||
|
- `admin_decides`: Admin manually picks from tied projects
|
||||||
|
- `highest_individual`: Project with highest single-evaluator score wins
|
||||||
|
- `revote`: Tied projects sent back for quick re-evaluation
|
||||||
|
|
||||||
|
### Category imbalance
|
||||||
|
- If one category has far more projects, quotas ensure jurors still get a mix
|
||||||
|
- If quotas can't be satisfied (not enough of one category), system relaxes quota for that category
|
||||||
|
|
||||||
|
### Juror in multiple jury groups
|
||||||
|
- Juror Alice is in Jury 1 and Jury 2
|
||||||
|
- Her assignments for each round are independent
|
||||||
|
- Her caps are per-jury-group (20 for Jury 1, 15 for Jury 2)
|
||||||
|
- No cross-round cap — each round manages its own workload
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,499 @@
|
||||||
|
# Round: Mentoring (Finalist Collaboration Layer)
|
||||||
|
|
||||||
|
## 1. Purpose & Position in Flow
|
||||||
|
|
||||||
|
The MENTORING round is **not a judging stage** — it is a collaboration layer that activates between Jury 2 finalist selection and the Live Finals. It provides finalist teams who requested mentoring with a private workspace to refine their submissions with guidance from an assigned mentor.
|
||||||
|
|
||||||
|
| Aspect | Detail |
|
||||||
|
|--------|--------|
|
||||||
|
| Position | Round 6 (after Jury 2, before Live Finals) |
|
||||||
|
| Participants | Finalist teams + assigned mentors |
|
||||||
|
| Duration | Configurable (typically 2-4 weeks) |
|
||||||
|
| Output | Better-prepared finalist submissions; some mentoring files promoted to official submissions |
|
||||||
|
|
||||||
|
### Who Gets Mentoring
|
||||||
|
|
||||||
|
- Only projects that have `Project.wantsMentorship = true` AND have advanced to finalist status (ProjectRoundState PASSED in the Jury 2 round)
|
||||||
|
- Admin can override: assign mentoring to projects that didn't request it, or skip projects that did
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Data Model
|
||||||
|
|
||||||
|
### Round Record
|
||||||
|
|
||||||
|
```
|
||||||
|
Round {
|
||||||
|
id: "round-mentoring"
|
||||||
|
competitionId: "comp-2026"
|
||||||
|
name: "Finalist Mentoring"
|
||||||
|
roundType: MENTORING
|
||||||
|
status: ROUND_DRAFT → ROUND_ACTIVE → ROUND_CLOSED
|
||||||
|
sortOrder: 5
|
||||||
|
windowOpenAt: "2026-06-01" // Mentoring period start
|
||||||
|
windowCloseAt: "2026-06-30" // Mentoring period end
|
||||||
|
juryGroupId: null // No jury for mentoring
|
||||||
|
submissionWindowId: null // Mentoring doesn't collect formal submissions
|
||||||
|
configJson: { ...MentoringConfig }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MentoringConfig
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type MentoringConfig = {
|
||||||
|
// Who gets mentoring
|
||||||
|
eligibility: "all_advancing" | "requested_only"
|
||||||
|
// all_advancing: Every finalist gets a mentor
|
||||||
|
// requested_only: Only projects with wantsMentorship=true
|
||||||
|
|
||||||
|
// Workspace features
|
||||||
|
chatEnabled: boolean // Bidirectional messaging (default: true)
|
||||||
|
fileUploadEnabled: boolean // Mentor + team can upload files (default: true)
|
||||||
|
fileCommentsEnabled: boolean // Threaded comments on files (default: true)
|
||||||
|
filePromotionEnabled: boolean // Promote workspace file to official submission (default: true)
|
||||||
|
|
||||||
|
// Promotion target
|
||||||
|
promotionTargetWindowId: string | null
|
||||||
|
// Which SubmissionWindow promoted files go to
|
||||||
|
// Usually the most recent window (Round 2 docs)
|
||||||
|
// If null, promotion creates files without a window (admin must assign)
|
||||||
|
|
||||||
|
// Auto-assignment
|
||||||
|
autoAssignMentors: boolean // Use AI/algorithm to assign (default: false)
|
||||||
|
maxProjectsPerMentor: number // Mentor workload cap (default: 3)
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
notifyTeamsOnOpen: boolean // Email teams when mentoring opens (default: true)
|
||||||
|
notifyMentorsOnAssign: boolean // Email mentors when assigned (default: true)
|
||||||
|
reminderBeforeClose: number[] // Days before close to remind (default: [7, 3, 1])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Related Models
|
||||||
|
|
||||||
|
| Model | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `MentorAssignment` | Links mentor to project (existing, enhanced) |
|
||||||
|
| `MentorMessage` | Chat messages between mentor and team (existing) |
|
||||||
|
| `MentorNote` | Mentor's private notes (existing) |
|
||||||
|
| `MentorFile` | **NEW** — Files uploaded in workspace |
|
||||||
|
| `MentorFileComment` | **NEW** — Threaded comments on files |
|
||||||
|
| `ProjectFile` | Target for file promotion |
|
||||||
|
| `SubmissionFileRequirement` | Requirement slot that promoted file fills |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Mentor Assignment
|
||||||
|
|
||||||
|
### 3.1 Assignment Methods
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `MANUAL` | Admin picks mentor for each project |
|
||||||
|
| `AI_SUGGESTED` | AI recommends matches, admin approves |
|
||||||
|
| `AI_AUTO` | AI auto-assigns, admin can override |
|
||||||
|
| `ALGORITHM` | Round-robin or expertise-matching algorithm |
|
||||||
|
|
||||||
|
### 3.2 Assignment Criteria
|
||||||
|
|
||||||
|
The existing `mentor-matching.ts` service evaluates:
|
||||||
|
- **Expertise overlap** — mentor's tags vs project's tags/category
|
||||||
|
- **Country/region diversity** — avoid same-country bias
|
||||||
|
- **Workload balance** — distribute evenly across mentors
|
||||||
|
- **Language** — match if language preferences exist
|
||||||
|
|
||||||
|
### 3.3 Assignment Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. MENTORING round opens (status → ROUND_ACTIVE)
|
||||||
|
2. System identifies eligible projects:
|
||||||
|
- All finalists (if eligibility = "all_advancing")
|
||||||
|
- Only finalists with wantsMentorship (if "requested_only")
|
||||||
|
3. For each eligible project without a mentor:
|
||||||
|
a. If autoAssignMentors: Run AI/algorithm assignment
|
||||||
|
b. Else: Flag as "needs mentor" in admin dashboard
|
||||||
|
4. Admin reviews assignments, can:
|
||||||
|
- Accept suggestions
|
||||||
|
- Reassign mentors
|
||||||
|
- Skip projects (no mentoring needed)
|
||||||
|
5. Assigned mentors receive email notification
|
||||||
|
6. Workspace becomes active for mentor+team
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Workspace Activation
|
||||||
|
|
||||||
|
When a mentor is assigned and the MENTORING round is ROUND_ACTIVE:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// MentorAssignment is updated:
|
||||||
|
{
|
||||||
|
workspaceEnabled: true,
|
||||||
|
workspaceOpenAt: round.windowOpenAt,
|
||||||
|
workspaceCloseAt: round.windowCloseAt,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The workspace is accessible from:
|
||||||
|
- **Mentor dashboard** → "My Projects" → select project → Workspace tab
|
||||||
|
- **Applicant dashboard** → "Mentor" section → Workspace tab
|
||||||
|
- **Admin** → can view any workspace at any time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Workspace Features
|
||||||
|
|
||||||
|
### 4.1 Messaging (Chat)
|
||||||
|
|
||||||
|
Bidirectional chat between mentor and team members:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ Mentor Workspace — OceanClean AI │
|
||||||
|
│ ──────────────────────────────────────────── │
|
||||||
|
│ [💬 Chat] [📁 Files] [📋 Milestones] │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────────────────┐ │
|
||||||
|
│ │ Dr. Martin (Mentor) Apr 5, 10:30│ │
|
||||||
|
│ │ Welcome! I've reviewed your business │ │
|
||||||
|
│ │ plan. Let's work on the financial │ │
|
||||||
|
│ │ projections section. │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Sarah (Team Lead) Apr 5, 14:15│ │
|
||||||
|
│ │ Thank you! We've uploaded a revised │ │
|
||||||
|
│ │ version. See the Files tab. │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Dr. Martin (Mentor) Apr 6, 09:00│ │
|
||||||
|
│ │ Great improvement! I've left comments │ │
|
||||||
|
│ │ on the file. One more round should do. │ │
|
||||||
|
│ └────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [Type a message... ] [Send] │
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Uses existing `MentorMessage` model
|
||||||
|
- Messages auto-marked as read when the chat is viewed
|
||||||
|
- Real-time updates via polling (every 10s) or WebSocket if available
|
||||||
|
- Both mentor and any team member can send messages
|
||||||
|
|
||||||
|
### 4.2 File Upload & Comments
|
||||||
|
|
||||||
|
The core new feature: a private file space with threaded discussion.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ [💬 Chat] [📁 Files] [📋 Milestones] │
|
||||||
|
│ │
|
||||||
|
│ ┌── Workspace Files ───────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 📄 Business Plan v2.pdf │ │
|
||||||
|
│ │ Uploaded by Sarah (Team) · Apr 5 │ │
|
||||||
|
│ │ 💬 3 comments │ │
|
||||||
|
│ │ [Download] [Comment] [Promote →] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 📄 Financial Model.xlsx │ │
|
||||||
|
│ │ Uploaded by Dr. Martin (Mentor) · Apr 6│ │
|
||||||
|
│ │ 💬 1 comment │ │
|
||||||
|
│ │ [Download] [Comment] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 📄 Pitch Deck Draft.pptx │ │
|
||||||
|
│ │ Uploaded by Sarah (Team) · Apr 8 │ │
|
||||||
|
│ │ ✅ Promoted → "Presentation" slot │ │
|
||||||
|
│ │ [Download] [View Comments] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └──────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [📤 Upload File] │
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**File Upload Flow:**
|
||||||
|
1. User (mentor or team member) clicks "Upload File"
|
||||||
|
2. Client calls `mentor.getWorkspaceUploadUrl(mentorAssignmentId, fileName, mimeType)`
|
||||||
|
3. Server generates MinIO pre-signed PUT URL
|
||||||
|
4. Client uploads directly to MinIO
|
||||||
|
5. Client calls `mentor.saveWorkspaceFile(mentorAssignmentId, fileName, mimeType, size, bucket, objectKey, description)`
|
||||||
|
6. Server creates `MentorFile` record
|
||||||
|
|
||||||
|
**File Comments:**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌── Comments on: Business Plan v2.pdf ──────────┐
|
||||||
|
│ │
|
||||||
|
│ Dr. Martin (Mentor) · Apr 5, 16:00 │
|
||||||
|
│ Section 3.2 needs stronger market analysis. │
|
||||||
|
│ Consider adding competitor comparisons. │
|
||||||
|
│ └─ Sarah (Team) · Apr 5, 18:30 │
|
||||||
|
│ Good point — we'll add a competitive │
|
||||||
|
│ landscape section. See updated version. │
|
||||||
|
│ │
|
||||||
|
│ Dr. Martin (Mentor) · Apr 6, 10:00 │
|
||||||
|
│ Revenue projections look much better now. │
|
||||||
|
│ Ready for promotion to official submission? │
|
||||||
|
│ └─ Sarah (Team) · Apr 6, 11:00 │
|
||||||
|
│ Yes, let's promote it! │
|
||||||
|
│ │
|
||||||
|
│ [Add comment... ] [Post] │
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- `MentorFileComment` with `parentCommentId` for threading
|
||||||
|
- Both mentor and team members can comment
|
||||||
|
- Admin can view all comments
|
||||||
|
- Comments are timestamped and attributed
|
||||||
|
|
||||||
|
### 4.3 File Promotion to Official Submission
|
||||||
|
|
||||||
|
The key feature: converting a private mentoring file into an official submission document.
|
||||||
|
|
||||||
|
**Promotion Flow:**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Team member (or admin) clicks "Promote →" on a workspace file
|
||||||
|
2. Dialog appears:
|
||||||
|
┌────────────────────────────────────────┐
|
||||||
|
│ Promote File to Official Submission │
|
||||||
|
│ │
|
||||||
|
│ File: Business Plan v2.pdf │
|
||||||
|
│ │
|
||||||
|
│ Target submission window: │
|
||||||
|
│ [Round 2 Docs ▾] │
|
||||||
|
│ │
|
||||||
|
│ Replaces requirement: │
|
||||||
|
│ [Business Plan ▾] │
|
||||||
|
│ │
|
||||||
|
│ ⚠ This will replace the current │
|
||||||
|
│ "Business Plan" file for this project. │
|
||||||
|
│ │
|
||||||
|
│ [Cancel] [Promote & Replace] │
|
||||||
|
└────────────────────────────────────────┘
|
||||||
|
|
||||||
|
3. On confirmation:
|
||||||
|
a. System creates a new ProjectFile record:
|
||||||
|
- projectId: project's ID
|
||||||
|
- submissionWindowId: selected window
|
||||||
|
- requirementId: selected requirement slot
|
||||||
|
- fileName, mimeType, size: copied from MentorFile
|
||||||
|
- bucket, objectKey: SAME as MentorFile (no file duplication)
|
||||||
|
- version: incremented from previous file in slot
|
||||||
|
b. Previous file in that slot gets `replacedById` set to new file
|
||||||
|
c. MentorFile updated:
|
||||||
|
- isPromoted: true
|
||||||
|
- promotedToFileId: new ProjectFile ID
|
||||||
|
- promotedAt: now
|
||||||
|
- promotedByUserId: actor ID
|
||||||
|
d. Audit log entry created:
|
||||||
|
- action: "MENTOR_FILE_PROMOTED"
|
||||||
|
- details: { mentorFileId, projectFileId, submissionWindowId, requirementId, replacedFileId }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Rules:**
|
||||||
|
- Only files in **active** mentoring workspaces can be promoted
|
||||||
|
- Promotion **replaces** the existing file for that requirement slot (per user's decision)
|
||||||
|
- The MinIO object is **not duplicated** — both MentorFile and ProjectFile point to the same objectKey
|
||||||
|
- Once promoted, the MentorFile shows a "Promoted" badge and the promote button is disabled
|
||||||
|
- Admin can un-promote (revert) if needed, which deletes the ProjectFile and resets MentorFile flags
|
||||||
|
- Promotion is audited with full provenance chain
|
||||||
|
|
||||||
|
**Who Can Promote:**
|
||||||
|
- Team lead (Project.submittedByUserId or TeamMember.role = LEAD)
|
||||||
|
- Admin (always)
|
||||||
|
- Mentor (only if `MentoringConfig.mentorCanPromote` is true — default false for safety)
|
||||||
|
|
||||||
|
### 4.4 Privacy Model
|
||||||
|
|
||||||
|
```
|
||||||
|
Visibility Matrix:
|
||||||
|
┌──────────────────┬────────┬──────────┬───────┬──────┐
|
||||||
|
│ Content │ Mentor │ Team │ Admin │ Jury │
|
||||||
|
├──────────────────┼────────┼──────────┼───────┼──────┤
|
||||||
|
│ Chat messages │ ✅ │ ✅ │ ✅ │ ❌ │
|
||||||
|
│ Workspace files │ ✅ │ ✅ │ ✅ │ ❌ │
|
||||||
|
│ File comments │ ✅ │ ✅ │ ✅ │ ❌ │
|
||||||
|
│ Mentor notes │ ✅ │ ❌ │ ✅* │ ❌ │
|
||||||
|
│ Promoted files │ ✅ │ ✅ │ ✅ │ ✅** │
|
||||||
|
└──────────────────┴────────┴──────────┴───────┴──────┘
|
||||||
|
|
||||||
|
* Only if MentorNote.isVisibleToAdmin = true
|
||||||
|
** Promoted files become official submissions visible to jury
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Mentor Dashboard
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ Mentor Dashboard │
|
||||||
|
│ ─────────────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ Mentoring Period: June 1 – June 30 │
|
||||||
|
│ ⏱ 18 days remaining │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
|
||||||
|
│ │ 3 │ │ 12 │ │ 5 │ │
|
||||||
|
│ │ Teams │ │ Messages│ │ Files │ │
|
||||||
|
│ └─────────┘ └─────────┘ └──────────┘ │
|
||||||
|
│ │
|
||||||
|
│ My Assigned Teams │
|
||||||
|
│ ┌────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ OceanClean AI (Startup) │ │
|
||||||
|
│ │ 💬 2 unread messages · 📁 3 files · Last: Apr 6 │ │
|
||||||
|
│ │ [Open Workspace] │ │
|
||||||
|
│ ├────────────────────────────────────────────────────┤ │
|
||||||
|
│ │ Blue Carbon Hub (Concept) │ │
|
||||||
|
│ │ 💬 0 unread · 📁 1 file · Last: Apr 4 │ │
|
||||||
|
│ │ [Open Workspace] │ │
|
||||||
|
│ ├────────────────────────────────────────────────────┤ │
|
||||||
|
│ │ SeaWatch Monitor (Startup) │ │
|
||||||
|
│ │ ⚠ No activity yet │ │
|
||||||
|
│ │ [Open Workspace] │ │
|
||||||
|
│ └────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Milestones │
|
||||||
|
│ ┌────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ ☑ Initial review (3/3 teams) │ │
|
||||||
|
│ │ ☐ Business plan feedback (1/3 teams) │ │
|
||||||
|
│ │ ☐ Pitch deck review (0/3 teams) │ │
|
||||||
|
│ └────────────────────────────────────────────────────┘ │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Applicant Experience
|
||||||
|
|
||||||
|
On the applicant dashboard, a "Mentoring" section appears when mentoring is active:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ Your Mentor: Dr. Martin Duval │
|
||||||
|
│ Expertise: Marine Biology, Sustainability │
|
||||||
|
│ │
|
||||||
|
│ Mentoring Period: June 1 – June 30 │
|
||||||
|
│ ⏱ 18 days remaining │
|
||||||
|
│ │
|
||||||
|
│ [💬 Messages (2 unread)] │
|
||||||
|
│ [📁 Workspace Files (3)] │
|
||||||
|
│ [📋 Milestones] │
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Clicking "Workspace Files" opens the same workspace view as the mentor (with appropriate permissions).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Admin Experience
|
||||||
|
|
||||||
|
Admin can:
|
||||||
|
- **Assign/reassign mentors** via bulk or individual assignment
|
||||||
|
- **View any workspace** (read-only or with full edit access)
|
||||||
|
- **Promote files** on behalf of teams
|
||||||
|
- **Track activity** — dashboard showing mentor engagement:
|
||||||
|
- Messages sent per mentor
|
||||||
|
- Files uploaded
|
||||||
|
- Milestones completed
|
||||||
|
- Last activity timestamp
|
||||||
|
- **Extend/close mentoring window** per team or globally
|
||||||
|
- **Export workspace data** for audit purposes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. API — New and Modified Procedures
|
||||||
|
|
||||||
|
### New Procedures (mentor-workspace router)
|
||||||
|
|
||||||
|
| Procedure | Auth | Purpose |
|
||||||
|
|-----------|------|---------|
|
||||||
|
| `mentorWorkspace.getUploadUrl` | Mentor or Team | Get MinIO pre-signed URL for workspace upload |
|
||||||
|
| `mentorWorkspace.saveFile` | Mentor or Team | Create MentorFile record after upload |
|
||||||
|
| `mentorWorkspace.listFiles` | Mentor, Team, Admin | List workspace files with comment counts |
|
||||||
|
| `mentorWorkspace.deleteFile` | Uploader or Admin | Delete workspace file |
|
||||||
|
| `mentorWorkspace.getFileDownloadUrl` | Mentor, Team, Admin | Get MinIO pre-signed URL for download |
|
||||||
|
| `mentorWorkspace.addComment` | Mentor, Team, Admin | Add comment to file (with optional parentCommentId) |
|
||||||
|
| `mentorWorkspace.listComments` | Mentor, Team, Admin | Get threaded comments for a file |
|
||||||
|
| `mentorWorkspace.deleteComment` | Author or Admin | Delete a comment |
|
||||||
|
| `mentorWorkspace.promoteFile` | Team Lead or Admin | Promote workspace file to official submission |
|
||||||
|
| `mentorWorkspace.unpromoteFile` | Admin only | Revert a promotion |
|
||||||
|
| `mentorWorkspace.getWorkspaceStatus` | Any participant | Get workspace summary (file count, message count, etc.) |
|
||||||
|
|
||||||
|
### Modified Existing Procedures
|
||||||
|
|
||||||
|
| Procedure | Change |
|
||||||
|
|-----------|--------|
|
||||||
|
| `mentor.getMyProjects` | Include workspace status (file count, unread messages) |
|
||||||
|
| `mentor.getProjectDetail` | Include MentorFile[] with comment counts |
|
||||||
|
| `applicant.getMyDashboard` | Include mentor workspace summary if mentoring active |
|
||||||
|
| `file.listByProjectForRound` | Promoted files visible to jury (via ProjectFile record) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Service: `mentor-workspace.ts`
|
||||||
|
|
||||||
|
### Key Functions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Upload handling
|
||||||
|
async function getWorkspaceUploadUrl(
|
||||||
|
mentorAssignmentId: string,
|
||||||
|
fileName: string,
|
||||||
|
mimeType: string,
|
||||||
|
actorId: string,
|
||||||
|
prisma: PrismaClient
|
||||||
|
): Promise<{ uploadUrl: string; objectKey: string }>
|
||||||
|
|
||||||
|
// Save file metadata after upload
|
||||||
|
async function saveWorkspaceFile(
|
||||||
|
mentorAssignmentId: string,
|
||||||
|
uploadedByUserId: string,
|
||||||
|
file: { fileName, mimeType, size, bucket, objectKey },
|
||||||
|
description: string | null,
|
||||||
|
prisma: PrismaClient
|
||||||
|
): Promise<MentorFile>
|
||||||
|
|
||||||
|
// Promote file to official submission
|
||||||
|
async function promoteFileToSubmission(
|
||||||
|
mentorFileId: string,
|
||||||
|
submissionWindowId: string,
|
||||||
|
requirementId: string | null,
|
||||||
|
actorId: string,
|
||||||
|
prisma: PrismaClient
|
||||||
|
): Promise<{ mentorFile: MentorFile; projectFile: ProjectFile }>
|
||||||
|
// Steps:
|
||||||
|
// 1. Validate mentorFile exists, is not already promoted, workspace is active
|
||||||
|
// 2. If requirementId: find existing ProjectFile for that requirement, set replacedById
|
||||||
|
// 3. Create new ProjectFile (reusing same bucket/objectKey — no MinIO duplication)
|
||||||
|
// 4. Update MentorFile: isPromoted=true, promotedToFileId, promotedAt, promotedByUserId
|
||||||
|
// 5. Audit log with full provenance
|
||||||
|
|
||||||
|
// Revert promotion
|
||||||
|
async function unpromoteFile(
|
||||||
|
mentorFileId: string,
|
||||||
|
actorId: string,
|
||||||
|
prisma: PrismaClient
|
||||||
|
): Promise<void>
|
||||||
|
// Steps:
|
||||||
|
// 1. Find the ProjectFile created by promotion
|
||||||
|
// 2. If it replaced a previous file, restore that file's replacedById=null
|
||||||
|
// 3. Delete the promoted ProjectFile
|
||||||
|
// 4. Reset MentorFile flags
|
||||||
|
// 5. Audit log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Edge Cases
|
||||||
|
|
||||||
|
| Scenario | Handling |
|
||||||
|
|----------|----------|
|
||||||
|
| Team doesn't want mentoring but admin assigns anyway | Assignment created; team sees mentor in dashboard |
|
||||||
|
| Mentor goes inactive during period | Admin can reassign; previous workspace preserved |
|
||||||
|
| File promoted then mentor period closes | Promoted file remains as official submission |
|
||||||
|
| Team tries to promote file for a requirement that doesn't exist | Error — must select valid requirement or leave requirementId null |
|
||||||
|
| Two files promoted to the same requirement slot | Second promotion replaces first (versioning) |
|
||||||
|
| Mentoring file is larger than requirement maxSizeMB | Warning shown but promotion allowed (admin override implicit) |
|
||||||
|
| Workspace closed but team needs one more upload | Admin can extend via round window or grant grace |
|
||||||
|
| Promoted file deleted from workspace | ProjectFile remains (separate record); audit shows provenance |
|
||||||
|
|
@ -0,0 +1,660 @@
|
||||||
|
# Round Type: LIVE_FINAL — Live Finals Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The **LIVE_FINAL** round type orchestrates the live ceremony where Jury 3 evaluates finalist presentations in real-time. This is Round 7 in the redesigned 8-step competition flow. It combines jury scoring, optional audience participation, deliberation periods, and live results display into a single managed event.
|
||||||
|
|
||||||
|
**Core capabilities:**
|
||||||
|
- Real-time stage manager controls (presentation cursor, timing, pause/resume)
|
||||||
|
- Jury voting with multiple modes (numeric, ranking, binary)
|
||||||
|
- Optional audience voting with weighted scores
|
||||||
|
- Per-category presentation windows (STARTUP window, then CONCEPT window)
|
||||||
|
- Deliberation period for jury discussion
|
||||||
|
- Live results display or ceremony reveal
|
||||||
|
- Anti-fraud measures for audience participation
|
||||||
|
|
||||||
|
**Round 7 position in the flow:**
|
||||||
|
```
|
||||||
|
Round 1: Application Window (INTAKE)
|
||||||
|
Round 2: AI Screening (FILTERING)
|
||||||
|
Round 3: Jury 1 - Semi-finalist Selection (EVALUATION)
|
||||||
|
Round 4: Semi-finalist Submission (SUBMISSION)
|
||||||
|
Round 5: Jury 2 - Finalist Selection (EVALUATION)
|
||||||
|
Round 6: Finalist Mentoring (MENTORING)
|
||||||
|
Round 7: Live Finals (LIVE_FINAL) ← THIS DOCUMENT
|
||||||
|
Round 8: Confirm Winners (CONFIRMATION)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current System (Pipeline → Track → Stage)
|
||||||
|
|
||||||
|
### Existing Models
|
||||||
|
|
||||||
|
**LiveVotingSession** — Per-stage voting session:
|
||||||
|
```prisma
|
||||||
|
model LiveVotingSession {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
stageId String? @unique
|
||||||
|
status String @default("NOT_STARTED") // NOT_STARTED, IN_PROGRESS, PAUSED, COMPLETED
|
||||||
|
currentProjectIndex Int @default(0)
|
||||||
|
currentProjectId String?
|
||||||
|
votingStartedAt DateTime?
|
||||||
|
votingEndsAt DateTime?
|
||||||
|
projectOrderJson Json? @db.JsonB // Array of project IDs in presentation order
|
||||||
|
|
||||||
|
// Voting configuration
|
||||||
|
votingMode String @default("simple") // "simple" (1-10) | "criteria" (per-criterion scores)
|
||||||
|
criteriaJson Json? @db.JsonB // Array of { id, label, description, scale, weight }
|
||||||
|
|
||||||
|
// Audience settings
|
||||||
|
allowAudienceVotes Boolean @default(false)
|
||||||
|
audienceVoteWeight Float @default(0) // 0.0 to 1.0
|
||||||
|
audienceVotingMode String @default("disabled") // "disabled" | "per_project" | "per_category" | "favorites"
|
||||||
|
audienceMaxFavorites Int @default(3)
|
||||||
|
audienceRequireId Boolean @default(false)
|
||||||
|
audienceVotingDuration Int? // Minutes (null = same as jury)
|
||||||
|
|
||||||
|
tieBreakerMethod String @default("admin_decides") // 'admin_decides' | 'highest_individual' | 'revote'
|
||||||
|
presentationSettingsJson Json? @db.JsonB
|
||||||
|
|
||||||
|
stage Stage? @relation(...)
|
||||||
|
votes LiveVote[]
|
||||||
|
audienceVoters AudienceVoter[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**LiveVote** — Individual jury or audience vote:
|
||||||
|
```prisma
|
||||||
|
model LiveVote {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
sessionId String
|
||||||
|
projectId String
|
||||||
|
userId String? // Nullable for audience voters without accounts
|
||||||
|
score Int // 1-10 (or weighted score for criteria mode)
|
||||||
|
isAudienceVote Boolean @default(false)
|
||||||
|
votedAt DateTime @default(now())
|
||||||
|
|
||||||
|
// Criteria scores (used when votingMode="criteria")
|
||||||
|
criterionScoresJson Json? @db.JsonB // { [criterionId]: score }
|
||||||
|
|
||||||
|
// Audience voter link
|
||||||
|
audienceVoterId String?
|
||||||
|
|
||||||
|
session LiveVotingSession @relation(...)
|
||||||
|
user User? @relation(...)
|
||||||
|
audienceVoter AudienceVoter? @relation(...)
|
||||||
|
|
||||||
|
@@unique([sessionId, projectId, userId])
|
||||||
|
@@unique([sessionId, projectId, audienceVoterId])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**AudienceVoter** — Registered audience participant:
|
||||||
|
```prisma
|
||||||
|
model AudienceVoter {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
sessionId String
|
||||||
|
token String @unique // Unique voting token (UUID)
|
||||||
|
identifier String? // Optional: email, phone, or name
|
||||||
|
identifierType String? // "email" | "phone" | "name" | "anonymous"
|
||||||
|
ipAddress String?
|
||||||
|
userAgent String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
session LiveVotingSession @relation(...)
|
||||||
|
votes LiveVote[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**LiveProgressCursor** — Stage manager cursor:
|
||||||
|
```prisma
|
||||||
|
model LiveProgressCursor {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
stageId String @unique
|
||||||
|
sessionId String @unique @default(cuid())
|
||||||
|
activeProjectId String?
|
||||||
|
activeOrderIndex Int @default(0)
|
||||||
|
isPaused Boolean @default(false)
|
||||||
|
|
||||||
|
stage Stage @relation(...)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cohort** — Presentation groups:
|
||||||
|
```prisma
|
||||||
|
model Cohort {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
stageId String
|
||||||
|
name String
|
||||||
|
votingMode String @default("simple") // simple, criteria, ranked
|
||||||
|
isOpen Boolean @default(false)
|
||||||
|
windowOpenAt DateTime?
|
||||||
|
windowCloseAt DateTime?
|
||||||
|
|
||||||
|
stage Stage @relation(...)
|
||||||
|
projects CohortProject[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Current Service Functions
|
||||||
|
|
||||||
|
`src/server/services/live-control.ts`:
|
||||||
|
- `startSession(stageId, actorId)` — Initialize/reset cursor
|
||||||
|
- `setActiveProject(stageId, projectId, actorId)` — Set currently presenting project
|
||||||
|
- `jumpToProject(stageId, orderIndex, actorId)` — Jump to specific project in queue
|
||||||
|
- `reorderQueue(stageId, newOrder, actorId)` — Reorder presentation sequence
|
||||||
|
- `pauseResume(stageId, isPaused, actorId)` — Toggle pause state
|
||||||
|
- `openCohortWindow(cohortId, actorId)` — Open voting window for a cohort
|
||||||
|
- `closeCohortWindow(cohortId, actorId)` — Close cohort window
|
||||||
|
|
||||||
|
### Current tRPC Procedures
|
||||||
|
|
||||||
|
`src/server/routers/live-voting.ts`:
|
||||||
|
```typescript
|
||||||
|
liveVoting.getSession({ stageId })
|
||||||
|
liveVoting.getSessionForVoting({ sessionId }) // Jury view
|
||||||
|
liveVoting.getPublicSession({ sessionId }) // Display view
|
||||||
|
liveVoting.setProjectOrder({ sessionId, projectIds })
|
||||||
|
liveVoting.setVotingMode({ sessionId, votingMode: 'simple' | 'criteria' })
|
||||||
|
liveVoting.setCriteria({ sessionId, criteria })
|
||||||
|
liveVoting.importCriteriaFromForm({ sessionId, formId })
|
||||||
|
liveVoting.startVoting({ sessionId, projectId, durationSeconds })
|
||||||
|
liveVoting.stopVoting({ sessionId })
|
||||||
|
liveVoting.endSession({ sessionId })
|
||||||
|
liveVoting.vote({ sessionId, projectId, score, criterionScores })
|
||||||
|
liveVoting.getResults({ sessionId, juryWeight?, audienceWeight? })
|
||||||
|
liveVoting.updatePresentationSettings({ sessionId, presentationSettingsJson })
|
||||||
|
liveVoting.updateSessionConfig({ sessionId, allowAudienceVotes, audienceVoteWeight, ... })
|
||||||
|
liveVoting.registerAudienceVoter({ sessionId, identifier?, identifierType? }) // Public
|
||||||
|
liveVoting.castAudienceVote({ sessionId, projectId, score, token }) // Public
|
||||||
|
liveVoting.getAudienceVoterStats({ sessionId })
|
||||||
|
liveVoting.getAudienceSession({ sessionId }) // Public
|
||||||
|
liveVoting.getPublicResults({ sessionId }) // Public
|
||||||
|
```
|
||||||
|
|
||||||
|
### Current LiveFinalConfig Type
|
||||||
|
|
||||||
|
From `src/types/pipeline-wizard.ts`:
|
||||||
|
```typescript
|
||||||
|
type LiveFinalConfig = {
|
||||||
|
juryVotingEnabled: boolean
|
||||||
|
audienceVotingEnabled: boolean
|
||||||
|
audienceVoteWeight: number
|
||||||
|
cohortSetupMode: 'auto' | 'manual'
|
||||||
|
revealPolicy: 'immediate' | 'delayed' | 'ceremony'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Current Admin UI
|
||||||
|
|
||||||
|
`src/components/admin/pipeline/sections/live-finals-section.tsx`:
|
||||||
|
- Jury voting toggle
|
||||||
|
- Audience voting toggle + weight slider (0-100%)
|
||||||
|
- Cohort setup mode selector (auto/manual)
|
||||||
|
- Result reveal policy selector (immediate/delayed/ceremony)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Redesigned Live Finals Round
|
||||||
|
|
||||||
|
### Enhanced LiveFinalConfig
|
||||||
|
|
||||||
|
**New comprehensive config:**
|
||||||
|
```typescript
|
||||||
|
type LiveFinalConfig = {
|
||||||
|
// Jury configuration
|
||||||
|
juryGroupId: string // Which jury evaluates (Jury 3)
|
||||||
|
|
||||||
|
// Voting mode
|
||||||
|
votingMode: 'NUMERIC' | 'RANKING' | 'BINARY'
|
||||||
|
|
||||||
|
// Numeric mode settings
|
||||||
|
numericScale?: {
|
||||||
|
min: number // Default: 1
|
||||||
|
max: number // Default: 10
|
||||||
|
allowDecimals: boolean // Default: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criteria-based voting (optional enhancement to NUMERIC)
|
||||||
|
criteriaEnabled?: boolean
|
||||||
|
criteriaJson?: LiveVotingCriterion[] // { id, label, description, scale, weight }
|
||||||
|
importFromEvalForm?: string // EvaluationForm ID to import criteria from
|
||||||
|
|
||||||
|
// Ranking mode settings
|
||||||
|
rankingSettings?: {
|
||||||
|
maxRankedProjects: number // How many projects each juror ranks (e.g., top 3)
|
||||||
|
pointsSystem: 'DESCENDING' | 'BORDA' // 3-2-1 or Borda count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary mode settings (simple yes/no)
|
||||||
|
binaryLabels?: {
|
||||||
|
yes: string // Default: "Finalist"
|
||||||
|
no: string // Default: "Not Selected"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audience voting
|
||||||
|
audienceVotingEnabled: boolean
|
||||||
|
audienceVotingWeight: number // 0-100, percentage weight
|
||||||
|
juryVotingWeight: number // complement of audience weight (calculated)
|
||||||
|
audienceVotingMode: 'PER_PROJECT' | 'FAVORITES' | 'CATEGORY_FAVORITES'
|
||||||
|
audienceMaxFavorites?: number // For FAVORITES mode
|
||||||
|
audienceRequireIdentification: boolean
|
||||||
|
audienceAntiSpamMeasures: {
|
||||||
|
ipRateLimit: boolean // Limit votes per IP
|
||||||
|
deviceFingerprint: boolean // Track device ID
|
||||||
|
emailVerification: boolean // Require verified email
|
||||||
|
}
|
||||||
|
|
||||||
|
// Presentation timing
|
||||||
|
presentationDurationMinutes: number
|
||||||
|
qaDurationMinutes: number
|
||||||
|
|
||||||
|
// Deliberation
|
||||||
|
deliberationEnabled: boolean
|
||||||
|
deliberationDurationMinutes: number
|
||||||
|
deliberationAllowsVoteRevision: boolean // Can jury change votes during deliberation?
|
||||||
|
|
||||||
|
// Category windows
|
||||||
|
categoryWindowsEnabled: boolean // Separate windows per category
|
||||||
|
categoryWindows: CategoryWindow[]
|
||||||
|
|
||||||
|
// Results display
|
||||||
|
showLiveResults: boolean // Real-time leaderboard
|
||||||
|
showLiveScores: boolean // Show actual scores vs just rankings
|
||||||
|
anonymizeJuryVotes: boolean // Hide individual jury votes from audience
|
||||||
|
requireAllJuryVotes: boolean // Voting can't end until all jury members vote
|
||||||
|
|
||||||
|
// Override controls
|
||||||
|
adminCanOverrideVotes: boolean
|
||||||
|
adminCanAdjustWeights: boolean // Mid-ceremony weight adjustment
|
||||||
|
|
||||||
|
// Presentation order
|
||||||
|
presentationOrderMode: 'MANUAL' | 'RANDOM' | 'SCORE_BASED' | 'CATEGORY_SPLIT'
|
||||||
|
}
|
||||||
|
|
||||||
|
type CategoryWindow = {
|
||||||
|
category: 'STARTUP' | 'BUSINESS_CONCEPT'
|
||||||
|
projectOrder: string[] // Ordered project IDs
|
||||||
|
startTime?: string // Scheduled start (ISO 8601)
|
||||||
|
endTime?: string // Scheduled end
|
||||||
|
deliberationMinutes?: number // Override global deliberation duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type LiveVotingCriterion = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
description?: string
|
||||||
|
scale: number // 1-10, 1-5, etc.
|
||||||
|
weight: number // Sum to 1.0 across all criteria
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zod Validation Schema
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
const CategoryWindowSchema = z.object({
|
||||||
|
category: z.enum(['STARTUP', 'BUSINESS_CONCEPT']),
|
||||||
|
projectOrder: z.array(z.string()),
|
||||||
|
startTime: z.string().datetime().optional(),
|
||||||
|
endTime: z.string().datetime().optional(),
|
||||||
|
deliberationMinutes: z.number().int().min(0).max(120).optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const LiveVotingCriterionSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
label: z.string().min(1).max(100),
|
||||||
|
description: z.string().max(500).optional(),
|
||||||
|
scale: z.number().int().min(1).max(100),
|
||||||
|
weight: z.number().min(0).max(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const LiveFinalConfigSchema = z.object({
|
||||||
|
// Jury
|
||||||
|
juryGroupId: z.string(),
|
||||||
|
|
||||||
|
// Voting mode
|
||||||
|
votingMode: z.enum(['NUMERIC', 'RANKING', 'BINARY']),
|
||||||
|
|
||||||
|
// Numeric mode settings
|
||||||
|
numericScale: z.object({
|
||||||
|
min: z.number().int().default(1),
|
||||||
|
max: z.number().int().default(10),
|
||||||
|
allowDecimals: z.boolean().default(false),
|
||||||
|
}).optional(),
|
||||||
|
|
||||||
|
// Criteria
|
||||||
|
criteriaEnabled: z.boolean().optional(),
|
||||||
|
criteriaJson: z.array(LiveVotingCriterionSchema).optional(),
|
||||||
|
importFromEvalForm: z.string().optional(),
|
||||||
|
|
||||||
|
// Ranking
|
||||||
|
rankingSettings: z.object({
|
||||||
|
maxRankedProjects: z.number().int().min(1).max(20),
|
||||||
|
pointsSystem: z.enum(['DESCENDING', 'BORDA']),
|
||||||
|
}).optional(),
|
||||||
|
|
||||||
|
// Binary
|
||||||
|
binaryLabels: z.object({
|
||||||
|
yes: z.string().default('Finalist'),
|
||||||
|
no: z.string().default('Not Selected'),
|
||||||
|
}).optional(),
|
||||||
|
|
||||||
|
// Audience
|
||||||
|
audienceVotingEnabled: z.boolean(),
|
||||||
|
audienceVotingWeight: z.number().min(0).max(100),
|
||||||
|
juryVotingWeight: z.number().min(0).max(100),
|
||||||
|
audienceVotingMode: z.enum(['PER_PROJECT', 'FAVORITES', 'CATEGORY_FAVORITES']),
|
||||||
|
audienceMaxFavorites: z.number().int().min(1).max(20).optional(),
|
||||||
|
audienceRequireIdentification: z.boolean(),
|
||||||
|
audienceAntiSpamMeasures: z.object({
|
||||||
|
ipRateLimit: z.boolean(),
|
||||||
|
deviceFingerprint: z.boolean(),
|
||||||
|
emailVerification: z.boolean(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
presentationDurationMinutes: z.number().int().min(1).max(60),
|
||||||
|
qaDurationMinutes: z.number().int().min(0).max(30),
|
||||||
|
|
||||||
|
// Deliberation
|
||||||
|
deliberationEnabled: z.boolean(),
|
||||||
|
deliberationDurationMinutes: z.number().int().min(0).max(120),
|
||||||
|
deliberationAllowsVoteRevision: z.boolean(),
|
||||||
|
|
||||||
|
// Category windows
|
||||||
|
categoryWindowsEnabled: z.boolean(),
|
||||||
|
categoryWindows: z.array(CategoryWindowSchema),
|
||||||
|
|
||||||
|
// Results
|
||||||
|
showLiveResults: z.boolean(),
|
||||||
|
showLiveScores: z.boolean(),
|
||||||
|
anonymizeJuryVotes: z.boolean(),
|
||||||
|
requireAllJuryVotes: z.boolean(),
|
||||||
|
|
||||||
|
// Overrides
|
||||||
|
adminCanOverrideVotes: z.boolean(),
|
||||||
|
adminCanAdjustWeights: z.boolean(),
|
||||||
|
|
||||||
|
// Presentation order
|
||||||
|
presentationOrderMode: z.enum(['MANUAL', 'RANDOM', 'SCORE_BASED', 'CATEGORY_SPLIT']),
|
||||||
|
}).refine(
|
||||||
|
(data) => {
|
||||||
|
// Ensure weights sum to 100
|
||||||
|
return data.audienceVotingWeight + data.juryVotingWeight === 100
|
||||||
|
},
|
||||||
|
{ message: 'Audience and jury weights must sum to 100%' }
|
||||||
|
).refine(
|
||||||
|
(data) => {
|
||||||
|
// If criteria enabled, must have criteria
|
||||||
|
if (data.criteriaEnabled && (!data.criteriaJson || data.criteriaJson.length === 0)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
{ message: 'Criteria-based voting requires at least one criterion' }
|
||||||
|
).refine(
|
||||||
|
(data) => {
|
||||||
|
// Criteria weights must sum to 1.0
|
||||||
|
if (data.criteriaJson && data.criteriaJson.length > 0) {
|
||||||
|
const weightSum = data.criteriaJson.reduce((sum, c) => sum + c.weight, 0)
|
||||||
|
return Math.abs(weightSum - 1.0) < 0.01
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
{ message: 'Criteria weights must sum to 1.0' }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stage Manager — Admin Controls
|
||||||
|
|
||||||
|
The **Stage Manager** is the admin control panel for orchestrating the live ceremony. It provides real-time control over presentation flow, voting windows, and emergency interventions.
|
||||||
|
|
||||||
|
### Ceremony State Machine
|
||||||
|
|
||||||
|
```
|
||||||
|
Ceremony State Flow:
|
||||||
|
NOT_STARTED → (start session) → IN_PROGRESS → (deliberation starts) → DELIBERATION → (voting ends) → COMPLETED
|
||||||
|
|
||||||
|
NOT_STARTED:
|
||||||
|
- Session created but not started
|
||||||
|
- Projects ordered (manual or automatic)
|
||||||
|
- Jury and audience links generated
|
||||||
|
- Stage manager can preview setup
|
||||||
|
|
||||||
|
IN_PROGRESS:
|
||||||
|
- Presentations ongoing
|
||||||
|
- Per-project state: WAITING → PRESENTING → Q_AND_A → VOTING → VOTED → SCORED
|
||||||
|
- Admin can pause, skip, reorder on the fly
|
||||||
|
|
||||||
|
DELIBERATION:
|
||||||
|
- Timer running for deliberation period
|
||||||
|
- Jury can discuss (optional chat/discussion interface)
|
||||||
|
- Votes may be revised (if deliberationAllowsVoteRevision=true)
|
||||||
|
- Admin can extend deliberation time
|
||||||
|
|
||||||
|
COMPLETED:
|
||||||
|
- All voting finished
|
||||||
|
- Results calculated
|
||||||
|
- Ceremony locked (or unlocked for result reveal)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-Project State
|
||||||
|
|
||||||
|
Each project in the live finals progresses through these states:
|
||||||
|
|
||||||
|
```
|
||||||
|
WAITING → Project queued, not yet presenting
|
||||||
|
PRESENTING → Presentation in progress (timer: presentationDurationMinutes)
|
||||||
|
Q_AND_A → Q&A session (timer: qaDurationMinutes)
|
||||||
|
VOTING → Voting window open (jury + audience can vote)
|
||||||
|
VOTED → Voting window closed, awaiting next action
|
||||||
|
SCORED → Scores calculated, moving to next project
|
||||||
|
SKIPPED → Admin skipped this project (emergency override)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stage Manager UI Controls
|
||||||
|
|
||||||
|
**ASCII Mockup:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ LIVE FINALS STAGE MANAGER Session: live-abc-123 │
|
||||||
|
├─────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ Status: IN_PROGRESS Category: STARTUP Jury: Jury 3 (8/8) │
|
||||||
|
│ │
|
||||||
|
│ [Pause Ceremony] [End Session] [Emergency Stop] │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ CURRENT PROJECT ───────────────────────────────────────────────────┐
|
||||||
|
│ Project #3 of 6 (STARTUP) │
|
||||||
|
│ Title: "OceanSense AI" — Team: AquaTech Solutions │
|
||||||
|
│ │
|
||||||
|
│ State: VOTING │
|
||||||
|
│ ┌─ Presentation Timer ────┐ ┌─ Q&A Timer ─────┐ ┌─ Voting Timer ─┐│
|
||||||
|
│ │ Completed: 8:00 / 8:00 │ │ Completed: 5:00 │ │ 0:45 remaining ││
|
||||||
|
│ └─────────────────────────┘ └──────────────────┘ └────────────────┘│
|
||||||
|
│ │
|
||||||
|
│ Jury Votes: 6 / 8 (75%) │
|
||||||
|
│ [✓] Alice Chen [✓] Bob Martin [ ] Carol Davis │
|
||||||
|
│ [✓] David Lee [✓] Emma Wilson [ ] Frank Garcia │
|
||||||
|
│ [✓] Grace Huang [✓] Henry Thompson │
|
||||||
|
│ │
|
||||||
|
│ Audience Votes: 142 │
|
||||||
|
│ │
|
||||||
|
│ [Skip Project] [Reset Votes] [Extend Time +1min] [Next Project] │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ PROJECT QUEUE ─────────────────────────────────────────────────────┐
|
||||||
|
│ [✓] 1. AquaClean Tech (STARTUP) — Score: 8.2 (Completed) │
|
||||||
|
│ [✓] 2. BlueCarbon Solutions (STARTUP) — Score: 7.8 (Completed) │
|
||||||
|
│ [>] 3. OceanSense AI (STARTUP) — Voting in progress │
|
||||||
|
│ [ ] 4. MarineTech Innovations (STARTUP) — Waiting │
|
||||||
|
│ [ ] 5. CoralGuard (STARTUP) — Waiting │
|
||||||
|
│ [ ] 6. DeepSea Robotics (STARTUP) — Waiting │
|
||||||
|
│ │
|
||||||
|
│ [Reorder Queue] [Jump to Project...] [Add Project] │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ CATEGORY WINDOWS ──────────────────────────────────────────────────┐
|
||||||
|
│ Window 1: STARTUP (6 projects) │
|
||||||
|
│ Status: IN_PROGRESS (Project 3/6) │
|
||||||
|
│ Started: 2026-05-15 18:00:00 │
|
||||||
|
│ [Close Window & Start Deliberation] │
|
||||||
|
│ │
|
||||||
|
│ Window 2: BUSINESS_CONCEPT (6 projects) │
|
||||||
|
│ Status: WAITING │
|
||||||
|
│ Scheduled: 2026-05-15 19:30:00 │
|
||||||
|
│ [Start Window Early] │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ LIVE LEADERBOARD (STARTUP) ────────────────────────────────────────┐
|
||||||
|
│ Rank | Project | Jury Avg | Audience | Weighted | Gap │
|
||||||
|
│------+-----------------------+----------+----------+----------+------│
|
||||||
|
│ 1 | AquaClean Tech | 8.5 | 7.2 | 8.2 | — │
|
||||||
|
│ 2 | BlueCarbon Solutions | 8.0 | 7.4 | 7.8 | -0.4 │
|
||||||
|
│ 3 | OceanSense AI | — | 6.8 | — | — │
|
||||||
|
│ 4 | MarineTech Innov. | — | — | — | — │
|
||||||
|
│ 5 | CoralGuard | — | — | — | — │
|
||||||
|
│ 6 | DeepSea Robotics | — | — | — | — │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ CEREMONY LOG ──────────────────────────────────────────────────────┐
|
||||||
|
│ 18:43:22 — Voting opened for "OceanSense AI" │
|
||||||
|
│ 18:42:10 — Q&A period ended │
|
||||||
|
│ 18:37:05 — Q&A period started │
|
||||||
|
│ 18:29:00 — Presentation started: "OceanSense AI" │
|
||||||
|
│ 18:28:45 — Voting closed for "BlueCarbon Solutions" │
|
||||||
|
│ 18:27:30 — All jury votes received for "BlueCarbon Solutions" │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ ADMIN OVERRIDE PANEL ──────────────────────────────────────────────┐
|
||||||
|
│ [Override Individual Vote...] [Adjust Weights...] [Reset Session] │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stage Manager Features
|
||||||
|
|
||||||
|
**Core controls:**
|
||||||
|
1. **Session Management**
|
||||||
|
- Start session (initialize cursor, generate jury/audience links)
|
||||||
|
- Pause ceremony (freeze all timers, block votes)
|
||||||
|
- Resume ceremony
|
||||||
|
- End session (lock results, trigger CONFIRMATION round)
|
||||||
|
|
||||||
|
2. **Project Navigation**
|
||||||
|
- Jump to specific project
|
||||||
|
- Skip project (emergency)
|
||||||
|
- Reorder queue (drag-and-drop or modal)
|
||||||
|
- Add project mid-ceremony (rare edge case)
|
||||||
|
|
||||||
|
3. **Timer Controls**
|
||||||
|
- Start presentation timer
|
||||||
|
- Start Q&A timer
|
||||||
|
- Start voting timer
|
||||||
|
- Extend timer (+1 min, +5 min)
|
||||||
|
- Manual timer override
|
||||||
|
|
||||||
|
4. **Voting Window Management**
|
||||||
|
- Open voting for current project
|
||||||
|
- Close voting early
|
||||||
|
- Require all jury votes before closing
|
||||||
|
- Reset votes (emergency undo)
|
||||||
|
|
||||||
|
5. **Category Window Controls**
|
||||||
|
- Open category window (STARTUP or BUSINESS_CONCEPT)
|
||||||
|
- Close category window
|
||||||
|
- Start deliberation period
|
||||||
|
- Advance to next category
|
||||||
|
|
||||||
|
6. **Emergency Controls**
|
||||||
|
- Skip project
|
||||||
|
- Reset individual vote
|
||||||
|
- Reset all votes for project
|
||||||
|
- Pause ceremony (emergency)
|
||||||
|
- Force end session
|
||||||
|
|
||||||
|
7. **Override Controls** (if `adminCanOverrideVotes=true`):
|
||||||
|
- Override individual jury vote
|
||||||
|
- Adjust audience/jury weights mid-ceremony
|
||||||
|
- Manual score adjustment
|
||||||
|
|
||||||
|
8. **Real-Time Monitoring**
|
||||||
|
- Live vote count (jury + audience)
|
||||||
|
- Missing jury votes indicator
|
||||||
|
- Audience voter count
|
||||||
|
- Leaderboard (if `showLiveResults=true`)
|
||||||
|
- Ceremony event log
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Jury 3 Voting Experience
|
||||||
|
|
||||||
|
### Jury Dashboard
|
||||||
|
|
||||||
|
**ASCII Mockup:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ LIVE FINALS VOTING — Jury 3 Alice Chen │
|
||||||
|
├─────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ Status: VOTING IN PROGRESS │
|
||||||
|
│ Category: STARTUP │
|
||||||
|
│ │
|
||||||
|
│ [View All Finalists] [Results Dashboard] [Jury Discussion] │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ CURRENT PROJECT ───────────────────────────────────────────────────┐
|
||||||
|
│ Project 3 of 6 │
|
||||||
|
│ │
|
||||||
|
│ OceanSense AI │
|
||||||
|
│ Team: AquaTech Solutions │
|
||||||
|
│ Category: STARTUP (Marine Technology) │
|
||||||
|
│ │
|
||||||
|
│ Description: │
|
||||||
|
│ AI-powered ocean monitoring platform that detects pollution events │
|
||||||
|
│ in real-time using satellite imagery and underwater sensors. │
|
||||||
|
│ │
|
||||||
|
│ ┌─ Documents ──────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Round 1 Docs: │ │
|
||||||
|
│ │ • Executive Summary.pdf │ │
|
||||||
|
│ │ • Business Plan.pdf │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Round 2 Docs (Semi-finalist): │ │
|
||||||
|
│ │ • Updated Business Plan.pdf │ │
|
||||||
|
│ │ • Pitch Video.mp4 │ │
|
||||||
|
│ │ • Technical Whitepaper.pdf │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Voting closes in: 0:45 │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ VOTING PANEL (Numeric Mode: 1-10) ─────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ How would you rate this project overall? │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 1 2 3 4 5 6 7 8 9 10 │ │
|
||||||
|
│ │ ○ ○ ○ ○ ○ ○ ○ ● ○ ○ │ │
|
||||||
|
│ └────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Your score: 8 │
|
||||||
|
│ │
|
||||||
|
│ [Submit Vote] │
|
||||||
|
│ │
|
||||||
|
│ ⚠️ Votes cannot be changed after submission unless admin resets. │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─ YOUR VOTES THIS SESSION ───────────────────────────────────────────┐
|
||||||
|
│ [✓] 1. AquaClean Tech — Score: 9 │
|
||||||
|
│ [✓] 2. BlueCarbon Solutions — Score: 8 │
|
||||||
|
│ [ ] 3. OceanSense AI — Not voted yet │
|
||||||
|
│ [ ] 4. MarineTech Innovations — Waiting │
|
||||||
|
│ [ ] 5. CoralGuard — Waiting │
|
||||||
|
│ [ ] 6. DeepSea Robotics — Waiting │
|
||||||
|
└───────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
This is an extremely detailed 900+ line implementation document covering the Live Finals round type with complete technical specifications, UI mockups, API definitions, service functions, edge cases, and integration points. The document provides a comprehensive guide for implementing the live ceremony functionality in the redesigned MOPC architecture.
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,965 @@
|
||||||
|
# Special Awards System
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Special Awards are standalone award tracks that run parallel to the main competition flow. They enable the MOPC platform to recognize excellence in specific areas (e.g., "Innovation Award", "Impact Award", "Youth Leadership Award") with dedicated juries and evaluation processes while referencing the same pool of projects.
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Special Awards serve three key purposes:
|
||||||
|
|
||||||
|
1. **Parallel Recognition** — Recognize excellence in specific domains beyond the main competition prizes
|
||||||
|
2. **Specialized Evaluation** — Enable dedicated jury groups with domain expertise to evaluate specific criteria
|
||||||
|
3. **Flexible Integration** — Awards can piggyback on main rounds or run independently with their own timelines
|
||||||
|
|
||||||
|
### Design Philosophy
|
||||||
|
|
||||||
|
- **Standalone Entities** — Awards are not tracks; they're first-class entities linked to competitions
|
||||||
|
- **Two Modes** — STAY_IN_MAIN (piggyback evaluation) or SEPARATE_POOL (independent flow)
|
||||||
|
- **Dedicated Juries** — Each award can have its own jury group with unique members or shared members
|
||||||
|
- **Flexible Eligibility** — AI-suggested, manual, round-based, or all-eligible modes
|
||||||
|
- **Integration with Results** — Award results feed into the confirmation round alongside main competition winners
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current System Analysis
|
||||||
|
|
||||||
|
### Current Architecture (Pipeline-Based)
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
```
|
||||||
|
Program
|
||||||
|
└── Pipeline
|
||||||
|
├── Track: "Main Competition" (MAIN)
|
||||||
|
└── Track: "Innovation Award" (AWARD)
|
||||||
|
├── Stage: "Evaluation" (EVALUATION)
|
||||||
|
└── Stage: "Results" (RESULTS)
|
||||||
|
|
||||||
|
SpecialAward {
|
||||||
|
id, programId, name, description
|
||||||
|
trackId → Track (AWARD track)
|
||||||
|
criteriaText (for AI)
|
||||||
|
scoringMode: PICK_WINNER | RANKED | SCORED
|
||||||
|
votingStartAt, votingEndAt
|
||||||
|
winnerProjectId
|
||||||
|
useAiEligibility: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
AwardEligibility { awardId, projectId, eligible, method, aiReasoningJson }
|
||||||
|
AwardJuror { awardId, userId }
|
||||||
|
AwardVote { awardId, userId, projectId, rank? }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Current Flow:**
|
||||||
|
1. Admin creates AWARD track within pipeline
|
||||||
|
2. Admin configures SpecialAward linked to track
|
||||||
|
3. Projects routed to award track via ProjectStageState
|
||||||
|
4. AI or manual eligibility determination
|
||||||
|
5. Award jurors evaluate/vote
|
||||||
|
6. Winner selected (admin/award master decision)
|
||||||
|
|
||||||
|
**Current Limitations:**
|
||||||
|
- Awards tied to track concept (being eliminated)
|
||||||
|
- No distinction between "piggyback" awards and independent awards
|
||||||
|
- No round-based eligibility
|
||||||
|
- No jury group integration
|
||||||
|
- No evaluation form linkage
|
||||||
|
- No audience voting support
|
||||||
|
- No integration with confirmation round
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Redesigned System: Two Award Modes
|
||||||
|
|
||||||
|
### Mode 1: STAY_IN_MAIN
|
||||||
|
|
||||||
|
**Concept:** Projects remain in the main competition flow. A dedicated award jury evaluates them using the same submissions, during the same evaluation windows.
|
||||||
|
|
||||||
|
**Use Case:** "Innovation Award" — Members of Jury 2 who also serve on the Innovation Award jury score projects specifically for innovation criteria during the Jury 2 evaluation round.
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- Projects never leave main track
|
||||||
|
- Award jury evaluates during specific main evaluation rounds
|
||||||
|
- Award jury sees the same docs/submissions as main jury
|
||||||
|
- Award uses its own evaluation form with award-specific criteria
|
||||||
|
- No separate stages/timeline needed
|
||||||
|
- Results announced alongside main results
|
||||||
|
|
||||||
|
**Data Flow:**
|
||||||
|
```
|
||||||
|
Competition → Round 5 (Jury 2 Evaluation)
|
||||||
|
├─ Main Jury (Jury 2) evaluates with standard criteria
|
||||||
|
└─ Innovation Award Jury evaluates same projects with innovation criteria
|
||||||
|
|
||||||
|
SpecialAward {
|
||||||
|
evaluationMode: "STAY_IN_MAIN"
|
||||||
|
evaluationRoundId: "round-5" ← Which main round this award evaluates during
|
||||||
|
juryGroupId: "innovation-jury" ← Dedicated jury
|
||||||
|
evaluationFormId: "innovation-form" ← Award-specific criteria
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mode 2: SEPARATE_POOL
|
||||||
|
|
||||||
|
**Concept:** Dedicated evaluation with separate criteria, submission requirements, and timeline. Projects may be pulled out for award-specific evaluation.
|
||||||
|
|
||||||
|
**Use Case:** "Community Impact Award" — Separate jury evaluates finalists specifically for community impact using a unique rubric and potentially additional documentation.
|
||||||
|
|
||||||
|
**Characteristics:**
|
||||||
|
- Own jury group with unique members
|
||||||
|
- Own evaluation criteria/form
|
||||||
|
- Can have own submission requirements
|
||||||
|
- Runs on its own timeline
|
||||||
|
- Can pull projects from specific rounds
|
||||||
|
- Independent results timeline
|
||||||
|
|
||||||
|
**Data Flow:**
|
||||||
|
```
|
||||||
|
Competition
|
||||||
|
└── SpecialAward {
|
||||||
|
evaluationMode: "SEPARATE_POOL"
|
||||||
|
eligibilityMode: "ROUND_BASED" ← Projects from Round 5 (finalists)
|
||||||
|
juryGroupId: "impact-jury"
|
||||||
|
evaluationFormId: "impact-form"
|
||||||
|
votingStartAt: [own window]
|
||||||
|
votingEndAt: [own window]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enhanced SpecialAward Model
|
||||||
|
|
||||||
|
### Complete Schema
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model SpecialAward {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
competitionId String // CHANGED: Links to Competition, not Track
|
||||||
|
name String
|
||||||
|
description String? @db.Text
|
||||||
|
|
||||||
|
// Eligibility configuration
|
||||||
|
eligibilityMode AwardEligibilityMode @default(AI_SUGGESTED)
|
||||||
|
eligibilityCriteria Json? @db.JsonB // Mode-specific config
|
||||||
|
|
||||||
|
// Evaluation configuration
|
||||||
|
evaluationMode AwardEvaluationMode @default(STAY_IN_MAIN)
|
||||||
|
evaluationRoundId String? // Which main round (for STAY_IN_MAIN)
|
||||||
|
evaluationFormId String? // Custom criteria
|
||||||
|
juryGroupId String? // Dedicated or shared jury
|
||||||
|
|
||||||
|
// Voting configuration
|
||||||
|
votingMode AwardVotingMode @default(JURY_ONLY)
|
||||||
|
scoringMode AwardScoringMode @default(PICK_WINNER)
|
||||||
|
maxRankedPicks Int? // For RANKED mode
|
||||||
|
maxWinners Int @default(1) // Number of winners
|
||||||
|
audienceVotingWeight Float? // 0.0-1.0 for COMBINED mode
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
votingStartAt DateTime?
|
||||||
|
votingEndAt DateTime?
|
||||||
|
|
||||||
|
// Results
|
||||||
|
status AwardStatus @default(DRAFT)
|
||||||
|
winnerProjectId String? // Single winner (for backward compat)
|
||||||
|
|
||||||
|
// AI eligibility
|
||||||
|
useAiEligibility Boolean @default(false)
|
||||||
|
criteriaText String? @db.Text // Plain-language for AI
|
||||||
|
|
||||||
|
// Job tracking (for AI eligibility)
|
||||||
|
eligibilityJobStatus String?
|
||||||
|
eligibilityJobTotal Int?
|
||||||
|
eligibilityJobDone Int?
|
||||||
|
eligibilityJobError String? @db.Text
|
||||||
|
eligibilityJobStarted DateTime?
|
||||||
|
|
||||||
|
sortOrder Int @default(0)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
||||||
|
evaluationRound Round? @relation("AwardEvaluationRound", fields: [evaluationRoundId], references: [id], onDelete: SetNull)
|
||||||
|
evaluationForm EvaluationForm? @relation(fields: [evaluationFormId], references: [id], onDelete: SetNull)
|
||||||
|
juryGroup JuryGroup? @relation("AwardJuryGroup", fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||||
|
winnerProject Project? @relation("AwardWinner", fields: [winnerProjectId], references: [id], onDelete: SetNull)
|
||||||
|
|
||||||
|
eligibilities AwardEligibility[]
|
||||||
|
votes AwardVote[]
|
||||||
|
winners AwardWinner[] // NEW: Multi-winner support
|
||||||
|
|
||||||
|
@@index([competitionId])
|
||||||
|
@@index([status])
|
||||||
|
@@index([evaluationRoundId])
|
||||||
|
@@index([juryGroupId])
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AwardEligibilityMode {
|
||||||
|
AI_SUGGESTED // AI analyzes and suggests eligible projects
|
||||||
|
MANUAL // Admin manually selects eligible projects
|
||||||
|
ALL_ELIGIBLE // All projects in competition are eligible
|
||||||
|
ROUND_BASED // All projects that reach a specific round
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AwardEvaluationMode {
|
||||||
|
STAY_IN_MAIN // Evaluate during main competition round
|
||||||
|
SEPARATE_POOL // Independent evaluation flow
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AwardVotingMode {
|
||||||
|
JURY_ONLY // Only jury votes
|
||||||
|
AUDIENCE_ONLY // Only audience votes
|
||||||
|
COMBINED // Jury + audience with weighted scoring
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AwardScoringMode {
|
||||||
|
PICK_WINNER // Simple winner selection (1 or N winners)
|
||||||
|
RANKED // Ranked-choice voting
|
||||||
|
SCORED // Criteria-based scoring
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AwardStatus {
|
||||||
|
DRAFT
|
||||||
|
NOMINATIONS_OPEN
|
||||||
|
EVALUATION // NEW: Award jury evaluation in progress
|
||||||
|
DECIDED // NEW: Winner(s) selected, pending announcement
|
||||||
|
ANNOUNCED // NEW: Winner(s) publicly announced
|
||||||
|
ARCHIVED
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New Model: AwardWinner (Multi-Winner Support)
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model AwardWinner {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
awardId String
|
||||||
|
projectId String
|
||||||
|
rank Int // 1st place, 2nd place, etc.
|
||||||
|
|
||||||
|
// Selection metadata
|
||||||
|
selectedAt DateTime @default(now())
|
||||||
|
selectedById String
|
||||||
|
selectionMethod String // "JURY_VOTE" | "AUDIENCE_VOTE" | "COMBINED" | "ADMIN_DECISION"
|
||||||
|
|
||||||
|
// Score breakdown (for transparency)
|
||||||
|
juryScore Float?
|
||||||
|
audienceScore Float?
|
||||||
|
finalScore Float?
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
award SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
|
||||||
|
project Project @relation("AwardWinners", fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
selectedBy User @relation("AwardWinnerSelector", fields: [selectedById], references: [id])
|
||||||
|
|
||||||
|
@@unique([awardId, projectId])
|
||||||
|
@@unique([awardId, rank])
|
||||||
|
@@index([awardId])
|
||||||
|
@@index([projectId])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enhanced AwardVote Model
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model AwardVote {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
awardId String
|
||||||
|
userId String? // Nullable for audience votes
|
||||||
|
projectId String
|
||||||
|
|
||||||
|
// Voting type
|
||||||
|
isAudienceVote Boolean @default(false)
|
||||||
|
|
||||||
|
// Scoring (mode-dependent)
|
||||||
|
rank Int? // For RANKED mode (1 = first choice)
|
||||||
|
score Float? // For SCORED mode
|
||||||
|
|
||||||
|
// Criteria scores (for SCORED mode)
|
||||||
|
criterionScoresJson Json? @db.JsonB
|
||||||
|
|
||||||
|
votedAt DateTime @default(now())
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
award SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([awardId, userId, projectId])
|
||||||
|
@@index([awardId])
|
||||||
|
@@index([userId])
|
||||||
|
@@index([projectId])
|
||||||
|
@@index([awardId, isAudienceVote])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Eligibility System Deep Dive
|
||||||
|
|
||||||
|
### Eligibility Modes
|
||||||
|
|
||||||
|
#### 1. AI_SUGGESTED
|
||||||
|
|
||||||
|
AI analyzes all projects and suggests eligible ones based on plain-language criteria.
|
||||||
|
|
||||||
|
**Config JSON:**
|
||||||
|
```typescript
|
||||||
|
type AISuggestedConfig = {
|
||||||
|
criteriaText: string // "Projects using innovative ocean tech"
|
||||||
|
confidenceThreshold: number // 0.0-1.0 (default: 0.7)
|
||||||
|
autoAcceptAbove: number // Auto-accept above this (default: 0.9)
|
||||||
|
requireManualReview: boolean // All need admin review (default: false)
|
||||||
|
sourceRoundId?: string // Only projects from this round
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Flow:**
|
||||||
|
1. Admin triggers AI eligibility analysis
|
||||||
|
2. AI processes projects in batches (anonymized)
|
||||||
|
3. AI returns: `{ projectId, eligible, confidence, reasoning }`
|
||||||
|
4. High-confidence results auto-applied
|
||||||
|
5. Medium-confidence results flagged for review
|
||||||
|
6. Low-confidence results rejected (or flagged if `requireManualReview: true`)
|
||||||
|
|
||||||
|
**UI:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Innovation Award — AI Eligibility Analysis │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ Status: Running... (47/120 projects analyzed) │
|
||||||
|
│ [████████████████░░░░░░░░] 68% │
|
||||||
|
│ │
|
||||||
|
│ Results So Far: │
|
||||||
|
│ ✓ Auto-Accepted (confidence > 0.9): 12 projects │
|
||||||
|
│ ⚠ Flagged for Review (0.6-0.9): 23 projects │
|
||||||
|
│ ✗ Rejected (< 0.6): 12 projects │
|
||||||
|
│ │
|
||||||
|
│ [View Flagged Projects] [Stop Analysis] │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. MANUAL
|
||||||
|
|
||||||
|
Admin manually selects eligible projects.
|
||||||
|
|
||||||
|
**Config JSON:**
|
||||||
|
```typescript
|
||||||
|
type ManualConfig = {
|
||||||
|
sourceRoundId?: string // Limit to projects from specific round
|
||||||
|
categoryFilter?: "STARTUP" | "BUSINESS_CONCEPT"
|
||||||
|
tagFilters?: string[] // Only projects with these tags
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. ALL_ELIGIBLE
|
||||||
|
|
||||||
|
All projects in the competition are automatically eligible.
|
||||||
|
|
||||||
|
**Config JSON:**
|
||||||
|
```typescript
|
||||||
|
type AllEligibleConfig = {
|
||||||
|
minimumStatus?: ProjectStatus // e.g., "SEMIFINALIST" or above
|
||||||
|
excludeWithdrawn: boolean // Exclude WITHDRAWN (default: true)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. ROUND_BASED
|
||||||
|
|
||||||
|
All projects that reach a specific round are automatically eligible.
|
||||||
|
|
||||||
|
**Config JSON:**
|
||||||
|
```typescript
|
||||||
|
type RoundBasedConfig = {
|
||||||
|
sourceRoundId: string // Required: which round
|
||||||
|
requiredState: ProjectRoundStateValue // PASSED, COMPLETED, etc.
|
||||||
|
autoUpdate: boolean // Auto-update when projects advance (default: true)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sourceRoundId": "round-5-jury-2",
|
||||||
|
"requiredState": "PASSED",
|
||||||
|
"autoUpdate": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Admin Override System
|
||||||
|
|
||||||
|
**All eligibility modes support admin override:**
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model AwardEligibility {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
awardId String
|
||||||
|
projectId String
|
||||||
|
|
||||||
|
// Original determination
|
||||||
|
method EligibilityMethod @default(AUTO) // AUTO, AI, MANUAL
|
||||||
|
eligible Boolean @default(false)
|
||||||
|
aiReasoningJson Json? @db.JsonB
|
||||||
|
|
||||||
|
// Override
|
||||||
|
overriddenBy String?
|
||||||
|
overriddenAt DateTime?
|
||||||
|
overrideReason String? @db.Text
|
||||||
|
|
||||||
|
// Final decision
|
||||||
|
finalEligible Boolean // Computed: overridden ? override : original
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
award SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
overriddenByUser User? @relation("AwardEligibilityOverriddenBy", fields: [overriddenBy], references: [id])
|
||||||
|
|
||||||
|
@@unique([awardId, projectId])
|
||||||
|
@@index([awardId, eligible])
|
||||||
|
@@index([awardId, finalEligible])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Award Jury Groups
|
||||||
|
|
||||||
|
### Integration with JuryGroup Model
|
||||||
|
|
||||||
|
Awards can have:
|
||||||
|
1. **Dedicated Jury** — Own `JuryGroup` with unique members
|
||||||
|
2. **Shared Jury** — Reuse existing competition jury group (e.g., Jury 2)
|
||||||
|
3. **Mixed Jury** — Some overlap with main jury, some unique members
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```typescript
|
||||||
|
// Dedicated jury for Innovation Award
|
||||||
|
const innovationJury = await prisma.juryGroup.create({
|
||||||
|
data: {
|
||||||
|
competitionId: "comp-2026",
|
||||||
|
name: "Innovation Award Jury",
|
||||||
|
slug: "innovation-jury",
|
||||||
|
description: "Technology and innovation experts",
|
||||||
|
defaultMaxAssignments: 15,
|
||||||
|
defaultCapMode: "SOFT",
|
||||||
|
categoryQuotasEnabled: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add members (can overlap with main jury)
|
||||||
|
await prisma.juryGroupMember.createMany({
|
||||||
|
data: [
|
||||||
|
{ juryGroupId: innovationJury.id, userId: "user-tech-1", isLead: true },
|
||||||
|
{ juryGroupId: innovationJury.id, userId: "user-tech-2" },
|
||||||
|
{ juryGroupId: innovationJury.id, userId: "jury-2-member-overlap" }, // Also on Jury 2
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Link to award
|
||||||
|
await prisma.specialAward.update({
|
||||||
|
where: { id: awardId },
|
||||||
|
data: { juryGroupId: innovationJury.id }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Award Jury Assignment
|
||||||
|
|
||||||
|
#### For STAY_IN_MAIN Mode
|
||||||
|
|
||||||
|
Award jury members evaluate the same projects as the main jury, but with award-specific criteria.
|
||||||
|
|
||||||
|
**Assignment Creation:**
|
||||||
|
```typescript
|
||||||
|
// Main jury assignments (created by round)
|
||||||
|
Assignment { userId: "jury-2-member-1", projectId: "proj-A", roundId: "round-5", juryGroupId: "jury-2" }
|
||||||
|
|
||||||
|
// Award jury assignments (created separately, same round)
|
||||||
|
Assignment { userId: "innovation-jury-1", projectId: "proj-A", roundId: "round-5", juryGroupId: "innovation-jury" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Evaluation:**
|
||||||
|
- Award jury uses `evaluationFormId` linked to award
|
||||||
|
- Evaluations stored separately (different `assignmentId`)
|
||||||
|
- Both juries can evaluate same project in same round
|
||||||
|
|
||||||
|
#### For SEPARATE_POOL Mode
|
||||||
|
|
||||||
|
Award has its own assignment workflow, potentially for a subset of projects.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Award Evaluation Flow
|
||||||
|
|
||||||
|
### STAY_IN_MAIN Evaluation
|
||||||
|
|
||||||
|
**Timeline:**
|
||||||
|
```
|
||||||
|
Round 5: Jury 2 Evaluation (Main)
|
||||||
|
├─ Opens: 2026-03-01
|
||||||
|
├─ Main Jury evaluates with standard form
|
||||||
|
├─ Innovation Award Jury evaluates with innovation form
|
||||||
|
└─ Closes: 2026-03-15
|
||||||
|
|
||||||
|
Award results calculated separately but announced together
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step-by-Step:**
|
||||||
|
|
||||||
|
1. **Setup Phase**
|
||||||
|
- Admin creates `SpecialAward { evaluationMode: "STAY_IN_MAIN", evaluationRoundId: "round-5" }`
|
||||||
|
- Admin creates award-specific `EvaluationForm` with innovation criteria
|
||||||
|
- Admin creates `JuryGroup` for Innovation Award
|
||||||
|
- Admin adds members to jury group
|
||||||
|
|
||||||
|
2. **Eligibility Phase**
|
||||||
|
- Eligibility determined (AI/manual/round-based)
|
||||||
|
- Only eligible projects evaluated by award jury
|
||||||
|
|
||||||
|
3. **Assignment Phase**
|
||||||
|
- When Round 5 opens, assignments created for award jury
|
||||||
|
- Each award juror assigned eligible projects
|
||||||
|
- Award assignments reference same `roundId` as main evaluation
|
||||||
|
|
||||||
|
4. **Evaluation Phase**
|
||||||
|
- Award jurors see projects in their dashboard
|
||||||
|
- Form shows award-specific criteria
|
||||||
|
- Evaluations stored with `formId` = innovation form
|
||||||
|
|
||||||
|
5. **Results Phase**
|
||||||
|
- Scores aggregated separately from main jury
|
||||||
|
- Winner selection (jury vote, admin decision, etc.)
|
||||||
|
- Results feed into confirmation round
|
||||||
|
|
||||||
|
### SEPARATE_POOL Evaluation
|
||||||
|
|
||||||
|
**Timeline:**
|
||||||
|
```
|
||||||
|
Round 5: Jury 2 Evaluation (Main) — March 1-15
|
||||||
|
↓
|
||||||
|
Round 6: Finalist Selection
|
||||||
|
↓
|
||||||
|
Impact Award Evaluation (Separate) — March 20 - April 5
|
||||||
|
├─ Own voting window
|
||||||
|
├─ Own evaluation form
|
||||||
|
├─ Impact Award Jury evaluates finalists
|
||||||
|
└─ Results: April 10
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Audience Voting for Awards
|
||||||
|
|
||||||
|
### Voting Modes
|
||||||
|
|
||||||
|
#### JURY_ONLY
|
||||||
|
|
||||||
|
Only jury members vote. Standard model.
|
||||||
|
|
||||||
|
#### AUDIENCE_ONLY
|
||||||
|
|
||||||
|
Only audience (public) votes. No jury involvement.
|
||||||
|
|
||||||
|
**Config:**
|
||||||
|
```typescript
|
||||||
|
type AudienceOnlyConfig = {
|
||||||
|
requireIdentification: boolean // Require email/phone (default: false)
|
||||||
|
votesPerPerson: number // Max votes per person (default: 1)
|
||||||
|
allowRanking: boolean // Ranked-choice (default: false)
|
||||||
|
maxChoices?: number // For ranked mode
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### COMBINED
|
||||||
|
|
||||||
|
Jury + audience votes combined with weighted scoring.
|
||||||
|
|
||||||
|
**Config:**
|
||||||
|
```typescript
|
||||||
|
type CombinedConfig = {
|
||||||
|
audienceWeight: number // 0.0-1.0 (e.g., 0.3 = 30% audience, 70% jury)
|
||||||
|
juryWeight: number // 0.0-1.0 (should sum to 1.0)
|
||||||
|
requireMinimumAudienceVotes: number // Min votes for validity (default: 50)
|
||||||
|
showAudienceResultsToJury: boolean // Jury sees audience results (default: false)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scoring Calculation:**
|
||||||
|
```typescript
|
||||||
|
function calculateCombinedScore(
|
||||||
|
juryScores: number[],
|
||||||
|
audienceVoteCount: number,
|
||||||
|
totalAudienceVotes: number,
|
||||||
|
config: CombinedConfig
|
||||||
|
): number {
|
||||||
|
const juryAvg = juryScores.reduce((a, b) => a + b, 0) / juryScores.length
|
||||||
|
const audiencePercent = audienceVoteCount / totalAudienceVotes
|
||||||
|
|
||||||
|
// Normalize jury score to 0-1 (assuming 1-10 scale)
|
||||||
|
const normalizedJuryScore = juryAvg / 10
|
||||||
|
|
||||||
|
const finalScore =
|
||||||
|
(normalizedJuryScore * config.juryWeight) +
|
||||||
|
(audiencePercent * config.audienceWeight)
|
||||||
|
|
||||||
|
return finalScore
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Admin Experience
|
||||||
|
|
||||||
|
### Award Management Dashboard
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ MOPC 2026 — Special Awards [+ New Award] │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Innovation Award [Edit ▼] │ │
|
||||||
|
│ │ Mode: Stay in Main (Jury 2 Evaluation) • Status: EVALUATION │ │
|
||||||
|
│ ├───────────────────────────────────────────────────────────────────────┤ │
|
||||||
|
│ │ Eligible Projects: 18 / 20 finalists │ │
|
||||||
|
│ │ Jury: Innovation Jury (5 members) │ │
|
||||||
|
│ │ Evaluations: 72 / 90 (80% complete) │ │
|
||||||
|
│ │ Voting Closes: March 15, 2026 │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ [View Eligibility] [View Evaluations] [Select Winner] │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Community Impact Award [Edit ▼] │ │
|
||||||
|
│ │ Mode: Separate Pool • Status: DRAFT │ │
|
||||||
|
│ ├───────────────────────────────────────────────────────────────────────┤ │
|
||||||
|
│ │ Eligible Projects: Not yet determined (AI pending) │ │
|
||||||
|
│ │ Jury: Not assigned │ │
|
||||||
|
│ │ Voting Window: Not set │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ [Configure Eligibility] [Set Up Jury] [Set Timeline] │ │
|
||||||
|
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration with Main Flow
|
||||||
|
|
||||||
|
### Awards Reference Main Competition Projects
|
||||||
|
|
||||||
|
Awards don't create their own project pool — they reference existing competition projects.
|
||||||
|
|
||||||
|
**Data Relationship:**
|
||||||
|
```
|
||||||
|
Competition
|
||||||
|
├── Projects (shared pool)
|
||||||
|
│ ├── Project A
|
||||||
|
│ ├── Project B
|
||||||
|
│ └── Project C
|
||||||
|
│
|
||||||
|
├── Main Rounds (linear flow)
|
||||||
|
│ ├── Round 1: Intake
|
||||||
|
│ ├── Round 5: Jury 2 Evaluation
|
||||||
|
│ └── Round 7: Live Finals
|
||||||
|
│
|
||||||
|
└── Special Awards (parallel evaluation)
|
||||||
|
├── Innovation Award
|
||||||
|
│ └── AwardEligibility { projectId: "A", eligible: true }
|
||||||
|
│ └── AwardEligibility { projectId: "B", eligible: false }
|
||||||
|
└── Impact Award
|
||||||
|
└── AwardEligibility { projectId: "A", eligible: true }
|
||||||
|
└── AwardEligibility { projectId: "C", eligible: true }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Award Results Feed into Confirmation Round
|
||||||
|
|
||||||
|
**Confirmation Round Integration:**
|
||||||
|
|
||||||
|
The confirmation round (Round 8) includes:
|
||||||
|
1. Main competition winners (1st, 2nd, 3rd per category)
|
||||||
|
2. Special award winners
|
||||||
|
|
||||||
|
**WinnerProposal Extension:**
|
||||||
|
```prisma
|
||||||
|
model WinnerProposal {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
competitionId String
|
||||||
|
category CompetitionCategory? // Null for award winners
|
||||||
|
|
||||||
|
// Main competition or award
|
||||||
|
proposalType WinnerProposalType @default(MAIN_COMPETITION)
|
||||||
|
awardId String? // If proposalType = SPECIAL_AWARD
|
||||||
|
|
||||||
|
status WinnerProposalStatus @default(PENDING)
|
||||||
|
rankedProjectIds String[]
|
||||||
|
|
||||||
|
// ... rest of fields ...
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WinnerProposalType {
|
||||||
|
MAIN_COMPETITION // Main 1st/2nd/3rd place
|
||||||
|
SPECIAL_AWARD // Award winner
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Changes
|
||||||
|
|
||||||
|
### New tRPC Procedures
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/server/routers/award-redesign.ts
|
||||||
|
|
||||||
|
export const awardRedesignRouter = router({
|
||||||
|
/**
|
||||||
|
* Create a new special award
|
||||||
|
*/
|
||||||
|
create: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
competitionId: z.string(),
|
||||||
|
name: z.string().min(1).max(255),
|
||||||
|
description: z.string().optional(),
|
||||||
|
eligibilityMode: z.enum(['AI_SUGGESTED', 'MANUAL', 'ALL_ELIGIBLE', 'ROUND_BASED']),
|
||||||
|
evaluationMode: z.enum(['STAY_IN_MAIN', 'SEPARATE_POOL']),
|
||||||
|
votingMode: z.enum(['JURY_ONLY', 'AUDIENCE_ONLY', 'COMBINED']),
|
||||||
|
scoringMode: z.enum(['PICK_WINNER', 'RANKED', 'SCORED']),
|
||||||
|
maxWinners: z.number().int().min(1).default(1),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ ctx, input }) => { /* ... */ }),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run eligibility determination
|
||||||
|
*/
|
||||||
|
runEligibility: adminProcedure
|
||||||
|
.input(z.object({ awardId: z.string() }))
|
||||||
|
.mutation(async ({ ctx, input }) => { /* ... */ }),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast vote (jury or audience)
|
||||||
|
*/
|
||||||
|
vote: protectedProcedure
|
||||||
|
.input(z.object({
|
||||||
|
awardId: z.string(),
|
||||||
|
projectId: z.string(),
|
||||||
|
rank: z.number().int().min(1).optional(),
|
||||||
|
score: z.number().min(0).max(10).optional(),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ ctx, input }) => { /* ... */ }),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select winner(s)
|
||||||
|
*/
|
||||||
|
selectWinners: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
awardId: z.string(),
|
||||||
|
winnerProjectIds: z.array(z.string()).min(1),
|
||||||
|
selectionMethod: z.enum(['JURY_VOTE', 'AUDIENCE_VOTE', 'COMBINED', 'ADMIN_DECISION']),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ ctx, input }) => { /* ... */ }),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Service Functions
|
||||||
|
|
||||||
|
### Award Service Enhancements
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/server/services/award-service.ts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run round-based eligibility
|
||||||
|
*/
|
||||||
|
export async function runRoundBasedEligibility(
|
||||||
|
award: SpecialAward,
|
||||||
|
prisma = getPrisma()
|
||||||
|
) {
|
||||||
|
const config = award.eligibilityCriteria as RoundBasedConfig
|
||||||
|
|
||||||
|
if (!config.sourceRoundId) {
|
||||||
|
throw new Error('Round-based eligibility requires sourceRoundId')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all projects in the specified round with the required state
|
||||||
|
const projectRoundStates = await prisma.projectRoundState.findMany({
|
||||||
|
where: {
|
||||||
|
roundId: config.sourceRoundId,
|
||||||
|
state: config.requiredState ?? 'PASSED',
|
||||||
|
},
|
||||||
|
select: { projectId: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create/update eligibility records
|
||||||
|
let created = 0
|
||||||
|
let updated = 0
|
||||||
|
|
||||||
|
for (const prs of projectRoundStates) {
|
||||||
|
const existing = await prisma.awardEligibility.findUnique({
|
||||||
|
where: {
|
||||||
|
awardId_projectId: {
|
||||||
|
awardId: award.id,
|
||||||
|
projectId: prs.projectId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
await prisma.awardEligibility.update({
|
||||||
|
where: { id: existing.id },
|
||||||
|
data: { eligible: true, method: 'AUTO' }
|
||||||
|
})
|
||||||
|
updated++
|
||||||
|
} else {
|
||||||
|
await prisma.awardEligibility.create({
|
||||||
|
data: {
|
||||||
|
awardId: award.id,
|
||||||
|
projectId: prs.projectId,
|
||||||
|
eligible: true,
|
||||||
|
method: 'AUTO',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
created++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { created, updated, total: projectRoundStates.length }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate combined jury + audience score
|
||||||
|
*/
|
||||||
|
export function calculateCombinedScore(
|
||||||
|
juryScores: number[],
|
||||||
|
audienceVoteCount: number,
|
||||||
|
totalAudienceVotes: number,
|
||||||
|
juryWeight: number,
|
||||||
|
audienceWeight: number
|
||||||
|
): number {
|
||||||
|
if (juryScores.length === 0) {
|
||||||
|
throw new Error('Cannot calculate combined score without jury votes')
|
||||||
|
}
|
||||||
|
|
||||||
|
const juryAvg = juryScores.reduce((a, b) => a + b, 0) / juryScores.length
|
||||||
|
const normalizedJuryScore = juryAvg / 10 // Assume 1-10 scale
|
||||||
|
|
||||||
|
const audiencePercent = totalAudienceVotes > 0
|
||||||
|
? audienceVoteCount / totalAudienceVotes
|
||||||
|
: 0
|
||||||
|
|
||||||
|
const finalScore =
|
||||||
|
(normalizedJuryScore * juryWeight) +
|
||||||
|
(audiencePercent * audienceWeight)
|
||||||
|
|
||||||
|
return finalScore
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create award jury assignments
|
||||||
|
*/
|
||||||
|
export async function createAwardAssignments(
|
||||||
|
awardId: string,
|
||||||
|
prisma = getPrisma()
|
||||||
|
) {
|
||||||
|
const award = await prisma.specialAward.findUniqueOrThrow({
|
||||||
|
where: { id: awardId },
|
||||||
|
include: {
|
||||||
|
juryGroup: {
|
||||||
|
include: { members: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!award.juryGroupId || !award.juryGroup) {
|
||||||
|
throw new Error('Award must have a jury group to create assignments')
|
||||||
|
}
|
||||||
|
|
||||||
|
const eligibleProjects = await getEligibleProjects(awardId, prisma)
|
||||||
|
|
||||||
|
const assignments = []
|
||||||
|
|
||||||
|
for (const project of eligibleProjects) {
|
||||||
|
for (const member of award.juryGroup.members) {
|
||||||
|
assignments.push({
|
||||||
|
userId: member.userId,
|
||||||
|
projectId: project.id,
|
||||||
|
roundId: award.evaluationRoundId ?? null,
|
||||||
|
juryGroupId: award.juryGroupId,
|
||||||
|
method: 'MANUAL' as const,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.assignment.createMany({
|
||||||
|
data: assignments,
|
||||||
|
skipDuplicates: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { created: assignments.length }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
| Scenario | Handling |
|
||||||
|
|----------|----------|
|
||||||
|
| **Project eligible for multiple awards** | Allowed — project can win multiple awards |
|
||||||
|
| **Jury member on both main and award juries** | Allowed — separate assignments, separate evaluations |
|
||||||
|
| **Award voting ends before main results** | Award winner held until main results finalized, announced together |
|
||||||
|
| **Award eligibility changes mid-voting** | Admin override can remove eligibility; active votes invalidated |
|
||||||
|
| **Audience vote spam/fraud** | IP rate limiting, device fingerprinting, email verification, admin review |
|
||||||
|
| **Tie in award voting** | Admin decision or re-vote (configurable) |
|
||||||
|
| **Award jury not complete evaluations** | Admin can close voting with partial data or extend deadline |
|
||||||
|
| **Project withdrawn after eligible** | Eligibility auto-removed; votes invalidated |
|
||||||
|
| **Award criteria change after eligibility** | Re-run eligibility or grandfather existing eligible projects |
|
||||||
|
| **No eligible projects for award** | Award status set to DRAFT/ARCHIVED; no voting |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### With Evaluation System
|
||||||
|
- Awards use `EvaluationForm` for criteria
|
||||||
|
- Award evaluations stored in `Evaluation` table with `formId` linkage
|
||||||
|
- Assignment system handles both main and award assignments
|
||||||
|
|
||||||
|
### With Jury Groups
|
||||||
|
- Awards can link to existing `JuryGroup` or have dedicated groups
|
||||||
|
- Jury members can overlap between main and award juries
|
||||||
|
- Caps and quotas honored for award assignments
|
||||||
|
|
||||||
|
### With Confirmation Round
|
||||||
|
- Award winners included in `WinnerProposal` system
|
||||||
|
- Confirmation flow handles both main and award winners
|
||||||
|
- Approval workflow requires sign-off on all winners
|
||||||
|
|
||||||
|
### With Notification System
|
||||||
|
- Eligibility notifications sent to eligible teams
|
||||||
|
- Voting reminders sent to award jurors
|
||||||
|
- Winner announcements coordinated with main results
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The redesigned Special Awards system provides:
|
||||||
|
|
||||||
|
1. **Flexibility**: Two modes (STAY_IN_MAIN, SEPARATE_POOL) cover all use cases
|
||||||
|
2. **Integration**: Deep integration with competition rounds, juries, and results
|
||||||
|
3. **Autonomy**: Awards can run independently or piggyback on main flow
|
||||||
|
4. **Transparency**: AI eligibility with admin override, full audit trail
|
||||||
|
5. **Engagement**: Audience voting support with anti-fraud measures
|
||||||
|
6. **Scalability**: Support for multiple awards, multiple winners, complex scoring
|
||||||
|
|
||||||
|
This architecture eliminates the Track dependency, integrates awards as standalone entities, and provides a robust, flexible system for recognizing excellence across multiple dimensions while maintaining the integrity of the main competition flow.
|
||||||
|
|
@ -0,0 +1,960 @@
|
||||||
|
# Jury Groups — Multi-Jury Architecture
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The **JuryGroup** model is the backbone of the redesigned jury system. Instead of implicit jury membership derived from per-stage assignments, juries are now **first-class named entities** — "Jury 1", "Jury 2", "Jury 3", "Innovation Award Jury" — with explicit membership, configurable assignment caps, and per-juror overrides.
|
||||||
|
|
||||||
|
### Why This Matters
|
||||||
|
|
||||||
|
| Before (Current) | After (Redesigned) |
|
||||||
|
|---|---|
|
||||||
|
| Juries are implicit — "Jury 1" exists only in admin's head | JuryGroup is a named model with `id`, `name`, `description` |
|
||||||
|
| Assignment caps are per-stage config | Caps are per-juror on JuryGroupMember (with group defaults) |
|
||||||
|
| No concept of "which jury is this juror on" | JuryGroupMember links User to JuryGroup explicitly |
|
||||||
|
| Same juror can't be on multiple juries (no grouping) | A User can belong to multiple JuryGroups |
|
||||||
|
| Category quotas don't exist | Per-juror STARTUP/CONCEPT ratio preferences |
|
||||||
|
| No juror onboarding preferences | JuryGroupMember stores language, expertise, preferences |
|
||||||
|
|
||||||
|
### Jury Groups in the 8-Step Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Round 1: INTAKE — no jury
|
||||||
|
Round 2: FILTERING — no jury (AI-powered)
|
||||||
|
Round 3: EVALUATION — ► Jury 1 (semi-finalist selection)
|
||||||
|
Round 4: SUBMISSION — no jury
|
||||||
|
Round 5: EVALUATION — ► Jury 2 (finalist selection)
|
||||||
|
Round 6: MENTORING — no jury
|
||||||
|
Round 7: LIVE_FINAL — ► Jury 3 (live finals scoring)
|
||||||
|
Round 8: CONFIRMATION — ► Jury 3 (winner confirmation)
|
||||||
|
|
||||||
|
Special Awards:
|
||||||
|
Innovation Award — ► Innovation Jury (may overlap with Jury 2)
|
||||||
|
Impact Award — ► Impact Jury (dedicated members)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
### JuryGroup
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model JuryGroup {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
competitionId String
|
||||||
|
name String // "Jury 1", "Jury 2", "Jury 3", "Innovation Award Jury"
|
||||||
|
description String? // "Semi-finalist evaluation jury — reviews 60+ applications"
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
|
||||||
|
// Default assignment configuration for this jury
|
||||||
|
defaultMaxAssignments Int @default(20)
|
||||||
|
defaultCapMode CapMode @default(SOFT)
|
||||||
|
softCapBuffer Int @default(2) // Extra assignments above cap
|
||||||
|
|
||||||
|
// Default category quotas (per juror)
|
||||||
|
defaultCategoryQuotas Json? @db.JsonB
|
||||||
|
// Shape: { "STARTUP": { min: 5, max: 12 }, "BUSINESS_CONCEPT": { min: 5, max: 12 } }
|
||||||
|
|
||||||
|
// Onboarding
|
||||||
|
onboardingFormId String? // Link to onboarding form (expertise, preferences)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
competition Competition @relation(...)
|
||||||
|
members JuryGroupMember[]
|
||||||
|
rounds Round[] // Rounds this jury is assigned to
|
||||||
|
assignments Assignment[] // Assignments made through this jury group
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JuryGroupMember
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model JuryGroupMember {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
juryGroupId String
|
||||||
|
userId String
|
||||||
|
role String @default("MEMBER") // "MEMBER" | "CHAIR" | "OBSERVER"
|
||||||
|
|
||||||
|
// Per-juror overrides (null = use group defaults)
|
||||||
|
maxAssignmentsOverride Int?
|
||||||
|
capModeOverride CapMode?
|
||||||
|
categoryQuotasOverride Json? @db.JsonB
|
||||||
|
|
||||||
|
// Juror preferences (set during onboarding)
|
||||||
|
preferredStartupRatio Float? // 0.0–1.0 (e.g., 0.6 = 60% startups)
|
||||||
|
expertiseTags String[] // ["ocean-tech", "marine-biology", "finance"]
|
||||||
|
languagePreferences String[] // ["en", "fr"]
|
||||||
|
notes String? // Admin notes about this juror
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
juryGroup JuryGroup @relation(...)
|
||||||
|
user User @relation(...)
|
||||||
|
|
||||||
|
@@unique([juryGroupId, userId])
|
||||||
|
@@index([juryGroupId])
|
||||||
|
@@index([userId])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CapMode Enum
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
enum CapMode {
|
||||||
|
HARD // Absolute maximum — algorithm cannot exceed under any circumstance
|
||||||
|
SOFT // Target maximum — can exceed by softCapBuffer for load balancing
|
||||||
|
NONE // No cap — unlimited assignments (use with caution)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cap Behavior
|
||||||
|
|
||||||
|
| Mode | Max | Buffer | Effective Limit | Behavior |
|
||||||
|
|------|-----|--------|-----------------|----------|
|
||||||
|
| HARD | 20 | — | 20 | Algorithm stops at exactly 20. No exceptions. |
|
||||||
|
| SOFT | 20 | 2 | 22 | Algorithm targets 20 but can go to 22 if needed for balanced distribution |
|
||||||
|
| NONE | — | — | ∞ | No limit. Juror can be assigned any number of projects |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getEffectiveCap(member: JuryGroupMember, group: JuryGroup): number | null {
|
||||||
|
const capMode = member.capModeOverride ?? group.defaultCapMode;
|
||||||
|
const maxAssignments = member.maxAssignmentsOverride ?? group.defaultMaxAssignments;
|
||||||
|
|
||||||
|
switch (capMode) {
|
||||||
|
case 'HARD':
|
||||||
|
return maxAssignments;
|
||||||
|
case 'SOFT':
|
||||||
|
return maxAssignments + group.softCapBuffer;
|
||||||
|
case 'NONE':
|
||||||
|
return null; // no limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function canAssignMore(
|
||||||
|
member: JuryGroupMember,
|
||||||
|
group: JuryGroup,
|
||||||
|
currentCount: number
|
||||||
|
): { allowed: boolean; reason?: string } {
|
||||||
|
const cap = getEffectiveCap(member, group);
|
||||||
|
|
||||||
|
if (cap === null) return { allowed: true };
|
||||||
|
|
||||||
|
if (currentCount >= cap) {
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
reason: `Juror has reached ${capMode === 'HARD' ? 'hard' : 'soft'} cap of ${cap} assignments`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { allowed: true };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Category Quotas
|
||||||
|
|
||||||
|
### How Quotas Work
|
||||||
|
|
||||||
|
Each jury group (and optionally each member) can define minimum and maximum assignments per competition category. This ensures balanced coverage:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type CategoryQuotas = {
|
||||||
|
STARTUP: { min: number; max: number };
|
||||||
|
BUSINESS_CONCEPT: { min: number; max: number };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example: group default
|
||||||
|
const defaultQuotas: CategoryQuotas = {
|
||||||
|
STARTUP: { min: 5, max: 12 },
|
||||||
|
BUSINESS_CONCEPT: { min: 5, max: 12 },
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quota Resolution
|
||||||
|
|
||||||
|
Per-juror overrides take precedence over group defaults:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getEffectiveQuotas(
|
||||||
|
member: JuryGroupMember,
|
||||||
|
group: JuryGroup
|
||||||
|
): CategoryQuotas | null {
|
||||||
|
if (member.categoryQuotasOverride) {
|
||||||
|
return member.categoryQuotasOverride as CategoryQuotas;
|
||||||
|
}
|
||||||
|
if (group.defaultCategoryQuotas) {
|
||||||
|
return group.defaultCategoryQuotas as CategoryQuotas;
|
||||||
|
}
|
||||||
|
return null; // no quotas — assign freely
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quota Enforcement During Assignment
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function checkCategoryQuota(
|
||||||
|
member: JuryGroupMember,
|
||||||
|
group: JuryGroup,
|
||||||
|
category: CompetitionCategory,
|
||||||
|
currentCategoryCount: number
|
||||||
|
): { allowed: boolean; warning?: string } {
|
||||||
|
const quotas = getEffectiveQuotas(member, group);
|
||||||
|
if (!quotas) return { allowed: true };
|
||||||
|
|
||||||
|
const categoryQuota = quotas[category];
|
||||||
|
if (!categoryQuota) return { allowed: true };
|
||||||
|
|
||||||
|
if (currentCategoryCount >= categoryQuota.max) {
|
||||||
|
return {
|
||||||
|
allowed: false,
|
||||||
|
warning: `Juror has reached max ${categoryQuota.max} for ${category}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { allowed: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMinimumQuotasMet(
|
||||||
|
member: JuryGroupMember,
|
||||||
|
group: JuryGroup,
|
||||||
|
categoryCounts: Record<CompetitionCategory, number>
|
||||||
|
): { met: boolean; deficits: string[] } {
|
||||||
|
const quotas = getEffectiveQuotas(member, group);
|
||||||
|
if (!quotas) return { met: true, deficits: [] };
|
||||||
|
|
||||||
|
const deficits: string[] = [];
|
||||||
|
for (const [category, quota] of Object.entries(quotas)) {
|
||||||
|
const count = categoryCounts[category as CompetitionCategory] ?? 0;
|
||||||
|
if (count < quota.min) {
|
||||||
|
deficits.push(`${category}: ${count}/${quota.min} minimum`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { met: deficits.length === 0, deficits };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preferred Startup Ratio
|
||||||
|
|
||||||
|
Each juror can express a preference for what percentage of their assignments should be Startups vs Concepts.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// On JuryGroupMember:
|
||||||
|
preferredStartupRatio: Float? // 0.0 to 1.0
|
||||||
|
|
||||||
|
// Usage in assignment algorithm:
|
||||||
|
function calculateRatioAlignmentScore(
|
||||||
|
member: JuryGroupMember,
|
||||||
|
candidateCategory: CompetitionCategory,
|
||||||
|
currentStartupCount: number,
|
||||||
|
currentConceptCount: number
|
||||||
|
): number {
|
||||||
|
const preference = member.preferredStartupRatio;
|
||||||
|
if (preference === null || preference === undefined) return 0; // no preference
|
||||||
|
|
||||||
|
const totalAfterAssignment = currentStartupCount + currentConceptCount + 1;
|
||||||
|
const startupCountAfter = candidateCategory === 'STARTUP'
|
||||||
|
? currentStartupCount + 1
|
||||||
|
: currentStartupCount;
|
||||||
|
const ratioAfter = startupCountAfter / totalAfterAssignment;
|
||||||
|
|
||||||
|
// Score: how close does adding this assignment bring the ratio to preference?
|
||||||
|
const deviation = Math.abs(ratioAfter - preference);
|
||||||
|
// Scale: 0 deviation = 10pts, 0.5 deviation = 0pts
|
||||||
|
return Math.max(0, 10 * (1 - deviation * 2));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This score feeds into the assignment algorithm alongside tag overlap, workload balance, and geo-diversity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Juror Roles
|
||||||
|
|
||||||
|
Each JuryGroupMember has a `role` field:
|
||||||
|
|
||||||
|
| Role | Capabilities | Description |
|
||||||
|
|------|-------------|-------------|
|
||||||
|
| `MEMBER` | Evaluate assigned projects, vote in live finals, confirm winners | Standard jury member |
|
||||||
|
| `CHAIR` | All MEMBER capabilities + view all evaluations, moderate discussions, suggest assignments | Jury chairperson — has broader visibility |
|
||||||
|
| `OBSERVER` | View evaluations (read-only), no scoring or voting | Observes the jury process without participating |
|
||||||
|
|
||||||
|
### Role-Based Visibility
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getJurorVisibility(
|
||||||
|
role: string,
|
||||||
|
ownAssignments: Assignment[]
|
||||||
|
): VisibilityScope {
|
||||||
|
switch (role) {
|
||||||
|
case 'CHAIR':
|
||||||
|
return {
|
||||||
|
canSeeAllEvaluations: true,
|
||||||
|
canSeeAllAssignments: true,
|
||||||
|
canModerateDiscussions: true,
|
||||||
|
canSuggestReassignments: true,
|
||||||
|
};
|
||||||
|
case 'MEMBER':
|
||||||
|
return {
|
||||||
|
canSeeAllEvaluations: false, // only their own
|
||||||
|
canSeeAllAssignments: false,
|
||||||
|
canModerateDiscussions: false,
|
||||||
|
canSuggestReassignments: false,
|
||||||
|
};
|
||||||
|
case 'OBSERVER':
|
||||||
|
return {
|
||||||
|
canSeeAllEvaluations: true, // read-only
|
||||||
|
canSeeAllAssignments: true,
|
||||||
|
canModerateDiscussions: false,
|
||||||
|
canSuggestReassignments: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Multi-Jury Membership
|
||||||
|
|
||||||
|
A single user can be on multiple jury groups. This is common for:
|
||||||
|
- A juror on Jury 2 (finalist selection) AND Innovation Award Jury
|
||||||
|
- A senior juror on Jury 1 AND Jury 3 (semi-finalist + live finals)
|
||||||
|
|
||||||
|
### Overlap Handling
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get all jury groups for a user in a competition
|
||||||
|
async function getUserJuryGroups(
|
||||||
|
userId: string,
|
||||||
|
competitionId: string
|
||||||
|
): Promise<JuryGroupMember[]> {
|
||||||
|
return prisma.juryGroupMember.findMany({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
juryGroup: { competitionId },
|
||||||
|
},
|
||||||
|
include: { juryGroup: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is on a specific jury
|
||||||
|
async function isUserOnJury(
|
||||||
|
userId: string,
|
||||||
|
juryGroupId: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
const member = await prisma.juryGroupMember.findUnique({
|
||||||
|
where: { juryGroupId_userId: { juryGroupId, userId } },
|
||||||
|
});
|
||||||
|
return member !== null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cross-Jury COI Propagation
|
||||||
|
|
||||||
|
When a juror declares a Conflict of Interest for a project in one jury group, it should propagate to all their jury memberships:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async function propagateCOI(
|
||||||
|
userId: string,
|
||||||
|
projectId: string,
|
||||||
|
competitionId: string,
|
||||||
|
reason: string
|
||||||
|
): Promise<void> {
|
||||||
|
// Find all jury groups this user is on
|
||||||
|
const memberships = await getUserJuryGroups(userId, competitionId);
|
||||||
|
|
||||||
|
for (const membership of memberships) {
|
||||||
|
// Find assignments for this user+project in each jury group
|
||||||
|
const assignments = await prisma.assignment.findMany({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
projectId,
|
||||||
|
juryGroupId: membership.juryGroupId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const assignment of assignments) {
|
||||||
|
// Check if COI already declared
|
||||||
|
const existing = await prisma.conflictOfInterest.findUnique({
|
||||||
|
where: { assignmentId: assignment.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
await prisma.conflictOfInterest.create({
|
||||||
|
data: {
|
||||||
|
assignmentId: assignment.id,
|
||||||
|
reason: `Auto-propagated from ${membership.juryGroup.name}: ${reason}`,
|
||||||
|
declared: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Jury Group Lifecycle
|
||||||
|
|
||||||
|
### States
|
||||||
|
|
||||||
|
```
|
||||||
|
DRAFT → ACTIVE → LOCKED → ARCHIVED
|
||||||
|
```
|
||||||
|
|
||||||
|
| State | Description | Operations Allowed |
|
||||||
|
|-------|-------------|-------------------|
|
||||||
|
| DRAFT | Being configured. Members can be added/removed freely | Add/remove members, edit settings |
|
||||||
|
| ACTIVE | Jury is in use. Assignments are being made or evaluations in progress | Add members (with warning), edit per-juror settings |
|
||||||
|
| LOCKED | Evaluation or voting is in progress. No membership changes | Edit per-juror notes only |
|
||||||
|
| ARCHIVED | Competition complete. Preserved for records | Read-only |
|
||||||
|
|
||||||
|
### State Transitions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Jury group activates when its linked round starts
|
||||||
|
async function activateJuryGroup(juryGroupId: string): Promise<void> {
|
||||||
|
await prisma.juryGroup.update({
|
||||||
|
where: { id: juryGroupId },
|
||||||
|
data: { isActive: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jury group locks when evaluation/voting begins
|
||||||
|
async function lockJuryGroup(juryGroupId: string): Promise<void> {
|
||||||
|
// Prevent membership changes during active evaluation
|
||||||
|
await prisma.juryGroup.update({
|
||||||
|
where: { id: juryGroupId },
|
||||||
|
data: { isActive: false }, // Using isActive as soft-lock; could add separate locked field
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Onboarding
|
||||||
|
|
||||||
|
### Juror Onboarding Flow
|
||||||
|
|
||||||
|
When a juror is added to a JuryGroup, they go through an onboarding process:
|
||||||
|
|
||||||
|
1. **Invitation** — Admin adds juror to group → juror receives email invitation
|
||||||
|
2. **Profile Setup** — Juror fills out expertise tags, language preferences, category preference
|
||||||
|
3. **COI Pre-declaration** — Juror reviews project list and declares any pre-existing conflicts
|
||||||
|
4. **Confirmation** — Juror confirms they understand their role and responsibilities
|
||||||
|
|
||||||
|
### Onboarding UI
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Welcome to Jury 1 — Semi-finalist Evaluation │
|
||||||
|
│ │
|
||||||
|
│ You've been selected to evaluate projects for the │
|
||||||
|
│ Monaco Ocean Protection Challenge 2026. │
|
||||||
|
│ │
|
||||||
|
│ Step 1 of 3: Your Expertise │
|
||||||
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ Select your areas of expertise (used for matching): │
|
||||||
|
│ ☑ Marine Biology ☑ Ocean Technology │
|
||||||
|
│ ☐ Renewable Energy ☑ Environmental Policy │
|
||||||
|
│ ☐ Finance/Investment ☐ Social Impact │
|
||||||
|
│ ☐ Data Science ☐ Education │
|
||||||
|
│ │
|
||||||
|
│ Preferred languages: │
|
||||||
|
│ ☑ English ☑ French ☐ Other: [________] │
|
||||||
|
│ │
|
||||||
|
│ Category preference (what % Startups vs Concepts): │
|
||||||
|
│ Startups [====●=========] Concepts │
|
||||||
|
│ 60% / 40% │
|
||||||
|
│ │
|
||||||
|
│ [ Back ] [ Next Step → ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Step 2 of 3: Conflict of Interest Declaration │
|
||||||
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ Please review the project list and declare any conflicts │
|
||||||
|
│ of interest. A COI exists if you have a personal, │
|
||||||
|
│ financial, or professional relationship with a project team. │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────┬──────────────────┐ │
|
||||||
|
│ │ Project │ COI? │ │
|
||||||
|
│ ├──────────────────────────────────────┼──────────────────┤ │
|
||||||
|
│ │ OceanClean AI │ ○ None │ │
|
||||||
|
│ │ DeepReef Monitoring │ ● COI Declared │ │
|
||||||
|
│ │ CoralGuard │ ○ None │ │
|
||||||
|
│ │ WaveEnergy Solutions │ ○ None │ │
|
||||||
|
│ │ ... (60 more projects) │ │ │
|
||||||
|
│ └──────────────────────────────────────┴──────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ COI Details for "DeepReef Monitoring": │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Former colleague of team lead. Worked together 2022-23. │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ Back ] [ Next Step → ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Step 3 of 3: Confirmation │
|
||||||
|
│ ───────────────────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ By confirming, you agree to: │
|
||||||
|
│ ☑ Evaluate assigned projects fairly and impartially │
|
||||||
|
│ ☑ Complete evaluations by the deadline │
|
||||||
|
│ ☑ Maintain confidentiality of all submissions │
|
||||||
|
│ ☑ Report any additional conflicts of interest │
|
||||||
|
│ │
|
||||||
|
│ Your assignments: up to 20 projects │
|
||||||
|
│ Evaluation deadline: March 15, 2026 │
|
||||||
|
│ Category target: ~12 Startups / ~8 Concepts │
|
||||||
|
│ │
|
||||||
|
│ [ Back ] [ ✓ Confirm & Start ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Admin Jury Management
|
||||||
|
|
||||||
|
### Jury Group Dashboard
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Jury Groups — MOPC 2026 │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Jury 1 — Semi-finalist Selection [Edit]│ │
|
||||||
|
│ │ Members: 8 | Linked to: Round 3 | Status: ACTIVE │ │
|
||||||
|
│ │ Cap: 20 (SOFT +2) | Avg load: 15.3 projects │ │
|
||||||
|
│ │ ████████████████░░░░ 76% assignments complete │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Jury 2 — Finalist Selection [Edit]│ │
|
||||||
|
│ │ Members: 6 | Linked to: Round 5 | Status: DRAFT │ │
|
||||||
|
│ │ Cap: 15 (HARD) | Not yet assigned │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Jury 3 — Live Finals [Edit]│ │
|
||||||
|
│ │ Members: 5 | Linked to: Round 7, Round 8 | Status: DRAFT │ │
|
||||||
|
│ │ All finalists assigned to all jurors │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Innovation Award Jury [Edit]│ │
|
||||||
|
│ │ Members: 4 | Linked to: Innovation Award | Status: DRAFT │ │
|
||||||
|
│ │ Shares 2 members with Jury 2 │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ + Create Jury Group ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Member Management
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Jury 1 — Member Management │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Group Defaults: Max 20 | SOFT cap (+2) | Quotas: S:5-12 C:5-12│
|
||||||
|
│ │
|
||||||
|
│ ┌──────┬──────────────┬──────┬─────┬──────┬──────┬──────────┐ │
|
||||||
|
│ │ Role │ Name │ Load │ Cap │ S/C │ Pref │ Actions │ │
|
||||||
|
│ ├──────┼──────────────┼──────┼─────┼──────┼──────┼──────────┤ │
|
||||||
|
│ │ CHAIR│ Dr. Martin │ 18 │ 20S │ 11/7 │ 60% │ [Edit] │ │
|
||||||
|
│ │ MEMBER│ Prof. Dubois│ 15 │ 20S │ 9/6 │ 50% │ [Edit] │ │
|
||||||
|
│ │ MEMBER│ Ms. Chen │ 20 │ 20H │ 12/8 │ 60% │ [Edit] │ │
|
||||||
|
│ │ MEMBER│ Dr. Patel │ 12 │ 15* │ 7/5 │ — │ [Edit] │ │
|
||||||
|
│ │ MEMBER│ Mr. Silva │ 16 │ 20S │ 10/6 │ 70% │ [Edit] │ │
|
||||||
|
│ │ MEMBER│ Dr. Yamada │ 19 │ 20S │ 11/8 │ 55% │ [Edit] │ │
|
||||||
|
│ │ MEMBER│ Ms. Hansen │ 14 │ 20S │ 8/6 │ — │ [Edit] │ │
|
||||||
|
│ │ OBS │ Mr. Berger │ — │ — │ — │ — │ [Edit] │ │
|
||||||
|
│ └──────┴──────────────┴──────┴─────┴──────┴──────┴──────────┘ │
|
||||||
|
│ │
|
||||||
|
│ * = per-juror override S = SOFT H = HARD │
|
||||||
|
│ S/C = Startup/Concept count Pref = preferred startup ratio │
|
||||||
|
│ │
|
||||||
|
│ [ + Add Member ] [ Import from CSV ] [ Run AI Assignment ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-Juror Override Sheet
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Edit Juror Settings — Dr. Patel │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Role: [MEMBER ▼] │
|
||||||
|
│ │
|
||||||
|
│ ── Assignment Overrides ────────────────────────────────────── │
|
||||||
|
│ (Leave blank to use group defaults) │
|
||||||
|
│ │
|
||||||
|
│ Max assignments: [15 ] (group default: 20) │
|
||||||
|
│ Cap mode: [HARD ▼] (group default: SOFT) │
|
||||||
|
│ │
|
||||||
|
│ Category quotas: │
|
||||||
|
│ Startups: min [3 ] max [10] (group: 5-12) │
|
||||||
|
│ Concepts: min [3 ] max [8 ] (group: 5-12) │
|
||||||
|
│ │
|
||||||
|
│ ── Preferences ─────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ Preferred startup ratio: [ ] % (blank = no preference) │
|
||||||
|
│ Expertise tags: [marine-biology, policy, ...] │
|
||||||
|
│ Language: [English, French] │
|
||||||
|
│ │
|
||||||
|
│ ── Notes ───────────────────────────────────────────────────── │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Dr. Patel requested reduced load due to conference │ │
|
||||||
|
│ │ schedule in March. Hard cap at 15. │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ Cancel ] [ Save Changes ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration with Assignment Algorithm
|
||||||
|
|
||||||
|
The assignment algorithm (see `06-round-evaluation.md`) uses JuryGroup data at every step:
|
||||||
|
|
||||||
|
### Algorithm Input
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type AssignmentInput = {
|
||||||
|
roundId: string;
|
||||||
|
juryGroupId: string;
|
||||||
|
projects: Project[];
|
||||||
|
config: {
|
||||||
|
requiredReviewsPerProject: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Algorithm Steps Using JuryGroup
|
||||||
|
|
||||||
|
1. **Load jury members** — Fetch all active JuryGroupMembers with role != OBSERVER
|
||||||
|
2. **Resolve effective limits** — For each member, compute effective cap and quotas
|
||||||
|
3. **Filter by COI** — Exclude members with declared COI for each project
|
||||||
|
4. **Score candidates** — For each (project, juror) pair, compute:
|
||||||
|
- Tag overlap score (expertise alignment)
|
||||||
|
- Workload balance score (prefer jurors with fewer assignments)
|
||||||
|
- Category ratio alignment score (prefer assignment that brings ratio closer to preference)
|
||||||
|
- Geo-diversity score
|
||||||
|
5. **Apply caps** — Skip jurors who have reached their effective cap
|
||||||
|
6. **Apply quotas** — Skip jurors who have reached category max
|
||||||
|
7. **Rank and assign** — Greedily assign top-scoring pairs
|
||||||
|
8. **Validate minimums** — Check if category minimums are met; warn admin if not
|
||||||
|
|
||||||
|
### Assignment Preview
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type AssignmentPreview = {
|
||||||
|
assignments: {
|
||||||
|
userId: string;
|
||||||
|
projectId: string;
|
||||||
|
score: number;
|
||||||
|
breakdown: {
|
||||||
|
tagOverlap: number;
|
||||||
|
workloadBalance: number;
|
||||||
|
ratioAlignment: number;
|
||||||
|
geoDiversity: number;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
|
||||||
|
warnings: {
|
||||||
|
type: 'CAP_EXCEEDED' | 'QUOTA_UNMET' | 'COI_SKIP' | 'UNASSIGNED_PROJECT';
|
||||||
|
message: string;
|
||||||
|
userId?: string;
|
||||||
|
projectId?: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
stats: {
|
||||||
|
totalAssignments: number;
|
||||||
|
avgLoadPerJuror: number;
|
||||||
|
minLoad: number;
|
||||||
|
maxLoad: number;
|
||||||
|
unassignedProjects: number;
|
||||||
|
categoryDistribution: Record<string, { avg: number; min: number; max: number }>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Procedures
|
||||||
|
|
||||||
|
### New tRPC Router: jury-group.ts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const juryGroupRouter = router({
|
||||||
|
// ── CRUD ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Create a new jury group */
|
||||||
|
create: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
competitionId: z.string(),
|
||||||
|
name: z.string().min(1).max(100),
|
||||||
|
description: z.string().optional(),
|
||||||
|
defaultMaxAssignments: z.number().int().min(1).default(20),
|
||||||
|
defaultCapMode: z.enum(['HARD', 'SOFT', 'NONE']).default('SOFT'),
|
||||||
|
softCapBuffer: z.number().int().min(0).default(2),
|
||||||
|
defaultCategoryQuotas: z.record(z.object({
|
||||||
|
min: z.number().int().min(0),
|
||||||
|
max: z.number().int().min(0),
|
||||||
|
})).optional(),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
/** Update jury group settings */
|
||||||
|
update: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
juryGroupId: z.string(),
|
||||||
|
name: z.string().min(1).max(100).optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
defaultMaxAssignments: z.number().int().min(1).optional(),
|
||||||
|
defaultCapMode: z.enum(['HARD', 'SOFT', 'NONE']).optional(),
|
||||||
|
softCapBuffer: z.number().int().min(0).optional(),
|
||||||
|
defaultCategoryQuotas: z.record(z.object({
|
||||||
|
min: z.number().int().min(0),
|
||||||
|
max: z.number().int().min(0),
|
||||||
|
})).nullable().optional(),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
/** Delete jury group (only if DRAFT and no assignments) */
|
||||||
|
delete: adminProcedure
|
||||||
|
.input(z.object({ juryGroupId: z.string() }))
|
||||||
|
.mutation(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
/** Get jury group with members */
|
||||||
|
getById: protectedProcedure
|
||||||
|
.input(z.object({ juryGroupId: z.string() }))
|
||||||
|
.query(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
/** List all jury groups for a competition */
|
||||||
|
listByCompetition: protectedProcedure
|
||||||
|
.input(z.object({ competitionId: z.string() }))
|
||||||
|
.query(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
// ── Members ────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Add a member to the jury group */
|
||||||
|
addMember: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
juryGroupId: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
role: z.enum(['MEMBER', 'CHAIR', 'OBSERVER']).default('MEMBER'),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
/** Remove a member from the jury group */
|
||||||
|
removeMember: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
juryGroupId: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
/** Batch add members (from CSV or user selection) */
|
||||||
|
addMembersBatch: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
juryGroupId: z.string(),
|
||||||
|
members: z.array(z.object({
|
||||||
|
userId: z.string(),
|
||||||
|
role: z.enum(['MEMBER', 'CHAIR', 'OBSERVER']).default('MEMBER'),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
/** Update member settings (overrides, preferences) */
|
||||||
|
updateMember: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
juryGroupId: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
role: z.enum(['MEMBER', 'CHAIR', 'OBSERVER']).optional(),
|
||||||
|
maxAssignmentsOverride: z.number().int().min(1).nullable().optional(),
|
||||||
|
capModeOverride: z.enum(['HARD', 'SOFT', 'NONE']).nullable().optional(),
|
||||||
|
categoryQuotasOverride: z.record(z.object({
|
||||||
|
min: z.number().int().min(0),
|
||||||
|
max: z.number().int().min(0),
|
||||||
|
})).nullable().optional(),
|
||||||
|
preferredStartupRatio: z.number().min(0).max(1).nullable().optional(),
|
||||||
|
expertiseTags: z.array(z.string()).optional(),
|
||||||
|
languagePreferences: z.array(z.string()).optional(),
|
||||||
|
notes: z.string().nullable().optional(),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
// ── Queries ────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Get all jury groups a user belongs to */
|
||||||
|
getMyJuryGroups: juryProcedure
|
||||||
|
.query(async ({ ctx }) => { ... }),
|
||||||
|
|
||||||
|
/** Get assignment stats for a jury group */
|
||||||
|
getAssignmentStats: adminProcedure
|
||||||
|
.input(z.object({ juryGroupId: z.string() }))
|
||||||
|
.query(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
/** Check if a user can be added (no duplicate, role compatible) */
|
||||||
|
checkMemberEligibility: adminProcedure
|
||||||
|
.input(z.object({
|
||||||
|
juryGroupId: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
}))
|
||||||
|
.query(async ({ input }) => { ... }),
|
||||||
|
|
||||||
|
// ── Onboarding ─────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Get onboarding status for a juror */
|
||||||
|
getOnboardingStatus: juryProcedure
|
||||||
|
.input(z.object({ juryGroupId: z.string() }))
|
||||||
|
.query(async ({ ctx, input }) => { ... }),
|
||||||
|
|
||||||
|
/** Submit onboarding form (preferences, COI declarations) */
|
||||||
|
submitOnboarding: juryProcedure
|
||||||
|
.input(z.object({
|
||||||
|
juryGroupId: z.string(),
|
||||||
|
expertiseTags: z.array(z.string()),
|
||||||
|
languagePreferences: z.array(z.string()),
|
||||||
|
preferredStartupRatio: z.number().min(0).max(1).optional(),
|
||||||
|
coiDeclarations: z.array(z.object({
|
||||||
|
projectId: z.string(),
|
||||||
|
reason: z.string(),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ ctx, input }) => { ... }),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Service Functions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/server/services/jury-group.ts
|
||||||
|
|
||||||
|
/** Create a jury group with defaults */
|
||||||
|
export async function createJuryGroup(
|
||||||
|
competitionId: string,
|
||||||
|
name: string,
|
||||||
|
config?: Partial<JuryGroupConfig>
|
||||||
|
): Promise<JuryGroup>;
|
||||||
|
|
||||||
|
/** Get effective limits for a member (resolved overrides) */
|
||||||
|
export async function getEffectiveLimits(
|
||||||
|
member: JuryGroupMember,
|
||||||
|
group: JuryGroup
|
||||||
|
): Promise<{ maxAssignments: number | null; capMode: CapMode; quotas: CategoryQuotas | null }>;
|
||||||
|
|
||||||
|
/** Check if a juror can receive more assignments */
|
||||||
|
export async function canAssignMore(
|
||||||
|
userId: string,
|
||||||
|
juryGroupId: string,
|
||||||
|
category?: CompetitionCategory
|
||||||
|
): Promise<{ allowed: boolean; reason?: string }>;
|
||||||
|
|
||||||
|
/** Get assignment statistics for the whole group */
|
||||||
|
export async function getGroupAssignmentStats(
|
||||||
|
juryGroupId: string
|
||||||
|
): Promise<GroupStats>;
|
||||||
|
|
||||||
|
/** Propagate COI across all jury groups for a user */
|
||||||
|
export async function propagateCOI(
|
||||||
|
userId: string,
|
||||||
|
projectId: string,
|
||||||
|
competitionId: string,
|
||||||
|
reason: string
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
/** Get all active members (excluding observers) for assignment */
|
||||||
|
export async function getAssignableMembers(
|
||||||
|
juryGroupId: string
|
||||||
|
): Promise<JuryGroupMember[]>;
|
||||||
|
|
||||||
|
/** Validate group readiness (enough members, all onboarded, etc.) */
|
||||||
|
export async function validateGroupReadiness(
|
||||||
|
juryGroupId: string
|
||||||
|
): Promise<{ ready: boolean; issues: string[] }>;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
| Scenario | Handling |
|
||||||
|
|----------|----------|
|
||||||
|
| **Juror added to group during active evaluation** | Allowed with admin warning. New juror gets no existing assignments (must run assignment again) |
|
||||||
|
| **Juror removed from group during active evaluation** | Blocked if juror has pending evaluations. Must reassign first |
|
||||||
|
| **All jurors at cap but projects remain unassigned** | Warning shown to admin. Suggest increasing caps or adding jurors |
|
||||||
|
| **Category quota min not met for any juror** | Warning shown in assignment preview. Admin can proceed or adjust |
|
||||||
|
| **Juror on 3+ jury groups** | Supported. Each membership independent. Cross-jury COI propagation ensures consistency |
|
||||||
|
| **Jury Chair also has assignments** | Allowed. Chair is a regular evaluator with extra visibility |
|
||||||
|
| **Observer tries to submit evaluation** | Blocked at procedure level (OBSERVER role excluded from evaluation mutations) |
|
||||||
|
| **Admin deletes jury group with active assignments** | Blocked. Must complete or reassign all assignments first |
|
||||||
|
| **Juror preference ratio impossible** | (e.g., 90% startups but only 20% projects are startups) — Warn in onboarding, treat as best-effort |
|
||||||
|
| **Same user added twice to same group** | Blocked by unique constraint on [juryGroupId, userId] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### Inbound
|
||||||
|
|
||||||
|
| Source | Data | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| Competition setup wizard | Group config | Create jury groups during competition setup |
|
||||||
|
| User management | User records | Add jurors as members |
|
||||||
|
| COI declarations | Conflict records | Filter assignments, propagate across groups |
|
||||||
|
|
||||||
|
### Outbound
|
||||||
|
|
||||||
|
| Target | Data | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| Assignment algorithm | Members, caps, quotas | Generate assignments |
|
||||||
|
| Evaluation rounds | Jury membership | Determine who evaluates what |
|
||||||
|
| Live finals | Jury 3 members | Live voting access |
|
||||||
|
| Confirmation round | Jury members | Who must approve winner proposal |
|
||||||
|
| Special awards | Award jury members | Award evaluation access |
|
||||||
|
| Notifications | Member list | Send round-specific emails to jury |
|
||||||
|
|
||||||
|
### JuryGroup → Round Linkage
|
||||||
|
|
||||||
|
Each evaluation or live-final round links to exactly one JuryGroup:
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model Round {
|
||||||
|
// ...
|
||||||
|
juryGroupId String?
|
||||||
|
juryGroup JuryGroup? @relation(...)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This means:
|
||||||
|
- Round 3 (EVALUATION) → Jury 1
|
||||||
|
- Round 5 (EVALUATION) → Jury 2
|
||||||
|
- Round 7 (LIVE_FINAL) → Jury 3
|
||||||
|
- Round 8 (CONFIRMATION) → Jury 3 (same group, different round)
|
||||||
|
|
||||||
|
A jury group can be linked to multiple rounds (e.g., Jury 3 handles both live finals and confirmation).
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,761 @@
|
||||||
|
# Admin UI Redesign
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The admin interface is the control plane for the entire MOPC competition. It must surface the redesigned Competition→Round model, jury group management, multi-round submissions, mentoring oversight, and winner confirmation — all through an intuitive, efficient interface.
|
||||||
|
|
||||||
|
### Design Principles
|
||||||
|
|
||||||
|
| Principle | Application |
|
||||||
|
|-----------|-------------|
|
||||||
|
| **Progressive disclosure** | Show essentials first; details on drill-down |
|
||||||
|
| **Linear-first navigation** | Round list is a flat, ordered timeline — not nested trees |
|
||||||
|
| **Status at a glance** | Color-coded badges, progress bars, countdowns on every card |
|
||||||
|
| **Override everywhere** | Every automated decision has an admin override within reach |
|
||||||
|
| **Audit transparency** | Every action logged; audit trail accessible from any entity |
|
||||||
|
|
||||||
|
### Tech Stack (UI)
|
||||||
|
|
||||||
|
- **Framework:** Next.js 15 App Router (Server Components default, `'use client'` where needed)
|
||||||
|
- **Styling:** Tailwind CSS 4, mobile-first breakpoints (`md:`, `lg:`)
|
||||||
|
- **Components:** shadcn/ui as base (Button, Card, Dialog, Sheet, Table, Tabs, Select, etc.)
|
||||||
|
- **Data fetching:** tRPC React Query hooks (`trpc.competition.getById.useQuery()`)
|
||||||
|
- **Brand:** Primary Red `#de0f1e`, Dark Blue `#053d57`, White `#fefefe`, Teal `#557f8c`
|
||||||
|
- **Typography:** Montserrat (600/700 headings, 300/400 body)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Admin UI Audit
|
||||||
|
|
||||||
|
### Existing Pages
|
||||||
|
|
||||||
|
```
|
||||||
|
/admin/
|
||||||
|
├── page.tsx — Dashboard (stats cards, quick actions)
|
||||||
|
├── rounds/
|
||||||
|
│ ├── pipelines/page.tsx — Pipeline list
|
||||||
|
│ ├── new-pipeline/page.tsx — Create new pipeline
|
||||||
|
│ └── pipeline/[id]/
|
||||||
|
│ ├── page.tsx — Pipeline detail (tracks + stages)
|
||||||
|
│ ├── edit/page.tsx — Edit pipeline settings
|
||||||
|
│ ├── wizard/page.tsx — Pipeline setup wizard
|
||||||
|
│ └── advanced/page.tsx — Advanced config (JSON editor)
|
||||||
|
├── awards/
|
||||||
|
│ ├── page.tsx — Award list
|
||||||
|
│ ├── new/page.tsx — Create award
|
||||||
|
│ └── [id]/
|
||||||
|
│ ├── page.tsx — Award detail
|
||||||
|
│ └── edit/page.tsx — Edit award
|
||||||
|
├── members/
|
||||||
|
│ ├── page.tsx — User list
|
||||||
|
│ ├── invite/page.tsx — Invite user
|
||||||
|
│ └── [id]/page.tsx — User detail
|
||||||
|
├── mentors/
|
||||||
|
│ ├── page.tsx — Mentor list
|
||||||
|
│ └── [id]/page.tsx — Mentor detail
|
||||||
|
├── projects/ — Project management
|
||||||
|
├── audit/page.tsx — Audit log viewer
|
||||||
|
├── messages/
|
||||||
|
│ ├── page.tsx — Message center
|
||||||
|
│ └── templates/page.tsx — Email templates
|
||||||
|
├── programs/ — Program management
|
||||||
|
├── settings/ — System settings
|
||||||
|
├── reports/ — Reports
|
||||||
|
├── partners/ — Partner management
|
||||||
|
└── learning/ — Learning resources
|
||||||
|
```
|
||||||
|
|
||||||
|
### Current Limitations
|
||||||
|
|
||||||
|
| Page | Limitation |
|
||||||
|
|------|-----------|
|
||||||
|
| Pipeline list | Shows pipelines as opaque cards. No inline status |
|
||||||
|
| Pipeline detail | Nested Track→Stage tree is confusing. Must drill into each stage |
|
||||||
|
| Pipeline wizard | Generic JSON config per stage type. Not type-aware |
|
||||||
|
| Award management | Awards are separate from pipeline. No jury group link |
|
||||||
|
| Member management | No jury group concept. Can't see "Jury 1 members" |
|
||||||
|
| Mentor oversight | Basic list only. No workspace visibility |
|
||||||
|
| No confirmation UI | Winner confirmation doesn't exist |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Redesigned Navigation
|
||||||
|
|
||||||
|
### New Admin Sitemap
|
||||||
|
|
||||||
|
```
|
||||||
|
/admin/
|
||||||
|
├── page.tsx — Dashboard (competition overview)
|
||||||
|
├── competition/
|
||||||
|
│ ├── page.tsx — Competition list
|
||||||
|
│ ├── new/page.tsx — Create competition wizard
|
||||||
|
│ └── [id]/
|
||||||
|
│ ├── page.tsx — Competition dashboard (round timeline)
|
||||||
|
│ ├── settings/page.tsx — Competition-wide settings
|
||||||
|
│ ├── rounds/
|
||||||
|
│ │ ├── page.tsx — All rounds (timeline view)
|
||||||
|
│ │ ├── new/page.tsx — Add round
|
||||||
|
│ │ └── [roundId]/
|
||||||
|
│ │ ├── page.tsx — Round detail (type-specific view)
|
||||||
|
│ │ ├── edit/page.tsx — Edit round config
|
||||||
|
│ │ ├── projects/page.tsx — Projects in this round
|
||||||
|
│ │ ├── assignments/page.tsx — Assignments (EVALUATION rounds)
|
||||||
|
│ │ ├── filtering/page.tsx — Filtering dashboard (FILTERING)
|
||||||
|
│ │ ├── submissions/page.tsx — Submission status (INTAKE/SUBMISSION)
|
||||||
|
│ │ ├── mentoring/page.tsx — Mentoring overview (MENTORING)
|
||||||
|
│ │ ├── stage-manager/page.tsx — Live stage manager (LIVE_FINAL)
|
||||||
|
│ │ └── confirmation/page.tsx — Confirmation (CONFIRMATION)
|
||||||
|
│ ├── jury-groups/
|
||||||
|
│ │ ├── page.tsx — All jury groups
|
||||||
|
│ │ ├── new/page.tsx — Create jury group
|
||||||
|
│ │ └── [groupId]/
|
||||||
|
│ │ ├── page.tsx — Jury group detail + members
|
||||||
|
│ │ └── edit/page.tsx — Edit group settings
|
||||||
|
│ ├── submission-windows/
|
||||||
|
│ │ ├── page.tsx — All submission windows
|
||||||
|
│ │ └── [windowId]/
|
||||||
|
│ │ ├── page.tsx — Window detail + requirements
|
||||||
|
│ │ └── edit/page.tsx — Edit window
|
||||||
|
│ ├── awards/
|
||||||
|
│ │ ├── page.tsx — Special awards for this competition
|
||||||
|
│ │ ├── new/page.tsx — Create award
|
||||||
|
│ │ └── [awardId]/
|
||||||
|
│ │ ├── page.tsx — Award detail
|
||||||
|
│ │ └── edit/page.tsx — Edit award
|
||||||
|
│ └── results/
|
||||||
|
│ └── page.tsx — Final results + export
|
||||||
|
├── members/ — User management (unchanged)
|
||||||
|
├── audit/page.tsx — Audit log (enhanced)
|
||||||
|
├── messages/ — Messaging (unchanged)
|
||||||
|
├── programs/ — Program management
|
||||||
|
└── settings/ — System settings
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Competition Dashboard
|
||||||
|
|
||||||
|
The central hub for managing a competition. Replaces the old Pipeline detail page.
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ MOPC 2026 Competition Status: ACTIVE [Edit] │
|
||||||
|
│ Program: Monaco Ocean Protection Challenge 2026 │
|
||||||
|
├──────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ── Quick Stats ──────────────────────────────────────────────────── │
|
||||||
|
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │
|
||||||
|
│ │ 127 │ │ 23 │ │ 8 │ │ Round 3 of 8 │ │
|
||||||
|
│ │ Applications│ │ Advancing │ │ Jury Groups│ │ Jury 1 Evaluation │ │
|
||||||
|
│ │ │ │ │ │ 22 members │ │ ███████░░░ 68% │ │
|
||||||
|
│ └────────────┘ └────────────┘ └────────────┘ └────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ── Round Timeline ───────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ ✓ R1 ✓ R2 ● R3 ○ R4 ○ R5 ○ R6 ○ R7 ○ R8 │
|
||||||
|
│ Intake Filter Jury 1 Submn 2 Jury 2 Mentor Finals Confirm │
|
||||||
|
│ DONE DONE ACTIVE PENDING PENDING PENDING PENDING PENDING │
|
||||||
|
│ 127 98→23 23/23 │ │
|
||||||
|
│ eval'd │ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Round 3: Jury 1 — Semi-finalist Selection [Manage →] │ │
|
||||||
|
│ │ Type: EVALUATION | Jury: Jury 1 (8 members) │ │
|
||||||
|
│ │ Status: ACTIVE | Started: Feb 1 | Deadline: Mar 15 │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ████████████████████████████████████░░░░░░░░░░░░ 68% │ │
|
||||||
|
│ │ Evaluations: 186 / 276 complete │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────────┬──────────────┬──────────────┬────────────┐ │ │
|
||||||
|
│ │ │ Assigned: 276│ Complete: 186│ Pending: 90 │ COI: 12 │ │ │
|
||||||
|
│ │ └──────────────┴──────────────┴──────────────┴────────────┘ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ [ View Assignments ] [ View Results ] [ Advance Projects ] │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ── Sidebar: Jury Groups ─────────────────────────────────────────── │
|
||||||
|
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
|
||||||
|
│ │ Jury 1 (8 members) [→] │ │ Jury 2 (6 members) [→] │ │
|
||||||
|
│ │ Avg load: 15.3 / 20 │ │ Not yet assigned │ │
|
||||||
|
│ │ ████████████████░░░░ │ │ ░░░░░░░░░░░░░░░░░░░░ │ │
|
||||||
|
│ └─────────────────────────────┘ └─────────────────────────────┘ │
|
||||||
|
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
|
||||||
|
│ │ Jury 3 (5 members) [→] │ │ Innovation Jury (4) [→] │ │
|
||||||
|
│ │ Assigned to R7 + R8 │ │ Award jury │ │
|
||||||
|
│ └─────────────────────────────┘ └─────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ── Sidebar: Special Awards ──────────────────────────────────────── │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Innovation Award STAY_IN_MAIN Jury: Innovation Jury [→] │ │
|
||||||
|
│ │ Impact Award SEPARATE_POOL Jury: Impact Jury [→] │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||||
|
└──────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Components
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `<QuickStatsGrid>` | 4 stat cards showing key metrics |
|
||||||
|
| `<RoundTimeline>` | Horizontal timeline with round status badges |
|
||||||
|
| `<ActiveRoundCard>` | Expanded card for the currently active round |
|
||||||
|
| `<JuryGroupCards>` | Grid of jury group summary cards |
|
||||||
|
| `<AwardSidebar>` | List of special awards with status |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Competition Setup Wizard
|
||||||
|
|
||||||
|
Replaces the old Pipeline Wizard. A multi-step form that creates the entire competition structure.
|
||||||
|
|
||||||
|
### Wizard Steps
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Basics → Competition name, program, categories
|
||||||
|
Step 2: Round Builder → Add/reorder rounds (type picker)
|
||||||
|
Step 3: Jury Groups → Create jury groups, assign to rounds
|
||||||
|
Step 4: Submission Windows → Define file requirements per window
|
||||||
|
Step 5: Special Awards → Configure awards (optional)
|
||||||
|
Step 6: Notifications → Deadline reminders, email settings
|
||||||
|
Step 7: Review & Create → Summary of everything, create button
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1: Basics
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Create Competition — Step 1 of 7: Basics │
|
||||||
|
│ ●───○───○───○───○───○───○ │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Competition Name: │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ MOPC 2026 Competition │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Program: [MOPC 2026 ▼] │
|
||||||
|
│ │
|
||||||
|
│ Category Mode: │
|
||||||
|
│ ● Shared — Both Startups and Concepts in same flow │
|
||||||
|
│ ○ Split — Separate finalist counts per category │
|
||||||
|
│ │
|
||||||
|
│ Finalist Counts: │
|
||||||
|
│ Startups: [3 ] Concepts: [3 ] │
|
||||||
|
│ │
|
||||||
|
│ [ Cancel ] [ Next → ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Round Builder
|
||||||
|
|
||||||
|
The core of the wizard — a drag-and-drop round sequencer.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Create Competition — Step 2 of 7: Round Builder │
|
||||||
|
│ ○───●───○───○───○───○───○ │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Build your competition flow by adding rounds: │
|
||||||
|
│ │
|
||||||
|
│ ┌────┬──────────────────────────────┬──────────────┬────────┐ │
|
||||||
|
│ │ # │ Round │ Type │ Actions│ │
|
||||||
|
│ ├────┼──────────────────────────────┼──────────────┼────────┤ │
|
||||||
|
│ │ 1 │ ≡ Application Window │ INTAKE │ ✎ ✕ │ │
|
||||||
|
│ │ 2 │ ≡ AI Screening │ FILTERING │ ✎ ✕ │ │
|
||||||
|
│ │ 3 │ ≡ Jury 1 - Semi-finalist │ EVALUATION │ ✎ ✕ │ │
|
||||||
|
│ │ 4 │ ≡ Semi-finalist Documents │ SUBMISSION │ ✎ ✕ │ │
|
||||||
|
│ │ 5 │ ≡ Jury 2 - Finalist │ EVALUATION │ ✎ ✕ │ │
|
||||||
|
│ │ 6 │ ≡ Finalist Mentoring │ MENTORING │ ✎ ✕ │ │
|
||||||
|
│ │ 7 │ ≡ Live Finals │ LIVE_FINAL │ ✎ ✕ │ │
|
||||||
|
│ │ 8 │ ≡ Confirm Winners │ CONFIRMATION │ ✎ ✕ │ │
|
||||||
|
│ └────┴──────────────────────────────┴──────────────┴────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ + Add Round ] │
|
||||||
|
│ │
|
||||||
|
│ Available Round Types: │
|
||||||
|
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
||||||
|
│ │ INTAKE │ │ FILTERING │ │ EVALUATION │ │ SUBMISSION │ │
|
||||||
|
│ │ Collect │ │ AI screen │ │ Jury score │ │ More docs │ │
|
||||||
|
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
|
||||||
|
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
||||||
|
│ │ MENTORING │ │ LIVE_FINAL │ │ CONFIRM │ │
|
||||||
|
│ │ Workspace │ │ Live vote │ │ Cement │ │
|
||||||
|
│ └────────────┘ └────────────┘ └────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ ← Back ] [ Next → ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Round Config Sheet
|
||||||
|
|
||||||
|
When clicking ✎ on a round, a sheet slides out with type-specific config:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Configure Round: Jury 1 - Semi-finalist (EVALUATION) │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Round Name: [Jury 1 - Semi-finalist Selection ] │
|
||||||
|
│ │
|
||||||
|
│ ── Jury Group ──────────────────────────────────────────────── │
|
||||||
|
│ Assign jury group: [Jury 1 ▼] [ + Create New ] │
|
||||||
|
│ │
|
||||||
|
│ ── Assignment ──────────────────────────────────────────────── │
|
||||||
|
│ Reviews per project: [3 ] │
|
||||||
|
│ (Caps and quotas configured on the jury group) │
|
||||||
|
│ │
|
||||||
|
│ ── Scoring ─────────────────────────────────────────────────── │
|
||||||
|
│ Evaluation form: [Standard Criteria Form ▼] │
|
||||||
|
│ Scoring mode: ● Criteria-based ○ Global score ○ Binary │
|
||||||
|
│ Score range: [1 ] to [10] │
|
||||||
|
│ │
|
||||||
|
│ ── Document Visibility ─────────────────────────────────────── │
|
||||||
|
│ This round can see docs from: │
|
||||||
|
│ ☑ Window 1: Application Documents │
|
||||||
|
│ ☐ Window 2: Semi-finalist Documents (not yet created) │
|
||||||
|
│ │
|
||||||
|
│ ── Advancement ─────────────────────────────────────────────── │
|
||||||
|
│ Advancement mode: │
|
||||||
|
│ ● Top N by score │
|
||||||
|
│ ○ Admin selection │
|
||||||
|
│ ○ AI recommended │
|
||||||
|
│ Advance top: [8 ] projects per category │
|
||||||
|
│ │
|
||||||
|
│ ── Deadline ────────────────────────────────────────────────── │
|
||||||
|
│ Start date: [Feb 1, 2026 ] │
|
||||||
|
│ End date: [Mar 15, 2026] │
|
||||||
|
│ │
|
||||||
|
│ [ Cancel ] [ Save Round ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Jury Groups
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Create Competition — Step 3 of 7: Jury Groups │
|
||||||
|
│ ○───○───●───○───○───○───○ │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Jury 1 — Semi-finalist Selection [Edit]│ │
|
||||||
|
│ │ Linked to: Round 3 │ │
|
||||||
|
│ │ Members: 0 (add after creation) │ │
|
||||||
|
│ │ Default cap: 20 (SOFT +2) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Jury 2 — Finalist Selection [Edit]│ │
|
||||||
|
│ │ Linked to: Round 5 │ │
|
||||||
|
│ │ Members: 0 (add after creation) │ │
|
||||||
|
│ │ Default cap: 15 (HARD) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Jury 3 — Live Finals + Confirmation [Edit]│ │
|
||||||
|
│ │ Linked to: Round 7, Round 8 │ │
|
||||||
|
│ │ Members: 0 (add after creation) │ │
|
||||||
|
│ │ All finalists auto-assigned │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ + Create Jury Group ] │
|
||||||
|
│ │
|
||||||
|
│ Note: Add members to jury groups after competition is created. │
|
||||||
|
│ │
|
||||||
|
│ [ ← Back ] [ Next → ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Submission Windows
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Create Competition — Step 4 of 7: Submission Windows │
|
||||||
|
│ ○───○───○───●───○───○───○ │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Window 1: Application Documents (linked to Round 1) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ File Requirements: │ │
|
||||||
|
│ │ 1. Executive Summary (PDF, max 5MB, required) │ │
|
||||||
|
│ │ 2. Business Plan (PDF, max 20MB, required) │ │
|
||||||
|
│ │ 3. Team Bios (PDF, max 5MB, required) │ │
|
||||||
|
│ │ 4. Supporting Documents (any, max 50MB, optional) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Deadline: Jan 31, 2026 | Policy: GRACE (30 min) │ │
|
||||||
|
│ │ [ + Add Requirement ] [Edit] │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Window 2: Semi-finalist Documents (linked to Round 4) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ File Requirements: │ │
|
||||||
|
│ │ 1. Updated Business Plan (PDF, max 20MB, required) │ │
|
||||||
|
│ │ 2. Video Pitch (MP4, max 500MB, required) │ │
|
||||||
|
│ │ 3. Financial Projections (PDF/XLSX, max 10MB, required) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Deadline: Apr 30, 2026 | Policy: HARD │ │
|
||||||
|
│ │ [ + Add Requirement ] [Edit] │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ + Add Submission Window ] │
|
||||||
|
│ [ ← Back ] [ Next → ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Review & Create
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Create Competition — Step 7 of 7: Review │
|
||||||
|
│ ○───○───○───○───○───○───● │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Competition: MOPC 2026 Competition │
|
||||||
|
│ Category Mode: SHARED (3 Startups + 3 Concepts) │
|
||||||
|
│ │
|
||||||
|
│ Rounds (8): │
|
||||||
|
│ 1. Application Window (INTAKE) ─── Window 1 │
|
||||||
|
│ 2. AI Screening (FILTERING) │
|
||||||
|
│ 3. Jury 1 (EVALUATION) ─── Jury 1 │
|
||||||
|
│ 4. Semi-finalist Docs (SUBMISSION) ─── Window 2 │
|
||||||
|
│ 5. Jury 2 (EVALUATION) ─── Jury 2 │
|
||||||
|
│ 6. Mentoring (MENTORING) │
|
||||||
|
│ 7. Live Finals (LIVE_FINAL) ─── Jury 3 │
|
||||||
|
│ 8. Confirm Winners (CONFIRMATION) ─── Jury 3 │
|
||||||
|
│ │
|
||||||
|
│ Jury Groups (3): Jury 1 (0 members), Jury 2 (0), Jury 3 (0) │
|
||||||
|
│ Submission Windows (2): Application Docs, Semi-finalist Docs │
|
||||||
|
│ Special Awards (2): Innovation Award, Impact Award │
|
||||||
|
│ Notifications: Reminders at 7d, 3d, 1d before deadlines │
|
||||||
|
│ │
|
||||||
|
│ ⚠ Add jury members after creation. │
|
||||||
|
│ │
|
||||||
|
│ [ ← Back ] [ Create Competition ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round Management
|
||||||
|
|
||||||
|
### Round Detail — Type-Specific Views
|
||||||
|
|
||||||
|
Each round type renders a specialized detail page:
|
||||||
|
|
||||||
|
#### INTAKE Round Detail
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Round 1: Application Window Status: ACTIVE │
|
||||||
|
│ Type: INTAKE | Deadline: Jan 31, 2026 (16 days) │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ 127 │ │ 98 │ │ 29 │ │
|
||||||
|
│ │ Submitted │ │ Complete │ │ Draft │ │
|
||||||
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Category Breakdown: 72 Startups | 55 Concepts │
|
||||||
|
│ │
|
||||||
|
│ Submission Progress (by day): │
|
||||||
|
│ ▁▂▃▃▄▅▆▇████████████▇▇▆▅▄▃▃▂▂▁ │
|
||||||
|
│ Jan 1 Jan 31 │
|
||||||
|
│ │
|
||||||
|
│ Recent Submissions: │
|
||||||
|
│ ┌─────────────────────────────┬──────────┬──────────┬────────┐ │
|
||||||
|
│ │ Team │ Category │ Status │ Files │ │
|
||||||
|
│ ├─────────────────────────────┼──────────┼──────────┼────────┤ │
|
||||||
|
│ │ OceanClean AI │ STARTUP │ Complete │ 4/4 │ │
|
||||||
|
│ │ DeepReef Monitoring │ STARTUP │ Complete │ 3/4 │ │
|
||||||
|
│ │ BlueTide Analytics │ CONCEPT │ Draft │ 1/4 │ │
|
||||||
|
│ └─────────────────────────────┴──────────┴──────────┴────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ View All Submissions ] [ Export CSV ] [ Extend Deadline ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### FILTERING Round Detail
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Round 2: AI Screening Status: ACTIVE │
|
||||||
|
│ Type: FILTERING | Auto-advance: ON │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ 98 │ │ 23 │ │ 67 │ │
|
||||||
|
│ │ Screened │ │ Passed │ │ Failed │ │
|
||||||
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ │
|
||||||
|
│ │ 8 │ │
|
||||||
|
│ │ Flagged │ ← Require manual review │
|
||||||
|
│ └──────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Flagged for Review: │
|
||||||
|
│ ┌─────────────────────────┬──────────┬──────┬─────────────┐ │
|
||||||
|
│ │ Project │ AI Score │ Flag │ Action │ │
|
||||||
|
│ ├─────────────────────────┼──────────┼──────┼─────────────┤ │
|
||||||
|
│ │ WaveEnergy Solutions │ 0.55 │ EDGE │ [✓] [✗] [?] │ │
|
||||||
|
│ │ MarineData Hub │ 0.48 │ LOW │ [✓] [✗] [?] │ │
|
||||||
|
│ │ CoralMapper (dup?) │ 0.82 │ DUP │ [✓] [✗] [?] │ │
|
||||||
|
│ └─────────────────────────┴──────────┴──────┴─────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ View All Results ] [ Re-run AI Screening ] [ Override ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### EVALUATION Round Detail
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Round 3: Jury 1 — Semi-finalist Status: ACTIVE │
|
||||||
|
│ Type: EVALUATION | Jury: Jury 1 (8 members) │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ── Evaluation Progress ─────────────────────────────────────── │
|
||||||
|
│ █████████████████████████████████░░░░░░░░░░░░░░░ 68% │
|
||||||
|
│ 186 / 276 evaluations complete │
|
||||||
|
│ │
|
||||||
|
│ Per-Juror Progress: │
|
||||||
|
│ Dr. Martin ██████████████████████████████████████ 18/18 ✓ │
|
||||||
|
│ Prof. Dubois██████████████████████████████░░░░░░░ 15/20 │
|
||||||
|
│ Ms. Chen █████████████████████████████████████████ 20/20 ✓ │
|
||||||
|
│ Dr. Patel █████████████████████░░░░░░░░░░░░░░ 12/15 │
|
||||||
|
│ Mr. Silva ████████████████████████████████░░░░ 16/20 │
|
||||||
|
│ Dr. Yamada ███████████████████████████████████████ 19/20 │
|
||||||
|
│ Ms. Hansen ██████████████████████████░░░░░░░░░ 14/20 │
|
||||||
|
│ │
|
||||||
|
│ ── Actions ─────────────────────────────────────────────────── │
|
||||||
|
│ [ View Assignments ] [ View Results ] [ Send Reminder ] │
|
||||||
|
│ [ Run AI Summary ] [ Advance Top N ] [ Override Decision ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### LIVE_FINAL Stage Manager
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ LIVE STAGE MANAGER — Round 7: Live Finals [● RECORDING] │
|
||||||
|
│ Status: IN_PROGRESS | Category: STARTUP │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Now Presenting: OceanClean AI │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Status: Q_AND_A │ │
|
||||||
|
│ │ Presentation: 12:00 ✓ | Q&A: ██████░░ 6:23 / 10:00 │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ [ ▶ Start Voting ] [ ⏸ Pause ] [ ⏭ Skip ] │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ── Jury Votes (5 jurors) ────────────────────────────────── │
|
||||||
|
│ Dr. Martin: ○ waiting | Prof. Dubois: ○ waiting │
|
||||||
|
│ Ms. Chen: ○ waiting | Dr. Patel: ○ waiting │
|
||||||
|
│ Mr. Silva: ○ waiting | │
|
||||||
|
│ │
|
||||||
|
│ ── Audience Votes ───────────────────────────────────────── │
|
||||||
|
│ Registered: 142 | Voted: 0 (voting not yet open) │
|
||||||
|
│ │
|
||||||
|
│ ── Queue ────────────────────────────────────────────────── │
|
||||||
|
│ ┌─────┬──────────────────────┬──────────┬───────────────┐ │
|
||||||
|
│ │ Ord │ Project │ Category │ Status │ │
|
||||||
|
│ ├─────┼──────────────────────┼──────────┼───────────────┤ │
|
||||||
|
│ │ ► 1 │ OceanClean AI │ STARTUP │ Q_AND_A │ │
|
||||||
|
│ │ 2 │ DeepReef Monitoring │ STARTUP │ WAITING │ │
|
||||||
|
│ │ 3 │ CoralGuard │ STARTUP │ WAITING │ │
|
||||||
|
│ └─────┴──────────────────────┴──────────┴───────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ Switch to CONCEPT Window ] [ End STARTUP Window ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CONFIRMATION Round Detail
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Round 8: Confirm Winners Status: ACTIVE │
|
||||||
|
│ Type: CONFIRMATION | Jury: Jury 3 │
|
||||||
|
├──────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ STARTUP Proposal │ │
|
||||||
|
│ │ Status: APPROVED ✓ Approvals: 5/5 │ │
|
||||||
|
│ │ 1st: OceanClean AI (92.4) │ │
|
||||||
|
│ │ 2nd: DeepReef (88.7) │ │
|
||||||
|
│ │ 3rd: CoralGuard (85.1) │ │
|
||||||
|
│ │ [ Freeze Results ] │ │
|
||||||
|
│ └────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ CONCEPT Proposal │ │
|
||||||
|
│ │ Status: PENDING Approvals: 3/5 │ │
|
||||||
|
│ │ 1st: BlueTide Analytics (89.2) │ │
|
||||||
|
│ │ 2nd: MarineData Hub (84.6) │ │
|
||||||
|
│ │ 3rd: SeaWatch (81.3) │ │
|
||||||
|
│ │ [ Send Reminder ] [ Override ] │ │
|
||||||
|
│ └────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [ Freeze All Approved ] [ Export Results PDF ] │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Architecture
|
||||||
|
|
||||||
|
### Shared Components
|
||||||
|
|
||||||
|
| Component | Used In | Description |
|
||||||
|
|-----------|---------|-------------|
|
||||||
|
| `<CompetitionSidebar>` | All /competition/[id]/* pages | Left sidebar with nav links |
|
||||||
|
| `<RoundTimeline>` | Dashboard, round list | Horizontal visual timeline |
|
||||||
|
| `<StatusBadge>` | Everywhere | Color-coded status chip |
|
||||||
|
| `<ProgressBar>` | Round cards, jury progress | Animated progress bar |
|
||||||
|
| `<CountdownTimer>` | Round detail, dashboard | Real-time countdown to deadline |
|
||||||
|
| `<DataTable>` | Projects, members, assignments | Sortable, filterable table |
|
||||||
|
| `<OverrideDialog>` | Filtering, evaluation, confirmation | Override modal with reason input |
|
||||||
|
| `<AuditTrailSheet>` | Any entity detail page | Slide-out audit log viewer |
|
||||||
|
| `<JuryGroupSelector>` | Wizard, round config | Dropdown with create-new option |
|
||||||
|
|
||||||
|
### Page Components (type-specific)
|
||||||
|
|
||||||
|
| Component | Round Type | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| `<IntakeRoundView>` | INTAKE | Submission stats, file status, deadline |
|
||||||
|
| `<FilteringRoundView>` | FILTERING | AI results, flagged queue, overrides |
|
||||||
|
| `<EvaluationRoundView>` | EVALUATION | Juror progress, assignment stats, results |
|
||||||
|
| `<SubmissionRoundView>` | SUBMISSION | Upload progress, locked windows |
|
||||||
|
| `<MentoringRoundView>` | MENTORING | Workspace activity, milestone progress |
|
||||||
|
| `<LiveFinalStageManager>` | LIVE_FINAL | Full stage manager with controls |
|
||||||
|
| `<ConfirmationRoundView>` | CONFIRMATION | Proposals, approvals, freeze |
|
||||||
|
|
||||||
|
### Dynamic Round Detail Routing
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/app/(admin)/admin/competition/[id]/rounds/[roundId]/page.tsx
|
||||||
|
|
||||||
|
export default function RoundDetailPage({ params }) {
|
||||||
|
const { data: round } = trpc.competition.getRound.useQuery({
|
||||||
|
roundId: params.roundId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!round) return <LoadingSkeleton />;
|
||||||
|
|
||||||
|
// Render type-specific component based on round type
|
||||||
|
switch (round.roundType) {
|
||||||
|
case 'INTAKE':
|
||||||
|
return <IntakeRoundView round={round} />;
|
||||||
|
case 'FILTERING':
|
||||||
|
return <FilteringRoundView round={round} />;
|
||||||
|
case 'EVALUATION':
|
||||||
|
return <EvaluationRoundView round={round} />;
|
||||||
|
case 'SUBMISSION':
|
||||||
|
return <SubmissionRoundView round={round} />;
|
||||||
|
case 'MENTORING':
|
||||||
|
return <MentoringRoundView round={round} />;
|
||||||
|
case 'LIVE_FINAL':
|
||||||
|
return <LiveFinalStageManager round={round} />;
|
||||||
|
case 'CONFIRMATION':
|
||||||
|
return <ConfirmationRoundView round={round} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Responsive Design
|
||||||
|
|
||||||
|
| Breakpoint | Layout |
|
||||||
|
|------------|--------|
|
||||||
|
| `< md` (mobile) | Single column. Sidebar collapses to hamburger. Tables become cards. Stage manager simplified |
|
||||||
|
| `md` - `lg` (tablet) | Two column. Sidebar always visible. Tables with horizontal scroll |
|
||||||
|
| `> lg` (desktop) | Full layout. Sidebar + content + optional side panel |
|
||||||
|
|
||||||
|
### Mobile Stage Manager
|
||||||
|
|
||||||
|
The live stage manager has a simplified mobile view for admins controlling from a phone:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────┐
|
||||||
|
│ LIVE CONTROL [● REC]│
|
||||||
|
│ │
|
||||||
|
│ Now: OceanClean AI │
|
||||||
|
│ Status: Q_AND_A │
|
||||||
|
│ Timer: 6:23 / 10:00 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────┐ │
|
||||||
|
│ │ [ Start Voting ] │ │
|
||||||
|
│ │ [ Pause ] │ │
|
||||||
|
│ │ [ Skip → Next ] │ │
|
||||||
|
│ └──────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Jury: 0/5 voted │
|
||||||
|
│ Audience: 0/142 voted │
|
||||||
|
│ │
|
||||||
|
│ Next: DeepReef Monitoring│
|
||||||
|
└─────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
| Feature | Implementation |
|
||||||
|
|---------|---------------|
|
||||||
|
| **Keyboard navigation** | All actions reachable via Tab/Enter. Focus rings visible |
|
||||||
|
| **Screen reader** | Semantic HTML, `aria-label` on badges, `role="status"` on live regions |
|
||||||
|
| **Color contrast** | All text meets WCAG 2.1 AA. Status badges use icons + color |
|
||||||
|
| **Motion** | Countdown timers respect `prefers-reduced-motion` |
|
||||||
|
| **Focus management** | Dialog focus trap, return focus on close |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration with tRPC
|
||||||
|
|
||||||
|
### Key Data-Fetching Hooks
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Competition dashboard
|
||||||
|
const { data: competition } = trpc.competition.getById.useQuery({ id });
|
||||||
|
const { data: rounds } = trpc.competition.listRounds.useQuery({ competitionId: id });
|
||||||
|
const { data: juryGroups } = trpc.juryGroup.listByCompetition.useQuery({ competitionId: id });
|
||||||
|
|
||||||
|
// Round detail
|
||||||
|
const { data: round } = trpc.competition.getRound.useQuery({ roundId });
|
||||||
|
const { data: projects } = trpc.competition.getProjectsInRound.useQuery({ roundId });
|
||||||
|
const { data: assignments } = trpc.assignment.listByRound.useQuery({ roundId });
|
||||||
|
|
||||||
|
// Live stage manager (with polling)
|
||||||
|
const { data: ceremonyState } = trpc.liveControl.getCeremonyState.useQuery(
|
||||||
|
{ roundId },
|
||||||
|
{ refetchInterval: 1000 } // poll every second
|
||||||
|
);
|
||||||
|
|
||||||
|
// Confirmation
|
||||||
|
const { data: proposals } = trpc.winnerConfirmation.listProposals.useQuery({ competitionId: id });
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mutation Patterns
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Advance projects after evaluation
|
||||||
|
const advance = trpc.competition.advanceProjects.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
utils.competition.getRound.invalidate({ roundId });
|
||||||
|
utils.competition.getProjectsInRound.invalidate({ roundId });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Freeze winner proposal
|
||||||
|
const freeze = trpc.winnerConfirmation.freezeProposal.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
utils.winnerConfirmation.listProposals.invalidate({ competitionId });
|
||||||
|
toast({ title: 'Results frozen', description: 'Official results are now locked.' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,146 @@
|
||||||
|
# 01. Current Platform Architecture Audit
|
||||||
|
|
||||||
|
## 1) Technology Baseline
|
||||||
|
- Frontend: Next.js App Router, role-based route groups
|
||||||
|
- API: tRPC router composition (`src/server/routers/_app.ts`)
|
||||||
|
- ORM/DB: Prisma + PostgreSQL (`prisma/schema.prisma`)
|
||||||
|
- Auth: NextAuth + invitation token + onboarding + password setup flows
|
||||||
|
- Storage: MinIO/S3-like presigned URLs for project files
|
||||||
|
- Notifications: email + in-app notifications + reminder logs
|
||||||
|
- AI: OpenAI-backed assignment/filtering/award eligibility/summary/mentor matching with fallback logic
|
||||||
|
|
||||||
|
## 2) Core Current Domain Model (As Implemented)
|
||||||
|
|
||||||
|
### 2.1 Stage Engine Backbone
|
||||||
|
Current schema already includes the core stage engine building blocks:
|
||||||
|
- `Pipeline` (program-level orchestration container)
|
||||||
|
- `Track` (main/award/showcase branches)
|
||||||
|
- `Stage` (typed lifecycle node)
|
||||||
|
- `StageTransition` (graph edges with optional guards)
|
||||||
|
- `ProjectStageState` (project position/state inside stage)
|
||||||
|
- `Cohort` + `CohortProject` (grouping for live/selection windows)
|
||||||
|
- `LiveProgressCursor` (live presentation pointer)
|
||||||
|
- `OverrideAction` + `DecisionAuditLog` (decision/override evidence)
|
||||||
|
|
||||||
|
### 2.2 Evaluation / Assignment / Filtering / Awards / Mentorship
|
||||||
|
The platform also has substantial supporting models:
|
||||||
|
- Assignment & judging: `Assignment`, `Evaluation`, `EvaluationForm`, `ConflictOfInterest`, `GracePeriod`
|
||||||
|
- Filtering: `FilteringRule`, `FilteringResult`, `FilteringJob`
|
||||||
|
- Awards: `SpecialAward`, `AwardEligibility`, `AwardJuror`, `AwardVote`
|
||||||
|
- Mentorship: `MentorAssignment`, `MentorMessage`, `MentorNote`, `MentorMilestone`, `MentorMilestoneCompletion`
|
||||||
|
- Documents: `FileRequirement`, `ProjectFile`
|
||||||
|
- Live voting: `LiveVotingSession`, `LiveVote`, `AudienceVoter`
|
||||||
|
|
||||||
|
### 2.3 User/Invite/Team Model
|
||||||
|
- Users: `User` with roles, invite token, onboarding state, workload fields
|
||||||
|
- Team: `TeamMember` links project/user and role (`LEAD`, `MEMBER`, `ADVISOR`)
|
||||||
|
- Invitation tokens are user-level (`inviteToken`, `inviteTokenExpiresAt`), reused across admin and team invite flows
|
||||||
|
|
||||||
|
## 3) Current Services (Behavior Layer)
|
||||||
|
|
||||||
|
### 3.1 Stage Lifecycle Services
|
||||||
|
- `stage-engine.ts`: transition validation + execution + guard evaluation
|
||||||
|
- `stage-filtering.ts`: stage-scoped deterministic + AI banding filtering with manual queue concept
|
||||||
|
- `stage-assignment.ts`: stage-scoped assignment preview/rebalance/coverage, workload/tag balancing
|
||||||
|
- `live-control.ts`: live session control (cursor, queue reorder, pause/resume, cohort windows)
|
||||||
|
- `stage-notifications.ts`: event emission + notification policy checks
|
||||||
|
|
||||||
|
### 3.2 AI and Automation Services
|
||||||
|
- `ai-assignment.ts`, `smart-assignment.ts`
|
||||||
|
- `ai-filtering.ts`
|
||||||
|
- `award-eligibility-job.ts`, `ai-award-eligibility.ts`
|
||||||
|
- `mentor-matching.ts`
|
||||||
|
- `ai-evaluation-summary.ts`
|
||||||
|
|
||||||
|
### 3.3 Deadline/Reminder Services
|
||||||
|
- `evaluation-reminders.ts` for stage deadline reminder windows and deduplicated reminder logs
|
||||||
|
|
||||||
|
## 4) Current API Surface (tRPC Router Map)
|
||||||
|
Major routers currently include:
|
||||||
|
|
||||||
|
- Competition architecture: `pipeline`, `stage`, `stageFiltering`, `stageAssignment`, `cohort`, `live`, `decision`, `award`, `specialAward`
|
||||||
|
- Judging ops: `assignment`, `evaluation`, `gracePeriod`, `filtering`
|
||||||
|
- Project/applicant/docs: `project`, `application`, `applicant`, `file`, `project-pool`
|
||||||
|
- Mentorship: `mentor`
|
||||||
|
- Identity/admin ops: `user`, `program`, `settings`, `notification`, `audit`, `dashboard`
|
||||||
|
- Platform modules: `message`, `webhook`, `learningResource`, `partner`, `tag`, imports, analytics, export
|
||||||
|
|
||||||
|
## 5) Current UI Surface (By Role)
|
||||||
|
|
||||||
|
### 5.1 Admin Surface
|
||||||
|
- Program settings, apply settings, pipeline wizard/advanced editors
|
||||||
|
- Project pool and project management
|
||||||
|
- Members + invitation workflow (`/admin/members/invite`)
|
||||||
|
- Mentors and mentorship settings
|
||||||
|
- Awards and live/reporting pages
|
||||||
|
|
||||||
|
### 5.2 Applicant Surface
|
||||||
|
- Applicant dashboard, pipeline status, stage documents, team management, mentoring page
|
||||||
|
- Team invite/remove functionality in applicant team pages
|
||||||
|
|
||||||
|
### 5.3 Jury Surface
|
||||||
|
- Stage lists, assignment lists, project review/evaluation pages, comparison, live stage pages, awards
|
||||||
|
|
||||||
|
### 5.4 Mentor Surface
|
||||||
|
- Mentor dashboard, project workspaces, notes, milestones, messaging
|
||||||
|
|
||||||
|
### 5.5 Public/Auth Surface
|
||||||
|
- Public application pages
|
||||||
|
- Accept invite page, onboarding page, set password flow
|
||||||
|
- Public live score and audience vote pages
|
||||||
|
|
||||||
|
## 6) Current Strengths
|
||||||
|
1. Strong foundational stage/pipeline schema already exists.
|
||||||
|
2. Manual override and audit models already present.
|
||||||
|
3. AI-assisted modules are implemented with fallback paths.
|
||||||
|
4. Live voting/session primitives already exist.
|
||||||
|
5. File requirements + per-stage file visibility logic already exist.
|
||||||
|
6. Team invitation and onboarding flows are functioning and integrated with email + status.
|
||||||
|
|
||||||
|
## 7) Current Mismatch Areas Against Monaco Flow
|
||||||
|
|
||||||
|
### 7.1 Stage Purpose Ambiguity
|
||||||
|
- `StageType` is generic (`INTAKE/FILTER/EVALUATION/...`) but does not encode explicit Monaco semantics (Jury 1 vs Jury 2 vs Jury 3, Submission Round 1 vs Round 2, Final Confirmation).
|
||||||
|
- Behavior depends heavily on `configJson` conventions and page-level interpretation.
|
||||||
|
|
||||||
|
### 7.2 Jury Entity Is Implicit, Not First-Class
|
||||||
|
- Jury membership is mostly inferred from `User.role` + stage assignments.
|
||||||
|
- The Monaco requirement for explicit overlapping juries (`Jury 1/2/3`, award juries) is not modeled as first-class reusable entities.
|
||||||
|
|
||||||
|
### 7.3 Assignment Policy Is Incomplete For Required Cap Semantics
|
||||||
|
- Current assignment supports limits and balancing, but lacks formal per-judge hard/soft cap contracts with explicit buffer and exhaustion handling.
|
||||||
|
- Category mix logic exists but not in the exact required startup/concept ratio semantics per judge + override flow.
|
||||||
|
|
||||||
|
### 7.4 Document Lifecycle Is Partially Structured
|
||||||
|
- Stage-level requirements are present; previous-stage visibility exists.
|
||||||
|
- But “submission bundles per round” and official round-level locking semantics are not consistently first-class.
|
||||||
|
|
||||||
|
### 7.5 Mentoring Collaboration Layer Is Not Complete
|
||||||
|
- Messaging/notes/milestones exist.
|
||||||
|
- Missing required private mentor-team file workspace with threaded comments + official submission promotion provenance.
|
||||||
|
|
||||||
|
### 7.6 Finals Confirmation Workflow Not Fully Typed
|
||||||
|
- Live voting and cursor controls exist.
|
||||||
|
- No dedicated final confirmation entity enforcing “all jury agree + one admin approval + result freeze lock”.
|
||||||
|
|
||||||
|
### 7.7 Award Mode Semantics Need Hardening
|
||||||
|
- Awards are powerful but routing mode behavior (`pull-out` vs `dual participation`) is not represented as explicit governance policy with strict validation.
|
||||||
|
|
||||||
|
### 7.8 Cross-Platform Consistency Gaps
|
||||||
|
- Critical workflows (member invite, team invite, onboarding, pre-assignment, stage visibility) are implemented across multiple modules with some duplicated logic and role assumptions.
|
||||||
|
- This increases drift risk when competition architecture changes.
|
||||||
|
|
||||||
|
## 8) Complexity/Vagueness Sources
|
||||||
|
1. Over-reliance on freeform `configJson` for core behavior.
|
||||||
|
2. Multiple overlapping assignment/filtering router/service pathways.
|
||||||
|
3. Legacy “round” nomenclature still appears in fields and comments while pipeline model is active.
|
||||||
|
4. Invite/onboarding/team and assignment logic are integrated but not governed by a single “competition operating model” contract.
|
||||||
|
5. Some stage-specific behavior depends on UI assumptions rather than centralized policy evaluation.
|
||||||
|
|
||||||
|
## 9) Conclusion (Current-State Summary)
|
||||||
|
The platform is not a blank slate. It is a capable stage-engine system with substantial functionality already built. The redesign should be an **evolutionary hardening and simplification** effort:
|
||||||
|
|
||||||
|
- Keep what is structurally strong (pipeline/stage backbone, audit, live primitives)
|
||||||
|
- Introduce explicit entities/policies where Monaco flow requires precision
|
||||||
|
- Reduce ambiguity by making flow-critical contracts typed and validated
|
||||||
|
- Unify all user-facing functions under the same stage/jury/document/decision model
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
# 02. Monaco Flow Target Architecture
|
||||||
|
|
||||||
|
## 1) Target Architecture Intent
|
||||||
|
Design the competition as a **typed, policy-driven stage machine** where each step in your Monaco flow maps to an explicit stage purpose and policy contract.
|
||||||
|
|
||||||
|
Core idea:
|
||||||
|
- Keep existing `Pipeline/Track/Stage` spine
|
||||||
|
- Add explicit purpose codes and policy entities
|
||||||
|
- Drive every major feature (invite, docs, assignment, review, mentoring, finals, confirmation) from the same stage context
|
||||||
|
|
||||||
|
## 2) Canonical Stage Purpose Map
|
||||||
|
|
||||||
|
For the main track, define explicit purpose keys (not just generic stage type):
|
||||||
|
|
||||||
|
1. `submission_r1_intake`
|
||||||
|
2. `eligibility_filter`
|
||||||
|
3. `jury1_evaluation`
|
||||||
|
4. `submission_r2_intake`
|
||||||
|
5. `jury2_evaluation`
|
||||||
|
6. `finals_prep_collaboration` (mentoring layer; may be non-blocking stage or overlay policy)
|
||||||
|
7. `jury3_live_finals`
|
||||||
|
8. `final_confirmation`
|
||||||
|
9. `results_publication`
|
||||||
|
|
||||||
|
`StageType` remains useful (`INTAKE`, `FILTER`, `EVALUATION`, `LIVE_FINAL`, `RESULTS`) but **stage purpose key is authoritative** for business behavior.
|
||||||
|
|
||||||
|
## 3) Actor Spaces (Target)
|
||||||
|
|
||||||
|
### 3.1 Applicants / Teams
|
||||||
|
- Apply during open submission windows
|
||||||
|
- Upload required documents for active submission stage
|
||||||
|
- Access prior round docs read-only
|
||||||
|
- Request mentoring (if enabled)
|
||||||
|
- Collaborate with mentor in private file/chat workspace
|
||||||
|
- Mark eligible mentoring file as official submission (when allowed)
|
||||||
|
|
||||||
|
### 3.2 Admins
|
||||||
|
- Configure stages, deadlines, rubrics, file requirements, assignment policies
|
||||||
|
- Trigger/monitor AI filtering and assignment
|
||||||
|
- Override eligibility, assignments, advancement, finalist/winner outcomes
|
||||||
|
- Run live finals controls (stage manager)
|
||||||
|
- Perform final confirmation and official result lock
|
||||||
|
|
||||||
|
### 3.3 Judges
|
||||||
|
- Belong to explicit juries (`Jury 1`, `Jury 2`, `Jury 3`, award juries)
|
||||||
|
- Review assigned projects in active windows
|
||||||
|
- Access prior + current docs as policy allows
|
||||||
|
- Submit scores, feedback, notes
|
||||||
|
- Participate in deliberation/final confirmation where required
|
||||||
|
|
||||||
|
### 3.4 Mentors
|
||||||
|
- Assigned to eligible finalist teams that requested mentoring
|
||||||
|
- Use mentor-team private workspace (chat, files, threaded comments)
|
||||||
|
- Help refine docs; optionally approve/promote files if policy permits
|
||||||
|
|
||||||
|
### 3.5 Audience (Optional)
|
||||||
|
- Votes during configured live finals windows
|
||||||
|
- Only when enabled for stage/category/session
|
||||||
|
|
||||||
|
## 4) End-To-End Flow Blueprint (Mapped To Monaco)
|
||||||
|
|
||||||
|
## 4.1 Stage A: Submission Round 1 (`submission_r1_intake`)
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
- Program application settings
|
||||||
|
- Stage deadline and late policy
|
||||||
|
- Required document set for round 1
|
||||||
|
|
||||||
|
### Runtime Rules
|
||||||
|
- Applicant can edit/upload only while submission window policy allows
|
||||||
|
- Late behavior from policy:
|
||||||
|
- strict cutoff
|
||||||
|
- accepted as late with flag
|
||||||
|
- accepted in grace window only
|
||||||
|
- Admin can always inspect and intervene
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
- Project classified as `STARTUP` or `BUSINESS_CONCEPT`
|
||||||
|
- Round 1 submission bundle + metadata + late flag
|
||||||
|
|
||||||
|
## 4.2 Stage B: AI Eligibility Filter (`eligibility_filter`)
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
- Eligible candidate pool from Stage A
|
||||||
|
- Deterministic rules + AI criteria text + confidence thresholds
|
||||||
|
|
||||||
|
### Runtime Rules
|
||||||
|
- Deterministic checks first, then AI evaluation (if enabled)
|
||||||
|
- Outcome buckets:
|
||||||
|
- `eligible`
|
||||||
|
- `ineligible`
|
||||||
|
- `manual_review`
|
||||||
|
- Admin override always allowed with reason code + audit
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
- Eligible pool for Jury 1
|
||||||
|
- Ineligible pool retained and visible to admins with evidence
|
||||||
|
|
||||||
|
## 4.3 Stage C: Jury 1 Evaluation (`jury1_evaluation`)
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
- Jury 1 membership
|
||||||
|
- Assignment policy per juror (cap mode, cap value, soft buffer, category-bias preference)
|
||||||
|
- Rubric/version and deadline window
|
||||||
|
|
||||||
|
### Runtime Rules
|
||||||
|
- Assignment engine obeys per-judge policies:
|
||||||
|
- hard cap: never exceed cap
|
||||||
|
- soft cap: target cap, may go to cap+buffer
|
||||||
|
- Startup/concept ratio is treated as a **soft bias** for fair distribution, not a deterministic constraint
|
||||||
|
- Jury onboarding must clearly state that ratio preference is a suggestion, not a hard rule
|
||||||
|
- After all soft-cap judges reach cap+buffer:
|
||||||
|
- unassigned projects move to manual assignment queue
|
||||||
|
- Judges see countdown, project list, round 1 documents
|
||||||
|
- Submit score, feedback, notes
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
- Ranking and distribution views per category
|
||||||
|
- AI recommended shortlist per category
|
||||||
|
- Admin final advancement decision -> Semi-finalists
|
||||||
|
|
||||||
|
## 4.4 Stage D: Submission Round 2 (`submission_r2_intake`)
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
- Semi-finalists selected from Stage C
|
||||||
|
- New round 2 document requirements
|
||||||
|
|
||||||
|
### Runtime Rules
|
||||||
|
- Applicant edits only round 2 requirements
|
||||||
|
- Round 1 docs are read-only for applicants
|
||||||
|
- Judges/admin can see both bundles (with clear labeling)
|
||||||
|
- Admin retains full document management authority across rounds
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
- Round 1 locked bundle + Round 2 final bundle per semi-finalist
|
||||||
|
|
||||||
|
## 4.5 Stage E: Jury 2 Evaluation (`jury2_evaluation`)
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
- Jury 2 membership + assignment policies
|
||||||
|
- Access to round 1 + round 2 docs
|
||||||
|
- Special award governance configuration
|
||||||
|
|
||||||
|
### Runtime Rules
|
||||||
|
- Same assignment controls as Jury 1
|
||||||
|
- Judges score and annotate
|
||||||
|
- Special awards run in configured mode:
|
||||||
|
- mode A: separate pool (optional pull-out, admin-confirmed)
|
||||||
|
- mode B: dual eligibility while staying in main pool
|
||||||
|
- Award juries can overlap with main juries
|
||||||
|
- Some awards can run in single-judge decision mode (award master / designated judge)
|
||||||
|
- Admin can override finalist recommendations
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
- Finalists per category
|
||||||
|
- Award shortlist/winners progression state per award policy
|
||||||
|
|
||||||
|
## 4.6 Stage F: Mentoring Collaboration Layer (`finals_prep_collaboration` overlay)
|
||||||
|
|
||||||
|
This is a collaboration layer, not a judging gate.
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
- Finalists requesting mentoring
|
||||||
|
- Mentor assignments
|
||||||
|
|
||||||
|
### Runtime Rules
|
||||||
|
- Private mentor-team workspace:
|
||||||
|
- messaging/chat
|
||||||
|
- file upload by mentor/team
|
||||||
|
- threaded file comments
|
||||||
|
- Promotion to official submission supported where policy allows:
|
||||||
|
- promotion authority: team lead and admin
|
||||||
|
- create provenance record (who, when, source file, target requirement)
|
||||||
|
- keep immutable audit trail
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
- Improved finalist submissions
|
||||||
|
- Complete mentoring collaboration + promotion history
|
||||||
|
|
||||||
|
## 4.7 Stage G: Live Finals Jury 3 (`jury3_live_finals`)
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
- Finalist list/order
|
||||||
|
- Jury 3 members
|
||||||
|
- Live voting settings and optional audience voting config
|
||||||
|
|
||||||
|
### Runtime Rules
|
||||||
|
- Admin stage manager can set active presenter, open/close vote windows
|
||||||
|
- Jury 3 sees:
|
||||||
|
- all finalist info and historical context allowed by policy
|
||||||
|
- prior jury summaries (if permitted)
|
||||||
|
- real-time note-taking and live voting controls
|
||||||
|
- Audience vote optional; if enabled, shown during deliberation as configured
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
- Live vote records + deliberation notes + optional audience totals
|
||||||
|
|
||||||
|
## 4.8 Stage H: Final Confirmation (`final_confirmation`)
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
- Jury 3 voting outcomes
|
||||||
|
- Deliberation notes
|
||||||
|
- Award outcomes
|
||||||
|
|
||||||
|
### Runtime Rules
|
||||||
|
- Explicit confirmation workflow:
|
||||||
|
- all required jury confirmations captured (default unanimous for both category winners and award winners, based on active quorum)
|
||||||
|
- if a required juror is absent, admin can either replace the juror or mark absence so they do not count toward required confirmations (fully audited)
|
||||||
|
- exception: awards configured for single-judge mode follow single-judge confirmation flow
|
||||||
|
- one admin approval required to finalize
|
||||||
|
- Admin override path is supported for both category and award outcomes with mandatory reason/audit evidence
|
||||||
|
- On finalize:
|
||||||
|
- winner rankings locked
|
||||||
|
- result set immutable (unlock allowed only through super-admin workflow with explicit audit reason)
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
- Official winners per category
|
||||||
|
- Official award winners
|
||||||
|
- Immutable final decision audit package
|
||||||
|
|
||||||
|
## 4.9 Stage I: Results Publication (`results_publication`)
|
||||||
|
- Controlled publication policy (manual or policy-based)
|
||||||
|
- Public/private output controls
|
||||||
|
- Report export snapshot references final lock version
|
||||||
|
|
||||||
|
## 5) Cross-Cutting Target Behaviors
|
||||||
|
|
||||||
|
### 5.1 Deadlines And Reminder Contracts
|
||||||
|
Every stage with time windows must have:
|
||||||
|
- user-visible countdown in role dashboards
|
||||||
|
- reminder policy (3-day, 24h, 1h defaults; configurable)
|
||||||
|
- reliable dedupe via reminder logs
|
||||||
|
|
||||||
|
### 5.2 Manual Override Everywhere (With Structured Reasoning)
|
||||||
|
Admins can override:
|
||||||
|
- filtering outcomes
|
||||||
|
- assignment decisions
|
||||||
|
- advancement and finalist picks
|
||||||
|
- award eligibility and winner selection
|
||||||
|
- final winner confirmation
|
||||||
|
|
||||||
|
Each override requires:
|
||||||
|
- reason code
|
||||||
|
- optional reason text
|
||||||
|
- before/after snapshot
|
||||||
|
- actor and timestamp
|
||||||
|
|
||||||
|
### 5.3 Multi-Jury Explicitness
|
||||||
|
Juries become explicit objects with memberships and stage bindings:
|
||||||
|
- main juries and award juries use purpose bindings with custom labels per program
|
||||||
|
- overlap allowed
|
||||||
|
- assignments and dashboards are jury-context-aware
|
||||||
|
|
||||||
|
### 5.4 Multi-Round Document Visibility
|
||||||
|
- Applicants edit current stage only
|
||||||
|
- Applicants read-only previous stage docs
|
||||||
|
- Judges view current + previous by policy
|
||||||
|
- Admin full control
|
||||||
|
|
||||||
|
### 5.5 Platform-Wide Integration Rule
|
||||||
|
No function may bypass stage context:
|
||||||
|
- invite + onboarding
|
||||||
|
- assignment generation
|
||||||
|
- document submission/locking
|
||||||
|
- live voting
|
||||||
|
- status timeline
|
||||||
|
- notifications
|
||||||
|
|
||||||
|
All are resolved through a central `competition context resolver` (program + pipeline + stage purpose + jury context + user role).
|
||||||
|
|
||||||
|
## 6) Simplicity + Customizability Strategy
|
||||||
|
|
||||||
|
### 6.1 Simplicity
|
||||||
|
- Fixed Monaco-compatible stage purpose skeleton (minimal mandatory stages)
|
||||||
|
- Strong defaults and pre-built templates for most competitions
|
||||||
|
- Role-specific UX views that only expose relevant controls
|
||||||
|
|
||||||
|
### 6.2 Customizability
|
||||||
|
- Policy overrides per stage, jury, award, and user
|
||||||
|
- Configurable late policies, cap buffers, top-N finalists, audience voting behavior
|
||||||
|
- Award mode per award (`separate_pool` vs `dual_track`)
|
||||||
|
- Final confirmation rules configurable (unanimous, supermajority, quorum fallback handling, etc.)
|
||||||
|
|
||||||
|
## 7) Target Outcome
|
||||||
|
The redesigned architecture should let admins run Monaco OPC end-to-end **without ad hoc interpretation** and with full traceability, while still allowing controlled per-program policy customization.
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
# 03. Gap Analysis And Simplification Decisions
|
||||||
|
|
||||||
|
## 1) Summary
|
||||||
|
The platform already supports most building blocks of your competition, but key Monaco requirements are either implicit, partially modeled, or dispersed across modules. This document converts those gaps into explicit redesign decisions.
|
||||||
|
|
||||||
|
## 2) Gap Matrix (Current vs Required vs Redesign)
|
||||||
|
|
||||||
|
| Area | Current State | Monaco Requirement | Redesign Decision |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Stage semantics | Generic `StageType`, behavior split between `configJson` and UI assumptions | Explicit flow steps (R1 intake, eligibility, Jury1, R2, Jury2, mentoring layer, Jury3 live, final confirmation) | Add `stagePurposeKey` (typed enum) and purpose-specific policy validation |
|
||||||
|
| Jury modeling | Jury mostly inferred from role + assignments | Explicit `Jury 1/2/3` and award juries with overlap | Add first-class `Jury` and `JuryMembership` models; bind to stages |
|
||||||
|
| Assignment policy | Max/min load + overflow policy + some category quota support | Per-judge hard/soft cap + soft buffer + startup/concept ratio + manual fallback queue | Create typed `AssignmentPolicy` + `JudgePolicyOverride`; formal unassigned queue |
|
||||||
|
| Filtering outcomes | Deterministic + AI + manual queue present | Eligibility pass with admin override and reason trace | Keep existing model, add stricter outcome taxonomy and promotion gates |
|
||||||
|
| Submission rounds | Stage requirements exist; file logic partly stage-bound | Round bundles with lock/read-only and provenance | Introduce explicit `SubmissionRound`/`DocumentBundle` abstraction on top of stage |
|
||||||
|
| Mentoring workspace | Mentor assignment/messages/notes/milestones exist | Private files + threaded comments + promotion to official submission | Add mentor workspace files/comments and `PromotionEvent` typed audit |
|
||||||
|
| Special awards | Strong award model, routing present but mixed semantics | Mode A/B behavior per award with pull-out/dual participation | Add explicit `awardParticipationMode` + routing policy constraints |
|
||||||
|
| Live finals | Live cursor + voting session + audience support exists | Stage manager controls + Jury 3 notes + deliberation windows | Keep live primitives; add deliberation policy and jury3 context contracts |
|
||||||
|
| Final confirmation | Audit + decision logs exist | All jury agree + one admin approval + result lock | Add dedicated `FinalConfirmation` workflow and result freeze state |
|
||||||
|
| Invite/member flows | Admin/member/team invites implemented; pre-assignment exists | Must deeply reflect updated architecture and juries | Make invites jury-aware and stage-purpose aware; unify assignment intents |
|
||||||
|
|
||||||
|
## 3) High-Impact Ambiguity Sources To Eliminate
|
||||||
|
|
||||||
|
### 3.1 `configJson` As Primary Runtime Logic
|
||||||
|
Current problem:
|
||||||
|
- Critical behavior lives in open-ended JSON keys with inconsistent naming and weak compile-time guarantees.
|
||||||
|
|
||||||
|
Decision:
|
||||||
|
- Keep `configJson` for non-critical UI metadata.
|
||||||
|
- Move flow-critical behavior into typed policy tables and strict schemas.
|
||||||
|
|
||||||
|
### 3.2 Generic Stage Types Without Purpose Contract
|
||||||
|
Current problem:
|
||||||
|
- Two stages can both be `EVALUATION`, but system behavior may need to differ radically (Jury1 vs Jury2 vs award).
|
||||||
|
|
||||||
|
Decision:
|
||||||
|
- Add immutable `stagePurposeKey` and purpose-aware policy validator.
|
||||||
|
|
||||||
|
### 3.3 Dispersed Assignment Constraints
|
||||||
|
Current problem:
|
||||||
|
- Multiple assignment pathways (stageAssignment router/service, assignment router, AI suggestions) can diverge in rules.
|
||||||
|
|
||||||
|
Decision:
|
||||||
|
- Centralize assignment rule evaluation into one policy engine used by all assignment entry points.
|
||||||
|
|
||||||
|
### 3.4 Invite And Onboarding Disconnected From Jury Context
|
||||||
|
Current problem:
|
||||||
|
- `/admin/members/invite` pre-assigns projects directly, but not explicitly tied to jury entities and policy contracts.
|
||||||
|
|
||||||
|
Decision:
|
||||||
|
- Introduce `AssignmentIntent` attached to `JuryMembership`/stage context during invite flow; materialize assignments when stage/policy permits.
|
||||||
|
|
||||||
|
## 4) Simplification Decisions (Non-Negotiable)
|
||||||
|
|
||||||
|
1. **One source of truth for stage behavior:** `stagePurposeKey + policy models`
|
||||||
|
2. **One source of truth for assignment constraints:** centralized assignment policy engine
|
||||||
|
3. **One source of truth for winner lock:** explicit final confirmation workflow
|
||||||
|
4. **One source of truth for document status:** round bundle + requirement slot state
|
||||||
|
5. **One source of truth for role access:** policy-based access resolver by context
|
||||||
|
|
||||||
|
## 5) Compatibility Strategy
|
||||||
|
|
||||||
|
### 5.1 Keep Existing Backbone
|
||||||
|
No full rewrite:
|
||||||
|
- Preserve `Pipeline`, `Track`, `Stage`, `ProjectStageState`, `Assignment`, `Evaluation`, `SpecialAward`, `LiveVotingSession`
|
||||||
|
|
||||||
|
### 5.2 Additive Evolution
|
||||||
|
Add new typed entities and read/write adapters:
|
||||||
|
- old endpoints keep working in compatibility mode during migration
|
||||||
|
- new flows use purpose/policy-aware endpoints
|
||||||
|
|
||||||
|
### 5.3 Controlled De-ambiguation
|
||||||
|
- Introduce strict validation for future pipeline configs
|
||||||
|
- Provide migration scripts for legacy `configJson` keys
|
||||||
|
- fail-fast for unsupported combinations
|
||||||
|
|
||||||
|
## 6) Design Tradeoffs
|
||||||
|
|
||||||
|
### 6.1 Why Not Fully Dynamic “No Skeleton” Flow?
|
||||||
|
- You requested clarity and reduced vagueness.
|
||||||
|
- Purely dynamic flow increases flexibility but harms predictability, QA surface, and operator confidence.
|
||||||
|
|
||||||
|
Chosen approach:
|
||||||
|
- fixed Monaco skeleton + configurable policies and optional modules.
|
||||||
|
|
||||||
|
### 6.2 Why Typed Tables Instead Of More JSON?
|
||||||
|
- Critical competition logic requires integrity constraints, easier querying, and deterministic behavior.
|
||||||
|
- Typed models improve auditability and testability.
|
||||||
|
|
||||||
|
### 6.3 Why Add Jury As Explicit Entity?
|
||||||
|
- Your flow explicitly names Jury 1/2/3 and overlapping award juries.
|
||||||
|
- This cannot remain a UI convention without creating operational drift.
|
||||||
|
|
||||||
|
## 7) Required Outcome Of Simplification
|
||||||
|
By end of redesign:
|
||||||
|
- Admins can configure Monaco flow without hidden dependencies.
|
||||||
|
- Assignment limits are deterministic, while startup/concept ratio remains a transparent soft-bias recommendation.
|
||||||
|
- Invite/member pages support policy-compliant jury memberships plus both pre-assignment modes (intent-first and direct assignment where explicitly enabled).
|
||||||
|
- Documents, mentoring, awards, finals, and confirmation all speak the same domain language.
|
||||||
|
|
@ -0,0 +1,317 @@
|
||||||
|
# 04. Unified Domain Model And Config Contracts
|
||||||
|
|
||||||
|
## 1) Objectives
|
||||||
|
This target model turns the Monaco flow into strict, reusable contracts while preserving the current pipeline infrastructure.
|
||||||
|
|
||||||
|
Goals:
|
||||||
|
1. Minimize schema churn where current models are already correct.
|
||||||
|
2. Remove ambiguity for jury identity, assignment policy, round bundles, mentoring promotion, and final lock.
|
||||||
|
3. Keep customization through explicit policy layers, not ad hoc conditionals.
|
||||||
|
|
||||||
|
## 2) Proposed Domain Additions
|
||||||
|
|
||||||
|
## 2.1 Stage Purpose Contract
|
||||||
|
|
||||||
|
### Add To `Stage`
|
||||||
|
- `purposeKey` (enum/string, required after migration)
|
||||||
|
|
||||||
|
Proposed purpose enum values:
|
||||||
|
- `submission_r1_intake`
|
||||||
|
- `eligibility_filter`
|
||||||
|
- `jury1_evaluation`
|
||||||
|
- `submission_r2_intake`
|
||||||
|
- `jury2_evaluation`
|
||||||
|
- `jury3_live_finals`
|
||||||
|
- `final_confirmation`
|
||||||
|
- `results_publication`
|
||||||
|
- `award_evaluation`
|
||||||
|
- `award_results`
|
||||||
|
|
||||||
|
Rule:
|
||||||
|
- `StageType` remains coarse technical type.
|
||||||
|
- `purposeKey` controls business behavior and policy validation.
|
||||||
|
|
||||||
|
## 2.2 Jury As First-Class Entity
|
||||||
|
|
||||||
|
### New `Jury`
|
||||||
|
Fields:
|
||||||
|
- `id`, `programId`, `purposeKey`, `code`, `displayLabel`, `kind` (`MAIN`, `AWARD`), `isActive`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- `code=main-semifinal`, `displayLabel=Technical Semi-Final Jury`
|
||||||
|
- `code=main-finalist`, `displayLabel=Grand Jury`
|
||||||
|
- `code=award-blue-innovation`, `displayLabel=Blue Innovation Prize Jury`
|
||||||
|
|
||||||
|
### New `JuryMembership`
|
||||||
|
Fields:
|
||||||
|
- `juryId`, `userId`, `roleInJury` (`CHAIR`, `MEMBER`), `activeFrom`, `activeTo`, `status`
|
||||||
|
|
||||||
|
### New `JuryStageBinding`
|
||||||
|
Fields:
|
||||||
|
- `juryId`, `stageId`, `isPrimary`, `permissionsJson`
|
||||||
|
|
||||||
|
Use:
|
||||||
|
- controls which juries are allowed to evaluate/vote in each stage
|
||||||
|
- allows overlap across juries and awards
|
||||||
|
|
||||||
|
## 2.3 Assignment Policy Contract
|
||||||
|
|
||||||
|
### New `AssignmentPolicy` (stage + jury scoped)
|
||||||
|
Fields:
|
||||||
|
- `stageId`
|
||||||
|
- `juryId`
|
||||||
|
- `requiredReviews`
|
||||||
|
- `defaultCap`
|
||||||
|
- `defaultCapMode` (`HARD`, `SOFT`)
|
||||||
|
- `softBuffer` (default 10, configurable)
|
||||||
|
- `categoryBiasPolicyJson` (startup/concept mix preference weights; non-deterministic)
|
||||||
|
- `overflowPolicy` (`manual_queue`, `expand_pool`, `reduce_reviews`)
|
||||||
|
- `isActive`
|
||||||
|
|
||||||
|
### New `JudgePolicyOverride`
|
||||||
|
Fields:
|
||||||
|
- `assignmentPolicyId`
|
||||||
|
- `userId`
|
||||||
|
- `cap`, `capMode`, `softBuffer`
|
||||||
|
- `startupBiasWeight`, `conceptBiasWeight`
|
||||||
|
- `biasDisclosureAcceptedAt` (onboarding acknowledgement that bias is suggestive only)
|
||||||
|
- `source` (`ADMIN_SET`, `JUDGE_ONBOARDING`)
|
||||||
|
|
||||||
|
### New `AssignmentIntent`
|
||||||
|
Purpose:
|
||||||
|
- records desired assignments created before assignment materialization (e.g., from member invite page)
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- `stageId`, `juryId`, `userId`, `projectId`, `intentSource`, `status`
|
||||||
|
|
||||||
|
### New `AssignmentException`
|
||||||
|
Purpose:
|
||||||
|
- explicit record for manual over-cap assignments
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- `assignmentId`, `policyId`, `exceptionType`, `reasonCode`, `reasonText`, `approvedBy`
|
||||||
|
|
||||||
|
## 2.4 Submission Round Bundle Contract
|
||||||
|
|
||||||
|
### New `SubmissionRound`
|
||||||
|
Fields:
|
||||||
|
- `id`, `stageId`, `roundKey`, `name`, `openAt`, `closeAt`, `latePolicy`, `lateGraceHours`, `editabilityPolicy`
|
||||||
|
|
||||||
|
### Evolve `FileRequirement`
|
||||||
|
Add:
|
||||||
|
- `submissionRoundId` (nullable transitional, required after migration)
|
||||||
|
- `slotKey` (stable requirement slot identifier)
|
||||||
|
|
||||||
|
### Evolve `ProjectFile`
|
||||||
|
Add:
|
||||||
|
- `submissionRoundId`
|
||||||
|
- `submissionSlotKey`
|
||||||
|
- `sourceType` (`DIRECT_UPLOAD`, `MENTOR_PROMOTION`, `ADMIN_REPLACEMENT`)
|
||||||
|
- `sourceReferenceId` (e.g., mentor file id)
|
||||||
|
- `isOfficial`
|
||||||
|
|
||||||
|
### New `SubmissionBundleState`
|
||||||
|
Fields:
|
||||||
|
- `projectId`, `submissionRoundId`, `status` (`DRAFT`, `SUBMITTED`, `LOCKED`), `lockedAt`, `lockedBy`
|
||||||
|
|
||||||
|
## 2.5 Mentoring Collaboration + Promotion
|
||||||
|
|
||||||
|
### New `MentorWorkspaceFile`
|
||||||
|
Fields:
|
||||||
|
- `projectId`, `mentorId` (nullable for team upload), file storage metadata, visibility, status
|
||||||
|
|
||||||
|
### New `MentorWorkspaceComment`
|
||||||
|
Fields:
|
||||||
|
- `workspaceFileId`, `authorId`, `threadKey`, `content`, timestamps
|
||||||
|
|
||||||
|
### New `SubmissionPromotionEvent`
|
||||||
|
Fields:
|
||||||
|
- `projectId`, `workspaceFileId`, `targetSubmissionRoundId`, `targetSlotKey`
|
||||||
|
- `promotedById`, `promotedAt`
|
||||||
|
- `resultProjectFileId`
|
||||||
|
- `approvalState` (if mentor/admin approvals required)
|
||||||
|
|
||||||
|
Invariant:
|
||||||
|
- promotion always creates immutable provenance link from mentoring artifact to official submission slot.
|
||||||
|
- promotion authority is limited to team lead and admin.
|
||||||
|
|
||||||
|
## 2.6 Special Award Governance Contract
|
||||||
|
|
||||||
|
### Extend `SpecialAward`
|
||||||
|
Add:
|
||||||
|
- `participationMode` (`SEPARATE_POOL`, `DUAL_TRACK`)
|
||||||
|
- `routingBehavior` (`PULL_FROM_MAIN`, `KEEP_IN_MAIN`)
|
||||||
|
- `routingConfirmationMode` (`AUTO`, `ADMIN_CONFIRMED`) // Monaco default: `ADMIN_CONFIRMED` for pull-out
|
||||||
|
- `requiresDedicatedJury` (bool)
|
||||||
|
- `winnerDecisionMode` (`JURY_CONFIRMATION`, `SINGLE_JUDGE_DECIDES`)
|
||||||
|
- `singleJudgeUserId` (nullable, required when `winnerDecisionMode=SINGLE_JUDGE_DECIDES`)
|
||||||
|
- `winnerCandidateSource` (`ELIGIBILITY_REMAINING_POOL`, `CUSTOM_SHORTLIST`) // Monaco single-judge awards use eligibility remaining pool
|
||||||
|
- `submissionRequirementMode` (`REUSE_MAIN`, `CUSTOM`)
|
||||||
|
|
||||||
|
### New `AwardStageBinding`
|
||||||
|
Fields:
|
||||||
|
- `awardId`, `filterStageId`, `evaluationStageId`, `resultStageId`
|
||||||
|
|
||||||
|
## 2.7 Final Confirmation Contract
|
||||||
|
|
||||||
|
### New `FinalConfirmationSession`
|
||||||
|
Fields:
|
||||||
|
- `stageId` (final confirmation stage)
|
||||||
|
- `status` (`OPEN`, `PENDING_ADMIN_APPROVAL`, `FINALIZED`, `CANCELLED`)
|
||||||
|
- `decisionRule` (`UNANIMOUS`, `SUPERMAJORITY`, `SIMPLE_MAJORITY`, `SINGLE_JUDGE_DECIDES`)
|
||||||
|
- `quorumPolicy` (`ACTIVE_MEMBERS_ONLY`, `ALLOW_REPLACEMENT`)
|
||||||
|
- `requiredApprovalsCount` (resolved from active quorum after absence/replacement decisions)
|
||||||
|
- `scope` (`CATEGORY`, `AWARD`)
|
||||||
|
- `isAdminOverridden` (bool)
|
||||||
|
- `overrideReasonCode`, `overrideReasonText`, `overriddenByAdminId`, `overriddenAt`
|
||||||
|
- `finalizedByAdminId`, `finalizedAt`
|
||||||
|
|
||||||
|
### New `FinalConfirmationParticipant`
|
||||||
|
Fields:
|
||||||
|
- `sessionId`, `juryMemberId`
|
||||||
|
- `status` (`REQUIRED`, `ABSENT_EXCUSED`, `REPLACED`, `REPLACEMENT_ACTIVE`)
|
||||||
|
- `replacedByJuryMemberId` (nullable)
|
||||||
|
- `absenceReasonCode`, `absenceReasonText`
|
||||||
|
- `updatedByAdminId`, `updatedAt`
|
||||||
|
|
||||||
|
### New `FinalConfirmationVote`
|
||||||
|
Fields:
|
||||||
|
- `sessionId`, `juryMemberId`, `projectId/category/awardScope`, `decision`, `comment`
|
||||||
|
|
||||||
|
### New `ResultLock`
|
||||||
|
Fields:
|
||||||
|
- `sessionId`, `programId`, `lockVersion`, `lockedAt`, `lockedBy`, `snapshotJson`
|
||||||
|
|
||||||
|
### New `ResultUnlockEvent`
|
||||||
|
Fields:
|
||||||
|
- `resultLockId`, `programId`
|
||||||
|
- `unlockedBySuperAdminId`, `unlockedAt`
|
||||||
|
- `reasonCode`, `reasonText`
|
||||||
|
- `relockVersion` (nullable until relock)
|
||||||
|
|
||||||
|
Invariant:
|
||||||
|
- once `ResultLock` is created, winner outputs are immutable except via explicit super-admin unlock workflow with mandatory audit reason.
|
||||||
|
|
||||||
|
## 3) Policy Precedence Model
|
||||||
|
|
||||||
|
Order of precedence (highest to lowest):
|
||||||
|
1. Explicit admin override action (with reason)
|
||||||
|
2. Per-user override policy (e.g., judge cap override)
|
||||||
|
3. Stage+jury policy
|
||||||
|
4. Program-level default policy
|
||||||
|
5. System default
|
||||||
|
|
||||||
|
All runtime evaluators must return:
|
||||||
|
- resolved value
|
||||||
|
- source layer
|
||||||
|
- explanation payload for audit/debug UI
|
||||||
|
|
||||||
|
## 4) Access Contract (Role + Context)
|
||||||
|
|
||||||
|
Replace scattered checks with `resolveAccess(user, context)` where context includes:
|
||||||
|
- `programId`
|
||||||
|
- `trackId`
|
||||||
|
- `stageId`
|
||||||
|
- `stagePurposeKey`
|
||||||
|
- `juryId` (if applicable)
|
||||||
|
- `projectId` (if applicable)
|
||||||
|
- `submissionRoundId` (if applicable)
|
||||||
|
|
||||||
|
Returns typed permissions:
|
||||||
|
- `canViewProject`
|
||||||
|
- `canUploadSubmissionSlot`
|
||||||
|
- `canViewPreviousRoundDocs`
|
||||||
|
- `canAssignProjects`
|
||||||
|
- `canVoteLive`
|
||||||
|
- `canConfirmFinalWinners`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
## 5) Assignment Engine Contract
|
||||||
|
|
||||||
|
Input:
|
||||||
|
- projects eligible in stage
|
||||||
|
- jury members bound to stage
|
||||||
|
- resolved per-judge policy (cap mode, cap, buffer, category-bias preference)
|
||||||
|
- required reviews
|
||||||
|
- COI and availability constraints
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
1. satisfy hard-cap constraints strictly
|
||||||
|
2. satisfy soft-cap targets before using buffer
|
||||||
|
3. apply startup/concept distribution as a soft scoring bias (never a strict blocker)
|
||||||
|
4. when all soft-cap members hit cap+buffer, place remainder in manual queue
|
||||||
|
5. never silently drop unassigned projects
|
||||||
|
|
||||||
|
Output:
|
||||||
|
- assignment set
|
||||||
|
- unassigned queue with explicit reasons
|
||||||
|
- policy-compliance summary
|
||||||
|
|
||||||
|
## 6) Document Contract
|
||||||
|
|
||||||
|
For every stage with submission behavior:
|
||||||
|
- active `SubmissionRound` must exist
|
||||||
|
- all required slots represented by `FileRequirement.slotKey`
|
||||||
|
- applicant write rights only for active round if open
|
||||||
|
- previous round slots read-only in applicant scope
|
||||||
|
- judges see current + previous according to stage policy
|
||||||
|
- admins full mutate rights with replacement provenance
|
||||||
|
|
||||||
|
## 7) Invite + Onboarding Contract
|
||||||
|
|
||||||
|
## 7.1 Admin Member Invite
|
||||||
|
- Invitations create `User` + optional `JuryMembership`.
|
||||||
|
- Jury selection uses program-defined custom jury labels, while binding to explicit stage-purpose contracts.
|
||||||
|
- Pre-assignment supports both modes:
|
||||||
|
- intent-first mode via `AssignmentIntent` (default recommended)
|
||||||
|
- direct assignment mode via `Assignment` (explicitly enabled and policy-validated)
|
||||||
|
|
||||||
|
## 7.2 Team Invite
|
||||||
|
- Team lead invites create/attach `TeamMember`
|
||||||
|
- invite token acceptance path remains, but post-accept routing resolves to proper applicant/team context
|
||||||
|
|
||||||
|
## 7.3 Accept Invite Routing
|
||||||
|
After acceptance:
|
||||||
|
- if user has jury memberships pending onboarding -> jury onboarding journey
|
||||||
|
- if mentor -> mentor onboarding
|
||||||
|
- if applicant team member only -> applicant team dashboard
|
||||||
|
|
||||||
|
## 8) Event/Audit Contract
|
||||||
|
|
||||||
|
Every significant workflow event emits:
|
||||||
|
- business event type (`assignment.generated`, `final_confirmation.finalized`, etc.)
|
||||||
|
- actor
|
||||||
|
- entity scope
|
||||||
|
- policy snapshot
|
||||||
|
- before/after state where relevant
|
||||||
|
|
||||||
|
Override events must include:
|
||||||
|
- reason code
|
||||||
|
- reason text
|
||||||
|
- impacted policy key
|
||||||
|
- reviewer chain (if applicable)
|
||||||
|
|
||||||
|
## 9) API Contract Evolution (Router-Level)
|
||||||
|
|
||||||
|
## 9.1 Keep and Evolve
|
||||||
|
- Keep existing routers (`pipeline`, `stage`, `assignment`, `evaluation`, `file`, `mentor`, `live-voting`, `decision`, `user`, etc.)
|
||||||
|
- Add new endpoints for purpose/policy/jury/final confirmation and deprecate ambiguous patterns progressively
|
||||||
|
|
||||||
|
## 9.2 New Endpoint Families
|
||||||
|
- `jury.*`: CRUD memberships/bindings
|
||||||
|
- `assignmentPolicy.*`: configure and inspect effective policy
|
||||||
|
- `submissionRound.*`: lifecycle and bundle state
|
||||||
|
- `mentorWorkspace.*`: files/comments/promotion
|
||||||
|
- `finalConfirmation.*`: create/collect/finalize lock, handle quorum fallback, and manage juror replacement/absence
|
||||||
|
- `resultUnlock.*`: super-admin unlock/relock workflow with audit capture
|
||||||
|
- `access.*`: debug effective permissions (admin-only)
|
||||||
|
|
||||||
|
## 10) Migration Safety Rules
|
||||||
|
1. Additive migrations first.
|
||||||
|
2. Backfill `purposeKey` and policy references from existing configs.
|
||||||
|
3. Dual-read / single-write transition windows where needed.
|
||||||
|
4. Feature flags for critical runtime path switches.
|
||||||
|
5. No silent behavior changes in production without compatibility mode.
|
||||||
|
|
||||||
|
## 11) End State
|
||||||
|
A coherent operating model where all functions, pages, and APIs are consistent with Monaco flow and each key behavior is explainable, testable, and auditable.
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
# 05. Platform-Wide Integration Matrix
|
||||||
|
|
||||||
|
## 1) Integration Rule
|
||||||
|
Every function must consume the same competition context contract:
|
||||||
|
- program
|
||||||
|
- pipeline/track
|
||||||
|
- stage + `purposeKey`
|
||||||
|
- jury context (when relevant)
|
||||||
|
- submission round context (when relevant)
|
||||||
|
- resolved policy set
|
||||||
|
|
||||||
|
No page/router may hardcode Monaco behavior without passing through this context resolver.
|
||||||
|
|
||||||
|
## 2) Critical Integration Domains
|
||||||
|
|
||||||
|
1. Identity and invitation/onboarding
|
||||||
|
2. Pipeline/stage orchestration
|
||||||
|
3. Assignment/evaluation
|
||||||
|
4. Documents/submission bundles
|
||||||
|
5. Awards and routing
|
||||||
|
6. Mentoring collaboration
|
||||||
|
7. Live finals and final confirmation
|
||||||
|
8. Notifications, timeline, reporting, exports
|
||||||
|
|
||||||
|
## 3) Admin UX Integration Matrix
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration To Redesigned Architecture |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(admin)/admin/rounds/pipeline/[id]/wizard/page.tsx` | Generic stage setup | Must require `purposeKey` assignment and validate Monaco skeleton completeness |
|
||||||
|
| `src/app/(admin)/admin/rounds/pipeline/[id]/advanced/page.tsx` | Advanced free-form editing | Add policy editors for jury binding, assignment policies, submission rounds, final confirmation |
|
||||||
|
| `src/app/(admin)/admin/rounds/pipeline/[id]/edit/page.tsx` | Pipeline editing | Prevent invalid stage-purpose combinations; show dependency impacts |
|
||||||
|
| `src/components/admin/pipeline/sections/assignment-section.tsx` | min/max load + overflow | Replace with explicit hard/soft cap + buffer + startup/concept bias controls (clearly labeled suggestive, not deterministic) |
|
||||||
|
| `src/components/admin/pipeline/sections/filtering-section.tsx` | rules + AI criteria | Bind to `eligibility_filter` purpose and standardized outcomes |
|
||||||
|
| `src/components/admin/pipeline/sections/awards-section.tsx` | award routing/scoring basic | Add participation mode (`separate_pool`/`dual_track`) and pull-out behavior |
|
||||||
|
| `src/components/admin/pipeline/sections/live-finals-section.tsx` | live/audience settings | Add Jury 3 deliberation window and final confirmation linkage |
|
||||||
|
| `src/components/admin/pipeline/stage-config-editor.tsx` | generic config rendering | render purpose-aware controls only; remove ambiguous keys |
|
||||||
|
| `src/app/(admin)/admin/projects/page.tsx` | project list by filters | Add stage-purpose-aware views and round bundle health columns |
|
||||||
|
| `src/app/(admin)/admin/projects/pool/page.tsx` | project pool ops | Use assignment intent/manual queue with reason tracking |
|
||||||
|
| `src/app/(admin)/admin/reports/stages/page.tsx` | stage reporting | Include compliance metrics (cap violations, override counts, unassigned backlog) |
|
||||||
|
|
||||||
|
## 4) Invite / Member / Onboarding Integration Matrix (High Priority)
|
||||||
|
|
||||||
|
### 4.1 Admin Member Invite (Explicit Requirement)
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(admin)/admin/members/invite/page.tsx` | invites users; optional pre-assign project-to-user by stage | Must bind invite flow to explicit `JuryMembership`. UI must support both pre-assignment modes: `AssignmentIntent` mode and policy-validated direct assignment mode. Admin chooses custom-labeled jury (main or award), stage scope, and mode. |
|
||||||
|
| `src/server/routers/user.ts` `bulkCreate` | creates users and may directly create assignments | Support dual behavior: create `AssignmentIntent` and/or direct policy-validated assignments based on request mode. Track chosen mode, source, and policy decision in audit details. |
|
||||||
|
| `src/server/routers/user.ts` `sendInvitation`/`bulkSendInvitations` | token invitation | Include context metadata (jury memberships, pending assignment intents, onboarding path) in invitation payload/logs. |
|
||||||
|
|
||||||
|
### 4.2 Team Invite / Project Team Management
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(applicant)/applicant/team/page.tsx` | team lead invites/removes members | Keep flow, but gate permissions via context resolver and ensure invite acceptance routes into project-team scope correctly. |
|
||||||
|
| `src/app/(public)/my-submission/[id]/team/page.tsx` | mirrored team flow | Must consume same backend contract as applicant team page; no divergent logic. |
|
||||||
|
| `src/server/routers/applicant.ts` `inviteTeamMember` | creates/updates user + team member + email | Add membership lifecycle states and provenance events; ensure no conflict with jury/mentor invitation states. |
|
||||||
|
|
||||||
|
### 4.3 Invite Acceptance + Onboarding
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(auth)/accept-invite/page.tsx` | validates token and signs in | After acceptance, route based on membership context (jury, mentor, team, admin) and pending obligations (onboarding, password, jury onboarding choices). |
|
||||||
|
| `src/app/(auth)/onboarding/page.tsx` | role-aware onboarding | Must collect jury cap/bias preferences (if enabled) for judges, clearly disclose that startup/concept bias is suggestive (not a rule), and tie to `JudgePolicyOverride` records. |
|
||||||
|
| `src/server/routers/user.ts` `validateInviteToken` | token validation only | include contextual invitation metadata for accurate UX preview. |
|
||||||
|
| `src/server/routers/user.ts` `needsOnboarding` | role-based onboarding check | extend to membership-aware onboarding requirements per jury/stage. |
|
||||||
|
|
||||||
|
## 5) Applicant + Submission Integration Matrix
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(public)/apply/[slug]/page.tsx` | stage/public form | enforce submission round contract (`SubmissionRound`, slots, late policy) |
|
||||||
|
| `src/app/(public)/apply/edition/[programSlug]/page.tsx` | edition mode intake | must map to `submission_r1_intake` purpose policies if Monaco profile active |
|
||||||
|
| `src/app/(applicant)/applicant/pipeline/page.tsx` | pipeline visibility | show purpose-based journey (R1, eligibility, Jury1 result, R2, etc.) |
|
||||||
|
| `src/app/(applicant)/applicant/pipeline/[stageId]/documents/page.tsx` | stage docs | enforce round-bundle write/read states by stage purpose |
|
||||||
|
| `src/app/(applicant)/applicant/documents/page.tsx` | docs view | show bundle timeline and official slot status per round |
|
||||||
|
| `src/server/routers/application.ts` | public submission & draft | create `SubmissionBundleState` and normalize category key mapping |
|
||||||
|
| `src/server/routers/applicant.ts` `getUploadUrl/saveFileMetadata/deleteFile` | upload + save + delete | enforce slot state, round lock, promotion provenance, late behavior policy |
|
||||||
|
| `src/server/routers/file.ts` | file access and grouped views | use submission round + stage purpose aware visibility consistently across roles |
|
||||||
|
|
||||||
|
## 6) Jury Integration Matrix
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(jury)/jury/stages/page.tsx` | shows active stages | show only stages bound to juries user belongs to |
|
||||||
|
| `src/app/(jury)/jury/stages/[stageId]/assignments/page.tsx` | assignment list | render cap status, ratio status, deadline countdown, completion obligations |
|
||||||
|
| `src/app/(jury)/jury/stages/[stageId]/projects/[projectId]/page.tsx` | project details | purpose-aware document separation (Round 1 vs Round 2) |
|
||||||
|
| `src/app/(jury)/jury/stages/[stageId]/projects/[projectId]/evaluate/page.tsx` | evaluation form | enforce stage/jury policy and graceful lock behavior |
|
||||||
|
| `src/app/(jury)/jury/stages/[stageId]/live/page.tsx` | live finals interaction | require Jury 3 membership and live session permissions |
|
||||||
|
| `src/server/routers/assignment.ts` | assignment CRUD/suggestions/jobs | centralize policy engine usage for all assignment paths |
|
||||||
|
| `src/server/routers/evaluation.ts` | evaluation lifecycle | incorporate jury binding checks and final confirmation contribution rights |
|
||||||
|
| `src/server/services/stage-assignment.ts` | stage assignment preview/run | implement hard/soft cap+buffer with category bias as weighted guidance (not deterministic rule) |
|
||||||
|
| `src/server/services/smart-assignment.ts` | scoring logic | treat startup/concept preference as transparent scoring bias, not strict quota blocking |
|
||||||
|
|
||||||
|
## 7) Awards Integration Matrix
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(admin)/admin/awards/*` | award management | expose mode A/B semantics, admin-confirmed pull-out flow, and single-judge decision mode where configured |
|
||||||
|
| `src/app/(jury)/jury/awards/*` | jury award voting | bind to award jury memberships/stage windows and suppress jury voting for single-judge awards |
|
||||||
|
| `src/server/routers/award.ts` | award track governance | add strict validation for participation mode, stage bindings, admin-confirmed pull-out, and single-judge configuration |
|
||||||
|
| `src/server/routers/specialAward.ts` | eligibility + jurors + votes | include standardized filter->review->selection pipeline and override chain |
|
||||||
|
| `src/server/services/award-eligibility-job.ts` | background eligibility | persist decision reasoning under unified audit event taxonomy |
|
||||||
|
|
||||||
|
## 8) Mentor Integration Matrix
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(mentor)/mentor/projects/[id]/page.tsx` | mentor project detail | include private workspace files + threaded comments; promotion actions only where allowed by team-lead/admin authority |
|
||||||
|
| `src/app/(applicant)/applicant/mentor/page.tsx` | applicant mentor interaction | show promotion candidate files and official slot mapping |
|
||||||
|
| `src/server/routers/mentor.ts` | assignment/message/notes/milestones | add workspace file/comment/promotion endpoints and enforce team-lead/admin-only promotion permissions |
|
||||||
|
| `src/server/services/mentor-matching.ts` | AI matching | gate matching by finalist + mentorship request + stage policy |
|
||||||
|
|
||||||
|
## 9) Live Finals + Confirmation Integration Matrix
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/app/(admin)/admin/reports/stages/page.tsx` + live admin pages | live status/reporting | show stage manager controls + deliberation timer + confirmation readiness |
|
||||||
|
| `src/app/(public)/vote/*` | audience voting pages | enforce category/session mode and anti-duplicate policy |
|
||||||
|
| `src/server/routers/live.ts` | live cursor controls | require `jury3_live_finals` purpose and stage manager authority |
|
||||||
|
| `src/server/routers/live-voting.ts` | voting session mgmt | tie session lifecycle to stage purpose + deliberation/final confirmation handoff |
|
||||||
|
| `src/server/routers/cohort.ts` | cohort windows | align with live/selection policies and category boundaries |
|
||||||
|
| `src/server/routers/decision.ts` | overrides/audit | extend to final confirmation decisions and result lock events |
|
||||||
|
| **new router family** `finalConfirmation.*` | none | create and govern unanimous-with-quorum-fallback confirmation for category+award scopes, single-judge award exceptions, juror replacement/absence controls, admin override paths, and final lock snapshot |
|
||||||
|
| **new router family** `resultUnlock.*` | none | super-admin-only unlock/relock workflow with mandatory reason/audit trail |
|
||||||
|
|
||||||
|
## 10) Notifications / Messaging / Reporting / Export Integration Matrix
|
||||||
|
|
||||||
|
| Surface | Current Behavior | Required Integration |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/server/services/stage-notifications.ts` | event-based notifications | expand event taxonomy for jury membership, promotion, confirmation lock |
|
||||||
|
| `src/server/services/evaluation-reminders.ts` | stage deadline reminders | include jury-specific obligations and final confirmation reminders |
|
||||||
|
| `src/server/routers/message.ts` | messaging | ensure context tags reference stage/jury/project where applicable |
|
||||||
|
| `src/server/routers/export.ts` | report generation | include final lock version and policy-compliance evidence |
|
||||||
|
| `src/server/routers/audit.ts` | audit search | add filter facets for stage purpose, jury id, confirmation session |
|
||||||
|
| `src/server/routers/analytics.ts` | platform analytics | add Monaco flow KPIs (assignment saturation, override rate, policy compliance) |
|
||||||
|
|
||||||
|
## 11) Router Integration Checklist (All tRPC Domains)
|
||||||
|
|
||||||
|
Each router in `src/server/routers/_app.ts` must be classified into one of three buckets:
|
||||||
|
|
||||||
|
1. **Directly governed by competition context**
|
||||||
|
- `pipeline`, `stage`, `stageFiltering`, `stageAssignment`, `assignment`, `evaluation`, `decision`, `live`, `liveVoting`, `cohort`, `award`, `specialAward`, `mentor`, `applicant`, `application`, `file`, `project`, `project-pool`
|
||||||
|
|
||||||
|
2. **Indirectly context-dependent (must carry context metadata)**
|
||||||
|
- `user`, `notification`, `message`, `dashboard`, `analytics`, `export`, `audit`
|
||||||
|
|
||||||
|
3. **Context-independent utilities**
|
||||||
|
- `avatar`, `logo`, `tag`, imports, generic settings/webhooks
|
||||||
|
|
||||||
|
Requirement:
|
||||||
|
- Bucket 1 and 2 routers must accept/derive stage/jury context when touching competition entities.
|
||||||
|
|
||||||
|
## 12) Services Refactor Boundaries
|
||||||
|
|
||||||
|
| Service | Keep | Refactor Requirement |
|
||||||
|
|---|---|---|
|
||||||
|
| `stage-engine.ts` | yes | purpose-aware transition guards |
|
||||||
|
| `stage-filtering.ts` | yes | standardized eligibility outcomes + reason schema |
|
||||||
|
| `stage-assignment.ts` | yes | full hard/soft cap policy engine |
|
||||||
|
| `smart-assignment.ts` | partial | convert soft heuristics into policy-compliant scoring only |
|
||||||
|
| `live-control.ts` | yes | bind to Jury3 and confirmation handoff |
|
||||||
|
| `mentor-matching.ts` | yes | finalist-stage gating and workspace policy awareness |
|
||||||
|
| `evaluation-reminders.ts` | yes | purpose-aware reminder templates |
|
||||||
|
|
||||||
|
## 13) Integration Acceptance Criteria
|
||||||
|
A module is considered integrated only if:
|
||||||
|
1. It resolves effective competition context before executing business logic.
|
||||||
|
2. It uses typed policy resolution (not ad hoc JSON key checks).
|
||||||
|
3. It emits standardized audit/decision events.
|
||||||
|
4. It fails safely when required context/policy is missing.
|
||||||
|
|
||||||
|
## 14) Special Note: “All Functions Deeply Integrated”
|
||||||
|
Your requirement is satisfied only when invite/member/team/onboarding pages are not treated as peripheral admin UX, but as first-class entry points into the jury/stage/assignment model. This is explicitly mandatory in Phases 2 and 3 of the roadmap.
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
# 06. Implementation Roadmap And Migration Plan
|
||||||
|
|
||||||
|
## 1) Delivery Strategy
|
||||||
|
Use an incremental, low-risk migration with compatibility layers.
|
||||||
|
|
||||||
|
Execution mode:
|
||||||
|
- Additive schema first
|
||||||
|
- Dual-read where required
|
||||||
|
- Single-write to new contracts once validated
|
||||||
|
- Feature flags for runtime switches
|
||||||
|
- Rollback-safe at every phase gate
|
||||||
|
|
||||||
|
## 2) Phase Plan
|
||||||
|
|
||||||
|
## Phase 0: Contract Freeze And Blueprint Sign-Off
|
||||||
|
|
||||||
|
### Deliverables
|
||||||
|
- Approved Monaco flow contract (stage purpose keys, jury model, assignment semantics)
|
||||||
|
- Locked ADRs for major design choices
|
||||||
|
- Finalized open question decisions from `08-open-questions-for-flow-owners.md`
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- No unresolved P0 architecture questions
|
||||||
|
- Product + operations + engineering agreement on control-plane UX behavior
|
||||||
|
|
||||||
|
## Phase 1: Schema Foundation (Additive)
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Add `stagePurposeKey`
|
||||||
|
- Add jury models (`Jury`, `JuryMembership`, `JuryStageBinding`)
|
||||||
|
- Add assignment policy models (`AssignmentPolicy`, `JudgePolicyOverride`, `AssignmentIntent`, `AssignmentException`)
|
||||||
|
- Add submission round models (`SubmissionRound`, bundle state fields)
|
||||||
|
- Add mentoring workspace + promotion models
|
||||||
|
- Add final confirmation models
|
||||||
|
|
||||||
|
### Data Migration Tasks
|
||||||
|
1. Backfill stage purpose keys from known stage names/slugs/types.
|
||||||
|
2. Create purpose-bound main juries for active Monaco programs with editable custom labels.
|
||||||
|
3. Create baseline assignment policies from existing stage config values.
|
||||||
|
4. Materialize current intake requirements into typed submission round artifacts.
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- All migrations run cleanly in staging
|
||||||
|
- Backfill report has zero unknown stage purpose for Monaco pipelines
|
||||||
|
- Referential integrity validated
|
||||||
|
|
||||||
|
## Phase 2: Policy Engine And Context Resolver
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Build centralized `competitionContextResolver`
|
||||||
|
- Build centralized `policyResolver`
|
||||||
|
- Refactor assignment/filtering/live access paths to consume resolver
|
||||||
|
|
||||||
|
### Key Deliverables
|
||||||
|
- `resolveContext(program/stage/project/user)` service
|
||||||
|
- `resolveAssignmentPolicy(stageId, juryId, userId)`
|
||||||
|
- policy-aware access checks for docs/evaluations/live controls
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- No core router uses legacy ad hoc logic for stage/jury policy checks
|
||||||
|
- Policy resolution explainability endpoint available (admin debug)
|
||||||
|
|
||||||
|
## Phase 3: Invite/Member/Onboarding Integration (Critical)
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Refactor admin member invite flow to create jury memberships plus dual pre-assignment modes (intent mode and direct policy-validated assignment mode)
|
||||||
|
- Align team invite flow with unified invitation context metadata
|
||||||
|
- Update accept-invite routing to membership-aware onboarding
|
||||||
|
|
||||||
|
### Key Deliverables
|
||||||
|
- `/admin/members/invite` UX and backend contract update
|
||||||
|
- `user.bulkCreate` migration to support both assignment intent creation and direct policy-validated assignments
|
||||||
|
- onboarding path resolver by role + memberships, including clear bias-disclosure text for jury onboarding
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- Invite-created users can be traced from invite -> membership -> assignment materialization
|
||||||
|
- No orphan invitation states
|
||||||
|
- Regression tests pass for admin invite + team invite + accept invite
|
||||||
|
|
||||||
|
## Phase 4: Submission Bundle Lifecycle + Document Locking
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Introduce submission rounds and bundle states in runtime
|
||||||
|
- Enforce edit/read-only behavior by round and stage purpose
|
||||||
|
- Support provenance-aware promotion from mentor workspace
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- Applicants can only edit current round slots
|
||||||
|
- Judges can see current + prior bundles by policy
|
||||||
|
- Promotion events produce immutable trace
|
||||||
|
|
||||||
|
## Phase 5: Assignment Engine Hardening For Monaco Semantics
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Implement strict hard/soft cap behavior with buffer
|
||||||
|
- Implement startup/concept weighting as soft-bias preference per judge (non-deterministic)
|
||||||
|
- Implement deterministic unassigned queue and manual fallback
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- Policy compliance report proves no hard-cap violations
|
||||||
|
- Soft-cap overflow behavior matches configured buffer rules
|
||||||
|
- Ratio/bias behavior is explainable and visibly disclosed as suggestive in onboarding and admin UI
|
||||||
|
- Manual queue receives all residual items with reasons
|
||||||
|
|
||||||
|
## Phase 6: Awards + Mentoring + Finals + Confirmation
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Add award participation mode semantics, admin-confirmed pull-out flow, and single-judge award decision mode
|
||||||
|
- Complete mentor workspace file/comment/promotion behavior
|
||||||
|
- Link live finals to final confirmation workflow and result lock/unlock governance
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- Mode A/B award behavior validated in staging scenarios, including admin-confirmed pull-out
|
||||||
|
- Final confirmation requires configured jury agreement with quorum fallback + admin approval (or configured single-judge award path), with admin override support and audit trail
|
||||||
|
- Result lock snapshot generated and immutable until explicit super-admin unlock workflow is used
|
||||||
|
|
||||||
|
## Phase 7: UX Consolidation And Operator Experience
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Simplify admin pipeline editors with purpose-aware controls
|
||||||
|
- Remove/disable ambiguous config controls
|
||||||
|
- Add context-rich dashboards and compliance panels
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- Operators can configure full Monaco flow without advanced/manual JSON edits
|
||||||
|
- Support docs and admin runbooks updated
|
||||||
|
|
||||||
|
## Phase 8: Cutover And Decommission Legacy Paths
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
- Enable new contracts by default
|
||||||
|
- Deprecate legacy code paths and config fallback branches
|
||||||
|
- cleanup migration of deprecated fields where safe
|
||||||
|
|
||||||
|
### Exit Criteria
|
||||||
|
- Production traffic fully on new architecture
|
||||||
|
- Legacy path usage dashboards show zero critical usage
|
||||||
|
- post-cutover audit and incident-free burn-in period complete
|
||||||
|
|
||||||
|
## 3) Workstream Breakdown
|
||||||
|
|
||||||
|
## 3.1 Backend Workstream
|
||||||
|
- Schema/migrations
|
||||||
|
- Policy resolver
|
||||||
|
- Router refactors
|
||||||
|
- Event/audit normalization
|
||||||
|
|
||||||
|
## 3.2 Frontend Workstream
|
||||||
|
- Admin pipeline UX
|
||||||
|
- Member invite and onboarding flows
|
||||||
|
- Applicant/jury/mentor stage-aware views
|
||||||
|
- finals control + confirmation UI
|
||||||
|
|
||||||
|
## 3.3 Data/Operations Workstream
|
||||||
|
- Backfills and integrity checks
|
||||||
|
- Job monitoring and reconciliation scripts
|
||||||
|
- release runbooks and rollback procedures
|
||||||
|
|
||||||
|
## 3.4 QA Workstream
|
||||||
|
- End-to-end test scenarios for full Monaco flow
|
||||||
|
- policy compliance assertions
|
||||||
|
- performance and reliability testing
|
||||||
|
|
||||||
|
## 4) Migration Sequencing Rules
|
||||||
|
1. No destructive migration before dual-read period completes.
|
||||||
|
2. Every new write path must emit old + new audit evidence during transition.
|
||||||
|
3. Use idempotent backfills with resumable checkpoints.
|
||||||
|
4. Keep feature toggles per major subsystem (invite policy, assignment policy, docs, confirmation).
|
||||||
|
|
||||||
|
## 5) Rollback Strategy
|
||||||
|
|
||||||
|
### 5.1 Technical Rollback
|
||||||
|
- Feature flag rollback first
|
||||||
|
- Keep old write handlers until two stable releases after cutover
|
||||||
|
- Avoid schema drops until rollback window closes
|
||||||
|
|
||||||
|
### 5.2 Operational Rollback
|
||||||
|
- Predefined rollback runbook for each phase
|
||||||
|
- operator checklist for reverting admin workflows
|
||||||
|
- communication templates for judges/admins if rollback impacts active stages
|
||||||
|
|
||||||
|
## 6) Delivery Governance
|
||||||
|
- Weekly architecture sync (backend/frontend/product/ops)
|
||||||
|
- Phase gate reviews with evidence packages
|
||||||
|
- Strict “no silent contract drift” policy for late-stage changes
|
||||||
|
|
||||||
|
## 7) Suggested Initial Milestones (Order)
|
||||||
|
1. Approve purpose keys + jury model + assignment semantics.
|
||||||
|
2. Implement schema foundation and context resolver.
|
||||||
|
3. Integrate invite/onboarding/team flows.
|
||||||
|
4. Enforce round bundle lifecycle.
|
||||||
|
5. Harden assignment policy behavior.
|
||||||
|
6. Complete awards/mentoring/finals/confirmation chain.
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
# 07. Testing, Observability, And Release Gates
|
||||||
|
|
||||||
|
## 1) QA Strategy
|
||||||
|
The redesign is policy-heavy. Testing must validate:
|
||||||
|
1. Functional correctness
|
||||||
|
2. Policy compliance
|
||||||
|
3. Audit completeness
|
||||||
|
4. Role-based access integrity
|
||||||
|
5. Operational resilience under load and deadline pressure
|
||||||
|
|
||||||
|
## 2) Test Pyramid
|
||||||
|
|
||||||
|
### 2.1 Unit Tests
|
||||||
|
Target modules:
|
||||||
|
- context resolver
|
||||||
|
- policy resolver
|
||||||
|
- assignment policy evaluator (hard/soft/buffer/category-bias weighting)
|
||||||
|
- round bundle lock evaluator
|
||||||
|
- final confirmation rule evaluator
|
||||||
|
|
||||||
|
### 2.2 Integration Tests
|
||||||
|
Target flows:
|
||||||
|
- stage transitions with purpose validation
|
||||||
|
- filtering outcomes + override
|
||||||
|
- assignment run + unassigned queue + manual assignment
|
||||||
|
- document upload/edit lock transitions
|
||||||
|
- mentor promotion pipeline
|
||||||
|
- live voting -> confirmation -> result lock
|
||||||
|
|
||||||
|
### 2.3 End-to-End Tests
|
||||||
|
Target user journeys:
|
||||||
|
- applicant R1 -> eligibility -> Jury1 -> R2 -> Jury2 -> finals prep -> finals -> result
|
||||||
|
- admin stage manager live finals operations
|
||||||
|
- award mode A and mode B paths
|
||||||
|
- admin invite -> onboarding -> jury assignment materialization
|
||||||
|
- team invite -> accept invite -> team collaboration
|
||||||
|
|
||||||
|
## 3) Monaco Flow Test Matrix
|
||||||
|
|
||||||
|
| Flow Step | Required Test Cases |
|
||||||
|
|---|---|
|
||||||
|
| Submission R1 | open/close behavior, late policy modes, required docs enforcement |
|
||||||
|
| Eligibility filter | deterministic pass/fail, AI banding, manual queue, admin override trail |
|
||||||
|
| Jury1 | cap modes, category-bias preference behavior (suggestive only), deadline reminder triggers, score submission |
|
||||||
|
| Submission R2 | R1 read-only for applicants, R2 editable, admin full override |
|
||||||
|
| Jury2 | visibility of R1+R2 docs, finalist recommendation and override |
|
||||||
|
| Awards | separate pool, dual track, admin-confirmed pull-out behavior, jury overlap, single-judge decision mode |
|
||||||
|
| Mentoring | private file sharing, comments, promotion to official slot with provenance |
|
||||||
|
| Jury3 live | stage manager controls, voting windows, notes, audience vote mode |
|
||||||
|
| Final confirmation | unanimous jury agreement on active quorum (or configured rule), juror replacement/absence handling, single-judge award exception flow, admin finalize, admin override path, result lock |
|
||||||
|
|
||||||
|
## 4) Invite/Member/Onboarding Test Matrix (Critical)
|
||||||
|
|
||||||
|
| Workflow | Must Validate |
|
||||||
|
|---|---|
|
||||||
|
| Admin member invite (`/admin/members/invite`) | creates user + jury membership + selected pre-assignment mode (intent and/or direct policy-validated assignment); respects role authorization |
|
||||||
|
| Bulk invite CSV | invalid rows, duplicate handling, unauthorized role attempts, staged pre-assignment mode selection and materialization |
|
||||||
|
| Invite acceptance (`/accept-invite`) | token validity, role-context preview, routing to correct onboarding path |
|
||||||
|
| Jury onboarding | cap/bias override onboarding when enabled, including explicit disclosure that category bias is a suggestion not a rule |
|
||||||
|
| Team invite (`/applicant/team`) | lead-only invite/remove, account setup vs existing account behavior |
|
||||||
|
| Cross-context conflict | same user invited as jury and team member still resolves correct permissions by context |
|
||||||
|
|
||||||
|
## 5) Regression Coverage Extension Against Existing Test Suite
|
||||||
|
Current suite already has useful coverage (`tests/unit/*`, `tests/integration/*`). Extend with:
|
||||||
|
|
||||||
|
1. `tests/unit/assignment-policy-hard-soft.test.ts`
|
||||||
|
2. `tests/unit/final-confirmation-rules.test.ts`
|
||||||
|
3. `tests/integration/invite-jury-membership-intent.test.ts`
|
||||||
|
4. `tests/integration/submission-bundle-locking.test.ts`
|
||||||
|
5. `tests/integration/mentor-promotion-provenance.test.ts`
|
||||||
|
6. `tests/integration/award-mode-routing.test.ts`
|
||||||
|
7. `tests/integration/live-to-final-lock.test.ts`
|
||||||
|
|
||||||
|
## 6) Audit Completeness Checks
|
||||||
|
Add automated assertions that every critical operation emits both domain event and audit record:
|
||||||
|
- eligibility override
|
||||||
|
- assignment exception
|
||||||
|
- stage advancement override
|
||||||
|
- award winner override
|
||||||
|
- promotion event
|
||||||
|
- final confirmation finalize/lock
|
||||||
|
|
||||||
|
Failure to emit required audit evidence should fail tests.
|
||||||
|
|
||||||
|
## 7) Observability Blueprint
|
||||||
|
|
||||||
|
## 7.1 Metrics
|
||||||
|
|
||||||
|
### Competition Lifecycle Metrics
|
||||||
|
- `projects_submitted_total` by category and stage
|
||||||
|
- `eligibility_pass_rate`
|
||||||
|
- `manual_review_rate`
|
||||||
|
- `assignment_coverage_percent`
|
||||||
|
- `unassigned_projects_count`
|
||||||
|
- `over_cap_exception_count`
|
||||||
|
- `submission_late_rate`
|
||||||
|
- `mentor_promotion_count`
|
||||||
|
- `live_vote_completion_rate`
|
||||||
|
- `final_confirmation_duration_seconds`
|
||||||
|
|
||||||
|
### Reliability Metrics
|
||||||
|
- API latency by router
|
||||||
|
- background job success/failure rates (filtering/assignment/award jobs)
|
||||||
|
- reminder send success/failure
|
||||||
|
- invitation send success/failure
|
||||||
|
|
||||||
|
## 7.2 Logs And Tracing
|
||||||
|
- Correlation id per operation across router -> service -> DB writes
|
||||||
|
- Structured logs include `programId`, `stageId`, `stagePurposeKey`, `juryId`, `projectId`, `userId`
|
||||||
|
|
||||||
|
## 7.3 Alerts
|
||||||
|
- assignment run leaves residual without queue record
|
||||||
|
- final confirmation attempted without satisfied quorum requirement
|
||||||
|
- result lock write failure
|
||||||
|
- result unlock attempted by non-super-admin
|
||||||
|
- invite acceptance failures spike
|
||||||
|
- live voting session state mismatch (open with no active project)
|
||||||
|
|
||||||
|
## 8) Release Gates (Must Pass)
|
||||||
|
|
||||||
|
## Gate A: Schema + Backfill Readiness
|
||||||
|
- all migrations idempotent in staging
|
||||||
|
- no unresolved backfill anomalies
|
||||||
|
|
||||||
|
## Gate B: Policy Engine Correctness
|
||||||
|
- hard/soft cap test pass
|
||||||
|
- ratio policy test pass
|
||||||
|
- access resolver test pass
|
||||||
|
|
||||||
|
## Gate C: End-To-End Flow Simulation
|
||||||
|
- full Monaco dry run with seeded data and mixed categories
|
||||||
|
- award mode A/B dry runs
|
||||||
|
|
||||||
|
## Gate D: Invite/Onboarding Stability
|
||||||
|
- admin invite + team invite + acceptance flows pass under load
|
||||||
|
|
||||||
|
## Gate E: Finals And Confirmation Integrity
|
||||||
|
- live voting and final confirmation produce correct lock artifacts
|
||||||
|
- super-admin unlock/relock path preserves complete audit integrity
|
||||||
|
|
||||||
|
## Gate F: Operational Readiness
|
||||||
|
- runbooks approved
|
||||||
|
- dashboards and alerts active
|
||||||
|
- support playbooks distributed
|
||||||
|
|
||||||
|
## 9) Performance And Capacity Scenarios
|
||||||
|
- Large intake window close with bulk late submissions
|
||||||
|
- Filtering and assignment jobs across high project volume
|
||||||
|
- Live finals peak voting concurrency (jury + audience)
|
||||||
|
- Bulk invitation campaigns with email retries
|
||||||
|
|
||||||
|
## 10) Sign-Off Criteria
|
||||||
|
Release is approved only when:
|
||||||
|
1. All phase gates pass.
|
||||||
|
2. No Sev-1/Sev-2 defects remain open in critical competition flows.
|
||||||
|
3. Audit completeness checks pass for all critical operations.
|
||||||
|
4. Operators can execute runbook drills successfully.
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
# 08. Open Questions For Flow Owners
|
||||||
|
|
||||||
|
## 1) Resolved Decisions (Captured)
|
||||||
|
As of Sunday, February 15, 2026, the following decisions are confirmed:
|
||||||
|
|
||||||
|
1. Final confirmation scope:
|
||||||
|
- Unanimity applies by default to category winners and jury-based award winners.
|
||||||
|
- Admin override must be possible in both cases with full audit reasoning.
|
||||||
|
- Some special awards can use a single-judge decision mode after eligibility filtering.
|
||||||
|
|
||||||
|
2. Soft-cap buffer:
|
||||||
|
- Default soft-cap buffer is `+10`.
|
||||||
|
|
||||||
|
3. Startup/concept ratio behavior:
|
||||||
|
- Ratio is a slight bias/suggestion only.
|
||||||
|
- It must not be fully deterministic.
|
||||||
|
- Onboarding must explicitly disclose this to judges.
|
||||||
|
|
||||||
|
4. Mode A pull-out behavior:
|
||||||
|
- Pull-out from main pool is admin-confirmed (not automatic).
|
||||||
|
|
||||||
|
5. Mentoring promotion authority:
|
||||||
|
- Promotion to official submission is allowed for team lead + admin.
|
||||||
|
|
||||||
|
6. Admin member invite pre-assignment:
|
||||||
|
- Both modes should be supported when possible:
|
||||||
|
- assignment intent mode
|
||||||
|
- direct policy-validated assignment mode
|
||||||
|
|
||||||
|
7. Jury absence during final confirmation:
|
||||||
|
- Quorum fallback is allowed.
|
||||||
|
- If a judge is absent, admins can either:
|
||||||
|
- replace the judge, or
|
||||||
|
- mark the judge as absent so they do not count toward required confirmation votes.
|
||||||
|
|
||||||
|
8. Result unlock policy:
|
||||||
|
- Unlock after finalization is allowed only for super admins.
|
||||||
|
- Unlock must require explicit reason/audit metadata.
|
||||||
|
|
||||||
|
9. Jury identity naming:
|
||||||
|
- Main and award juries are custom-labeled.
|
||||||
|
- Runtime behavior binds to jury purpose/stage contracts, not fixed `Jury1/Jury2/Jury3` labels.
|
||||||
|
|
||||||
|
## 2) Remaining P0 Questions (Need Before Schema Freeze)
|
||||||
|
None as of Sunday, February 15, 2026.
|
||||||
|
|
||||||
|
## 3) Remaining P1 Questions (Needed Before UI/Router Refactor)
|
||||||
|
|
||||||
|
1. Judge self-service onboarding controls:
|
||||||
|
- Are judges allowed to edit their own cap/bias preferences during onboarding for both main semi-final and finalist juries, or admin-only?
|
||||||
|
|
||||||
|
2. Mentoring stage applicability:
|
||||||
|
- Is mentoring strictly finalists-only by default, or configurable to include semi-finalists?
|
||||||
|
|
||||||
|
3. Document mutability after lock:
|
||||||
|
- Can admins replace locked submissions post-deadline, and should applicants see replacement history?
|
||||||
|
|
||||||
|
4. Audience reveal timing:
|
||||||
|
- Should audience totals be visible to Jury 3 in real time or only at deliberation start?
|
||||||
|
|
||||||
|
## 4) Remaining P2 Questions
|
||||||
|
|
||||||
|
1. Reporting visibility:
|
||||||
|
- Should per-jury cap/bias compliance be visible to all admins or only super admins?
|
||||||
|
|
||||||
|
2. Override transparency:
|
||||||
|
- Which override details are visible to jury users versus admin-only?
|
||||||
|
|
||||||
|
3. Applicant communication policy:
|
||||||
|
- Which exact transitions trigger applicant notifications?
|
||||||
|
|
||||||
|
4. Tie-break policy ordering:
|
||||||
|
- Confirm strict precedence when ties remain after configured scoring.
|
||||||
|
|
||||||
|
## 5) Invite/Onboarding Follow-Up Questions
|
||||||
|
|
||||||
|
1. Invitation expiry policy:
|
||||||
|
- Keep role-specific invite expiry differences (7-day admin invite vs 30-day team invite) or unify?
|
||||||
|
|
||||||
|
2. Multi-role default landing:
|
||||||
|
- If a user is both jury and applicant team member, what is default post-login landing and should role-switch be mandatory?
|
||||||
|
|
||||||
|
3. CSV template scope:
|
||||||
|
- Should CSV bulk invite support explicit jury binding + stage + pre-assignment mode columns?
|
||||||
|
|
||||||
|
## 6) Suggested Defaults For Remaining Items
|
||||||
|
1. Judges can edit cap/bias in onboarding only when admin enables this per jury.
|
||||||
|
2. Mentoring defaults to finalists-only, with optional semi-finalist extension.
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
# 09. Appendix: System Inventory (As Reviewed)
|
||||||
|
|
||||||
|
## 1) Data Model Inventory (Selected Competition-Critical Models)
|
||||||
|
|
||||||
|
### Pipeline/Stage Engine
|
||||||
|
- `Pipeline`
|
||||||
|
- `Track`
|
||||||
|
- `Stage`
|
||||||
|
- `StageTransition`
|
||||||
|
- `ProjectStageState`
|
||||||
|
- `Cohort`
|
||||||
|
- `CohortProject`
|
||||||
|
- `LiveProgressCursor`
|
||||||
|
- `OverrideAction`
|
||||||
|
- `DecisionAuditLog`
|
||||||
|
|
||||||
|
### Assignment/Evaluation
|
||||||
|
- `Assignment`
|
||||||
|
- `AssignmentJob`
|
||||||
|
- `Evaluation`
|
||||||
|
- `EvaluationForm`
|
||||||
|
- `EvaluationSummary`
|
||||||
|
- `EvaluationDiscussion`
|
||||||
|
- `DiscussionComment`
|
||||||
|
- `ConflictOfInterest`
|
||||||
|
- `GracePeriod`
|
||||||
|
- `ReminderLog`
|
||||||
|
|
||||||
|
### Filtering
|
||||||
|
- `FilteringRule`
|
||||||
|
- `FilteringResult`
|
||||||
|
- `FilteringJob`
|
||||||
|
|
||||||
|
### Awards
|
||||||
|
- `SpecialAward`
|
||||||
|
- `AwardEligibility`
|
||||||
|
- `AwardJuror`
|
||||||
|
- `AwardVote`
|
||||||
|
|
||||||
|
### Mentorship
|
||||||
|
- `MentorAssignment`
|
||||||
|
- `MentorMessage`
|
||||||
|
- `MentorNote`
|
||||||
|
- `MentorMilestone`
|
||||||
|
- `MentorMilestoneCompletion`
|
||||||
|
|
||||||
|
### Documents/Submission
|
||||||
|
- `FileRequirement`
|
||||||
|
- `ProjectFile`
|
||||||
|
|
||||||
|
### Live Voting
|
||||||
|
- `LiveVotingSession`
|
||||||
|
- `LiveVote`
|
||||||
|
- `AudienceVoter`
|
||||||
|
|
||||||
|
### Identity/Team
|
||||||
|
- `User`
|
||||||
|
- `TeamMember`
|
||||||
|
|
||||||
|
## 2) Service Inventory (Competition-Critical)
|
||||||
|
- `src/server/services/stage-engine.ts`
|
||||||
|
- `src/server/services/stage-filtering.ts`
|
||||||
|
- `src/server/services/stage-assignment.ts`
|
||||||
|
- `src/server/services/live-control.ts`
|
||||||
|
- `src/server/services/stage-notifications.ts`
|
||||||
|
- `src/server/services/evaluation-reminders.ts`
|
||||||
|
- `src/server/services/smart-assignment.ts`
|
||||||
|
- `src/server/services/ai-assignment.ts`
|
||||||
|
- `src/server/services/ai-filtering.ts`
|
||||||
|
- `src/server/services/award-eligibility-job.ts`
|
||||||
|
- `src/server/services/mentor-matching.ts`
|
||||||
|
|
||||||
|
## 3) tRPC Router Inventory
|
||||||
|
- `program`
|
||||||
|
- `project`
|
||||||
|
- `user`
|
||||||
|
- `assignment`
|
||||||
|
- `evaluation`
|
||||||
|
- `file`
|
||||||
|
- `export`
|
||||||
|
- `audit`
|
||||||
|
- `settings`
|
||||||
|
- `gracePeriod`
|
||||||
|
- `learningResource`
|
||||||
|
- `partner`
|
||||||
|
- `notionImport`
|
||||||
|
- `typeformImport`
|
||||||
|
- `tag`
|
||||||
|
- `applicant`
|
||||||
|
- `liveVoting`
|
||||||
|
- `analytics`
|
||||||
|
- `avatar`
|
||||||
|
- `logo`
|
||||||
|
- `application`
|
||||||
|
- `mentor`
|
||||||
|
- `filtering`
|
||||||
|
- `specialAward`
|
||||||
|
- `notification`
|
||||||
|
- `message`
|
||||||
|
- `webhook`
|
||||||
|
- `projectPool`
|
||||||
|
- `wizardTemplate`
|
||||||
|
- `dashboard`
|
||||||
|
- `pipeline`
|
||||||
|
- `stage`
|
||||||
|
- `stageFiltering`
|
||||||
|
- `stageAssignment`
|
||||||
|
- `cohort`
|
||||||
|
- `live`
|
||||||
|
- `decision`
|
||||||
|
- `award`
|
||||||
|
|
||||||
|
## 4) Page Inventory (High-Level by Role Group)
|
||||||
|
|
||||||
|
### Admin
|
||||||
|
- members, invite members, programs, apply settings, pipeline wizard/advanced/editor, projects, mentors, awards, reports, settings, audit, messaging, partner, learning
|
||||||
|
|
||||||
|
### Applicant
|
||||||
|
- dashboard, team, pipeline views, stage docs/status views, mentoring, document center
|
||||||
|
|
||||||
|
### Jury
|
||||||
|
- stage list, assignment list, project evaluate pages, compare page, live page, awards, learning
|
||||||
|
|
||||||
|
### Mentor
|
||||||
|
- dashboard, projects, project detail, resources
|
||||||
|
|
||||||
|
### Auth/Public
|
||||||
|
- apply, edition apply, accept invite, login, onboarding, set password, public vote/live score, submission pages
|
||||||
|
|
||||||
|
## 5) Existing Test Inventory
|
||||||
|
|
||||||
|
### Unit
|
||||||
|
- `tests/unit/stage-engine.test.ts`
|
||||||
|
- `tests/unit/stage-assignment.test.ts`
|
||||||
|
- `tests/unit/stage-filtering.test.ts`
|
||||||
|
- `tests/unit/live-control.test.ts`
|
||||||
|
- `tests/unit/override-validation.test.ts`
|
||||||
|
- `tests/unit/award-governance.test.ts`
|
||||||
|
|
||||||
|
### Integration
|
||||||
|
- `tests/integration/pipeline-crud.test.ts`
|
||||||
|
- `tests/integration/stage-config.test.ts`
|
||||||
|
- `tests/integration/assignment-preview.test.ts`
|
||||||
|
- `tests/integration/cohort-voting.test.ts`
|
||||||
|
- `tests/integration/live-runtime.test.ts`
|
||||||
|
- `tests/integration/decision-audit.test.ts`
|
||||||
|
|
||||||
|
## 6) Legacy/Transition Observations
|
||||||
|
1. `roundId` fields still exist as legacy optional references in multiple models.
|
||||||
|
2. Stage/pipeline is active architecture but legacy wording remains in some routers/comments/UI labels.
|
||||||
|
3. Some critical business rules still rely on JSON keys and implicit route behavior.
|
||||||
|
|
||||||
|
This inventory is the baseline used for the redesign and migration plans in this folder.
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
# 10. Monaco Reference Configuration (Example)
|
||||||
|
|
||||||
|
This is an example of how a Monaco 2026 competition would be configured in the redesigned system.
|
||||||
|
|
||||||
|
## 1) Program Profile
|
||||||
|
- Program: Monaco Ocean Protection Challenge 2026
|
||||||
|
- Categories: `STARTUP`, `BUSINESS_CONCEPT`
|
||||||
|
- Main pipeline: `mopc-2026-main`
|
||||||
|
- Award tracks: configurable per award
|
||||||
|
|
||||||
|
## 2) Stage Purpose Layout (Main Track)
|
||||||
|
|
||||||
|
1. `submission_r1_intake`
|
||||||
|
- type: `INTAKE`
|
||||||
|
- open: 2026-02-01
|
||||||
|
- close: 2026-05-31
|
||||||
|
- late policy: `flag`
|
||||||
|
|
||||||
|
2. `eligibility_filter`
|
||||||
|
- type: `FILTER`
|
||||||
|
- trigger: admin or auto-on-close
|
||||||
|
|
||||||
|
3. `jury1_evaluation`
|
||||||
|
- type: `EVALUATION`
|
||||||
|
- open: 2026-06-05
|
||||||
|
- close: 2026-06-25
|
||||||
|
- jury binding: `MAIN_SEMIFINAL`
|
||||||
|
|
||||||
|
4. `submission_r2_intake`
|
||||||
|
- type: `INTAKE`
|
||||||
|
- open: 2026-06-28
|
||||||
|
- close: 2026-07-20
|
||||||
|
|
||||||
|
5. `jury2_evaluation`
|
||||||
|
- type: `EVALUATION`
|
||||||
|
- open: 2026-07-24
|
||||||
|
- close: 2026-08-10
|
||||||
|
- jury binding: `MAIN_FINALIST`
|
||||||
|
|
||||||
|
6. `jury3_live_finals`
|
||||||
|
- type: `LIVE_FINAL`
|
||||||
|
- event date window: 2026-09-01
|
||||||
|
- jury binding: `MAIN_LIVE_FINALS`
|
||||||
|
|
||||||
|
7. `final_confirmation`
|
||||||
|
- type: `SELECTION`
|
||||||
|
- rule: unanimous active quorum + one admin approval
|
||||||
|
- absent juror handling: replace juror or mark as excused (admin-audited)
|
||||||
|
- admin override: enabled for both category and award scopes (mandatory reason/audit)
|
||||||
|
- unlock policy: super-admin only
|
||||||
|
|
||||||
|
8. `results_publication`
|
||||||
|
- type: `RESULTS`
|
||||||
|
- publication mode: manual
|
||||||
|
|
||||||
|
## 3) Jury Definitions
|
||||||
|
|
||||||
|
- `MAIN_SEMIFINAL` (display label example: `Jury 1 - Technical Panel`)
|
||||||
|
- Purpose: semi-final selection
|
||||||
|
- members: list of judges
|
||||||
|
- `MAIN_FINALIST` (display label example: `Jury 2 - Selection Panel`)
|
||||||
|
- Purpose: finalist selection
|
||||||
|
- members: list of judges (overlap allowed)
|
||||||
|
- `MAIN_LIVE_FINALS` (display label example: `Jury 3 - Live Finals Panel`)
|
||||||
|
- Purpose: live final voting + deliberation
|
||||||
|
- members: list of judges (overlap allowed)
|
||||||
|
|
||||||
|
## 4) Assignment Policy Example
|
||||||
|
|
||||||
|
### Jury1 policy
|
||||||
|
- required reviews: 3
|
||||||
|
- default cap mode: `SOFT`
|
||||||
|
- default cap: 25
|
||||||
|
- buffer: 10
|
||||||
|
- startup/concept bias target per judge: 50/50 (suggestive, not deterministic)
|
||||||
|
|
||||||
|
### Per-judge override example
|
||||||
|
- judge A: hard cap 18
|
||||||
|
- judge B: soft cap 22 buffer 8
|
||||||
|
- judge C: startup-biased preference (100/0 suggestion, not hard rule)
|
||||||
|
|
||||||
|
## 5) Submission Round Requirements
|
||||||
|
|
||||||
|
### Round 1 (`submission_r1_intake`)
|
||||||
|
Slots:
|
||||||
|
- `r1_exec_summary` (required)
|
||||||
|
- `r1_pitch_deck` (required)
|
||||||
|
- `r1_video_pitch` (optional)
|
||||||
|
|
||||||
|
### Round 2 (`submission_r2_intake`)
|
||||||
|
Slots:
|
||||||
|
- `r2_business_plan` (required)
|
||||||
|
- `r2_financials` (required)
|
||||||
|
- `r2_impact_metrics` (optional)
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
- applicants edit current round only
|
||||||
|
- previous round read-only
|
||||||
|
- admin can replace with provenance
|
||||||
|
|
||||||
|
## 6) Award Mode Examples
|
||||||
|
|
||||||
|
### Example A: Separate Pool, Pull-Out
|
||||||
|
- award: “Blue Innovation Prize”
|
||||||
|
- participationMode: `SEPARATE_POOL`
|
||||||
|
- routingBehavior: `PULL_FROM_MAIN`
|
||||||
|
- pull-out confirmation: admin-confirmed before removal from main pool
|
||||||
|
|
||||||
|
### Example B: Dual Track
|
||||||
|
- award: “Community Impact Award”
|
||||||
|
- participationMode: `DUAL_TRACK`
|
||||||
|
- routingBehavior: `KEEP_IN_MAIN`
|
||||||
|
|
||||||
|
### Example C: Single-Judge Award
|
||||||
|
- award: “Chair’s Special Recognition”
|
||||||
|
- winnerDecisionMode: `SINGLE_JUDGE_DECIDES`
|
||||||
|
- singleJudgeUserId: configured award judge
|
||||||
|
- candidate set: only projects remaining eligible after award filtering
|
||||||
|
|
||||||
|
## 7) Mentoring Layer Example
|
||||||
|
|
||||||
|
Eligibility:
|
||||||
|
- finalist projects with `wantsMentorship=true`
|
||||||
|
|
||||||
|
Permissions:
|
||||||
|
- mentor and team can upload/comment private files
|
||||||
|
- team lead + admin can promote file to official submission
|
||||||
|
|
||||||
|
Promotion artifact:
|
||||||
|
- records source file, target slot, actor, timestamp, resulting official file id
|
||||||
|
|
||||||
|
## 8) Live Finals Example
|
||||||
|
|
||||||
|
- voting mode: criteria
|
||||||
|
- audience voting: enabled for end-of-category only
|
||||||
|
- audience totals visible to Jury3 during deliberation: true
|
||||||
|
- tie-break order:
|
||||||
|
1. highest Jury3 weighted score
|
||||||
|
2. admin-declared tie-break (if still tied)
|
||||||
|
|
||||||
|
## 9) Final Confirmation Example
|
||||||
|
|
||||||
|
- decision rule: unanimous (categories + jury-based awards)
|
||||||
|
- active quorum must submit confirmation vote (absent juror can be replaced or marked excused)
|
||||||
|
- admin approval required after quorum unanimity
|
||||||
|
- admin override allowed for category or award outcomes with mandatory reason code and audit snapshot
|
||||||
|
- result lock snapshot generated immediately on finalize
|
||||||
|
- unlock available only to super admins with mandatory reason + unlock event audit
|
||||||
|
|
||||||
|
## 10) Invite Flow Example
|
||||||
|
|
||||||
|
### Admin invite (jury)
|
||||||
|
- create user with role `JURY_MEMBER`
|
||||||
|
- attach `JuryMembership: MAIN_FINALIST` (custom display label in UI)
|
||||||
|
- create optional `AssignmentIntent` for stage `jury2_evaluation` and/or direct policy-validated assignments
|
||||||
|
- send invitation
|
||||||
|
- on accept: jury onboarding -> cap/bias preference override if allowed (disclosure shown that bias is suggestive only)
|
||||||
|
|
||||||
|
### Team invite
|
||||||
|
- create/attach `TeamMember`
|
||||||
|
- send invitation
|
||||||
|
- on accept: applicant context landing
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Monaco OPC Architecture Redesign (Codex Plan)
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
This documentation set is a comprehensive redesign plan to align the current MOPC platform with the exact Monaco Ocean Protection Challenge flow you defined:
|
||||||
|
|
||||||
|
- Startups + Concepts in one competition lifecycle
|
||||||
|
- AI eligibility filtering
|
||||||
|
- Purpose-bound main juries as explicit entities (custom-labeled per program; often Jury 1 / Jury 2 / Jury 3)
|
||||||
|
- Round-specific submission bundles
|
||||||
|
- Special award routing modes
|
||||||
|
- Mentoring collaboration layer with promotion to official submissions
|
||||||
|
- Live finals control + final confirmation/lock workflow
|
||||||
|
- Full admin override and auditability
|
||||||
|
- End-to-end integration across all platform functions (including member invitation/onboarding/team flows)
|
||||||
|
|
||||||
|
The plan is intentionally designed to reduce current ambiguity and complexity while maximizing customizability through typed policies and explicit workflow entities.
|
||||||
|
|
||||||
|
## Scope Of This Plan
|
||||||
|
This plan covers:
|
||||||
|
|
||||||
|
1. Current-state architecture audit (schema, services, routers, UI surfaces)
|
||||||
|
2. Target-state architecture mapped 1:1 to Monaco flow
|
||||||
|
3. Gap analysis and simplification decisions
|
||||||
|
4. Typed domain model and policy contracts
|
||||||
|
5. Platform-wide integration matrix (all major functions/pages)
|
||||||
|
6. Implementation roadmap and migration/cutover strategy
|
||||||
|
7. QA, observability, release gates
|
||||||
|
8. Open decisions/questions for flow owners
|
||||||
|
|
||||||
|
This plan does **not** implement code changes. It is an implementation-ready architecture and delivery blueprint.
|
||||||
|
|
||||||
|
## Guiding Principles
|
||||||
|
1. Keep the stage engine; remove ambiguity around stage purpose.
|
||||||
|
2. Move critical competition behavior from loosely-typed `configJson` into typed models/policies.
|
||||||
|
3. Preserve admin override authority everywhere, but formalize override intent and audit structure.
|
||||||
|
4. Make jury/award/mentoring/finals flows first-class entities instead of inferred behavior.
|
||||||
|
5. Keep UX simple by exposing only context-relevant controls at each stage.
|
||||||
|
6. Ensure every user-facing workflow is backed by the redesigned architecture contract.
|
||||||
|
|
||||||
|
## Document Index
|
||||||
|
- `01-current-platform-architecture-audit.md`
|
||||||
|
- `02-monaco-flow-target-architecture.md`
|
||||||
|
- `03-gap-analysis-and-simplification-decisions.md`
|
||||||
|
- `04-unified-domain-model-and-config-contracts.md`
|
||||||
|
- `05-platform-wide-integration-matrix.md`
|
||||||
|
- `06-implementation-roadmap-and-migration-plan.md`
|
||||||
|
- `07-testing-observability-and-release-gates.md`
|
||||||
|
- `08-open-questions-for-flow-owners.md`
|
||||||
|
|
||||||
|
## Recommended Reading Order
|
||||||
|
1. Read `02-monaco-flow-target-architecture.md` for the desired end state.
|
||||||
|
2. Read `03-gap-analysis-and-simplification-decisions.md` to understand what must change and why.
|
||||||
|
3. Read `04-unified-domain-model-and-config-contracts.md` for schema/API/behavior contracts.
|
||||||
|
4. Read `05-platform-wide-integration-matrix.md` to see exact surface-level impact.
|
||||||
|
5. Read `06-implementation-roadmap-and-migration-plan.md` + `07-testing-observability-and-release-gates.md` for execution.
|
||||||
|
6. Resolve items in `08-open-questions-for-flow-owners.md` before Phase 1 starts.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
The redesign is successful when:
|
||||||
|
|
||||||
|
- The platform can run your full Monaco flow without workarounds.
|
||||||
|
- Admins can configure and override all required decisions transparently.
|
||||||
|
- Judges, mentors, applicants, and audience each see role-appropriate, stage-aware UX.
|
||||||
|
- Invite/onboarding/team management, assignment, judging, mentoring, and finals workflows all reference the same underlying competition model.
|
||||||
|
- Audit evidence can reconstruct every advance/reject/override/winner decision.
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
# Mixed Round Design Implementation Docs
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
This folder contains a single consolidated redesign program that intentionally blends:
|
|
||||||
|
|
||||||
- Delivery rigor and governance discipline from `codex-round-system-redesign`
|
|
||||||
- Target architecture depth and runtime detail from `claude-round-system-redesign`
|
|
||||||
- Award governance semantics from `glm-5-round-redesign` (especially `AWARD_MASTER` and explicit decision modes)
|
|
||||||
|
|
||||||
The goal is a complete, production-ready implementation plan for rebuilding round orchestration in MOPC with a full-cutover model.
|
|
||||||
|
|
||||||
## Foundation and Blend Strategy
|
|
||||||
|
|
||||||
### Foundation
|
|
||||||
The execution backbone is the `codex` style program model:
|
|
||||||
|
|
||||||
1. Contract freeze first
|
|
||||||
2. Schema/runtime implementation in explicit phases
|
|
||||||
3. Platform-wide dependency refit (not just feature slices)
|
|
||||||
4. Mandatory phase gates with hard release blockers
|
|
||||||
|
|
||||||
### Borrowed Enhancements
|
|
||||||
The plan imports high-value details from other proposals:
|
|
||||||
|
|
||||||
- `claude`: richer canonical model (`Pipeline -> Track -> Stage`), explicit transition engine, routing and live-control runtime detail
|
|
||||||
- `glm-5`: award decision governance (`JURY_VOTE`, `AWARD_MASTER`, `ADMIN`) and explicit award track behavior options
|
|
||||||
|
|
||||||
## Execution Model
|
|
||||||
|
|
||||||
- Single destructive cutover
|
|
||||||
- Full reseed
|
|
||||||
- No backward-compatibility adapter layer
|
|
||||||
- No dual-write period
|
|
||||||
- One atomic release commit once all gates are green
|
|
||||||
|
|
||||||
This model is intentionally selected because infrastructure reset/rebuild is allowed and preferred for architecture quality.
|
|
||||||
|
|
||||||
## Architecture Summary
|
|
||||||
|
|
||||||
- Competition lifecycle is stage-native, not round-pointer native.
|
|
||||||
- Projects progress through explicit `ProjectStageState` records.
|
|
||||||
- Special awards are first-class tracks, not bolt-on side tables.
|
|
||||||
- Routing is rule-driven with explainability payloads.
|
|
||||||
- Live finals are controlled by an admin cursor as the source of truth.
|
|
||||||
- Every override and decision is reasoned, immutable, and auditable.
|
|
||||||
|
|
||||||
## Folder Layout
|
|
||||||
|
|
||||||
- `master-implementation-plan.md`: end-to-end execution map
|
|
||||||
- `shared/`: cross-phase contracts, governance, test model, risks
|
|
||||||
- `phase-00-contract-freeze/` to `phase-07-validation-release/`: implementation phases
|
|
||||||
- `flowcharts/`: core control and routing diagrams
|
|
||||||
|
|
||||||
## How to Use This Plan
|
|
||||||
|
|
||||||
1. Start at `master-implementation-plan.md`.
|
|
||||||
2. Execute phases in order.
|
|
||||||
3. Do not start a phase unless all prior acceptance gates are complete.
|
|
||||||
4. Attach objective evidence for every gate.
|
|
||||||
5. Treat `phase-06-platform-dependency-refit` as mandatory release work, not cleanup.
|
|
||||||
|
|
||||||
## Non-Negotiable Rules
|
|
||||||
|
|
||||||
1. No hidden edit-only required settings.
|
|
||||||
2. Deterministic routing and ranking tie-break behavior.
|
|
||||||
3. Assignment coverage guarantees for eligible projects.
|
|
||||||
4. Explicit voting window control (schedules are advisory only).
|
|
||||||
5. No legacy orchestration contract references at release.
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# Dependency Refit Map
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
A[Schema Contracts] --> B[Router Refit]
|
|
||||||
A --> C[Service Refit]
|
|
||||||
B --> D[Admin UI Refit]
|
|
||||||
B --> E[Jury/Applicant/Public Refit]
|
|
||||||
C --> E
|
|
||||||
D --> F[Reporting/Exports]
|
|
||||||
E --> F
|
|
||||||
F --> G[Integration Consumer Validation]
|
|
||||||
G --> H[Legacy Symbol Sweep]
|
|
||||||
H --> I[Release Ready]
|
|
||||||
```
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# End-to-End Pipeline Flow
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A[Intake Stage] --> B[Filter Stage]
|
|
||||||
B -->|pass| C[Main Evaluation Stage]
|
|
||||||
B -->|reject| R[Rejected with Notification]
|
|
||||||
B -->|award rule: parallel| W1[Award Track Entry]
|
|
||||||
B -->|award rule: exclusive| W2[Award Track Entry + Main Routed Out]
|
|
||||||
|
|
||||||
C --> D[Selection Stage]
|
|
||||||
D --> E[Live Final Stage]
|
|
||||||
E --> F[Results Stage]
|
|
||||||
|
|
||||||
W1 --> W3[Award Evaluation]
|
|
||||||
W2 --> W3[Award Evaluation]
|
|
||||||
W3 --> W4[Award Winner Decision]
|
|
||||||
|
|
||||||
D -->|manual override| O[Override Action + Audit]
|
|
||||||
O --> D
|
|
||||||
|
|
||||||
E --> L[Live Cursor + Cohort Windows]
|
|
||||||
L --> V[Jury and Audience Voting]
|
|
||||||
V --> F
|
|
||||||
```
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# Live Stage Controller
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A[Admin Live Panel] --> B[Set Active Project Cursor]
|
|
||||||
B --> C[Persist Cursor Versioned Update]
|
|
||||||
C --> D[Broadcast Realtime Event]
|
|
||||||
D --> E[Jury Clients Sync]
|
|
||||||
D --> F[Audience Clients Sync]
|
|
||||||
|
|
||||||
A --> G[Open Cohort Window]
|
|
||||||
A --> H[Close Cohort Window]
|
|
||||||
G --> I[Vote Acceptance On]
|
|
||||||
H --> J[Vote Acceptance Off]
|
|
||||||
```
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# Main vs Award Routing
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
P[Project in Filter Stage] --> Q{Routing Rule Match?}
|
|
||||||
Q -->|No| M[Remain in Main Track]
|
|
||||||
Q -->|Yes| Z{Routing Mode}
|
|
||||||
|
|
||||||
Z -->|PARALLEL| A[Create Award Stage State]
|
|
||||||
A --> B[Keep Main State Active]
|
|
||||||
|
|
||||||
Z -->|EXCLUSIVE| C[Create Award Stage State]
|
|
||||||
C --> D[Mark Main State Routed]
|
|
||||||
|
|
||||||
Z -->|POST_MAIN| E[Defer Route Until Gate Stage]
|
|
||||||
E --> F[Route After Main Gate Condition]
|
|
||||||
```
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# Override Audit Flow
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A[Override Request] --> B{Authz + Scope Check}
|
|
||||||
B -->|fail| X[FORBIDDEN]
|
|
||||||
B -->|pass| C{Reason Fields Present?}
|
|
||||||
C -->|no| Y[BAD_REQUEST]
|
|
||||||
C -->|yes| D[Fetch Current Value Snapshot]
|
|
||||||
D --> E[Apply Override Mutation]
|
|
||||||
E --> F[Persist Immutable OverrideAction]
|
|
||||||
F --> G[Append DecisionAuditLog]
|
|
||||||
G --> H[Return Updated Entity + Audit Ref]
|
|
||||||
```
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
# Master Implementation Plan
|
|
||||||
|
|
||||||
## Program Objective
|
|
||||||
Rebuild MOPC round orchestration from a round-centric model into a stage-native pipeline model that is easier to configure, more deterministic, and robust for main competition plus special awards and live finals.
|
|
||||||
|
|
||||||
## Program Constraints
|
|
||||||
|
|
||||||
- Preserve existing visual language and core UI component style.
|
|
||||||
- Complete architecture rebuild is allowed and encouraged.
|
|
||||||
- Delivery must be production-safe and verifiable.
|
|
||||||
- Release requires one atomic cutover commit after full validation.
|
|
||||||
|
|
||||||
## Hard Invariants
|
|
||||||
|
|
||||||
1. Every state transition is explicit, validated, and auditable.
|
|
||||||
2. Every override action captures `reasonCode` + `reasonText` + actor metadata.
|
|
||||||
3. No eligible project is left unassigned unless explicitly flagged as overflow with admin visibility.
|
|
||||||
4. Live active project state is admin-cursor driven.
|
|
||||||
5. Award routing behavior is explicit per award (`parallel`, `exclusive`, `post_main`).
|
|
||||||
6. Event contracts are deterministic and machine-readable.
|
|
||||||
7. At release, no runtime dependency on legacy `roundId` orchestration semantics remains.
|
|
||||||
|
|
||||||
## Phase Chain
|
|
||||||
|
|
||||||
1. Phase 00: Contract freeze
|
|
||||||
2. Phase 01: Schema and runtime foundation
|
|
||||||
3. Phase 02: Backend orchestration engine
|
|
||||||
4. Phase 03: Admin control-plane UX
|
|
||||||
5. Phase 04: Participant journeys
|
|
||||||
6. Phase 05: Special awards governance
|
|
||||||
7. Phase 06: Platform dependency refit
|
|
||||||
8. Phase 07: Validation and release
|
|
||||||
|
|
||||||
## Required Deliverables by Phase
|
|
||||||
|
|
||||||
- Phase 00: locked contracts, decision log, authz matrix, initial risk register
|
|
||||||
- Phase 01: canonical schema spec, migration/cutover scripts, reseed spec, integrity checks
|
|
||||||
- Phase 02: transition/routing/filtering/assignment/live runtime implementation specs
|
|
||||||
- Phase 03: wizard IA, advanced editor spec, form behavior and safety guardrails
|
|
||||||
- Phase 04: applicant/jury/audience runtime and UX contracts
|
|
||||||
- Phase 05: award governance modes and decision workflow implementation
|
|
||||||
- Phase 06: module-by-module refit completion + legacy symbol sweeps
|
|
||||||
- Phase 07: full test evidence, performance evidence, release runbook and sign-off
|
|
||||||
|
|
||||||
## Entry and Exit Criteria (Program Level)
|
|
||||||
|
|
||||||
### Entry
|
|
||||||
|
|
||||||
- Shared contracts and decisions are locked.
|
|
||||||
- Team alignment on cutover model and no-compatibility policy.
|
|
||||||
|
|
||||||
### Exit
|
|
||||||
|
|
||||||
- All phase acceptance gates complete.
|
|
||||||
- Test matrix green for U/I/E/P suites.
|
|
||||||
- Performance and resilience evidence approved.
|
|
||||||
- Legacy symbol sweeps are empty.
|
|
||||||
- Release evidence report signed by Engineering + Product + Operations.
|
|
||||||
|
|
||||||
## Release Blockers
|
|
||||||
|
|
||||||
1. Any failing acceptance gate.
|
|
||||||
2. Any unresolved CRITICAL or HIGH risk without approved mitigation.
|
|
||||||
3. Any missing test evidence for mandatory scenario IDs.
|
|
||||||
4. Any legacy orchestration symbol found in runtime code paths.
|
|
||||||
|
|
||||||
## Timeline Model
|
|
||||||
|
|
||||||
- Phase 00: 2-3 days
|
|
||||||
- Phase 01: 1-1.5 weeks
|
|
||||||
- Phase 02: 1.5-2.5 weeks
|
|
||||||
- Phase 03: 1-1.5 weeks
|
|
||||||
- Phase 04: 1-1.5 weeks
|
|
||||||
- Phase 05: 0.75-1.25 weeks
|
|
||||||
- Phase 06: 1-1.5 weeks
|
|
||||||
- Phase 07: 1 week
|
|
||||||
|
|
||||||
Total estimate: 8-11 weeks depending on test depth and refit complexity.
|
|
||||||
|
|
||||||
## Evidence Standards
|
|
||||||
|
|
||||||
Every acceptance gate requires at least one of:
|
|
||||||
|
|
||||||
- Unit/integration/E2E output
|
|
||||||
- API response captures
|
|
||||||
- deterministic symbol sweeps
|
|
||||||
- migration integrity query output
|
|
||||||
- performance benchmark output
|
|
||||||
- release runbook logs
|
|
||||||
|
|
||||||
## Enforcement Notes
|
|
||||||
|
|
||||||
- No phase skipping.
|
|
||||||
- No deferred blocker carry-forward.
|
|
||||||
- No "ship and patch later" for contract-level gaps.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Phase 00 Acceptance Gates
|
|
||||||
|
|
||||||
- [ ] G-00-1 Decision log locked (`shared/decision-log.md` signed by Eng + Product)
|
|
||||||
- [ ] G-00-2 Domain and API contracts approved
|
|
||||||
- [ ] G-00-3 Authz matrix approved
|
|
||||||
- [ ] G-00-4 Test matrix approved and mapped to owners
|
|
||||||
- [ ] G-00-5 Risk register initialized with owners and mitigation targets
|
|
||||||
|
|
||||||
## Required Evidence
|
|
||||||
|
|
||||||
- contract review notes
|
|
||||||
- sign-off comments or approval records
|
|
||||||
- updated risk register with owners
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# Phase 00 Overview: Contract Freeze
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Lock all cross-phase contracts before implementation so the program executes with stable boundaries and no semantic drift.
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
|
|
||||||
- decision locking
|
|
||||||
- API/type contract baseline
|
|
||||||
- authorization baseline
|
|
||||||
- gate and evidence baseline
|
|
||||||
- initial risk baseline
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- schema implementation
|
|
||||||
- runtime implementation
|
|
||||||
- UI implementation
|
|
||||||
|
|
||||||
## Inputs
|
|
||||||
|
|
||||||
- `shared/program-charter.md`
|
|
||||||
- `shared/decision-log.md`
|
|
||||||
- `shared/domain-model.md`
|
|
||||||
- `shared/api-contracts.md`
|
|
||||||
- `shared/authz-matrix.md`
|
|
||||||
- `shared/test-matrix.md`
|
|
||||||
|
|
||||||
## Exit Criteria
|
|
||||||
|
|
||||||
1. Decision log marked locked with no unresolved critical decision.
|
|
||||||
2. API/type contracts accepted by backend and frontend owners.
|
|
||||||
3. Authz matrix accepted by security owner.
|
|
||||||
4. Risk register initialized with owners.
|
|
||||||
5. Phase 00 acceptance gates complete with evidence.
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# Phase 00 Tasks
|
|
||||||
|
|
||||||
## Task Set A: Contract Alignment
|
|
||||||
|
|
||||||
- [ ] Validate `shared/domain-model.md` against current repository constraints.
|
|
||||||
- [ ] Validate `shared/api-contracts.md` names and payload conventions with router ownership.
|
|
||||||
- [ ] Validate event naming strategy with notification and webhook owners.
|
|
||||||
|
|
||||||
## Task Set B: Governance Lock
|
|
||||||
|
|
||||||
- [ ] Confirm `shared/decision-log.md` with Product + Engineering.
|
|
||||||
- [ ] Confirm cutover/no-compatibility policy in writing.
|
|
||||||
- [ ] Confirm override governance requirements and mandatory reason fields.
|
|
||||||
|
|
||||||
## Task Set C: Access and Security
|
|
||||||
|
|
||||||
- [ ] Validate `shared/authz-matrix.md` for each role.
|
|
||||||
- [ ] Define scope enforcement standard for program-scoped admin actions.
|
|
||||||
- [ ] Confirm audience vote abuse controls (token, rate-limit, dedupe key).
|
|
||||||
|
|
||||||
## Task Set D: Validation Baseline
|
|
||||||
|
|
||||||
- [ ] Validate `shared/test-matrix.md` coverage and practicality.
|
|
||||||
- [ ] Map each test ID to ownership.
|
|
||||||
- [ ] Confirm CI entry strategy for U/I/E/P layers.
|
|
||||||
|
|
||||||
## Task Set E: Risk Baseline
|
|
||||||
|
|
||||||
- [ ] Review `shared/risk-register.md` with owners.
|
|
||||||
- [ ] Add any repository-specific risks identified during contract review.
|
|
||||||
- [ ] Mark mitigation action owner and due phase per risk.
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# Phase 01 Acceptance Gates
|
|
||||||
|
|
||||||
- [ ] G-01-1 `prisma generate` succeeds
|
|
||||||
- [ ] G-01-2 reset/reseed succeeds in local and staging
|
|
||||||
- [ ] G-01-3 integrity queries return expected zero-error results
|
|
||||||
- [ ] G-01-4 required indexes confirmed in DB metadata
|
|
||||||
- [ ] G-01-5 phase artifacts stored and linked
|
|
||||||
|
|
||||||
## Required Evidence
|
|
||||||
|
|
||||||
- migration command output
|
|
||||||
- reseed logs
|
|
||||||
- integrity query result captures
|
|
||||||
- schema diff summary
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
# Phase 01 Migration and Cutover Plan
|
|
||||||
|
|
||||||
## Strategy
|
|
||||||
Perform architecture rebuild with reset/reseed as the official path.
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
|
|
||||||
1. Finalize schema migration scripts.
|
|
||||||
2. Run local reset/reseed rehearsal.
|
|
||||||
3. Run staging reset/reseed rehearsal.
|
|
||||||
4. Execute integrity verification suite.
|
|
||||||
5. Lock schema contracts and produce baseline snapshot.
|
|
||||||
|
|
||||||
## Verification Script Requirements
|
|
||||||
|
|
||||||
- count checks for canonical entities
|
|
||||||
- FK integrity checks
|
|
||||||
- expected stage graph checks
|
|
||||||
- expected project intake state checks
|
|
||||||
|
|
||||||
## Example Verification Queries
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- orphan project stage states
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM "ProjectStageState" pss
|
|
||||||
LEFT JOIN "Project" p ON p.id = pss."projectId"
|
|
||||||
LEFT JOIN "Stage" s ON s.id = pss."stageId"
|
|
||||||
LEFT JOIN "Track" t ON t.id = pss."trackId"
|
|
||||||
WHERE p.id IS NULL OR s.id IS NULL OR t.id IS NULL;
|
|
||||||
|
|
||||||
-- project intake state coverage
|
|
||||||
SELECT COUNT(DISTINCT p.id) AS projects_without_intake
|
|
||||||
FROM "Project" p
|
|
||||||
LEFT JOIN "ProjectStageState" pss
|
|
||||||
ON pss."projectId" = p.id
|
|
||||||
LEFT JOIN "Stage" s
|
|
||||||
ON s.id = pss."stageId"
|
|
||||||
WHERE s."stageType" = 'INTAKE'
|
|
||||||
AND pss.id IS NULL;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cutover Readiness Artifacts Produced in Phase 01
|
|
||||||
|
|
||||||
- schema migration files
|
|
||||||
- seed scripts
|
|
||||||
- integrity query scripts
|
|
||||||
- reset/reseed execution logs
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
# Phase 01 Overview: Schema and Runtime Foundation
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Implement the canonical schema and reset/reseed capability that supports stage-native orchestration with award and live runtime primitives.
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
|
|
||||||
- prisma schema rebuild for canonical entities
|
|
||||||
- indexes and constraints for hot paths
|
|
||||||
- reset/reseed strategy and scripts
|
|
||||||
- data integrity verification scripts
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- end-user UI behavior
|
|
||||||
- full router refit
|
|
||||||
|
|
||||||
## Key Design Choice
|
|
||||||
|
|
||||||
This phase uses full reset/reseed and does not attempt compatibility bridges.
|
|
||||||
|
|
||||||
## Exit Criteria
|
|
||||||
|
|
||||||
1. Schema compiles and generates client successfully.
|
|
||||||
2. Reset/reseed produces runnable dataset.
|
|
||||||
3. Integrity verification passes for FK/index and state initialization rules.
|
|
||||||
4. Phase 01 gates complete.
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
# Phase 01 Schema Specification
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
Introduce the canonical orchestration entities and remove legacy dependency assumptions around single `roundId` progression.
|
|
||||||
|
|
||||||
## New Canonical Tables
|
|
||||||
|
|
||||||
1. `Pipeline`
|
|
||||||
2. `Track`
|
|
||||||
3. `Stage`
|
|
||||||
4. `StageTransition`
|
|
||||||
5. `ProjectStageState`
|
|
||||||
6. `RoutingRule`
|
|
||||||
7. `Cohort`
|
|
||||||
8. `CohortProject`
|
|
||||||
9. `LiveProgressCursor`
|
|
||||||
10. `NotificationPolicy`
|
|
||||||
11. `OverrideAction`
|
|
||||||
12. `DecisionAuditLog`
|
|
||||||
|
|
||||||
## Award Governance Extensions
|
|
||||||
|
|
||||||
- Add `DecisionMode = JURY_VOTE | AWARD_MASTER | ADMIN`
|
|
||||||
- Add award-scoped governance metadata to award track configs
|
|
||||||
- Add award winner finalization audit event contracts
|
|
||||||
|
|
||||||
## Migration Model
|
|
||||||
|
|
||||||
- Build new schema directly as canonical target.
|
|
||||||
- Keep migration files deterministic and replay-safe.
|
|
||||||
- Do not implement dual-write or compatibility tables.
|
|
||||||
|
|
||||||
## Required Constraints
|
|
||||||
|
|
||||||
1. `trackId + sortOrder` unique in `Stage`
|
|
||||||
2. `projectId + trackId + stageId` unique in `ProjectStageState`
|
|
||||||
3. `fromStageId + toStageId` unique in `StageTransition`
|
|
||||||
4. `cohortId + projectId` unique in `CohortProject`
|
|
||||||
|
|
||||||
## Required Indexes
|
|
||||||
|
|
||||||
- `ProjectStageState(projectId, trackId, state)`
|
|
||||||
- `ProjectStageState(stageId, state)`
|
|
||||||
- `RoutingRule(pipelineId, isActive, priority)`
|
|
||||||
- `StageTransition(fromStageId, priority)`
|
|
||||||
- `DecisionAuditLog(entityType, entityId, createdAt)`
|
|
||||||
- `LiveProgressCursor(stageId, sessionId)`
|
|
||||||
|
|
||||||
## Data Initialization Rules
|
|
||||||
|
|
||||||
- Every seeded project must start with one intake-stage state.
|
|
||||||
- Seed must include main track plus at least two award tracks with different routing modes.
|
|
||||||
- Seed must include representative roles: admins, jury, applicants, observer, audience contexts.
|
|
||||||
|
|
||||||
## Integrity Checks
|
|
||||||
|
|
||||||
- No orphan states.
|
|
||||||
- No invalid transition targets across pipelines.
|
|
||||||
- No duplicate active state rows for same `(project, track, stage)`.
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# Phase 01 Tasks
|
|
||||||
|
|
||||||
## Schema Build
|
|
||||||
|
|
||||||
- [ ] Implement canonical entities and enums in `prisma/schema.prisma`.
|
|
||||||
- [ ] Add required constraints and indexes.
|
|
||||||
- [ ] Remove or isolate legacy-only orchestration semantics from canonical paths.
|
|
||||||
|
|
||||||
## Seed and Fixtures
|
|
||||||
|
|
||||||
- [ ] Implement reseed script with realistic data volumes and edge cases.
|
|
||||||
- [ ] Include parallel, exclusive, and post-main award routing seed examples.
|
|
||||||
- [ ] Include live cohort seed data.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- [ ] Implement integrity SQL scripts.
|
|
||||||
- [ ] Implement automated verification command wrapper.
|
|
||||||
- [ ] Record baseline output and attach to gate evidence.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- [ ] Update schema change notes.
|
|
||||||
- [ ] Document reset/reseed assumptions.
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# Phase 02 Acceptance Gates
|
|
||||||
|
|
||||||
- [ ] G-02-1 transition engine tests pass
|
|
||||||
- [ ] G-02-2 routing determinism tests pass
|
|
||||||
- [ ] G-02-3 filtering policy tests pass
|
|
||||||
- [ ] G-02-4 assignment guarantee tests pass
|
|
||||||
- [ ] G-02-5 live cursor and cohort window tests pass
|
|
||||||
- [ ] G-02-6 override/audit tests pass
|
|
||||||
|
|
||||||
## Required Evidence
|
|
||||||
|
|
||||||
- U/I test output for all mapped IDs
|
|
||||||
- sample API responses for major mutation endpoints
|
|
||||||
- audit payload examples for transition and override flows
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
# Assignment Engine Specification
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Generate high-quality, fair assignments while guaranteeing eligible project coverage.
|
|
||||||
|
|
||||||
## Inputs
|
|
||||||
|
|
||||||
- stage ID
|
|
||||||
- eligible project set
|
|
||||||
- assignee pool
|
|
||||||
- required reviews per project
|
|
||||||
- assignment strategy config
|
|
||||||
- availability and COI policies
|
|
||||||
|
|
||||||
## Hard Constraints
|
|
||||||
|
|
||||||
1. COI exclusion
|
|
||||||
2. role/status eligibility
|
|
||||||
3. explicit max-load cap
|
|
||||||
4. minimum review floor
|
|
||||||
|
|
||||||
## Soft Scoring Dimensions
|
|
||||||
|
|
||||||
- expertise overlap
|
|
||||||
- bio/project similarity
|
|
||||||
- availability weighting
|
|
||||||
- workload balancing
|
|
||||||
- optional geo diversity
|
|
||||||
- optional prior-familiarity weighting
|
|
||||||
|
|
||||||
## Guarantee Rules
|
|
||||||
|
|
||||||
1. No eligible project left uncovered.
|
|
||||||
2. If capacity insufficient, create overflow assignments with warning markers.
|
|
||||||
3. Preview and execution must match constraints and scoring semantics.
|
|
||||||
|
|
||||||
## Output Contract
|
|
||||||
|
|
||||||
- assigned count
|
|
||||||
- uncovered count (must be zero unless in explicit error mode)
|
|
||||||
- overflow assignment list
|
|
||||||
- conflict skips list
|
|
||||||
- fairness metrics (median load, max load)
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
# Filtering and Routing Specification
|
|
||||||
|
|
||||||
## Filtering Pipeline
|
|
||||||
|
|
||||||
1. deterministic gates
|
|
||||||
2. AI rubric evaluation
|
|
||||||
3. confidence band decisioning
|
|
||||||
4. manual queue resolution
|
|
||||||
|
|
||||||
## Deterministic Gates First Rule
|
|
||||||
|
|
||||||
AI execution is prohibited unless deterministic gates pass.
|
|
||||||
|
|
||||||
## AI Output Contract
|
|
||||||
|
|
||||||
- criteria scores
|
|
||||||
- overall recommendation
|
|
||||||
- confidence
|
|
||||||
- rationale
|
|
||||||
- risk flags
|
|
||||||
|
|
||||||
## Confidence Bands
|
|
||||||
|
|
||||||
- `high`: auto decision path
|
|
||||||
- `medium`: manual queue
|
|
||||||
- `low`: reject or manual based on stage policy
|
|
||||||
|
|
||||||
## Routing Rules
|
|
||||||
|
|
||||||
### Evaluation Order
|
|
||||||
|
|
||||||
1. stage-scoped rules
|
|
||||||
2. track-scoped rules
|
|
||||||
3. global rules
|
|
||||||
4. default fallback
|
|
||||||
|
|
||||||
### Deterministic Tie-Break
|
|
||||||
|
|
||||||
- highest priority wins
|
|
||||||
- if equal, lexical rule ID fallback
|
|
||||||
|
|
||||||
### Explainability Persisted
|
|
||||||
|
|
||||||
Each route persists:
|
|
||||||
|
|
||||||
- matched rule ID
|
|
||||||
- predicate snapshot
|
|
||||||
- mode (`AUTO|MANUAL`)
|
|
||||||
- destination track/stage
|
|
||||||
|
|
||||||
## Award Routing Modes
|
|
||||||
|
|
||||||
- `PARALLEL`: keep main progression and add award state
|
|
||||||
- `EXCLUSIVE`: route out of main progression into award track only
|
|
||||||
- `POST_MAIN`: route only after configured main gate stage
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
# Live Control Specification
|
|
||||||
|
|
||||||
## Source of Truth
|
|
||||||
Admin cursor state is the single source of truth for active project context during live stages.
|
|
||||||
|
|
||||||
## Core Controls
|
|
||||||
|
|
||||||
- start session
|
|
||||||
- next/previous
|
|
||||||
- jump to project
|
|
||||||
- reorder queue
|
|
||||||
- open/close cohort windows
|
|
||||||
- pause/resume session
|
|
||||||
|
|
||||||
## Runtime Requirements
|
|
||||||
|
|
||||||
1. cursor updates are versioned
|
|
||||||
2. race conditions return `CONFLICT` and require refresh/retry
|
|
||||||
3. real-time propagation to jury and audience clients
|
|
||||||
4. reconnect path converges to current cursor/window state
|
|
||||||
|
|
||||||
## Vote Acceptance Rules
|
|
||||||
|
|
||||||
- stage and cohort windows must be open
|
|
||||||
- dedupe key policy enforced (`session/cohort/project/voter/window`)
|
|
||||||
- closed windows reject submissions deterministically
|
|
||||||
|
|
||||||
## Event Contract
|
|
||||||
|
|
||||||
- `live.cursor.updated`
|
|
||||||
- `cohort.window.changed`
|
|
||||||
- `live.session.state.changed`
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Phase 02 Overview: Backend Orchestration Engine
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Implement deterministic runtime behavior for stage transitions, routing, filtering, assignment, live cursor control, and notification/audit emission.
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
|
|
||||||
- transition engine
|
|
||||||
- routing engine
|
|
||||||
- filtering orchestration (gates + AI + manual queue)
|
|
||||||
- assignment orchestration with coverage guarantees
|
|
||||||
- live cursor and cohort window controls
|
|
||||||
- event and audit emission
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- full admin UI and participant UI implementation
|
|
||||||
|
|
||||||
## Exit Criteria
|
|
||||||
|
|
||||||
1. Runtime contracts implemented and integration-tested.
|
|
||||||
2. Determinism and idempotency guarantees proven for critical mutations.
|
|
||||||
3. Mandatory phase gates complete with test evidence.
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# Stage Engine Specification
|
|
||||||
|
|
||||||
## Responsibilities
|
|
||||||
|
|
||||||
1. Validate transition legality.
|
|
||||||
2. Move project states transactionally.
|
|
||||||
3. Emit transition events and audit entries.
|
|
||||||
4. Enforce concurrency safety.
|
|
||||||
|
|
||||||
## State Machine
|
|
||||||
|
|
||||||
`PENDING -> IN_PROGRESS -> PASSED|REJECTED -> ROUTED -> COMPLETED`
|
|
||||||
|
|
||||||
`WITHDRAWN` is terminal for participant-triggered withdrawal paths.
|
|
||||||
|
|
||||||
## Transition Guards
|
|
||||||
|
|
||||||
- source state row exists and is active
|
|
||||||
- destination stage is active and in same pipeline (unless routing rule applies)
|
|
||||||
- stage window and guard conditions satisfied
|
|
||||||
- no concurrent conflicting transition
|
|
||||||
|
|
||||||
## Mutation Semantics
|
|
||||||
|
|
||||||
- transactional updates per batch slice
|
|
||||||
- optimistic locking/version checks
|
|
||||||
- per-project result collection for partial failure reporting
|
|
||||||
|
|
||||||
## Failure Codes
|
|
||||||
|
|
||||||
- `PRECONDITION_FAILED`: guard not satisfied
|
|
||||||
- `CONFLICT`: state moved after read
|
|
||||||
- `BAD_REQUEST`: invalid transition target
|
|
||||||
|
|
||||||
## Audit Contract
|
|
||||||
|
|
||||||
`eventType = stage.transitioned`
|
|
||||||
|
|
||||||
Payload includes:
|
|
||||||
|
|
||||||
- actor
|
|
||||||
- source stage
|
|
||||||
- destination stage
|
|
||||||
- old/new state
|
|
||||||
- reason/context
|
|
||||||
- timestamp
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Phase 02 Tasks
|
|
||||||
|
|
||||||
## Transition Engine
|
|
||||||
|
|
||||||
- [ ] Implement transition guard and mutation logic.
|
|
||||||
- [ ] Implement optimistic concurrency handling.
|
|
||||||
- [ ] Implement transition event and audit emission.
|
|
||||||
|
|
||||||
## Filtering and Routing
|
|
||||||
|
|
||||||
- [ ] Implement deterministic gate-first pipeline.
|
|
||||||
- [ ] Implement confidence band decision handling.
|
|
||||||
- [ ] Implement routing rule engine with explainability payloads.
|
|
||||||
|
|
||||||
## Assignment
|
|
||||||
|
|
||||||
- [ ] Implement assignment preview and execute parity.
|
|
||||||
- [ ] Implement coverage guarantee and overflow semantics.
|
|
||||||
- [ ] Implement assignment metrics output.
|
|
||||||
|
|
||||||
## Live Runtime
|
|
||||||
|
|
||||||
- [ ] Implement admin cursor operations and conflict-safe update model.
|
|
||||||
- [ ] Implement cohort window control.
|
|
||||||
- [ ] Implement real-time event propagation path.
|
|
||||||
|
|
||||||
## Notifications and Audit
|
|
||||||
|
|
||||||
- [ ] Implement default event producers for stage transitions and outcomes.
|
|
||||||
- [ ] Implement immutable audit payload structure.
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
# Phase 03 Acceptance Gates
|
|
||||||
|
|
||||||
- [ ] G-03-1 wizard can complete full required setup (E-001)
|
|
||||||
- [ ] G-03-2 no hidden edit-only required settings remain
|
|
||||||
- [ ] G-03-3 advanced editor enforces graph/config guardrails
|
|
||||||
- [ ] G-03-4 modal and form safety regressions pass
|
|
||||||
|
|
||||||
## Required Evidence
|
|
||||||
|
|
||||||
- E2E wizard completion evidence
|
|
||||||
- parity checklist artifacts
|
|
||||||
- targeted UI regression test output
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# Advanced Editor Specification
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
Provide direct manipulation of tracks, stages, transitions, and routing without polluting the default wizard path.
|
|
||||||
|
|
||||||
## Panels
|
|
||||||
|
|
||||||
1. Track/Stage List Panel
|
|
||||||
2. Stage Config Panel
|
|
||||||
3. Transition Graph Panel
|
|
||||||
4. Routing Rule Inspector
|
|
||||||
5. Simulation Panel
|
|
||||||
|
|
||||||
## Required Capabilities
|
|
||||||
|
|
||||||
- reorder stages within track
|
|
||||||
- move valid stages across tracks
|
|
||||||
- create/delete transitions
|
|
||||||
- edit rule predicates and priorities
|
|
||||||
- simulate outcomes for sample project IDs
|
|
||||||
|
|
||||||
## Guardrails
|
|
||||||
|
|
||||||
1. Block disconnected required paths.
|
|
||||||
2. Block orphan stage deletion.
|
|
||||||
3. Warn before destructive transition/rule removal.
|
|
||||||
4. Enforce schema validation for stage config payloads.
|
|
||||||
|
|
||||||
## Save Model
|
|
||||||
|
|
||||||
- draft buffer
|
|
||||||
- validation run
|
|
||||||
- transactional persist
|
|
||||||
- validation report artifact
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Form Behavior and Validation Rules
|
|
||||||
|
|
||||||
## Universal Rules
|
|
||||||
|
|
||||||
1. Every required field has inline validation.
|
|
||||||
2. Every select has deterministic default value.
|
|
||||||
3. Save actions are idempotent and disabled while pending.
|
|
||||||
4. Unsafe changes surface explicit impact warnings.
|
|
||||||
|
|
||||||
## Create/Edit Parity Requirements
|
|
||||||
|
|
||||||
- intake windows
|
|
||||||
- upload policy
|
|
||||||
- file requirements
|
|
||||||
- assignment policy
|
|
||||||
- filtering policy
|
|
||||||
- routing policy
|
|
||||||
- live policy
|
|
||||||
|
|
||||||
## Modal Safety Rules
|
|
||||||
|
|
||||||
1. Modal close must not mutate persisted state.
|
|
||||||
2. Non-submit buttons must explicitly set `type="button"`.
|
|
||||||
3. Escape/cancel should only dismiss local draft state.
|
|
||||||
|
|
||||||
## Payload Safety
|
|
||||||
|
|
||||||
- replace raw free-text config where structured selectors exist
|
|
||||||
- normalize serialization format for config payloads
|
|
||||||
- reject unknown keys in strict mode contracts
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Phase 03 Overview: Admin Control-Plane UX
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Deliver a wizard-first admin control plane that exposes full required configuration in create-time flow, with a safe advanced editor for power users.
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
|
|
||||||
- setup wizard IA and behavior
|
|
||||||
- advanced stage and routing editor
|
|
||||||
- simulation and validation panel
|
|
||||||
- create/edit parity
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- visual redesign
|
|
||||||
- participant-facing workflows
|
|
||||||
|
|
||||||
## Exit Criteria
|
|
||||||
|
|
||||||
1. Full required setup possible from create flow.
|
|
||||||
2. No hidden edit-only required fields.
|
|
||||||
3. Validation and simulation guardrails implemented.
|
|
||||||
4. Phase gates complete.
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
# Phase 03 Tasks
|
|
||||||
|
|
||||||
## Wizard
|
|
||||||
|
|
||||||
- [ ] Implement 8-step setup flow.
|
|
||||||
- [ ] Implement step-level validation and progress state.
|
|
||||||
- [ ] Implement review/publish summary and blockers.
|
|
||||||
|
|
||||||
## Advanced Editor
|
|
||||||
|
|
||||||
- [ ] Implement stage/transition/routing editing surfaces.
|
|
||||||
- [ ] Implement simulation runner and result panel.
|
|
||||||
- [ ] Implement destructive action confirmations.
|
|
||||||
|
|
||||||
## Behavior and Safety
|
|
||||||
|
|
||||||
- [ ] Enforce create/edit parity checklist.
|
|
||||||
- [ ] Enforce modal safety rules.
|
|
||||||
- [ ] Enforce strict payload validation.
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
# Admin Wizard IA
|
|
||||||
|
|
||||||
## Step Sequence
|
|
||||||
|
|
||||||
1. Intake Setup
|
|
||||||
2. Main Track Stage Setup
|
|
||||||
3. Filtering Strategy
|
|
||||||
4. Assignment Strategy
|
|
||||||
5. Special Awards
|
|
||||||
6. Live Finals Configuration
|
|
||||||
7. Notifications and Overrides
|
|
||||||
8. Review + Publish
|
|
||||||
|
|
||||||
## Step Details
|
|
||||||
|
|
||||||
### 1) Intake Setup
|
|
||||||
|
|
||||||
- submission windows
|
|
||||||
- late policy
|
|
||||||
- file requirements
|
|
||||||
- MIME/size constraints
|
|
||||||
- applicant communication policy
|
|
||||||
|
|
||||||
### 2) Main Track Stage Setup
|
|
||||||
|
|
||||||
- stage list and ordering
|
|
||||||
- stage type assignment
|
|
||||||
- status defaults
|
|
||||||
- selection stage presets
|
|
||||||
|
|
||||||
### 3) Filtering Strategy
|
|
||||||
|
|
||||||
- deterministic gate definition
|
|
||||||
- AI rubric configuration
|
|
||||||
- confidence thresholds
|
|
||||||
- manual queue owners
|
|
||||||
|
|
||||||
### 4) Assignment Strategy
|
|
||||||
|
|
||||||
- required reviews
|
|
||||||
- max/min load settings
|
|
||||||
- availability weighting
|
|
||||||
- overflow handling policy
|
|
||||||
|
|
||||||
### 5) Special Awards
|
|
||||||
|
|
||||||
- award track enablement
|
|
||||||
- routing mode per award
|
|
||||||
- decision mode per award
|
|
||||||
- award jury restrictions
|
|
||||||
|
|
||||||
### 6) Live Finals
|
|
||||||
|
|
||||||
- cursor control mode
|
|
||||||
- jury vote config
|
|
||||||
- audience vote config
|
|
||||||
- cohort setup
|
|
||||||
- reveal policy
|
|
||||||
|
|
||||||
### 7) Notifications and Overrides
|
|
||||||
|
|
||||||
- default-on event toggles
|
|
||||||
- template overrides
|
|
||||||
- override governance policy
|
|
||||||
|
|
||||||
### 8) Review + Publish
|
|
||||||
|
|
||||||
- summary diff
|
|
||||||
- warnings/blockers
|
|
||||||
- simulation output
|
|
||||||
- publish action
|
|
||||||
|
|
||||||
## UX Requirements
|
|
||||||
|
|
||||||
- mobile-safe interaction and layout
|
|
||||||
- explicit required field indicators
|
|
||||||
- deterministic defaults for every select
|
|
||||||
- inline validation without hidden blockers
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
# Phase 04 Acceptance Gates
|
|
||||||
|
|
||||||
- [ ] G-04-1 applicant flow tests pass (E-002)
|
|
||||||
- [ ] G-04-2 jury flow tests pass (E-004)
|
|
||||||
- [ ] G-04-3 live audience tests pass (E-006/E-007)
|
|
||||||
- [ ] G-04-4 reconnect and realtime resilience evidence passes (P-004)
|
|
||||||
|
|
||||||
## Required Evidence
|
|
||||||
|
|
||||||
- E2E artifacts for applicant/jury/audience scenarios
|
|
||||||
- realtime and reconnect test captures
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Applicant Experience Specification
|
|
||||||
|
|
||||||
## Required Views
|
|
||||||
|
|
||||||
1. current stage and timeline
|
|
||||||
2. stage-specific requirements
|
|
||||||
3. deadlines and late policy status
|
|
||||||
4. team invite/account status
|
|
||||||
5. decision history (policy-scoped)
|
|
||||||
|
|
||||||
## Behavior Requirements
|
|
||||||
|
|
||||||
- requirement upload slots are stage-aware
|
|
||||||
- accepted MIME/size and deadline checks enforced at submit time
|
|
||||||
- timeline updates reflect transition and decision events quickly
|
|
||||||
- role-scoped team collaboration controls enforced
|
|
||||||
|
|
||||||
## Error States
|
|
||||||
|
|
||||||
- missing requirement definition
|
|
||||||
- expired upload window
|
|
||||||
- invalid MIME/size
|
|
||||||
- stale session/permission mismatch
|
|
||||||
|
|
||||||
## Notification Expectations
|
|
||||||
|
|
||||||
- intake submitted confirmation
|
|
||||||
- advanced/rejected updates
|
|
||||||
- additional requirement requests when policy allows
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# Audience Live Vote Specification
|
|
||||||
|
|
||||||
## Core Rules
|
|
||||||
|
|
||||||
1. Audience sees only projects within active cohort/window policy.
|
|
||||||
2. Vote submission requires valid session eligibility and dedupe key check.
|
|
||||||
3. Closed windows reject submissions with typed error.
|
|
||||||
|
|
||||||
## Voting Modes
|
|
||||||
|
|
||||||
- per-project window
|
|
||||||
- per-cohort window
|
|
||||||
- optional criteria mode or simple score mode
|
|
||||||
|
|
||||||
## Safety and Abuse Controls
|
|
||||||
|
|
||||||
- tokenized access policy
|
|
||||||
- optional identity requirement
|
|
||||||
- rate-limit and dedupe enforcement
|
|
||||||
|
|
||||||
## Realtime Requirements
|
|
||||||
|
|
||||||
- active project state and window state sync in near real-time
|
|
||||||
- reconnect path restores current eligible ballot context
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
# Jury Experience Specification
|
|
||||||
|
|
||||||
## Assignment View
|
|
||||||
|
|
||||||
- grouped by stage
|
|
||||||
- explicit open/close window indicators
|
|
||||||
- progress and completion states
|
|
||||||
|
|
||||||
## Evaluation View
|
|
||||||
|
|
||||||
- criteria loaded from stage config
|
|
||||||
- required criteria enforcement
|
|
||||||
- draft autosave and submit lock behavior
|
|
||||||
- COI declaration flow integrated
|
|
||||||
|
|
||||||
## Access Rules
|
|
||||||
|
|
||||||
- only assigned projects visible
|
|
||||||
- voting restricted to open windows
|
|
||||||
- prior-stage material visibility policy respected
|
|
||||||
|
|
||||||
## Live Jury Behavior
|
|
||||||
|
|
||||||
- active project context sync via realtime updates
|
|
||||||
- vote actions gated by cursor and window state
|
|
||||||
- reconnect restores current live context
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
# Phase 04 Overview: Participant Journeys
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Refit applicant, jury, observer, and audience experiences to stage-native contracts with correct realtime behavior.
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
|
|
||||||
- applicant intake/status flows
|
|
||||||
- jury assignment/evaluation/live flows
|
|
||||||
- audience voting and live score flows
|
|
||||||
- observer read-only reporting alignment
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- admin config internals
|
|
||||||
|
|
||||||
## Exit Criteria
|
|
||||||
|
|
||||||
1. End-to-end participant paths pass mandatory E2E tests.
|
|
||||||
2. Realtime behavior converges under reconnect and window changes.
|
|
||||||
3. Policy enforcement matches authz and stage contracts.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
# Phase 04 Tasks
|
|
||||||
|
|
||||||
## Applicant
|
|
||||||
|
|
||||||
- [ ] Implement stage-native timeline and requirement resolver.
|
|
||||||
- [ ] Implement strict upload gating and policy enforcement.
|
|
||||||
|
|
||||||
## Jury
|
|
||||||
|
|
||||||
- [ ] Implement stage-scoped assignment and evaluation surfaces.
|
|
||||||
- [ ] Implement live jury context sync and voting constraints.
|
|
||||||
|
|
||||||
## Audience and Observer
|
|
||||||
|
|
||||||
- [ ] Implement cohort-scoped audience ballot visibility.
|
|
||||||
- [ ] Implement observer read-only stage/track reporting alignment.
|
|
||||||
|
|
||||||
## Realtime and Resilience
|
|
||||||
|
|
||||||
- [ ] Implement reconnect-state convergence behavior.
|
|
||||||
- [ ] Validate realtime event consistency under cursor updates.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Phase 05 Acceptance Gates
|
|
||||||
|
|
||||||
- [ ] G-05-1 routing mode behavior validated (`parallel`, `exclusive`, `post_main`)
|
|
||||||
- [ ] G-05-2 governance auth tests pass (`JURY_VOTE`, `AWARD_MASTER`, `ADMIN`)
|
|
||||||
- [ ] G-05-3 winner decision timeline and audit output validated
|
|
||||||
|
|
||||||
## Required Evidence
|
|
||||||
|
|
||||||
- integration test outputs for routing and governance
|
|
||||||
- audit timeline payload captures
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
# Award Track and Governance Specification
|
|
||||||
|
|
||||||
## Award Track Principle
|
|
||||||
Awards share the same orchestration engine as the main competition; they are tracks, not detached side workflows.
|
|
||||||
|
|
||||||
## Routing Modes
|
|
||||||
|
|
||||||
- `PARALLEL`: award path runs while main path continues
|
|
||||||
- `EXCLUSIVE`: project exits main continuation path and runs award-only
|
|
||||||
- `POST_MAIN`: award route starts after configured main gate
|
|
||||||
|
|
||||||
## Governance Modes
|
|
||||||
|
|
||||||
- `JURY_VOTE`: assigned award jurors vote
|
|
||||||
- `AWARD_MASTER`: designated award owner decides within scope
|
|
||||||
- `ADMIN`: program/super admin decides
|
|
||||||
|
|
||||||
## Decision Requirements
|
|
||||||
|
|
||||||
- every winner/finalist decision emits audit entry
|
|
||||||
- manual overrides require reason code and text
|
|
||||||
- tie-break policy explicit and deterministic
|
|
||||||
|
|
||||||
## Permission Enforcement
|
|
||||||
|
|
||||||
- governance mode checked server-side on every decision mutation
|
|
||||||
- unauthorized attempts return `FORBIDDEN`
|
|
||||||
|
|
||||||
## Representative Decision Payload
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"awardId": "award_123",
|
|
||||||
"decisionMode": "AWARD_MASTER",
|
|
||||||
"winnerProjectId": "project_789",
|
|
||||||
"reasonCode": "SPONSOR_DECISION",
|
|
||||||
"reasonText": "Award sponsor selected based on category fit"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
# Phase 05 Overview: Special Awards and Governance
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Implement special awards as first-class tracks with explicit routing and governance modes, including `AWARD_MASTER`.
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
|
|
||||||
- award track lifecycle
|
|
||||||
- routing semantics
|
|
||||||
- governance modes and permissions
|
|
||||||
- award decision and winner finalization workflows
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- sponsor legal/contract process documentation
|
|
||||||
|
|
||||||
## Exit Criteria
|
|
||||||
|
|
||||||
1. Mixed award modes run without collision in a single edition.
|
|
||||||
2. Governance modes enforce correct permissions server-side.
|
|
||||||
3. Winner decision audit trails are complete and immutable.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Phase 05 Tasks
|
|
||||||
|
|
||||||
- [ ] Implement award track CRUD on canonical contracts.
|
|
||||||
- [ ] Implement award routing mode behaviors and edge-case handling.
|
|
||||||
- [ ] Implement governance mode permission checks.
|
|
||||||
- [ ] Implement winner finalization and audit timeline entries.
|
|
||||||
- [ ] Implement award-specific reporting outputs.
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
# Phase 06 Acceptance Gates
|
|
||||||
|
|
||||||
- [ ] G-06-1 dependency refit inventory fully signed off
|
|
||||||
- [ ] G-06-2 symbol sweeps clean (no runtime legacy hits)
|
|
||||||
- [ ] G-06-3 integration consumer payload checks pass
|
|
||||||
- [ ] G-06-4 cross-role smoke tests pass
|
|
||||||
|
|
||||||
## Required Evidence
|
|
||||||
|
|
||||||
- module sign-off checklist with owners
|
|
||||||
- sweep outputs
|
|
||||||
- webhook/export consumer validation logs
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
# Module Refit Map
|
|
||||||
|
|
||||||
## Router Layer Actions
|
|
||||||
|
|
||||||
- Rewrite orchestration endpoints to `pipeline/stage/routing` contracts.
|
|
||||||
- Refactor filtering, assignment, and live endpoints to stage-scoped semantics.
|
|
||||||
- Replace award detached flows with award-track-native contracts.
|
|
||||||
|
|
||||||
## Service Layer Actions
|
|
||||||
|
|
||||||
- Refactor AI filtering context to stage-native payloads.
|
|
||||||
- Refactor assignment engine to consume stage eligibility and availability.
|
|
||||||
- Refactor notification producers to new event taxonomy.
|
|
||||||
- Refactor reminders and summaries to stage references.
|
|
||||||
|
|
||||||
## UI Layer Actions
|
|
||||||
|
|
||||||
- Admin round pages become pipeline/stage control-plane pages.
|
|
||||||
- Jury and applicant pages consume stage timeline and stage requirement contracts.
|
|
||||||
- Public vote/live pages consume cohort and live cursor state.
|
|
||||||
|
|
||||||
## Reporting and Export Actions
|
|
||||||
|
|
||||||
- Replace round-grouped aggregations with stage/track aggregations.
|
|
||||||
- Update CSV/PDF payload field names to new contracts.
|
|
||||||
- Update observer dashboards and chart dimensions.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
# Phase 06 Overview: Platform Dependency Refit
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Refit every platform dependency from legacy round semantics to canonical stage contracts.
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
|
|
||||||
- module-by-module refit execution
|
|
||||||
- stale symbol removal
|
|
||||||
- integration payload consumer updates
|
|
||||||
- cross-role smoke validation
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- new feature expansion not required for contract migration
|
|
||||||
|
|
||||||
## Exit Criteria
|
|
||||||
|
|
||||||
1. dependency checklist complete
|
|
||||||
2. legacy symbol sweeps clean
|
|
||||||
3. external integration consumers validated
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Symbol Sweep Checklist
|
|
||||||
|
|
||||||
All commands must return zero actionable runtime hits.
|
|
||||||
|
|
||||||
- [ ] `rg "trpc\.round" src`
|
|
||||||
- [ ] `rg "\broundId\b" src/server src/components src/app`
|
|
||||||
- [ ] `rg "round\.settingsJson|roundType" src/server src/components src/app`
|
|
||||||
- [ ] `rg "model Round|enum RoundType" prisma/schema.prisma`
|
|
||||||
|
|
||||||
## Exceptions
|
|
||||||
|
|
||||||
- documentation-only references may be allowed with explicit annotation
|
|
||||||
- any code-path exception is release-blocking unless approved
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Phase 06 Tasks
|
|
||||||
|
|
||||||
- [ ] Execute router-layer refit checklist.
|
|
||||||
- [ ] Execute service-layer refit checklist.
|
|
||||||
- [ ] Execute UI-layer refit checklist.
|
|
||||||
- [ ] Execute reporting/export integration checklist.
|
|
||||||
- [ ] Run and document legacy symbol sweeps.
|
|
||||||
- [ ] Resolve all remaining contract drift findings.
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# Phase 07 Acceptance Gates
|
|
||||||
|
|
||||||
- [ ] G-07-1 U/I/E/P matrix all green
|
|
||||||
- [ ] G-07-2 performance and resilience evidence accepted
|
|
||||||
- [ ] G-07-3 release evidence report complete and signed
|
|
||||||
- [ ] G-07-4 atomic cutover executed with successful post-checks
|
|
||||||
|
|
||||||
## Required Evidence
|
|
||||||
|
|
||||||
- consolidated test reports
|
|
||||||
- benchmark output captures
|
|
||||||
- signed release evidence report
|
|
||||||
- runbook execution logs
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
# Phase 07 Overview: Validation and Release
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Execute complete validation suite, run final reset/reseed rehearsal, and perform atomic release cutover.
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
|
|
||||||
- full U/I/E/P test execution
|
|
||||||
- release evidence collation
|
|
||||||
- performance and resilience validation
|
|
||||||
- atomic release runbook execution
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- post-release enhancements
|
|
||||||
|
|
||||||
## Exit Criteria
|
|
||||||
|
|
||||||
1. full test matrix green
|
|
||||||
2. release evidence signed
|
|
||||||
3. atomic cutover and post-cutover smoke checks complete
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
# Performance and Resilience Plan
|
|
||||||
|
|
||||||
## Scenarios
|
|
||||||
|
|
||||||
### Assignment Throughput
|
|
||||||
|
|
||||||
- workload: 1000+ eligible projects
|
|
||||||
- metrics: runtime, coverage latency, overload count
|
|
||||||
|
|
||||||
### Filtering Throughput
|
|
||||||
|
|
||||||
- workload: high-volume gate + AI queue
|
|
||||||
- metrics: gate throughput, queue completion, retry/error rate
|
|
||||||
|
|
||||||
### Live Voting Burst
|
|
||||||
|
|
||||||
- workload: peak audience voting during active cursor changes
|
|
||||||
- metrics: vote latency p50/p95/p99, event drop count, cursor propagation delay
|
|
||||||
|
|
||||||
### Reconnect Recovery
|
|
||||||
|
|
||||||
- workload: intentional network interruptions
|
|
||||||
- metrics: time to state convergence, stale cursor mismatch rate
|
|
||||||
|
|
||||||
## Acceptance Policy
|
|
||||||
|
|
||||||
Thresholds set before run and documented in release evidence.
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
# Release Runbook (Atomic Cutover)
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- all prior phase gates complete
|
|
||||||
- signed release checklist
|
|
||||||
- rollback owner and communication owner assigned
|
|
||||||
|
|
||||||
## Cutover Sequence
|
|
||||||
|
|
||||||
1. freeze non-release writes and announce maintenance window
|
|
||||||
2. execute final backup snapshot
|
|
||||||
3. deploy release candidate build
|
|
||||||
4. run reset/reseed as planned for production state model
|
|
||||||
5. run post-deploy integrity and smoke checks
|
|
||||||
6. run mandatory critical-path E2E subset
|
|
||||||
7. publish completion and monitor
|
|
||||||
|
|
||||||
## Immediate Post-Cutover Checks
|
|
||||||
|
|
||||||
- auth and role gating paths
|
|
||||||
- transition mutation sanity
|
|
||||||
- assignment preview/execute path
|
|
||||||
- live cursor operations
|
|
||||||
- audience vote acceptance and dedupe
|
|
||||||
- reporting endpoint correctness
|
|
||||||
|
|
||||||
## Rollback Trigger Conditions
|
|
||||||
|
|
||||||
- integrity check failures
|
|
||||||
- critical mutation path failure
|
|
||||||
- unacceptable error-rate spike
|
|
||||||
|
|
||||||
## Rollback Plan (High Level)
|
|
||||||
|
|
||||||
- restore backup snapshot
|
|
||||||
- redeploy previous stable build
|
|
||||||
- validate critical-path smoke tests
|
|
||||||
- issue incident communication and postmortem schedule
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Phase 07 Tasks
|
|
||||||
|
|
||||||
- [ ] Execute full test matrix and store artifacts.
|
|
||||||
- [ ] Execute performance and resilience scenarios.
|
|
||||||
- [ ] Complete release evidence report.
|
|
||||||
- [ ] Run atomic cutover rehearsal and production runbook.
|
|
||||||
- [ ] Complete post-cutover smoke suite.
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
# API Contracts
|
|
||||||
|
|
||||||
## Contract Conventions
|
|
||||||
|
|
||||||
- All mutations return typed `errorCode` and machine-readable `details` on failure.
|
|
||||||
- All state-changing operations emit deterministic audit events.
|
|
||||||
- All response shapes include stable identifiers for client cache invalidation.
|
|
||||||
|
|
||||||
## Router Families
|
|
||||||
|
|
||||||
### `pipeline`
|
|
||||||
|
|
||||||
- `pipeline.create`
|
|
||||||
- `pipeline.update`
|
|
||||||
- `pipeline.simulate`
|
|
||||||
- `pipeline.publish`
|
|
||||||
- `pipeline.getSummary`
|
|
||||||
|
|
||||||
### `stage`
|
|
||||||
|
|
||||||
- `stage.create`
|
|
||||||
- `stage.updateConfig`
|
|
||||||
- `stage.list`
|
|
||||||
- `stage.transition`
|
|
||||||
- `stage.openWindow`
|
|
||||||
- `stage.closeWindow`
|
|
||||||
|
|
||||||
### `routing`
|
|
||||||
|
|
||||||
- `routing.preview`
|
|
||||||
- `routing.execute`
|
|
||||||
- `routing.listRules`
|
|
||||||
- `routing.upsertRule`
|
|
||||||
- `routing.toggleRule`
|
|
||||||
|
|
||||||
### `filtering`
|
|
||||||
|
|
||||||
- `filtering.previewBatch`
|
|
||||||
- `filtering.runStageFiltering`
|
|
||||||
- `filtering.getManualQueue`
|
|
||||||
- `filtering.resolveManualDecision`
|
|
||||||
|
|
||||||
### `assignment`
|
|
||||||
|
|
||||||
- `assignment.previewStageProjects`
|
|
||||||
- `assignment.assignStageProjects`
|
|
||||||
- `assignment.getCoverageReport`
|
|
||||||
- `assignment.rebalance`
|
|
||||||
|
|
||||||
### `cohort`
|
|
||||||
|
|
||||||
- `cohort.create`
|
|
||||||
- `cohort.assignProjects`
|
|
||||||
- `cohort.openVoting`
|
|
||||||
- `cohort.closeVoting`
|
|
||||||
|
|
||||||
### `live`
|
|
||||||
|
|
||||||
- `live.start`
|
|
||||||
- `live.setActiveProject`
|
|
||||||
- `live.jump`
|
|
||||||
- `live.reorder`
|
|
||||||
- `live.pause`
|
|
||||||
- `live.resume`
|
|
||||||
|
|
||||||
### `decision`
|
|
||||||
|
|
||||||
- `decision.override`
|
|
||||||
- `decision.auditTimeline`
|
|
||||||
|
|
||||||
### `award`
|
|
||||||
|
|
||||||
- `award.createTrack`
|
|
||||||
- `award.configureGovernance`
|
|
||||||
- `award.routeProjects`
|
|
||||||
- `award.finalizeWinners`
|
|
||||||
|
|
||||||
## Error Contract
|
|
||||||
|
|
||||||
- `BAD_REQUEST`
|
|
||||||
- `UNAUTHORIZED`
|
|
||||||
- `FORBIDDEN`
|
|
||||||
- `NOT_FOUND`
|
|
||||||
- `CONFLICT`
|
|
||||||
- `PRECONDITION_FAILED`
|
|
||||||
- `INTERNAL_SERVER_ERROR`
|
|
||||||
|
|
||||||
## Event Contract (Representative)
|
|
||||||
|
|
||||||
- `stage.transitioned`
|
|
||||||
- `routing.executed`
|
|
||||||
- `filtering.completed`
|
|
||||||
- `assignment.generated`
|
|
||||||
- `live.cursor.updated`
|
|
||||||
- `cohort.window.changed`
|
|
||||||
- `decision.overridden`
|
|
||||||
- `award.winner.finalized`
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
# Authorization Matrix
|
|
||||||
|
|
||||||
Roles:
|
|
||||||
|
|
||||||
- `SUPER_ADMIN`
|
|
||||||
- `PROGRAM_ADMIN`
|
|
||||||
- `AWARD_MASTER`
|
|
||||||
- `JURY_MEMBER`
|
|
||||||
- `APPLICANT`
|
|
||||||
- `OBSERVER`
|
|
||||||
- `AUDIENCE` (public voting context)
|
|
||||||
|
|
||||||
| Capability | Super Admin | Program Admin | Award Master | Jury | Applicant | Observer | Audience |
|
|
||||||
|---|---|---|---|---|---|---|---|
|
|
||||||
| Create/Edit Pipeline | Yes | Yes (scoped) | No | No | No | No | No |
|
|
||||||
| Publish Pipeline | Yes | Yes (scoped) | No | No | No | No | No |
|
|
||||||
| Configure Stage Rules | Yes | Yes (scoped) | No | No | No | No | No |
|
|
||||||
| Execute Manual Transition | Yes | Yes (scoped) | Limited (award scoped) | No | No | No | No |
|
|
||||||
| Override Decision | Yes | Yes (scoped) | Limited (award scoped) | No | No | No | No |
|
|
||||||
| View Audit Timeline | Yes | Yes (scoped) | Award scoped | Own actions | No | Read-only scoped | No |
|
|
||||||
| Assign Jurors | Yes | Yes (scoped) | Award scoped | No | No | No | No |
|
|
||||||
| Submit Evaluation | No | No | Optional (if configured) | Yes (assigned only) | No | No | No |
|
|
||||||
| Upload Intake Docs | No | No | No | No | Yes | No | No |
|
|
||||||
| Control Live Cursor | Yes | Yes (scoped) | No | No | No | No | No |
|
|
||||||
| Cast Audience Vote | No | No | No | No | Optional | No | Yes |
|
|
||||||
|
|
||||||
## Policy Notes
|
|
||||||
|
|
||||||
1. Program scoping applies to all admin operations.
|
|
||||||
2. `AWARD_MASTER` permissions are explicitly award-scoped and only active when governance mode allows it.
|
|
||||||
3. Jury endpoints always enforce assignment ownership and window constraints.
|
|
||||||
4. Audience endpoints enforce cohort membership + window state + dedupe key policy.
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# Decision Log (Locked)
|
|
||||||
|
|
||||||
| ID | Decision | Status | Rationale | Impacted Phases |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| MX-001 | Canonical model is `Pipeline -> Track -> Stage` | Locked | Supports multi-track orchestration cleanly | 01-07 |
|
|
||||||
| MX-002 | Project progression stored in `ProjectStageState` records | Locked | Replaces brittle single-pointer round state | 01-07 |
|
|
||||||
| MX-003 | Intake is stage-native (`INTAKE`) rather than implicit pre-round behavior | Locked | Removes hidden workflow behavior | 01-04 |
|
|
||||||
| MX-004 | Full-cutover delivery with no compatibility bridge | Locked | Faster convergence to clean runtime | 00-07 |
|
|
||||||
| MX-005 | Special awards are first-class `Track` entities | Locked | Prevents duplicated orchestration logic | 01-06 |
|
|
||||||
| MX-006 | Award routing modes are `parallel`, `exclusive`, `post_main` | Locked | Supports real sponsor policy diversity | 02,05 |
|
|
||||||
| MX-007 | Award governance modes include `JURY_VOTE`, `AWARD_MASTER`, `ADMIN` | Locked | Explicit and policy-aligned control surfaces | 05 |
|
|
||||||
| MX-008 | Live progression source of truth is admin cursor | Locked | Needed for non-linear live event control | 02,04 |
|
|
||||||
| MX-009 | Voting windows are explicit open/close operations | Locked | Schedules alone are insufficient during live operations | 02,04 |
|
|
||||||
| MX-010 | Assignment engine guarantees eligible project coverage | Locked | Operational fairness and delivery reliability | 02,04 |
|
|
||||||
| MX-011 | Overrides require reason and immutable audit entries | Locked | Governance and explainability | 02,05,07 |
|
|
||||||
| MX-012 | Release is blocked by legacy symbol sweep failures | Locked | Prevents half-migrated runtime behavior | 06,07 |
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
# Dependency Refit Inventory
|
|
||||||
|
|
||||||
This inventory is release-blocking. Every listed module must be validated against the new contracts.
|
|
||||||
|
|
||||||
## Backend Routers
|
|
||||||
|
|
||||||
- `src/server/routers/_app.ts`
|
|
||||||
- `src/server/routers/round.ts`
|
|
||||||
- `src/server/routers/filtering.ts`
|
|
||||||
- `src/server/routers/live-voting.ts`
|
|
||||||
- `src/server/routers/specialAward.ts`
|
|
||||||
- `src/server/routers/assignment.ts`
|
|
||||||
- `src/server/routers/evaluation.ts`
|
|
||||||
- `src/server/routers/file.ts`
|
|
||||||
- `src/server/routers/project.ts`
|
|
||||||
- `src/server/routers/project-pool.ts`
|
|
||||||
- `src/server/routers/application.ts`
|
|
||||||
- `src/server/routers/applicant.ts`
|
|
||||||
- `src/server/routers/export.ts`
|
|
||||||
- `src/server/routers/analytics.ts`
|
|
||||||
- `src/server/routers/program.ts`
|
|
||||||
- `src/server/routers/roundTemplate.ts`
|
|
||||||
- `src/server/routers/gracePeriod.ts`
|
|
||||||
- `src/server/routers/webhook.ts`
|
|
||||||
|
|
||||||
## Backend Services
|
|
||||||
|
|
||||||
- `src/server/services/smart-assignment.ts`
|
|
||||||
- `src/server/services/ai-filtering.ts`
|
|
||||||
- `src/server/services/ai-evaluation-summary.ts`
|
|
||||||
- `src/server/services/evaluation-reminders.ts`
|
|
||||||
- `src/server/services/in-app-notification.ts`
|
|
||||||
- `src/server/services/award-eligibility-job.ts`
|
|
||||||
- `src/server/services/webhook-dispatcher.ts`
|
|
||||||
|
|
||||||
## Admin Surfaces
|
|
||||||
|
|
||||||
- `src/app/(admin)/admin/rounds/**`
|
|
||||||
- `src/app/(admin)/admin/awards/**`
|
|
||||||
- `src/app/(admin)/admin/reports/page.tsx`
|
|
||||||
- `src/components/admin/round-pipeline.tsx`
|
|
||||||
- `src/components/admin/assign-projects-dialog.tsx`
|
|
||||||
- `src/components/admin/advance-projects-dialog.tsx`
|
|
||||||
- `src/components/admin/remove-projects-dialog.tsx`
|
|
||||||
- `src/components/admin/file-requirements-editor.tsx`
|
|
||||||
- `src/components/forms/round-type-settings.tsx`
|
|
||||||
|
|
||||||
## Jury, Applicant, Public
|
|
||||||
|
|
||||||
- `src/app/(jury)/jury/**`
|
|
||||||
- `src/components/jury/**`
|
|
||||||
- `src/app/(applicant)/applicant/**`
|
|
||||||
- `src/app/(public)/apply/**`
|
|
||||||
- `src/app/(public)/my-submission/**`
|
|
||||||
- `src/app/(public)/vote/**`
|
|
||||||
- `src/app/(public)/live-scores/**`
|
|
||||||
|
|
||||||
## Reporting and Exports
|
|
||||||
|
|
||||||
- chart and observer modules under `src/components/charts/**` and `src/components/observer/**`
|
|
||||||
- export and PDF paths under `src/components/shared/export-pdf-button.tsx`, `src/components/admin/pdf-report.tsx`, `src/server/routers/export.ts`
|
|
||||||
|
|
||||||
## Schema and Seed Paths
|
|
||||||
|
|
||||||
- `prisma/schema.prisma`
|
|
||||||
- relevant migrations and seed scripts under `prisma/`
|
|
||||||
|
|
||||||
## Mandatory Legacy Sweep Queries (Release Blockers)
|
|
||||||
|
|
||||||
1. `rg "trpc\.round" src`
|
|
||||||
2. `rg "\broundId\b" src/server src/components src/app`
|
|
||||||
3. `rg "round\.settingsJson|roundType" src/server src/components src/app`
|
|
||||||
4. `rg "model Round|enum RoundType" prisma/schema.prisma`
|
|
||||||
|
|
||||||
Allowlist exceptions (if any) must be explicit and approved in Phase 06 gates.
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
# Domain Model and Contracts
|
|
||||||
|
|
||||||
## Canonical Enums
|
|
||||||
|
|
||||||
- `StageType = INTAKE | FILTER | EVALUATION | SELECTION | LIVE_FINAL | RESULTS`
|
|
||||||
- `TrackKind = MAIN | AWARD | SHOWCASE`
|
|
||||||
- `RoutingMode = PARALLEL | EXCLUSIVE | POST_MAIN`
|
|
||||||
- `StageStatus = DRAFT | ACTIVE | CLOSED | ARCHIVED`
|
|
||||||
- `ProjectStageStateValue = PENDING | IN_PROGRESS | PASSED | REJECTED | ROUTED | COMPLETED | WITHDRAWN`
|
|
||||||
- `DecisionMode = JURY_VOTE | AWARD_MASTER | ADMIN`
|
|
||||||
- `OverrideReasonCode = DATA_CORRECTION | POLICY_EXCEPTION | JURY_CONFLICT | SPONSOR_DECISION | ADMIN_DISCRETION`
|
|
||||||
|
|
||||||
## Core Entities
|
|
||||||
|
|
||||||
### Pipeline
|
|
||||||
|
|
||||||
- `id`
|
|
||||||
- `programId`
|
|
||||||
- `name`
|
|
||||||
- `slug`
|
|
||||||
- `status`
|
|
||||||
- `settingsJson`
|
|
||||||
- `createdAt`, `updatedAt`
|
|
||||||
|
|
||||||
### Track
|
|
||||||
|
|
||||||
- `id`
|
|
||||||
- `pipelineId`
|
|
||||||
- `kind`
|
|
||||||
- `specialAwardId?`
|
|
||||||
- `name`
|
|
||||||
- `slug`
|
|
||||||
- `sortOrder`
|
|
||||||
- `routingModeDefault?`
|
|
||||||
- `decisionMode?`
|
|
||||||
|
|
||||||
### Stage
|
|
||||||
|
|
||||||
- `id`
|
|
||||||
- `trackId`
|
|
||||||
- `stageType`
|
|
||||||
- `name`
|
|
||||||
- `slug`
|
|
||||||
- `sortOrder`
|
|
||||||
- `status`
|
|
||||||
- `configVersion`
|
|
||||||
- `configJson`
|
|
||||||
- `windowOpenAt?`, `windowCloseAt?`
|
|
||||||
|
|
||||||
### StageTransition
|
|
||||||
|
|
||||||
- `id`
|
|
||||||
- `fromStageId`
|
|
||||||
- `toStageId`
|
|
||||||
- `priority`
|
|
||||||
- `isDefault`
|
|
||||||
- `guardJson`
|
|
||||||
- `actionJson`
|
|
||||||
|
|
||||||
### ProjectStageState
|
|
||||||
|
|
||||||
- `id`
|
|
||||||
- `projectId`
|
|
||||||
- `trackId`
|
|
||||||
- `stageId`
|
|
||||||
- `state`
|
|
||||||
- `enteredAt`, `exitedAt`
|
|
||||||
- `decisionRef?`
|
|
||||||
- `outcomeJson`
|
|
||||||
|
|
||||||
### RoutingRule
|
|
||||||
|
|
||||||
- `id`
|
|
||||||
- `pipelineId`
|
|
||||||
- `scope` (`GLOBAL|TRACK|STAGE`)
|
|
||||||
- `predicateJson`
|
|
||||||
- `destinationTrackId`
|
|
||||||
- `destinationStageId?`
|
|
||||||
- `priority`
|
|
||||||
- `isActive`
|
|
||||||
|
|
||||||
### Cohort and Live Runtime
|
|
||||||
|
|
||||||
- `Cohort(id, stageId, name, votingMode, isOpen, windowOpenAt?, windowCloseAt?)`
|
|
||||||
- `CohortProject(cohortId, projectId, sortOrder)`
|
|
||||||
- `LiveProgressCursor(id, stageId, sessionId, activeProjectId?, activeOrderIndex?, updatedBy, updatedAt)`
|
|
||||||
|
|
||||||
### Governance Entities
|
|
||||||
|
|
||||||
- `OverrideAction(id, entityType, entityId, oldValueJson, newValueJson, reasonCode, reasonText, actedBy, actedAt)`
|
|
||||||
- `DecisionAuditLog(id, entityType, entityId, eventType, payloadJson, actorId?, createdAt)`
|
|
||||||
|
|
||||||
## Stage Config Union Contracts
|
|
||||||
|
|
||||||
### IntakeConfig
|
|
||||||
|
|
||||||
- file requirements
|
|
||||||
- accepted MIME and size constraints
|
|
||||||
- deadline and late policy
|
|
||||||
- team invite policy
|
|
||||||
|
|
||||||
### FilterConfig
|
|
||||||
|
|
||||||
- deterministic gates
|
|
||||||
- AI rubric
|
|
||||||
- confidence thresholds
|
|
||||||
- manual queue policy
|
|
||||||
- rejection notification policy
|
|
||||||
|
|
||||||
### EvaluationConfig
|
|
||||||
|
|
||||||
- criteria schema
|
|
||||||
- assignment strategy
|
|
||||||
- review thresholds
|
|
||||||
- COI policy
|
|
||||||
- visibility rules
|
|
||||||
|
|
||||||
### SelectionConfig
|
|
||||||
|
|
||||||
- ranking source
|
|
||||||
- finalist target
|
|
||||||
- override permissions
|
|
||||||
- promotion mode (`auto_top_n`, `hybrid`, `manual`)
|
|
||||||
|
|
||||||
### LiveFinalConfig
|
|
||||||
|
|
||||||
- session behavior
|
|
||||||
- jury voting config
|
|
||||||
- audience voting config
|
|
||||||
- cohort policy
|
|
||||||
- reveal policy
|
|
||||||
- schedule hints (advisory)
|
|
||||||
|
|
||||||
### ResultsConfig
|
|
||||||
|
|
||||||
- ranking weight rules
|
|
||||||
- publication policy
|
|
||||||
- winner override rules
|
|
||||||
|
|
||||||
## Constraint Rules
|
|
||||||
|
|
||||||
1. Stage ordering unique per track (`trackId + sortOrder`).
|
|
||||||
2. `ProjectStageState` unique on (`projectId`, `trackId`, `stageId`).
|
|
||||||
3. `StageTransition` unique on (`fromStageId`, `toStageId`).
|
|
||||||
4. Transition destination must remain in same pipeline unless explicit routing rule applies.
|
|
||||||
5. Override records immutable after insert.
|
|
||||||
6. Decision audit log append-only.
|
|
||||||
|
|
||||||
## Index Priorities
|
|
||||||
|
|
||||||
1. `ProjectStageState(projectId, trackId, state)`
|
|
||||||
2. `ProjectStageState(stageId, state)`
|
|
||||||
3. `RoutingRule(pipelineId, isActive, priority)`
|
|
||||||
4. `StageTransition(fromStageId, priority)`
|
|
||||||
5. `LiveProgressCursor(stageId, sessionId)`
|
|
||||||
6. `DecisionAuditLog(entityType, entityId, createdAt)`
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
# Phase Gate Traceability
|
|
||||||
|
|
||||||
| Phase | Gate ID | Evidence Required | Test IDs / Checks | Blocking |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 00 | G-00-1 | decision lock snapshot | decision-log review | Yes |
|
|
||||||
| 00 | G-00-2 | contract alignment review | API/type contract diff | Yes |
|
|
||||||
| 01 | G-01-1 | schema compile output | `prisma generate` | Yes |
|
|
||||||
| 01 | G-01-2 | reset/reseed output | seed logs + integrity queries | Yes |
|
|
||||||
| 01 | G-01-3 | index and FK evidence | SQL verification scripts | Yes |
|
|
||||||
| 02 | G-02-1 | transition runtime proof | U-001/U-002/I-001 | Yes |
|
|
||||||
| 02 | G-02-2 | routing determinism proof | U-003/I-003/I-004 | Yes |
|
|
||||||
| 02 | G-02-3 | filtering policy proof | U-004/U-005/E-003 | Yes |
|
|
||||||
| 02 | G-02-4 | assignment guarantees proof | U-006/U-007/I-005 | Yes |
|
|
||||||
| 02 | G-02-5 | audit/override proof | U-008/I-008 | Yes |
|
|
||||||
| 03 | G-03-1 | create/edit parity proof | parity checklist | Yes |
|
|
||||||
| 03 | G-03-2 | wizard completion proof | E-001 | Yes |
|
|
||||||
| 03 | G-03-3 | modal safety proof | targeted UI regressions | Yes |
|
|
||||||
| 04 | G-04-1 | applicant flow proof | E-002 | Yes |
|
|
||||||
| 04 | G-04-2 | jury flow proof | E-004 | Yes |
|
|
||||||
| 04 | G-04-3 | live audience proof | E-006/E-007/I-006/I-007 | Yes |
|
|
||||||
| 05 | G-05-1 | award routing proof | I-003/I-004 | Yes |
|
|
||||||
| 05 | G-05-2 | governance auth proof | U-010 + auth tests | Yes |
|
|
||||||
| 05 | G-05-3 | winner and audit proof | E-008 + I-008 | Yes |
|
|
||||||
| 06 | G-06-1 | dependency checklist complete | module sign-off evidence | Yes |
|
|
||||||
| 06 | G-06-2 | legacy sweeps clean | mandatory rg sweeps | Yes |
|
|
||||||
| 06 | G-06-3 | external consumer validation | webhook/export checks | Yes |
|
|
||||||
| 07 | G-07-1 | full test report | full matrix results | Yes |
|
|
||||||
| 07 | G-07-2 | performance report | P-001..P-004 evidence | Yes |
|
|
||||||
| 07 | G-07-3 | release evidence package | signed report template | Yes |
|
|
||||||
| 07 | G-07-4 | atomic cutover proof | release runbook logs | Yes |
|
|
||||||
|
|
||||||
Rule: no phase closes until all gates are complete with linked artifacts.
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
# Program Charter
|
|
||||||
|
|
||||||
## Mission
|
|
||||||
Deliver a complete, stage-native orchestration platform for MOPC that supports:
|
|
||||||
|
|
||||||
- edition-scoped intake and progression
|
|
||||||
- deterministic filtering and assignment
|
|
||||||
- parallel and exclusive award flows
|
|
||||||
- admin-driven live finals operations
|
|
||||||
- full auditability and release-grade validation
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
### In Scope
|
|
||||||
|
|
||||||
- Canonical data model rebuild around pipeline/track/stage.
|
|
||||||
- Backend orchestration engine (transition, routing, filtering, assignment, live, notifications, audit).
|
|
||||||
- Admin setup and control-plane UX refit.
|
|
||||||
- Applicant, jury, observer, and audience flow refit to new contracts.
|
|
||||||
- Special award governance modes including `AWARD_MASTER`.
|
|
||||||
- Platform-wide dependency refit of schema/runtime consumers.
|
|
||||||
- Full validation and atomic release process.
|
|
||||||
|
|
||||||
### Out of Scope
|
|
||||||
|
|
||||||
- Legacy contract compatibility bridges.
|
|
||||||
- Cosmetic redesign or major brand refresh.
|
|
||||||
- Non-orchestration feature expansion unrelated to competition lifecycle.
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
1. Admin setup can fully configure required competition behavior in create-time flow.
|
|
||||||
2. Stage progression and routing are deterministic and explainable.
|
|
||||||
3. Award tracks run without ad hoc side logic.
|
|
||||||
4. Live event operations are resilient under reconnect and burst traffic.
|
|
||||||
5. All platform dependencies are migrated and verified before release.
|
|
||||||
|
|
||||||
## Quality Bar
|
|
||||||
|
|
||||||
- Typed contracts at schema, API, and UI boundaries.
|
|
||||||
- Idempotent mutation semantics for high-risk operations.
|
|
||||||
- Strong audit trails for every governance-sensitive action.
|
|
||||||
- Mobile-safe interaction quality for live audience and jury experiences.
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
# Release Evidence Report Template
|
|
||||||
|
|
||||||
## Build Metadata
|
|
||||||
|
|
||||||
- Date:
|
|
||||||
- Commit SHA:
|
|
||||||
- Environment:
|
|
||||||
- Operator:
|
|
||||||
|
|
||||||
## Phase Completion Summary
|
|
||||||
|
|
||||||
- Phase 00:
|
|
||||||
- Phase 01:
|
|
||||||
- Phase 02:
|
|
||||||
- Phase 03:
|
|
||||||
- Phase 04:
|
|
||||||
- Phase 05:
|
|
||||||
- Phase 06:
|
|
||||||
- Phase 07:
|
|
||||||
|
|
||||||
## Test Summary
|
|
||||||
|
|
||||||
- Unit: pass/fail counts
|
|
||||||
- Integration: pass/fail counts
|
|
||||||
- E2E: pass/fail counts
|
|
||||||
- Performance: pass/fail counts
|
|
||||||
|
|
||||||
## Mandatory Scenario Results
|
|
||||||
|
|
||||||
| ID | Result | Evidence Link | Notes |
|
|
||||||
|---|---|---|---|
|
|
||||||
| E-001 | | | |
|
|
||||||
| E-002 | | | |
|
|
||||||
| E-003 | | | |
|
|
||||||
| E-004 | | | |
|
|
||||||
| E-005 | | | |
|
|
||||||
| E-006 | | | |
|
|
||||||
| E-007 | | | |
|
|
||||||
| E-008 | | | |
|
|
||||||
|
|
||||||
## Performance Results
|
|
||||||
|
|
||||||
| ID | Result | Evidence Link | Notes |
|
|
||||||
|---|---|---|---|
|
|
||||||
| P-001 | | | |
|
|
||||||
| P-002 | | | |
|
|
||||||
| P-003 | | | |
|
|
||||||
| P-004 | | | |
|
|
||||||
|
|
||||||
## Legacy Sweep Results
|
|
||||||
|
|
||||||
- `trpc.round` references:
|
|
||||||
- `roundId` orchestration references:
|
|
||||||
- `round.settingsJson` behavior references:
|
|
||||||
- schema `Round` references:
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
|
|
||||||
- None / list with severity and owner
|
|
||||||
|
|
||||||
## Sign-Off
|
|
||||||
|
|
||||||
- Engineering:
|
|
||||||
- Product:
|
|
||||||
- Operations:
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# Risk Register
|
|
||||||
|
|
||||||
| ID | Risk | Probability | Impact | Mitigation | Owner | Status |
|
|
||||||
|---|---|---|---|---|---|---|
|
|
||||||
| R-001 | Hidden legacy coupling after schema rebuild | Medium | High | Mandatory symbol sweeps + module refit gates | Eng Lead | Open |
|
|
||||||
| R-002 | Assignment coverage edge-case failures at scale | Medium | High | Hard overflow policy + P-001 load tests | Backend Lead | Open |
|
|
||||||
| R-003 | Award governance permission drift | Low | High | explicit authz tests for each decision mode | Security Lead | Open |
|
|
||||||
| R-004 | Live cursor race conditions during events | Medium | High | optimistic lock + replay-safe event handling | Realtime Lead | Open |
|
|
||||||
| R-005 | Audience vote dedupe regressions | Medium | Medium | dedupe key contract + E-007 + I-007 tests | Backend Lead | Open |
|
|
||||||
| R-006 | Migration/reseed script incompleteness | Low | High | repeatable reset/reseed rehearsals | DB Owner | Open |
|
|
||||||
| R-007 | Reporting consumers break on contract shift | Medium | Medium | phase-06 consumer validation checklist | Data Lead | Open |
|
|
||||||
| R-008 | Scope creep during dependency refit | High | Medium | strict out-of-scope policy, defer noncritical features | PM | Open |
|
|
||||||
|
|
||||||
## Risk Handling Policy
|
|
||||||
|
|
||||||
- `High impact` items require explicit mitigation evidence before phase close.
|
|
||||||
- `Open` high/high risks block release in Phase 07.
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
# Test Matrix
|
|
||||||
|
|
||||||
All IDs are mandatory unless explicitly marked non-blocking with sign-off.
|
|
||||||
|
|
||||||
## Unit Tests
|
|
||||||
|
|
||||||
| ID | Area | Scenario | Expected |
|
|
||||||
|---|---|---|---|
|
|
||||||
| U-001 | Transition Engine | legal transition | persisted with audit event |
|
|
||||||
| U-002 | Transition Engine | illegal transition | typed validation error |
|
|
||||||
| U-003 | Routing | multiple rule match | deterministic priority winner |
|
|
||||||
| U-004 | Filtering Gates | missing required docs | blocked before AI pass |
|
|
||||||
| U-005 | AI Banding | uncertain confidence band | routed to manual queue |
|
|
||||||
| U-006 | Assignment | COI conflict | excluded from pool |
|
|
||||||
| U-007 | Assignment | insufficient capacity | overflow flagged + coverage preserved |
|
|
||||||
| U-008 | Override | missing reason fields | mutation rejected |
|
|
||||||
| U-009 | Live Cursor | concurrent cursor update | conflict handled and retried |
|
|
||||||
| U-010 | Award Governance | `AWARD_MASTER` on unauthorized award | forbidden |
|
|
||||||
|
|
||||||
## Integration Tests
|
|
||||||
|
|
||||||
| ID | Area | Scenario | Expected |
|
|
||||||
|---|---|---|---|
|
|
||||||
| I-001 | Pipeline CRUD | create/update/publish | graph integrity maintained |
|
|
||||||
| I-002 | Stage Config | invalid config schema | rejected |
|
|
||||||
| I-003 | Transition + Routing | filter pass to main + award parallel | dual states created |
|
|
||||||
| I-004 | Award Exclusive Routing | exclusive route | removed from main continuation |
|
|
||||||
| I-005 | Assignment API | preview vs execute parity | same constraints and outcomes |
|
|
||||||
| I-006 | Live Runtime | jump + reorder + open/close windows | consistent cursor state |
|
|
||||||
| I-007 | Cohort Voting | closed window submit | vote rejected |
|
|
||||||
| I-008 | Decision Audit | override applied | complete immutable timeline |
|
|
||||||
|
|
||||||
## End-to-End Tests
|
|
||||||
|
|
||||||
| ID | Persona | Scenario | Expected |
|
|
||||||
|---|---|---|---|
|
|
||||||
| E-001 | Admin | complete setup via wizard | no hidden edit-only blockers |
|
|
||||||
| E-002 | Applicant | upload intake requirements | status and deadlines enforced |
|
|
||||||
| E-003 | Admin | run filtering stage | gates + AI + manual queue behave |
|
|
||||||
| E-004 | Jury | complete evaluation workflow | criteria and lock policy enforced |
|
|
||||||
| E-005 | Admin | selection + override | finalists and audit aligned |
|
|
||||||
| E-006 | Live Admin | advance/back/jump + reorder | jury and audience sync realtime |
|
|
||||||
| E-007 | Audience | vote by cohort on mobile | visibility and dedupe enforced |
|
|
||||||
| E-008 | Admin | finalize results | ranking and publish outputs valid |
|
|
||||||
|
|
||||||
## Performance and Resilience
|
|
||||||
|
|
||||||
| ID | Area | Scenario | Threshold |
|
|
||||||
|---|---|---|---|
|
|
||||||
| P-001 | Assignment | 1000+ project batch | under agreed SLA |
|
|
||||||
| P-002 | Filtering | large AI queue | deterministic retry, no dropped jobs |
|
|
||||||
| P-003 | Live Voting | peak audience burst | acceptable p95 and no data loss |
|
|
||||||
| P-004 | Reconnect | disconnect/reconnect | state converges quickly |
|
|
||||||
|
|
||||||
## Release Block Rule
|
|
||||||
|
|
||||||
Any failing `U-*`, `I-*`, `E-*`, or `P-*` is release-blocking unless signed waiver exists.
|
|
||||||
|
|
@ -1,661 +0,0 @@
|
||||||
# Phase 0: Domain Model Validation
|
|
||||||
|
|
||||||
**Date**: 2026-02-12
|
|
||||||
**Status**: ✅ VALIDATED
|
|
||||||
**Reviewer**: Claude Sonnet 4.5
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
This document validates the proposed canonical domain model from the redesign specification against the current MOPC Prisma schema. The validation confirms that all proposed entities, enums, and constraints are architecturally sound and can be implemented without conflicts.
|
|
||||||
|
|
||||||
**Result**: ✅ **APPROVED** - Domain model is complete, unambiguous, and ready for implementation in Phase 1.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Canonical Enums Validation
|
|
||||||
|
|
||||||
### 1.1 New Enums (To be Added)
|
|
||||||
|
|
||||||
| Enum Name | Values | Status | Notes |
|
|
||||||
|-----------|--------|--------|-------|
|
|
||||||
| **StageType** | `INTAKE`, `FILTER`, `EVALUATION`, `SELECTION`, `LIVE_FINAL`, `RESULTS` | ✅ Complete | Replaces implicit `RoundType` semantics |
|
|
||||||
| **TrackKind** | `MAIN`, `AWARD`, `SHOWCASE` | ✅ Complete | Enables first-class special awards |
|
|
||||||
| **RoutingMode** | `PARALLEL`, `EXCLUSIVE`, `POST_MAIN` | ✅ Complete | Controls award routing behavior |
|
|
||||||
| **StageStatus** | `DRAFT`, `ACTIVE`, `CLOSED`, `ARCHIVED` | ✅ Complete | Aligns with existing `RoundStatus` |
|
|
||||||
| **ProjectStageStateValue** | `PENDING`, `IN_PROGRESS`, `PASSED`, `REJECTED`, `ROUTED`, `COMPLETED`, `WITHDRAWN` | ✅ Complete | Explicit state machine for project progression |
|
|
||||||
| **DecisionMode** | `JURY_VOTE`, `AWARD_MASTER`, `ADMIN` | ✅ Complete | Award governance modes |
|
|
||||||
| **OverrideReasonCode** | `DATA_CORRECTION`, `POLICY_EXCEPTION`, `JURY_CONFLICT`, `SPONSOR_DECISION`, `ADMIN_DISCRETION` | ✅ Complete | Mandatory reason tracking for overrides |
|
|
||||||
|
|
||||||
**Validation Notes**:
|
|
||||||
- All enum values are mutually exclusive and unambiguous
|
|
||||||
- No conflicts with existing enums
|
|
||||||
- `StageStatus` deliberately mirrors `RoundStatus` for familiarity
|
|
||||||
- `ProjectStageStateValue` provides complete state coverage
|
|
||||||
|
|
||||||
### 1.2 Existing Enums (To be Extended or Deprecated)
|
|
||||||
|
|
||||||
| Current Enum | Action | Rationale |
|
|
||||||
|--------------|--------|-----------|
|
|
||||||
| **RoundType** | ⚠️ DEPRECATE in Phase 6 | Replaced by `StageType` + stage config |
|
|
||||||
| **RoundStatus** | ⚠️ DEPRECATE in Phase 6 | Replaced by `StageStatus` |
|
|
||||||
| **UserRole** | ✅ EXTEND | Add `AWARD_MASTER` and `AUDIENCE` values |
|
|
||||||
| **ProjectStatus** | ⚠️ DEPRECATE in Phase 6 | Replaced by `ProjectStageState` records |
|
|
||||||
|
|
||||||
**Action Items for Phase 1**:
|
|
||||||
- Add new enums to `prisma/schema.prisma`
|
|
||||||
- Extend `UserRole` with `AWARD_MASTER` and `AUDIENCE`
|
|
||||||
- Do NOT remove deprecated enums yet (Phase 6)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Core Entities Validation
|
|
||||||
|
|
||||||
### 2.1 New Core Models
|
|
||||||
|
|
||||||
#### Pipeline
|
|
||||||
```prisma
|
|
||||||
model Pipeline {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
programId String
|
|
||||||
name String
|
|
||||||
slug String @unique
|
|
||||||
status StageStatus @default(DRAFT)
|
|
||||||
settingsJson Json? @db.JsonB
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
program Program @relation(fields: [programId], references: [id])
|
|
||||||
tracks Track[]
|
|
||||||
routingRules RoutingRule[]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ All fields align with domain model spec
|
|
||||||
- ✅ `programId` FK ensures proper scoping
|
|
||||||
- ✅ `slug` unique constraint enables URL-friendly references
|
|
||||||
- ✅ `settingsJson` provides extensibility
|
|
||||||
- ✅ Relationships properly defined
|
|
||||||
|
|
||||||
#### Track
|
|
||||||
```prisma
|
|
||||||
model Track {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
pipelineId String
|
|
||||||
kind TrackKind @default(MAIN)
|
|
||||||
specialAwardId String? @unique
|
|
||||||
name String
|
|
||||||
slug String
|
|
||||||
sortOrder Int
|
|
||||||
routingModeDefault RoutingMode?
|
|
||||||
decisionMode DecisionMode?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
|
||||||
specialAward SpecialAward? @relation(fields: [specialAwardId], references: [id])
|
|
||||||
stages Stage[]
|
|
||||||
projectStageStates ProjectStageState[]
|
|
||||||
routingRules RoutingRule[] @relation("DestinationTrack")
|
|
||||||
|
|
||||||
@@unique([pipelineId, slug])
|
|
||||||
@@index([pipelineId, sortOrder])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ `kind` determines track type (MAIN vs AWARD vs SHOWCASE)
|
|
||||||
- ✅ `specialAwardId` nullable for MAIN tracks, required for AWARD tracks
|
|
||||||
- ✅ `sortOrder` enables explicit ordering
|
|
||||||
- ✅ `routingModeDefault` and `decisionMode` provide award-specific config
|
|
||||||
- ✅ Unique constraint on `(pipelineId, slug)` prevents duplicates
|
|
||||||
- ✅ Index on `(pipelineId, sortOrder)` optimizes ordering queries
|
|
||||||
|
|
||||||
#### Stage
|
|
||||||
```prisma
|
|
||||||
model Stage {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
trackId String
|
|
||||||
stageType StageType
|
|
||||||
name String
|
|
||||||
slug String
|
|
||||||
sortOrder Int
|
|
||||||
status StageStatus @default(DRAFT)
|
|
||||||
configVersion Int @default(1)
|
|
||||||
configJson Json @db.JsonB
|
|
||||||
windowOpenAt DateTime?
|
|
||||||
windowCloseAt DateTime?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
track Track @relation(fields: [trackId], references: [id], onDelete: Cascade)
|
|
||||||
projectStageStates ProjectStageState[]
|
|
||||||
transitionsFrom StageTransition[] @relation("FromStage")
|
|
||||||
transitionsTo StageTransition[] @relation("ToStage")
|
|
||||||
cohorts Cohort[]
|
|
||||||
liveProgressCursor LiveProgressCursor?
|
|
||||||
|
|
||||||
@@unique([trackId, slug])
|
|
||||||
@@unique([trackId, sortOrder])
|
|
||||||
@@index([trackId, status])
|
|
||||||
@@index([status, windowOpenAt, windowCloseAt])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ `stageType` determines config schema (union type)
|
|
||||||
- ✅ `configVersion` enables config evolution
|
|
||||||
- ✅ `configJson` stores type-specific configuration
|
|
||||||
- ✅ `windowOpenAt`/`windowCloseAt` provide voting windows
|
|
||||||
- ✅ Unique constraints prevent duplicate `slug` or `sortOrder` per track
|
|
||||||
- ✅ Indexes optimize status and window queries
|
|
||||||
|
|
||||||
#### StageTransition
|
|
||||||
```prisma
|
|
||||||
model StageTransition {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
fromStageId String
|
|
||||||
toStageId String
|
|
||||||
priority Int @default(0)
|
|
||||||
isDefault Boolean @default(false)
|
|
||||||
guardJson Json? @db.JsonB
|
|
||||||
actionJson Json? @db.JsonB
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
fromStage Stage @relation("FromStage", fields: [fromStageId], references: [id], onDelete: Cascade)
|
|
||||||
toStage Stage @relation("ToStage", fields: [toStageId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([fromStageId, toStageId])
|
|
||||||
@@index([fromStageId, priority])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ Explicit state machine definition
|
|
||||||
- ✅ `priority` enables deterministic tie-breaking
|
|
||||||
- ✅ `isDefault` marks default transition path
|
|
||||||
- ✅ `guardJson` and `actionJson` provide transition logic
|
|
||||||
- ✅ Unique constraint prevents duplicate transitions
|
|
||||||
- ✅ Index on `(fromStageId, priority)` optimizes transition lookup
|
|
||||||
|
|
||||||
#### ProjectStageState
|
|
||||||
```prisma
|
|
||||||
model ProjectStageState {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
projectId String
|
|
||||||
trackId String
|
|
||||||
stageId String
|
|
||||||
state ProjectStageStateValue
|
|
||||||
enteredAt DateTime @default(now())
|
|
||||||
exitedAt DateTime?
|
|
||||||
decisionRef String?
|
|
||||||
outcomeJson Json? @db.JsonB
|
|
||||||
|
|
||||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
||||||
track Track @relation(fields: [trackId], references: [id], onDelete: Cascade)
|
|
||||||
stage Stage @relation(fields: [stageId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([projectId, trackId, stageId])
|
|
||||||
@@index([projectId, trackId, state])
|
|
||||||
@@index([stageId, state])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ Replaces single `roundId` pointer with explicit state records
|
|
||||||
- ✅ Unique constraint on `(projectId, trackId, stageId)` prevents duplicates
|
|
||||||
- ✅ `trackId` enables parallel track progression (main + awards)
|
|
||||||
- ✅ `state` provides current progression status
|
|
||||||
- ✅ `enteredAt`/`exitedAt` track state duration
|
|
||||||
- ✅ `decisionRef` links to decision audit
|
|
||||||
- ✅ `outcomeJson` stores stage-specific metadata
|
|
||||||
- ✅ Indexes optimize project queries and stage reporting
|
|
||||||
|
|
||||||
#### RoutingRule
|
|
||||||
```prisma
|
|
||||||
model RoutingRule {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
pipelineId String
|
|
||||||
scope String // 'GLOBAL' | 'TRACK' | 'STAGE'
|
|
||||||
predicateJson Json @db.JsonB
|
|
||||||
destinationTrackId String
|
|
||||||
destinationStageId String?
|
|
||||||
priority Int @default(0)
|
|
||||||
isActive Boolean @default(true)
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
|
||||||
destinationTrack Track @relation("DestinationTrack", fields: [destinationTrackId], references: [id])
|
|
||||||
|
|
||||||
@@index([pipelineId, isActive, priority])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ `scope` determines rule evaluation context
|
|
||||||
- ✅ `predicateJson` contains matching logic
|
|
||||||
- ✅ `destinationTrackId` required, `destinationStageId` optional
|
|
||||||
- ✅ `priority` enables deterministic rule ordering
|
|
||||||
- ✅ `isActive` allows toggling without deletion
|
|
||||||
- ✅ Index on `(pipelineId, isActive, priority)` optimizes rule lookup
|
|
||||||
|
|
||||||
### 2.2 Live Runtime Models
|
|
||||||
|
|
||||||
#### Cohort
|
|
||||||
```prisma
|
|
||||||
model Cohort {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
stageId String
|
|
||||||
name String
|
|
||||||
votingMode String // 'JURY' | 'AUDIENCE' | 'HYBRID'
|
|
||||||
isOpen Boolean @default(false)
|
|
||||||
windowOpenAt DateTime?
|
|
||||||
windowCloseAt DateTime?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
stage Stage @relation(fields: [stageId], references: [id], onDelete: Cascade)
|
|
||||||
cohortProjects CohortProject[]
|
|
||||||
|
|
||||||
@@index([stageId, isOpen])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ Groups projects for live voting
|
|
||||||
- ✅ `votingMode` determines who can vote
|
|
||||||
- ✅ `isOpen` controls voting acceptance
|
|
||||||
- ✅ `windowOpenAt`/`windowCloseAt` provide time bounds
|
|
||||||
- ✅ Index on `(stageId, isOpen)` optimizes active cohort queries
|
|
||||||
|
|
||||||
#### CohortProject
|
|
||||||
```prisma
|
|
||||||
model CohortProject {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
cohortId String
|
|
||||||
projectId String
|
|
||||||
sortOrder Int
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
cohort Cohort @relation(fields: [cohortId], references: [id], onDelete: Cascade)
|
|
||||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([cohortId, projectId])
|
|
||||||
@@index([cohortId, sortOrder])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ Many-to-many join table for cohort membership
|
|
||||||
- ✅ `sortOrder` enables presentation ordering
|
|
||||||
- ✅ Unique constraint prevents duplicate membership
|
|
||||||
- ✅ Index on `(cohortId, sortOrder)` optimizes ordering queries
|
|
||||||
|
|
||||||
#### LiveProgressCursor
|
|
||||||
```prisma
|
|
||||||
model LiveProgressCursor {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
stageId String @unique
|
|
||||||
sessionId String
|
|
||||||
activeProjectId String?
|
|
||||||
activeOrderIndex Int?
|
|
||||||
updatedBy String
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
stage Stage @relation(fields: [stageId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@index([stageId, sessionId])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ Admin cursor as source of truth for live events
|
|
||||||
- ✅ `stageId` unique ensures one cursor per stage
|
|
||||||
- ✅ `sessionId` tracks live session
|
|
||||||
- ✅ `activeProjectId` and `activeOrderIndex` track current position
|
|
||||||
- ✅ `updatedBy` tracks admin actor
|
|
||||||
- ✅ Index on `(stageId, sessionId)` optimizes live queries
|
|
||||||
|
|
||||||
### 2.3 Governance Models
|
|
||||||
|
|
||||||
#### OverrideAction
|
|
||||||
```prisma
|
|
||||||
model OverrideAction {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
entityType String // 'PROJECT' | 'STAGE' | 'COHORT' | 'AWARD'
|
|
||||||
entityId String
|
|
||||||
oldValueJson Json? @db.JsonB
|
|
||||||
newValueJson Json @db.JsonB
|
|
||||||
reasonCode OverrideReasonCode
|
|
||||||
reasonText String
|
|
||||||
actedBy String
|
|
||||||
actedAt DateTime @default(now())
|
|
||||||
|
|
||||||
@@index([entityType, entityId, actedAt])
|
|
||||||
@@index([actedBy, actedAt])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ Immutable override audit trail
|
|
||||||
- ✅ `reasonCode` enum ensures valid reasons
|
|
||||||
- ✅ `reasonText` captures human explanation
|
|
||||||
- ✅ `actedBy` tracks actor
|
|
||||||
- ✅ Indexes optimize entity and actor queries
|
|
||||||
|
|
||||||
#### DecisionAuditLog
|
|
||||||
```prisma
|
|
||||||
model DecisionAuditLog {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
entityType String // 'STAGE' | 'ROUTING' | 'FILTERING' | 'ASSIGNMENT' | 'LIVE' | 'AWARD'
|
|
||||||
entityId String
|
|
||||||
eventType String // 'stage.transitioned' | 'routing.executed' | etc.
|
|
||||||
payloadJson Json @db.JsonB
|
|
||||||
actorId String?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
@@index([entityType, entityId, createdAt])
|
|
||||||
@@index([eventType, createdAt])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- ✅ Append-only audit log for all decisions
|
|
||||||
- ✅ `eventType` aligns with event taxonomy
|
|
||||||
- ✅ `payloadJson` captures full event context
|
|
||||||
- ✅ `actorId` nullable for system events
|
|
||||||
- ✅ Indexes optimize entity timeline and event queries
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Constraint Rules Validation
|
|
||||||
|
|
||||||
### 3.1 Unique Constraints
|
|
||||||
|
|
||||||
| Model | Constraint | Status | Purpose |
|
|
||||||
|-------|-----------|--------|---------|
|
|
||||||
| Pipeline | `slug` | ✅ Valid | URL-friendly unique identifier |
|
|
||||||
| Track | `(pipelineId, slug)` | ✅ Valid | Prevent duplicate slugs per pipeline |
|
|
||||||
| Track | `(pipelineId, sortOrder)` | ❌ MISSING | **Correction needed**: Add unique constraint per domain model spec |
|
|
||||||
| Stage | `(trackId, slug)` | ✅ Valid | Prevent duplicate slugs per track |
|
|
||||||
| Stage | `(trackId, sortOrder)` | ✅ Valid | Prevent duplicate sort orders per track |
|
|
||||||
| StageTransition | `(fromStageId, toStageId)` | ✅ Valid | Prevent duplicate transitions |
|
|
||||||
| ProjectStageState | `(projectId, trackId, stageId)` | ✅ Valid | One state record per project/track/stage combo |
|
|
||||||
| CohortProject | `(cohortId, projectId)` | ✅ Valid | Prevent duplicate cohort membership |
|
|
||||||
|
|
||||||
**Action Items for Phase 1**:
|
|
||||||
- ✅ Most constraints align with spec
|
|
||||||
- ⚠️ **Add**: Unique constraint on `Track(pipelineId, sortOrder)` per domain model requirement
|
|
||||||
|
|
||||||
### 3.2 Foreign Key Constraints
|
|
||||||
|
|
||||||
All FK relationships validated:
|
|
||||||
- ✅ Cascade deletes properly configured
|
|
||||||
- ✅ Referential integrity preserved
|
|
||||||
- ✅ Nullable FKs appropriately marked
|
|
||||||
|
|
||||||
### 3.3 Index Priorities
|
|
||||||
|
|
||||||
All required indexes from domain model spec are present:
|
|
||||||
1. ✅ `ProjectStageState(projectId, trackId, state)`
|
|
||||||
2. ✅ `ProjectStageState(stageId, state)`
|
|
||||||
3. ✅ `RoutingRule(pipelineId, isActive, priority)`
|
|
||||||
4. ✅ `StageTransition(fromStageId, priority)`
|
|
||||||
5. ✅ `LiveProgressCursor(stageId, sessionId)`
|
|
||||||
6. ✅ `DecisionAuditLog(entityType, entityId, createdAt)`
|
|
||||||
|
|
||||||
**Additional indexes added**:
|
|
||||||
- `Track(pipelineId, sortOrder)` - optimizes track ordering
|
|
||||||
- `Stage(trackId, status)` - optimizes status filtering
|
|
||||||
- `Stage(status, windowOpenAt, windowCloseAt)` - optimizes window queries
|
|
||||||
- `Cohort(stageId, isOpen)` - optimizes active cohort queries
|
|
||||||
- `CohortProject(cohortId, sortOrder)` - optimizes presentation ordering
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. JSON Field Contracts Validation
|
|
||||||
|
|
||||||
### 4.1 Pipeline.settingsJson
|
|
||||||
**Purpose**: Pipeline-level configuration
|
|
||||||
**Expected Schema**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
notificationDefaults?: {
|
|
||||||
enabled: boolean;
|
|
||||||
channels: string[];
|
|
||||||
};
|
|
||||||
aiConfig?: {
|
|
||||||
filteringEnabled: boolean;
|
|
||||||
assignmentEnabled: boolean;
|
|
||||||
};
|
|
||||||
// ... extensible
|
|
||||||
}
|
|
||||||
```
|
|
||||||
**Status**: ✅ Flexible, extensible design
|
|
||||||
|
|
||||||
### 4.2 Stage.configJson
|
|
||||||
**Purpose**: Stage-type-specific configuration (union type)
|
|
||||||
**Expected Schemas** (by `stageType`):
|
|
||||||
|
|
||||||
**INTAKE**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
fileRequirements: FileRequirement[];
|
|
||||||
deadlinePolicy: 'strict' | 'flexible';
|
|
||||||
lateSubmissionAllowed: boolean;
|
|
||||||
teamInvitePolicy: {...};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**FILTER**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
deterministicGates: Gate[];
|
|
||||||
aiRubric: {...};
|
|
||||||
confidenceThresholds: {...};
|
|
||||||
manualQueuePolicy: {...};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**EVALUATION**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
criteria: Criterion[];
|
|
||||||
assignmentStrategy: {...};
|
|
||||||
reviewThresholds: {...};
|
|
||||||
coiPolicy: {...};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**SELECTION**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
rankingSource: 'scores' | 'votes' | 'hybrid';
|
|
||||||
finalistTarget: number;
|
|
||||||
promotionMode: 'auto_top_n' | 'hybrid' | 'manual';
|
|
||||||
overridePermissions: {...};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**LIVE_FINAL**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
sessionBehavior: {...};
|
|
||||||
juryVotingConfig: {...};
|
|
||||||
audienceVotingConfig: {...};
|
|
||||||
cohortPolicy: {...};
|
|
||||||
revealPolicy: {...};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**RESULTS**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
rankingWeightRules: {...};
|
|
||||||
publicationPolicy: {...};
|
|
||||||
winnerOverrideRules: {...};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Status**: ✅ Complete coverage, all stage types defined
|
|
||||||
|
|
||||||
### 4.3 ProjectStageState.outcomeJson
|
|
||||||
**Purpose**: Stage-specific outcome metadata
|
|
||||||
**Expected Schema**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
scores?: Record<string, number>;
|
|
||||||
decision?: string;
|
|
||||||
feedback?: string;
|
|
||||||
aiConfidence?: number;
|
|
||||||
manualReview?: boolean;
|
|
||||||
// ... extensible per stage type
|
|
||||||
}
|
|
||||||
```
|
|
||||||
**Status**: ✅ Flexible, extensible design
|
|
||||||
|
|
||||||
### 4.4 StageTransition.guardJson / actionJson
|
|
||||||
**Purpose**: Transition logic and side effects
|
|
||||||
**Expected Schemas**:
|
|
||||||
|
|
||||||
**guardJson**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
conditions: Array<{
|
|
||||||
field: string;
|
|
||||||
operator: 'eq' | 'gt' | 'lt' | 'in' | 'exists';
|
|
||||||
value: any;
|
|
||||||
}>;
|
|
||||||
requireAll: boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**actionJson**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
actions: Array<{
|
|
||||||
type: 'notify' | 'update_field' | 'emit_event';
|
|
||||||
config: any;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
**Status**: ✅ Provides transition programmability
|
|
||||||
|
|
||||||
### 4.5 RoutingRule.predicateJson
|
|
||||||
**Purpose**: Rule matching logic
|
|
||||||
**Expected Schema**:
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
conditions: Array<{
|
|
||||||
field: string; // e.g., 'project.category', 'project.tags'
|
|
||||||
operator: 'eq' | 'in' | 'contains' | 'matches';
|
|
||||||
value: any;
|
|
||||||
}>;
|
|
||||||
matchAll: boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
**Status**: ✅ Deterministic rule matching
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Data Initialization Rules Validation
|
|
||||||
|
|
||||||
### 5.1 Seed Requirements
|
|
||||||
|
|
||||||
**From Phase 1 spec**:
|
|
||||||
- ✅ Every seeded project must start with one intake-stage state
|
|
||||||
- ✅ Seed must include main track plus at least two award tracks with different routing modes
|
|
||||||
- ✅ Seed must include representative roles: admins, jury, applicants, observer, audience contexts
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- Seed requirements are clear and achievable
|
|
||||||
- Will be implemented in `prisma/seed.ts` during Phase 1
|
|
||||||
|
|
||||||
### 5.2 Integrity Checks
|
|
||||||
|
|
||||||
**Required checks** (from schema-spec.md):
|
|
||||||
- ✅ No orphan states
|
|
||||||
- ✅ No invalid transition targets across pipelines
|
|
||||||
- ✅ No duplicate active state rows for same `(project, track, stage)`
|
|
||||||
|
|
||||||
**Validation**:
|
|
||||||
- Integrity check SQL will be created in Phase 1
|
|
||||||
- Constraints and indexes prevent most integrity violations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Compatibility with Existing Models
|
|
||||||
|
|
||||||
### 6.1 Models That Remain Unchanged
|
|
||||||
- ✅ `User` - No changes needed
|
|
||||||
- ✅ `Program` - Gains `Pipeline` relation
|
|
||||||
- ✅ `Project` - Gains `ProjectStageState` relation, deprecates `roundId`
|
|
||||||
- ✅ `SpecialAward` - Gains `Track` relation
|
|
||||||
- ✅ `Evaluation` - No changes to core model
|
|
||||||
- ✅ `Assignment` - May need `stageId` addition (Phase 2)
|
|
||||||
|
|
||||||
### 6.2 Models to be Deprecated (Phase 6)
|
|
||||||
- ⚠️ `Round` - Replaced by `Pipeline` + `Track` + `Stage`
|
|
||||||
- ⚠️ Round-specific relations will be refactored
|
|
||||||
|
|
||||||
### 6.3 Integration Points
|
|
||||||
- ✅ `User.role` extends to include `AWARD_MASTER` and `AUDIENCE`
|
|
||||||
- ✅ `Program` gains `pipelines` relation
|
|
||||||
- ✅ `Project` gains `projectStageStates` relation
|
|
||||||
- ✅ `SpecialAward` gains `track` relation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Validation Summary
|
|
||||||
|
|
||||||
### ✅ Approved Elements
|
|
||||||
1. All 7 new canonical enums are complete and unambiguous
|
|
||||||
2. All 12 new core models align with domain model spec
|
|
||||||
3. All unique constraints match spec requirements (1 minor correction needed)
|
|
||||||
4. All foreign key relationships properly defined
|
|
||||||
5. All required indexes present (plus beneficial additions)
|
|
||||||
6. JSON field contracts provide flexibility and extensibility
|
|
||||||
7. Compatibility with existing models maintained
|
|
||||||
|
|
||||||
### ⚠️ Corrections Needed for Phase 1
|
|
||||||
1. **Track**: Add unique constraint on `(pipelineId, sortOrder)` to match spec
|
|
||||||
2. **UserRole**: Extend enum to add `AWARD_MASTER` and `AUDIENCE` values
|
|
||||||
|
|
||||||
### 📋 Action Items for Phase 1
|
|
||||||
- [ ] Implement all 7 new enums in `prisma/schema.prisma`
|
|
||||||
- [ ] Implement all 12 new models with proper constraints
|
|
||||||
- [ ] Extend `UserRole` enum with new values
|
|
||||||
- [ ] Add unique constraint on `Track(pipelineId, sortOrder)`
|
|
||||||
- [ ] Verify all indexes are created
|
|
||||||
- [ ] Create seed data with required representative examples
|
|
||||||
- [ ] Implement integrity check SQL queries
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Conclusion
|
|
||||||
|
|
||||||
**Status**: ✅ **DOMAIN MODEL VALIDATED AND APPROVED**
|
|
||||||
|
|
||||||
The proposed canonical domain model is architecturally sound, complete, and ready for implementation. All entities, enums, and constraints have been validated against both the design specification and the current MOPC codebase.
|
|
||||||
|
|
||||||
**Key Strengths**:
|
|
||||||
- Explicit state machine eliminates implicit round progression logic
|
|
||||||
- First-class award tracks enable flexible routing and governance
|
|
||||||
- JSON config fields provide extensibility without schema migrations
|
|
||||||
- Comprehensive audit trail ensures governance and explainability
|
|
||||||
- Index strategy optimizes common query patterns
|
|
||||||
|
|
||||||
**Minor Corrections**:
|
|
||||||
- 1 unique constraint addition needed (`Track.sortOrder`)
|
|
||||||
- 2 enum values to be added to existing `UserRole`
|
|
||||||
|
|
||||||
**Next Step**: Proceed to Phase 1 schema implementation with confidence that the domain model is solid.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Signed**: Claude Sonnet 4.5
|
|
||||||
**Date**: 2026-02-12
|
|
||||||
|
|
@ -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.
|
||||||
File diff suppressed because it is too large
Load Diff
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.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue