566 lines
28 KiB
Markdown
566 lines
28 KiB
Markdown
# Migration Strategy
|
||
|
||
## Overview
|
||
|
||
The migration from Pipeline/Track/Stage to Competition/Round follows a 4-phase approach designed to minimize risk. New tables are added alongside old ones, data is backfilled, code is migrated, and finally old tables are dropped. Feature flags allow granular control of the transition.
|
||
|
||
---
|
||
|
||
## Migration Phases
|
||
|
||
### Phase 1: Schema Additions (Non-Breaking)
|
||
|
||
Add all new tables and enums alongside existing tables. No existing tables are modified or dropped.
|
||
|
||
**New tables added:**
|
||
- `Competition` (parallel to `Pipeline`)
|
||
- `Round` (parallel to `Stage`)
|
||
- `JuryGroup`, `JuryGroupMember`
|
||
- `SubmissionWindow`, `SubmissionFileRequirement`
|
||
- `RoundSubmissionVisibility`
|
||
- `ProjectRoundState` (parallel to `ProjectStageState`)
|
||
- `DeliberationSession`, `DeliberationVote`, `DeliberationResult`, `DeliberationParticipant`
|
||
- `ResultLock`, `ResultUnlockEvent`
|
||
- `MentorMessage`
|
||
- `AssignmentIntent`, `AssignmentException`
|
||
- `SubmissionPromotionEvent`
|
||
- `AwardWinner`
|
||
|
||
**New enums added:**
|
||
- `CompetitionStatus`: DRAFT, ACTIVE, COMPLETED, ARCHIVED
|
||
- `RoundType`: INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
|
||
- `RoundStatus`: DRAFT, READY, ACTIVE, COMPLETED, SKIPPED
|
||
- `CapMode`: HARD, SOFT, NONE
|
||
- `DeadlinePolicy`: HARD, FLAG, GRACE
|
||
- `DeliberationMode`: SINGLE_WINNER_VOTE, FULL_RANKING
|
||
- `DeliberationStatus`: OPEN, VOTING, TALLYING, RUNOFF, LOCKED
|
||
- `TieBreakMethod`: RUNOFF, ADMIN_DECIDES, SCORE_FALLBACK
|
||
- `DeliberationParticipantStatus`: REQUIRED, ABSENT_EXCUSED, REPLACED, REPLACEMENT_ACTIVE
|
||
- `AwardRoutingMode`: STAY_IN_MAIN, SEPARATE_POOL
|
||
- `AwardEligibilityMode`: AI_SUGGESTED, MANUAL, ALL_ELIGIBLE, ROUND_BASED
|
||
- `WinnerDecisionMode`: JURY_VOTE, SINGLE_JUDGE
|
||
- `MentorMessageRole`: MENTOR, APPLICANT, ADMIN
|
||
- `PromotionSourceType`: MENTOR_FILE, ADMIN_REPLACEMENT
|
||
- `AssignmentIntentSource`: INVITE, ADMIN, SYSTEM
|
||
|
||
**Rollback**: Drop new tables and enums. Zero impact on existing system.
|
||
|
||
---
|
||
|
||
### Phase 2: Data Migration (Backfill)
|
||
|
||
Backfill new tables from existing data. Old tables remain populated and active.
|
||
|
||
**Mapping table:**
|
||
|
||
| Old Model | New Model | Mapping Logic |
|
||
|-----------|-----------|---------------|
|
||
| `Pipeline` | `Competition` | 1:1. Copy id, programId, name, status, settings → typed config split |
|
||
| `Track` (MAIN) | *(eliminated)* | Main track's stages become Competition's rounds directly |
|
||
| `Track` (AWARD) | `SpecialAward` | One SpecialAward per AWARD track |
|
||
| `Stage` | `Round` | 1:1. Copy type mapping (see below), order, status |
|
||
| `Stage.configJson` | `Round.configJson` | Parse and validate against typed schema for the round type |
|
||
| `ProjectStageState` | `ProjectRoundState` | Map stageId → roundId, trackId dropped |
|
||
| Judge assignments | `JuryGroupMember` + assignments | Create JuryGroups from distinct jury configurations |
|
||
| `SpecialAward` | `SpecialAward` (enhanced) | Add routing mode, eligibility mode fields |
|
||
|
||
**Stage type to Round type mapping:**
|
||
|
||
| Old StageType | New RoundType | Notes |
|
||
|---------------|---------------|-------|
|
||
| `INTAKE` | `INTAKE` | Direct mapping |
|
||
| `FILTER` | `FILTERING` | Renamed for clarity |
|
||
| `EVALUATION` | `EVALUATION` | Direct mapping (used for both Jury 1 and Jury 2) |
|
||
| `SELECTION` | *(absorbed)* | Selection logic moves into evaluation round config |
|
||
| `LIVE_FINAL` | `LIVE_FINAL` | Direct mapping |
|
||
| `RESULTS` | *(absorbed)* | Results are part of the deliberation round |
|
||
| *(new)* | `SUBMISSION` | New round type for multi-round document collection |
|
||
| *(new)* | `MENTORING` | New round type for mentor collaboration |
|
||
| *(new)* | `DELIBERATION` | New round type replacing confirmation |
|
||
|
||
**Backfill script**: A TypeScript migration script reads all existing Pipelines/Tracks/Stages and creates corresponding Competition/Round records. The script is idempotent (can be run multiple times safely).
|
||
|
||
**Rollback**: Delete backfilled data from new tables. Old tables unchanged.
|
||
|
||
---
|
||
|
||
### Phase 3: Code Migration
|
||
|
||
Update all services, routers, and UI to use new models. Feature flags control which code path is active.
|
||
|
||
**Feature flag strategy:**
|
||
|
||
```typescript
|
||
const FEATURE_FLAGS = {
|
||
USE_COMPETITION_MODEL: false, // Phase 3: switch to Competition/Round queries
|
||
USE_JURY_GROUPS: false, // Phase 3: switch to JuryGroup-based assignment
|
||
USE_DELIBERATION: false, // Phase 6: switch to new deliberation model
|
||
USE_MENTOR_WORKSPACE: false, // Phase 4: enable enhanced mentor features
|
||
USE_SUBMISSION_WINDOWS: false, // Phase 4: enable multi-round submissions
|
||
HIDE_LEGACY_PIPELINE_UI: false, // Phase 7: hide old Pipeline/Stage UI
|
||
}
|
||
```
|
||
|
||
**Dual-read pattern**: During transition, services can read from both old and new tables:
|
||
|
||
```typescript
|
||
async function getCompetitionContext(id: string) {
|
||
if (FEATURE_FLAGS.USE_COMPETITION_MODEL) {
|
||
return getFromCompetitionModel(id)
|
||
}
|
||
return getFromPipelineModel(id) // legacy path
|
||
}
|
||
```
|
||
|
||
**Router migration order:**
|
||
1. `pipeline.ts` → `competition.ts` (create new router, keep old active behind flag)
|
||
2. `stage.ts` → `round.ts` (same approach)
|
||
3. Remove `track.ts` (logic absorbed into competition + specialAward routers)
|
||
4. Update `assignment.ts` to use JuryGroup model
|
||
5. Update `evaluation.ts` to use Round model
|
||
6. Add `deliberation.ts` (new router)
|
||
7. Update `mentor.ts` with workspace features
|
||
8. Update all UI pages to use new routers
|
||
|
||
**Rollback**: Flip feature flags back to legacy. Both code paths exist during this phase.
|
||
|
||
---
|
||
|
||
### Phase 4: Cleanup (Point of No Return)
|
||
|
||
Drop old tables and remove legacy code paths. This is irreversible.
|
||
|
||
**Tables dropped:**
|
||
- `Pipeline`
|
||
- `Track`
|
||
- `Stage`
|
||
- `ProjectStageState`
|
||
- Legacy configJson schemas
|
||
|
||
**Enums dropped:**
|
||
- `StageType` (replaced by `RoundType`)
|
||
- `TrackKind` (MAIN/AWARD — eliminated)
|
||
- `RoutingMode` (SHARED/EXCLUSIVE — replaced by AwardRoutingMode)
|
||
- `WinnerProposalStatus` (replaced by `DeliberationStatus`)
|
||
- `WinnerApprovalRole` (replaced by `DeliberationParticipantStatus`)
|
||
|
||
**Models dropped:**
|
||
- `WinnerProposal` (replaced by `DeliberationSession`)
|
||
- `WinnerApproval` (replaced by `DeliberationVote`)
|
||
|
||
**Code removed:**
|
||
- All feature flag conditionals (only new path remains)
|
||
- All dual-read logic
|
||
- All legacy router files
|
||
- All legacy service files
|
||
|
||
**Rollback**: Restore from database backup. This phase should only be executed after thorough testing in Phases 1–3 and the burn-in period in Phase 8 of the implementation roadmap.
|
||
|
||
---
|
||
|
||
## Data Mapping Reference
|
||
|
||
### Complete Field Mapping
|
||
|
||
| Old Field | New Field | Notes |
|
||
|-----------|-----------|-------|
|
||
| `Pipeline.id` | `Competition.id` | Preserve IDs for foreign key consistency |
|
||
| `Pipeline.programId` | `Competition.programId` | Direct |
|
||
| `Pipeline.name` | `Competition.name` | Direct |
|
||
| `Pipeline.description` | `Competition.description` | Direct |
|
||
| `Pipeline.status` | `Competition.status` | Map to CompetitionStatus enum |
|
||
| `Pipeline.settingsJson` | `Competition.settingsJson` | Carry forward, validate |
|
||
| `Stage.id` | `Round.id` | Preserve IDs |
|
||
| `Stage.trackId` | *(dropped)* | No more track reference |
|
||
| `Stage.pipelineId` | `Round.competitionId` | Rename |
|
||
| `Stage.type` | `Round.type` | Map StageType → RoundType |
|
||
| `Stage.order` | `Round.order` | Direct |
|
||
| `Stage.status` | `Round.status` | Map to RoundStatus |
|
||
| `Stage.configJson` | `Round.configJson` | Validate against typed schema |
|
||
| `Stage.startsAt` | `Round.startsAt` | Direct |
|
||
| `Stage.endsAt` | `Round.endsAt` | Direct |
|
||
| `ProjectStageState.stageId` | `ProjectRoundState.roundId` | Rename |
|
||
| `ProjectStageState.trackId` | *(dropped)* | No more track |
|
||
| `ProjectStageState.projectId` | `ProjectRoundState.projectId` | Direct |
|
||
| `ProjectStageState.state` | `ProjectRoundState.state` | Map values |
|
||
|
||
### Additional FK Migrations (stageId → roundId)
|
||
|
||
These existing models have `stageId` foreign keys that must be renamed to `roundId`. They are NOT being replaced by new models — just renamed:
|
||
|
||
| Model | Old FK | New FK | Notes |
|
||
|-------|--------|--------|-------|
|
||
| `AudienceVote` | `stageId` | `roundId` | Live voting records |
|
||
| `COIDeclaration` | `stageId` | `roundId` | Conflict of interest declarations |
|
||
| `ProjectStatusHistory` | `stageId` | `roundId` | Project state change log |
|
||
| `DigestLog` | `stageId` | `roundId` | Notification digest tracking |
|
||
| `MentorNote` | `stageId` | `roundId` | Mentor notes (legacy model, still used) |
|
||
| `MentorMilestone` | `stageId` | `roundId` | Mentor milestone definitions |
|
||
| `PartnerStageAccess` | `stageId` | `roundId` | Rename model to `PartnerRoundAccess` |
|
||
| `LearningResource` | `stageId` | `roundId` | Learning resources linked to rounds |
|
||
| `TaggingJob` | `stageId` | `roundId` | AI tagging job references |
|
||
| `FilteringResult` | `stageId` | `roundId` | Filtering results per project |
|
||
| `FilteringJob` | `stageId` | `roundId` | Filtering batch job records |
|
||
| `EvaluationReminder` | `stageId` | `roundId` | Cron-triggered reminder records |
|
||
| `GracePeriod` | `stageId` | `roundId` | Grace period extensions |
|
||
| `LiveProgressCursor` | `stageId` | `roundId` | Live ceremony cursor |
|
||
| `LiveVotingSession` | `stageId` | `roundId` | Live voting session |
|
||
|
||
**Migration approach**: These are simple column renames with FK constraint updates. Run as a single migration after the main table additions in Phase 1.
|
||
|
||
### New Field Additions (Non-Breaking)
|
||
|
||
These fields are added to existing new models during Phase 1. All have defaults, so they are non-breaking:
|
||
|
||
| Model | Field | Type | Default | Purpose |
|
||
|-------|-------|------|---------|---------|
|
||
| `Round` | `purposeKey` | `String?` | null | Optional analytics tag |
|
||
| `JuryGroupMember` | `role` | `JuryGroupMemberRole` | `MEMBER` | Replaces `isLead: Boolean` |
|
||
| `SubmissionWindow` | `isLocked` | `Boolean` | `false` | Manual lock independent of window close |
|
||
| `AssignmentIntent` | `status` | `AssignmentIntentStatus` | `PENDING` | Proper lifecycle enum |
|
||
|
||
**Data migration for `isLead → role`**: For existing data where `isLead = true`, set `role = CHAIR`. For `isLead = false`, set `role = MEMBER`. Drop `isLead` column after backfill.
|
||
|
||
### New Enums (Phase 1)
|
||
|
||
| Enum | Values | Used By |
|
||
|------|--------|---------|
|
||
| `JuryGroupMemberRole` | CHAIR, MEMBER, OBSERVER | `JuryGroupMember.role` |
|
||
| `AssignmentIntentStatus` | PENDING, HONORED, OVERRIDDEN, EXPIRED, CANCELLED | `AssignmentIntent.status` |
|
||
|
||
---
|
||
|
||
## Pre-Migration Checklist
|
||
|
||
Before starting Phase 1:
|
||
- [ ] All type definitions finalized (Phase 0 of roadmap)
|
||
- [ ] Database backup taken
|
||
- [ ] Migration script written and tested on staging
|
||
- [ ] Feature flags infrastructure in place
|
||
- [ ] Rollback procedures documented and tested
|
||
- [ ] All team members briefed on migration plan
|
||
|
||
Before starting Phase 4 (cleanup):
|
||
- [ ] All feature flags pointing to new code paths
|
||
- [ ] 72-hour burn-in period completed with zero critical errors
|
||
- [ ] All release gates A–F passed (see [12-observability-and-release-gates.md](./12-observability-and-release-gates.md))
|
||
- [ ] Database backup taken (point-of-no-return backup)
|
||
- [ ] Rollback from backup tested on staging
|
||
|
||
---
|
||
|
||
## Timeline Alignment
|
||
|
||
| Migration Phase | Roadmap Phase | When |
|
||
|----------------|---------------|------|
|
||
| Phase 1: Schema additions | Implementation Phase 1 | Weeks 2–3 |
|
||
| Phase 2: Data backfill | Implementation Phase 1 | Weeks 3–4 |
|
||
| Phase 3: Code migration | Implementation Phases 3–6 | Weeks 4–12 |
|
||
| Phase 4: Cleanup | Implementation Phase 7 | Week 12–13 |
|
||
|
||
See [09-implementation-roadmap.md](./09-implementation-roadmap.md) for the full implementation timeline.
|
||
|
||
---
|
||
|
||
## System Inventory Appendix
|
||
|
||
Complete file-by-file migration effort estimate. Risk: L = Low (rename only), M = Medium (logic changes), H = High (rewrite).
|
||
|
||
### Database & Schema (3 files, ~28 hours)
|
||
|
||
| File | Changes | Risk |
|
||
|------|---------|------|
|
||
| `prisma/schema.prisma` | 40+ model/enum updates, 17+ FK renames | H |
|
||
| `prisma/seed.ts` | 300+ lines referencing Pipeline/Stage | M |
|
||
| `prisma/migrations/*` | New migration files for all schema changes | M |
|
||
|
||
### tRPC Routers (24 files, ~104 hours)
|
||
|
||
| File | Changes | Risk |
|
||
|------|---------|------|
|
||
| `src/server/routers/pipeline.ts` | Rename to `competition.ts`, update all queries | H |
|
||
| `src/server/routers/stage.ts` | Rename to `round.ts`, update all queries | H |
|
||
| `src/server/routers/track.ts` | Remove entirely, absorb into competition + specialAward | H |
|
||
| `src/server/routers/evaluation.ts` | stageId→roundId, add JuryGroup awareness | M |
|
||
| `src/server/routers/assignment.ts` | JuryGroup model, intent lifecycle | H |
|
||
| `src/server/routers/file.ts` | submissionWindowId, multi-round grouping | M |
|
||
| `src/server/routers/mentor.ts` | Workspace features, file promotion | M |
|
||
| `src/server/routers/award.ts` | competitionId, evaluationRoundId | M |
|
||
| `src/server/routers/audience-vote.ts` | stageId→roundId | L |
|
||
| `src/server/routers/coi.ts` | roundId awareness | L |
|
||
| `src/server/routers/notification.ts` | Stage event→round event mapping | M |
|
||
| `src/server/routers/cron.ts` | stageId→roundId in all scheduled jobs | M |
|
||
| `src/server/routers/deliberation.ts` | **NEW** — full deliberation router | H |
|
||
| `src/server/routers/result-lock.ts` | **NEW** — result lock/unlock procedures | M |
|
||
| `src/server/routers/jury-group.ts` | **NEW** — jury management procedures | H |
|
||
| `src/server/routers/submission-window.ts` | **NEW** — submission window CRUD | M |
|
||
| Other 8 routers | Indirect references (imports, type refs) | L |
|
||
|
||
### Services (11 files, ~50 hours)
|
||
|
||
| File | Changes | Risk |
|
||
|------|---------|------|
|
||
| `src/server/services/stage-engine.ts` | Rename to `round-engine.ts`, full rewrite | H |
|
||
| `src/server/services/stage-filtering.ts` | Rename to `round-filtering.ts`, roundId refs | M |
|
||
| `src/server/services/stage-assignment.ts` | Rename to `round-assignment.ts`, JuryGroup model | H |
|
||
| `src/server/services/stage-notifications.ts` | Rename to `round-notifications.ts`, event types | M |
|
||
| `src/server/services/live-control.ts` | stageId→roundId | M |
|
||
| `src/server/services/ai-filtering.ts` | stageId→roundId in AI context | L |
|
||
| `src/server/services/ai-assignment.ts` | JuryGroup awareness | M |
|
||
| `src/server/services/ai-evaluation-summary.ts` | roundId refs | L |
|
||
| `src/server/services/ai-tagging.ts` | stageId→roundId | L |
|
||
| `src/server/services/ai-award-eligibility.ts` | competitionId awareness | L |
|
||
| `src/server/services/anonymization.ts` | No changes expected | L |
|
||
|
||
### Types & Libraries (6 files, ~32 hours)
|
||
|
||
| File | Changes | Risk |
|
||
|------|---------|------|
|
||
| `src/types/pipeline-wizard.ts` | Complete rewrite → `competition-wizard.ts` | H |
|
||
| `src/types/wizard-config.ts` | Complete rewrite for round-based wizard | H |
|
||
| `src/lib/pipeline-defaults.ts` | Rename to `competition-defaults.ts` | M |
|
||
| `src/lib/pipeline-validation.ts` | Rename to `competition-validation.ts` | M |
|
||
| `src/lib/pipeline-conversions.ts` | Rename to `competition-conversions.ts` | M |
|
||
| `src/lib/stage-config-schema.ts` | Rename to `round-config-schema.ts`, Zod schemas | M |
|
||
|
||
### Admin Pages (35+ files, ~52 hours)
|
||
|
||
| File Pattern | Count | Changes | Risk |
|
||
|-------------|-------|---------|------|
|
||
| `src/app/(admin)/admin/pipelines/*` | 8 pages | Rename to `competitions/*` | H |
|
||
| `src/app/(admin)/admin/stages/*` | 5 pages | Rename to `rounds/*` | H |
|
||
| Pipeline wizard components | 6 | Full rewrite for competition model | H |
|
||
| Stage config components | 8 | Update to round-type configs | M |
|
||
| Other admin components | 10 | Indirect references | L |
|
||
|
||
### Jury Pages (16 files, ~18 hours)
|
||
|
||
| File Pattern | Count | Changes | Risk |
|
||
|-------------|-------|---------|------|
|
||
| `src/app/(jury)/jury/stages/[stageId]/*` | 6 pages | Rename to `rounds/[roundId]/*` | M |
|
||
| Jury evaluation components | 4 | roundId, JuryGroup filtering | M |
|
||
| Jury shared components | 6 | Breadcrumbs, timeline, badges | L |
|
||
|
||
### Applicant Pages (8 files, ~12 hours)
|
||
|
||
| File Pattern | Count | Changes | Risk |
|
||
|-------------|-------|---------|------|
|
||
| `src/app/(applicant)/applicant/pipeline/*` | 4 pages | Rename to `competition/*` | H |
|
||
| Applicant components | 4 | StageTimeline→RoundTimeline | M |
|
||
|
||
### Tests (13+ files, ~45 hours)
|
||
|
||
| File | Changes | Risk |
|
||
|------|---------|------|
|
||
| `tests/helpers.ts` | Update all factories | M |
|
||
| `tests/unit/stage-engine.test.ts` | Rename + update references | M |
|
||
| `tests/unit/stage-filtering.test.ts` | Rename + update | M |
|
||
| `tests/unit/stage-assignment.test.ts` | Rename + add JuryGroup tests | M |
|
||
| `tests/integration/pipeline-crud.test.ts` | Rename to competition-crud | M |
|
||
| `tests/integration/evaluation-flow.test.ts` | Round model, multi-round docs | M |
|
||
| New test files (8 files) | See [11-testing-and-qa.md](./11-testing-and-qa.md) | H |
|
||
|
||
### Infrastructure (4 files, ~11 hours)
|
||
|
||
| File | Changes | Risk |
|
||
|------|---------|------|
|
||
| `docker/docker-entrypoint.sh` | Migration flow update | L |
|
||
| Cron job configs | stageId→roundId in scheduled tasks | M |
|
||
| Webhook event definitions | Stage→Round event names | M |
|
||
| Email templates | Stage references in copy | L |
|
||
|
||
### Totals
|
||
|
||
| Category | Files | Est. Hours |
|
||
|----------|-------|------------|
|
||
| Database & Schema | 3 | 28 |
|
||
| tRPC Routers | 24 | 104 |
|
||
| Services | 11 | 50 |
|
||
| Types & Libraries | 6 | 32 |
|
||
| Admin UI | 35+ | 52 |
|
||
| Jury UI | 16 | 18 |
|
||
| Applicant UI | 8 | 12 |
|
||
| Tests | 13+ | 45 |
|
||
| Infrastructure | 4 | 11 |
|
||
| **Total** | **120+** | **~352** |
|
||
|
||
> **Note**: Conservative estimates for one developer. Parallel frontend/backend work reduces calendar time. Critical path: Schema → Services → Routers → UI.
|
||
|
||
---
|
||
|
||
## Appendix: System Inventory — File-by-File Migration Map
|
||
|
||
Complete inventory of all files requiring changes, grouped by category with effort estimates.
|
||
|
||
### Prisma & Database (1 file, HIGH effort)
|
||
|
||
| File | Changes Required | Effort |
|
||
|------|-----------------|--------|
|
||
| `prisma/schema.prisma` | 40+ model/enum updates, 17+ FK renames (stageId→roundId), add new enums, add new fields | HIGH |
|
||
|
||
### tRPC Routers (24 files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `src/server/routers/pipeline.ts` | **Rename** → `competition.ts` + all procedures | HIGH |
|
||
| `src/server/routers/stage.ts` | **Rename** → `round.ts` + all procedures | HIGH |
|
||
| `src/server/routers/track.ts` | **Remove** — logic absorbed into competition + specialAward | HIGH |
|
||
| `src/server/routers/evaluation.ts` | Update to use Round model, roundId params, JuryGroup queries | HIGH |
|
||
| `src/server/routers/assignment.ts` | Update to use JuryGroup model, roundId params | HIGH |
|
||
| `src/server/routers/file.ts` | Update `listByProjectForStage` → `listByProjectForRound`, multi-round grouping | MEDIUM |
|
||
| `src/server/routers/mentor.ts` | Add workspace features, file promotion procedures | MEDIUM |
|
||
| `src/server/routers/applicant.ts` | Update `getMyDashboard` (openStages→openRounds), pipeline view | MEDIUM |
|
||
| `src/server/routers/application.ts` | Update stage mode config → round-type-aware config | MEDIUM |
|
||
| `src/server/routers/live-control.ts` | Update stageId → roundId in cursor management | MEDIUM |
|
||
| `src/server/routers/award.ts` | Update to use competitionId, enhanced routing modes | MEDIUM |
|
||
| `src/server/routers/notification.ts` | Update event keys from stageId → roundId | LOW |
|
||
| `src/server/routers/analytics.ts` | Update stage references → round references | LOW |
|
||
| `src/server/routers/digest.ts` | Update stageId references | LOW |
|
||
| `src/server/routers/coi.ts` | Update stageId → roundId in COI declarations | LOW |
|
||
| `src/server/routers/audit.ts` | Update event type names (stage.* → round.*) | LOW |
|
||
| **New:** `src/server/routers/deliberation.ts` | New router for deliberation lifecycle | HIGH |
|
||
| **New:** `src/server/routers/result-lock.ts` | New router for result locking/unlocking | MEDIUM |
|
||
| **New:** `src/server/routers/jury-group.ts` | New router for jury group management | MEDIUM |
|
||
| **New:** `src/server/routers/submission-window.ts` | New router for submission window management | MEDIUM |
|
||
|
||
### Services (11 files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `src/server/services/stage-engine.ts` | **Rename** → `round-engine.ts`, update all type references | HIGH |
|
||
| `src/server/services/stage-filtering.ts` | **Rename** → `round-filtering.ts`, update to RoundType.FILTERING | HIGH |
|
||
| `src/server/services/stage-assignment.ts` | **Rename** → `round-assignment.ts`, add JuryGroup-based assignment | HIGH |
|
||
| `src/server/services/stage-notifications.ts` | **Rename** → `round-notifications.ts`, update event keys | MEDIUM |
|
||
| `src/server/services/live-control.ts` | Update stageId → roundId in cursor management | MEDIUM |
|
||
| `src/server/services/ai-filtering.ts` | Update stage references → round references | LOW |
|
||
| `src/server/services/ai-assignment.ts` | Update to use JuryGroup model | MEDIUM |
|
||
| `src/server/services/ai-evaluation-summary.ts` | Update stage references | LOW |
|
||
| **New:** `src/server/services/deliberation-engine.ts` | Deliberation lifecycle, vote aggregation, Borda count | HIGH |
|
||
| **New:** `src/server/services/result-lock.ts` | Lock/unlock with audit trail | MEDIUM |
|
||
| **New:** `src/server/services/mentor-workspace.ts` | Messaging, file management, promotion | MEDIUM |
|
||
|
||
### Types & Libraries (6 files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `src/types/pipeline-wizard.ts` | **Rewrite** → `competition-wizard.ts` with Competition/Round types | HIGH |
|
||
| `src/types/wizard-config.ts` | **Rewrite** → update all Stage→Round type references | HIGH |
|
||
| `src/lib/pipeline-defaults.ts` | **Rename** → `competition-defaults.ts`, update all defaults | MEDIUM |
|
||
| `src/lib/pipeline-validation.ts` | **Rename** → `competition-validation.ts` | MEDIUM |
|
||
| `src/lib/pipeline-conversions.ts` | **Rename** → `competition-conversions.ts` | MEDIUM |
|
||
| `src/lib/stage-config-schema.ts` | **Rename** → `round-config-schema.ts`, update to 7 typed Zod schemas | HIGH |
|
||
|
||
### Admin Pages (13+ files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `src/app/(admin)/pipelines/*` | **Rename** → `competitions/*`, update all router calls | HIGH |
|
||
| `src/app/(admin)/pipelines/[id]/stages/*` | **Rename** → `competitions/[id]/rounds/*` | HIGH |
|
||
| `src/app/(admin)/pipelines/[id]/tracks/*` | **Remove** — track UI eliminated | MEDIUM |
|
||
| **New:** `src/app/(admin)/competitions/[id]/juries/*` | New jury management section (3 pages) | HIGH |
|
||
| **New:** `src/app/(admin)/competitions/[id]/deliberation/*` | New deliberation management | HIGH |
|
||
| **New:** `src/app/(admin)/competitions/[id]/results/*` | New results & locks page | MEDIUM |
|
||
| **New:** `src/app/(admin)/competitions/[id]/submission-windows/*` | New submission window management | MEDIUM |
|
||
|
||
### Jury Pages (6 files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `src/app/(jury)/stages/[stageId]/*` | **Rename** → `rounds/[roundId]/*` (6 routes) | HIGH |
|
||
| `src/app/(jury)/stages/[stageId]/evaluate/*` | Update to multi-round doc viewing, JuryGroup context | HIGH |
|
||
| `src/app/(jury)/stages/[stageId]/compare/*` | Update cross-round project comparison | MEDIUM |
|
||
| **New:** `src/app/(jury)/rounds/[roundId]/deliberation/*` | Juror deliberation voting interface | HIGH |
|
||
|
||
### Applicant Pages (8 files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `src/app/(applicant)/pipeline/*` | **Rename** → `competition/*`, redesign progress visualization | HIGH |
|
||
| `src/app/(applicant)/pipeline/[stageId]/documents/*` | **Rename** → multi-round aware doc upload with read-only enforcement | HIGH |
|
||
| `src/app/(applicant)/pipeline/[stageId]/status/*` | **Rename** → round-based status with cross-round visibility | MEDIUM |
|
||
|
||
### Shared Components (24+ files)
|
||
|
||
| Component | Change | Effort |
|
||
|-----------|--------|--------|
|
||
| `StageTimeline` | **Rename** → `RoundTimeline`, update props from stageId to roundId | MEDIUM |
|
||
| `StageWindowBadge` | **Rename** → `RoundStatusBadge` | LOW |
|
||
| `RequirementUploadSlot` | Update `stageId` prop → `roundId` + `submissionWindowId` | LOW |
|
||
| `PipelineWizard` | **Rename** → `CompetitionWizard`, rewrite step flow | HIGH |
|
||
| `StageSidebar` | **Rename** → `RoundSidebar` | LOW |
|
||
| `StageConfigEditor` | **Rename** → `RoundConfigEditor`, update to typed Zod schemas | HIGH |
|
||
| `TrackSelector` | **Remove** | LOW |
|
||
| Breadcrumb components | Update Pipeline→Track→Stage to Competition→Round | LOW |
|
||
|
||
### Infrastructure & Config (5 files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `prisma/seed.ts` | Update all Pipeline/Stage creation to Competition/Round (300+ lines) | HIGH |
|
||
| `docker/docker-entrypoint.sh` | Update migration flow for new tables | LOW |
|
||
| `tests/helpers.ts` | Add new factory functions for Competition/Round/JuryGroup/etc. | MEDIUM |
|
||
| `.env.example` | Add any new feature flag env vars | LOW |
|
||
| `CLAUDE.md` | Update architecture documentation | LOW |
|
||
|
||
### Background Jobs / Cron (3 files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `src/server/cron/evaluation-reminders.ts` | Update stageId → roundId in deadline checks | LOW |
|
||
| `src/server/cron/digest-sender.ts` | Update stageId references in digest generation | LOW |
|
||
| `src/server/cron/ai-tagging.ts` | Update stageId → roundId in tagging jobs | LOW |
|
||
|
||
### Test Files (12 files)
|
||
|
||
| File | Change Type | Effort |
|
||
|------|------------|--------|
|
||
| `tests/unit/stage-engine.test.ts` | **Rename** → `round-engine.test.ts`, update all references | MEDIUM |
|
||
| `tests/unit/stage-filtering.test.ts` | **Rename** → `round-filtering.test.ts` | MEDIUM |
|
||
| `tests/unit/stage-assignment.test.ts` | **Rename**, add JuryGroup-based tests, cap mode tests | MEDIUM |
|
||
| `tests/integration/pipeline-crud.test.ts` | **Rename** → `competition-crud.test.ts` | MEDIUM |
|
||
| `tests/integration/evaluation-flow.test.ts` | Update to Round model, multi-round doc visibility | MEDIUM |
|
||
| **New:** `tests/unit/policy-resolution.test.ts` | 5-layer policy precedence tests | HIGH |
|
||
| **New:** `tests/unit/borda-count.test.ts` | Borda count aggregation tests | LOW |
|
||
| **New:** `tests/unit/deliberation-tally.test.ts` | Vote tallying for both modes | MEDIUM |
|
||
| **New:** `tests/unit/config-validation.test.ts` | All 7 Zod schema validation tests | MEDIUM |
|
||
| **New:** `tests/integration/deliberation-flow.test.ts` | Full deliberation lifecycle | HIGH |
|
||
| **New:** `tests/integration/mentor-workspace.test.ts` | Messaging, files, comments, promotion | MEDIUM |
|
||
| **New:** `tests/e2e/monaco-full-flow.test.ts` | Complete 8-round simulation | HIGH |
|
||
|
||
### Webhook & Event Mapping
|
||
|
||
| Old Event Type | New Event Type |
|
||
|----------------|----------------|
|
||
| `stage.transitioned` | `round.transitioned` |
|
||
| `stage.opened` | `round.opened` |
|
||
| `stage.closed` | `round.closed` |
|
||
| `pipeline.created` | `competition.created` |
|
||
| `pipeline.statusChanged` | `competition.statusChanged` |
|
||
| `stage.assignmentCompleted` | `round.assignmentCompleted` |
|
||
| `stage.evaluationCompleted` | `round.evaluationCompleted` |
|
||
| `stage.filteringCompleted` | `round.filteringCompleted` |
|
||
| *(new)* | `deliberation.sessionCreated` |
|
||
| *(new)* | `deliberation.voteSubmitted` |
|
||
| *(new)* | `deliberation.resultLocked` |
|
||
| *(new)* | `deliberation.resultUnlocked` |
|
||
| *(new)* | `mentoring.filePromoted` |
|
||
| *(new)* | `submission.windowOpened` |
|
||
| *(new)* | `submission.windowClosed` |
|
||
|
||
### Effort Summary
|
||
|
||
| Category | Files | New | Modified | Removed | Estimated Hours |
|
||
|----------|-------|-----|----------|---------|----------------|
|
||
| Prisma & DB | 1 | 0 | 1 | 0 | 16–24 |
|
||
| tRPC Routers | 24 | 4 | 16 | 1 | 40–60 |
|
||
| Services | 11 | 3 | 8 | 0 | 30–40 |
|
||
| Types & Libs | 6 | 0 | 6 | 0 | 16–24 |
|
||
| Admin Pages | 13+ | 4 | 9 | 1 | 30–40 |
|
||
| Jury Pages | 6+ | 1 | 5 | 0 | 20–30 |
|
||
| Applicant Pages | 8 | 0 | 8 | 0 | 16–24 |
|
||
| Components | 24+ | 0 | 20+ | 2+ | 20–30 |
|
||
| Infrastructure | 5 | 0 | 5 | 0 | 8–12 |
|
||
| Background Jobs | 3 | 0 | 3 | 0 | 4–6 |
|
||
| Tests | 12 | 7 | 5 | 0 | 30–40 |
|
||
| **Total** | **113+** | **19** | **86+** | **4+** | **230–330** |
|