import type { StoryGroup } from '../types/jira' import type { FunctionalGapBadge } from './dashboardConfig' import { detectWorkLane, type LaneLabelsConfig } from './laneDetection' import type { StatusBucketConfig } from './statusBuckets' import { isIssueCanceled, isIssueDone } from './statusBuckets' import { storyMatchesGapBadge } from './functionalGaps' import { stepperStates } from './storyMetrics' export type MacroTrafficLight = 'green' | 'amber' | 'red' export type MacroPipelineHealth = { light: MacroTrafficLight title: string detail: string violatingStoryKeys: string[] hasCriticalViolation: boolean } /** * Feux macro (DSI) : rouge / orange si l’intégration (piste I) est encore ouverte * alors que l’étape Design du stepper n’est pas validée à 100 %. */ export function computeMacroPipelineHealth( groups: StoryGroup[], bucketCfg: StatusBucketConfig, laneCfg: LaneLabelsConfig, gapBadges: FunctionalGapBadge[] | undefined, ): MacroPipelineHealth { const violating: { key: string; critical: boolean }[] = [] const badges = gapBadges ?? [] for (const g of groups) { const { story, subtasks } = g if (subtasks.length === 0) continue const steps = stepperStates(subtasks, bucketCfg, laneCfg) const designValidated = steps.design const integrationStillOpen = subtasks.some((st) => { if (isIssueDone(st, bucketCfg) || isIssueCanceled(st, bucketCfg)) return false return detectWorkLane(st, laneCfg) === 'integration' }) if (integrationStillOpen && !designValidated) { const critical = badges.some((b) => b.criticalFlow && storyMatchesGapBadge(g, b)) violating.push({ key: story.key, critical }) } } if (violating.length === 0) { return { light: 'green', title: 'Séquence A → D → I', detail: 'Aucun conflit détecté : pas d’intégration active tant que le design n’est pas validé sur une même story.', violatingStoryKeys: [], hasCriticalViolation: false, } } const hasCriticalViolation = violating.some((v) => v.critical) const light: MacroTrafficLight = hasCriticalViolation ? 'red' : 'amber' return { light, title: hasCriticalViolation ? 'Alerte — flux critique' : 'Attention — séquence', detail: `${violating.length} story(s) avec intégration ouverte sans validation design complète.`, violatingStoryKeys: violating.map((v) => v.key), hasCriticalViolation, } }