Round system redesign: Phases 1-7 complete
Full pipeline/track/stage architecture replacing the legacy round system. Schema: 11 new models (Pipeline, Track, Stage, StageTransition, ProjectStageState, RoutingRule, Cohort, CohortProject, LiveProgressCursor, OverrideAction, AudienceVoter) + 8 new enums. Backend: 9 new routers (pipeline, stage, routing, stageFiltering, stageAssignment, cohort, live, decision, award) + 6 new services (stage-engine, routing-engine, stage-filtering, stage-assignment, stage-notifications, live-control). Frontend: Pipeline wizard (17 components), jury stage pages (7), applicant pipeline pages (3), public stage pages (2), admin pipeline pages (5), shared stage components (3), SSE route, live hook. Phase 6 refit: 23 routers/services migrated from roundId to stageId, all frontend components refitted. Deleted round.ts (985 lines), roundTemplate.ts, round-helpers.ts, round-settings.ts, round-type-settings.tsx, 10 legacy admin pages, 7 legacy jury pages, 3 legacy dialogs. Phase 7 validation: 36 tests (10 unit + 8 integration files) all passing, TypeScript 0 errors, Next.js build succeeds, 13 integrity checks, legacy symbol sweep clean, auto-seed on first Docker startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
132
src/types/pipeline-wizard.ts
Normal file
132
src/types/pipeline-wizard.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import type { StageType, TrackKind, RoutingMode, DecisionMode, AwardScoringMode } from '@prisma/client'
|
||||
|
||||
// ============================================================================
|
||||
// Stage Config Discriminated Unions
|
||||
// ============================================================================
|
||||
|
||||
export type IntakeConfig = {
|
||||
submissionWindowEnabled: boolean
|
||||
lateSubmissionPolicy: 'reject' | 'flag' | 'accept'
|
||||
lateGraceHours: number
|
||||
fileRequirements: FileRequirementConfig[]
|
||||
}
|
||||
|
||||
export type FileRequirementConfig = {
|
||||
name: string
|
||||
description?: string
|
||||
acceptedMimeTypes: string[]
|
||||
maxSizeMB?: number
|
||||
isRequired: boolean
|
||||
}
|
||||
|
||||
export type FilterConfig = {
|
||||
rules: FilterRuleConfig[]
|
||||
aiRubricEnabled: boolean
|
||||
aiConfidenceThresholds: {
|
||||
high: number
|
||||
medium: number
|
||||
low: number
|
||||
}
|
||||
manualQueueEnabled: boolean
|
||||
}
|
||||
|
||||
export type FilterRuleConfig = {
|
||||
field: string
|
||||
operator: string
|
||||
value: string | number | boolean
|
||||
weight: number
|
||||
}
|
||||
|
||||
export type EvaluationConfig = {
|
||||
requiredReviews: number
|
||||
maxLoadPerJuror: number
|
||||
minLoadPerJuror: number
|
||||
availabilityWeighting: boolean
|
||||
overflowPolicy: 'queue' | 'expand_pool' | 'reduce_reviews'
|
||||
}
|
||||
|
||||
export type SelectionConfig = {
|
||||
finalistCount?: number
|
||||
rankingMethod: 'score_average' | 'weighted_criteria' | 'binary_pass'
|
||||
tieBreaker: 'admin_decides' | 'highest_individual' | 'revote'
|
||||
}
|
||||
|
||||
export type LiveFinalConfig = {
|
||||
juryVotingEnabled: boolean
|
||||
audienceVotingEnabled: boolean
|
||||
audienceVoteWeight: number
|
||||
cohortSetupMode: 'auto' | 'manual'
|
||||
revealPolicy: 'immediate' | 'delayed' | 'ceremony'
|
||||
}
|
||||
|
||||
export type ResultsConfig = {
|
||||
publicationMode: 'manual' | 'auto_on_close'
|
||||
showDetailedScores: boolean
|
||||
showRankings: boolean
|
||||
}
|
||||
|
||||
export type StageConfigMap = {
|
||||
INTAKE: IntakeConfig
|
||||
FILTER: FilterConfig
|
||||
EVALUATION: EvaluationConfig
|
||||
SELECTION: SelectionConfig
|
||||
LIVE_FINAL: LiveFinalConfig
|
||||
RESULTS: ResultsConfig
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Wizard Stage / Track / State Types
|
||||
// ============================================================================
|
||||
|
||||
export type WizardStageConfig = {
|
||||
id?: string
|
||||
name: string
|
||||
slug: string
|
||||
stageType: StageType
|
||||
sortOrder: number
|
||||
configJson: Record<string, unknown>
|
||||
windowOpenAt?: Date | null
|
||||
windowCloseAt?: Date | null
|
||||
}
|
||||
|
||||
export type WizardTrackConfig = {
|
||||
id?: string
|
||||
name: string
|
||||
slug: string
|
||||
kind: TrackKind
|
||||
sortOrder: number
|
||||
routingModeDefault?: RoutingMode
|
||||
decisionMode?: DecisionMode
|
||||
stages: WizardStageConfig[]
|
||||
awardConfig?: {
|
||||
name: string
|
||||
description?: string
|
||||
scoringMode?: AwardScoringMode
|
||||
}
|
||||
}
|
||||
|
||||
export type WizardState = {
|
||||
name: string
|
||||
slug: string
|
||||
programId: string
|
||||
settingsJson: Record<string, unknown>
|
||||
tracks: WizardTrackConfig[]
|
||||
notificationConfig: Record<string, boolean>
|
||||
overridePolicy: Record<string, unknown>
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Validation Result
|
||||
// ============================================================================
|
||||
|
||||
export type ValidationResult = {
|
||||
valid: boolean
|
||||
errors: string[]
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export type SectionValidation = {
|
||||
basics: ValidationResult
|
||||
tracks: ValidationResult
|
||||
notifications: ValidationResult
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/**
|
||||
* Round type-specific settings interfaces
|
||||
*/
|
||||
|
||||
// Filtering round settings (Round 1: High-volume screening)
|
||||
export interface FilteringRoundSettings {
|
||||
// Auto-elimination configuration
|
||||
autoEliminationEnabled: boolean
|
||||
autoEliminationThreshold: number // Minimum average score (e.g., 4)
|
||||
autoEliminationMinReviews: number // Min reviews required before elimination
|
||||
targetAdvancing: number // Target number of projects to advance (e.g., 60)
|
||||
|
||||
// Auto-run filtering when round closes
|
||||
autoFilterOnClose: boolean
|
||||
|
||||
// Display options
|
||||
showAverageScore: boolean
|
||||
showRanking: boolean
|
||||
}
|
||||
|
||||
// Evaluation round settings (Round 2: In-depth review)
|
||||
export interface EvaluationRoundSettings {
|
||||
// Requirements
|
||||
detailedCriteriaRequired: boolean
|
||||
minimumFeedbackLength: number // Minimum characters for feedback
|
||||
targetFinalists: number // Target number of finalists (e.g., 6)
|
||||
|
||||
// Display options
|
||||
showAverageScore: boolean
|
||||
showRanking: boolean
|
||||
requireAllCriteria: boolean
|
||||
}
|
||||
|
||||
// Live event round settings (Round 3: Event day)
|
||||
export interface LiveEventRoundSettings {
|
||||
// Presentation
|
||||
presentationDurationMinutes: number
|
||||
presentationOrder: string[] // Project IDs in order
|
||||
|
||||
// Voting
|
||||
votingWindowSeconds: number
|
||||
showLiveScores: boolean
|
||||
allowVoteChange: boolean
|
||||
votingMode: 'simple' | 'criteria'
|
||||
|
||||
// Audience voting
|
||||
audienceVotingMode: 'disabled' | 'per_project' | 'per_category' | 'favorites'
|
||||
audienceMaxFavorites: number
|
||||
audienceRequireId: boolean
|
||||
audienceVotingDuration: number | null
|
||||
|
||||
// Display
|
||||
displayMode: 'SCORES' | 'RANKING' | 'NONE'
|
||||
}
|
||||
|
||||
// Union type for all round settings
|
||||
export type RoundSettings =
|
||||
| { type: 'FILTERING'; settings: FilteringRoundSettings }
|
||||
| { type: 'EVALUATION'; settings: EvaluationRoundSettings }
|
||||
| { type: 'LIVE_EVENT'; settings: LiveEventRoundSettings }
|
||||
|
||||
// Default settings for each round type
|
||||
export const defaultFilteringSettings: FilteringRoundSettings = {
|
||||
autoEliminationEnabled: false,
|
||||
autoEliminationThreshold: 4,
|
||||
autoEliminationMinReviews: 0,
|
||||
targetAdvancing: 60,
|
||||
autoFilterOnClose: true,
|
||||
showAverageScore: true,
|
||||
showRanking: true,
|
||||
}
|
||||
|
||||
export const defaultEvaluationSettings: EvaluationRoundSettings = {
|
||||
detailedCriteriaRequired: true,
|
||||
minimumFeedbackLength: 50,
|
||||
targetFinalists: 6,
|
||||
showAverageScore: true,
|
||||
showRanking: true,
|
||||
requireAllCriteria: true,
|
||||
}
|
||||
|
||||
export const defaultLiveEventSettings: LiveEventRoundSettings = {
|
||||
presentationDurationMinutes: 5,
|
||||
presentationOrder: [],
|
||||
votingWindowSeconds: 30,
|
||||
showLiveScores: true,
|
||||
allowVoteChange: false,
|
||||
votingMode: 'simple',
|
||||
audienceVotingMode: 'disabled',
|
||||
audienceMaxFavorites: 3,
|
||||
audienceRequireId: false,
|
||||
audienceVotingDuration: null,
|
||||
displayMode: 'RANKING',
|
||||
}
|
||||
|
||||
// Round type labels
|
||||
export const roundTypeLabels: Record<string, string> = {
|
||||
FILTERING: 'Filtering Round',
|
||||
EVALUATION: 'Evaluation Round',
|
||||
LIVE_EVENT: 'Live Event',
|
||||
}
|
||||
|
||||
// Round type descriptions
|
||||
export const roundTypeDescriptions: Record<string, string> = {
|
||||
FILTERING: 'High-volume initial screening with auto-elimination options',
|
||||
EVALUATION: 'In-depth evaluation with detailed criteria and feedback',
|
||||
LIVE_EVENT: 'Real-time voting during presentations',
|
||||
}
|
||||
|
||||
// Field visibility per round type
|
||||
export const ROUND_FIELD_VISIBILITY: Record<string, {
|
||||
showRequiredReviews: boolean
|
||||
showAssignmentLimits: boolean
|
||||
showVotingWindow: boolean
|
||||
showSubmissionDates: boolean
|
||||
showEvaluationForm: boolean
|
||||
}> = {
|
||||
FILTERING: {
|
||||
showRequiredReviews: false,
|
||||
showAssignmentLimits: false,
|
||||
showVotingWindow: false,
|
||||
showSubmissionDates: true,
|
||||
showEvaluationForm: false,
|
||||
},
|
||||
EVALUATION: {
|
||||
showRequiredReviews: true,
|
||||
showAssignmentLimits: true,
|
||||
showVotingWindow: true,
|
||||
showSubmissionDates: true,
|
||||
showEvaluationForm: true,
|
||||
},
|
||||
LIVE_EVENT: {
|
||||
showRequiredReviews: false,
|
||||
showAssignmentLimits: false,
|
||||
showVotingWindow: false,
|
||||
showSubmissionDates: false,
|
||||
showEvaluationForm: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Live voting criterion type
|
||||
export interface LiveVotingCriterion {
|
||||
id: string
|
||||
label: string
|
||||
description?: string
|
||||
scale: number // max score (e.g. 10)
|
||||
weight: number // 0-1, weights must sum to 1
|
||||
}
|
||||
Reference in New Issue
Block a user