69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
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,
|
||
}
|
||
}
|