Pipeline UX: clickable cards, wizard edit, routing rules redesign, category quotas
All checks were successful
Build and Push Docker Image / build (push) Successful in 18s

- Simplify pipeline list cards: whole card is clickable, remove clutter
- Add wizard edit page for existing pipelines with full state pre-population
- Extract toWizardTrackConfig to shared utility for reuse
- Rewrite predicate builder with 3 modes: Simple (sentence-style), AI (NLP), Advanced (JSON)
- Fix routing operators to match backend (eq/neq/in/contains/gt/lt)
- Rewrite routing rules editor with collapsible cards and natural language summaries
- Add parseNaturalLanguageRule AI procedure for routing rules
- Add per-category quotas to SelectionConfig and EvaluationConfig
- Add category quota UI toggles to selection and assignment sections
- Add category breakdown display to selection panel
- Add category-aware scoring to smart assignment (penalty/bonus)
- Add category-aware filtering targets with excess demotion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-02-14 20:10:24 +01:00
parent c634982835
commit 382570cebd
17 changed files with 2577 additions and 1095 deletions

View File

@@ -0,0 +1,67 @@
import { normalizeStageConfig } from '@/lib/stage-config-schema'
import type { WizardTrackConfig } from '@/types/pipeline-wizard'
type TrackInput = {
id: string
name: string
slug: string
kind: 'MAIN' | 'AWARD' | 'SHOWCASE'
sortOrder: number
routingMode: 'PARALLEL' | 'EXCLUSIVE' | 'POST_MAIN' | null
decisionMode:
| 'JURY_VOTE'
| 'AWARD_MASTER_DECISION'
| 'ADMIN_DECISION'
| null
stages: Array<{
id: string
name: string
slug: string
stageType:
| 'INTAKE'
| 'FILTER'
| 'EVALUATION'
| 'SELECTION'
| 'LIVE_FINAL'
| 'RESULTS'
sortOrder: number
configJson: unknown
}>
specialAward?: {
name: string
description: string | null
scoringMode: 'PICK_WINNER' | 'RANKED' | 'SCORED'
} | null
}
export function toWizardTrackConfig(track: TrackInput): WizardTrackConfig {
return {
id: track.id,
name: track.name,
slug: track.slug,
kind: track.kind,
sortOrder: track.sortOrder,
routingModeDefault: track.routingMode ?? undefined,
decisionMode: track.decisionMode ?? undefined,
stages: track.stages
.map((stage) => ({
id: stage.id,
name: stage.name,
slug: stage.slug,
stageType: stage.stageType,
sortOrder: stage.sortOrder,
configJson: normalizeStageConfig(
stage.stageType,
stage.configJson as Record<string, unknown> | null
),
}))
.sort((a, b) => a.sortOrder - b.sortOrder),
awardConfig: track.specialAward
? {
name: track.specialAward.name,
description: track.specialAward.description ?? undefined,
scoringMode: track.specialAward.scoringMode,
}
: undefined,
}
}

View File

@@ -52,6 +52,8 @@ export function defaultSelectionConfig(): SelectionConfig {
finalistCount: undefined,
rankingMethod: 'score_average',
tieBreaker: 'admin_decides',
categoryQuotasEnabled: false,
categoryQuotas: { STARTUP: 3, BUSINESS_CONCEPT: 3 },
}
}