init
This commit is contained in:
96
src/lib/executiveKpis.ts
Normal file
96
src/lib/executiveKpis.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import type { JiraIssue, StoryGroup } from '../types/jira'
|
||||
import { statusToPhase } from './statusPhase'
|
||||
|
||||
function norm(s: string): string {
|
||||
return s
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/\p{M}/gu, '')
|
||||
}
|
||||
|
||||
export function isBlockingStatus(statusName: string): boolean {
|
||||
const n = norm(statusName)
|
||||
return (
|
||||
n.includes('bloque') ||
|
||||
n.includes('blocked') ||
|
||||
n.includes('recette ko') ||
|
||||
n.includes('recetteko') ||
|
||||
n.includes('recette nok')
|
||||
)
|
||||
}
|
||||
|
||||
function allSubtasks(groups: StoryGroup[]): JiraIssue[] {
|
||||
return groups.flatMap((g) => g.subtasks)
|
||||
}
|
||||
|
||||
export function maquetteRelatedSubtasks(groups: StoryGroup[]): JiraIssue[] {
|
||||
return allSubtasks(groups).filter(isMaquetteRelated)
|
||||
}
|
||||
|
||||
export function goldenCarbonSubtasks(groups: StoryGroup[]): JiraIssue[] {
|
||||
return groups.flatMap((g) =>
|
||||
g.subtasks.filter((st) => isGoldenCarbonRelated(st, g.story)),
|
||||
)
|
||||
}
|
||||
|
||||
/** Progression globale : sous-tâches terminées / sous-tâches totales. */
|
||||
export function globalProgressPercent(groups: StoryGroup[]): number {
|
||||
const subs = allSubtasks(groups)
|
||||
if (subs.length === 0) return 0
|
||||
const done = subs.filter((s) => statusToPhase(s.fields.status.name) === 'done').length
|
||||
return Math.round((done / subs.length) * 100)
|
||||
}
|
||||
|
||||
/** Sous-tâches considérées comme « maquette » (libellé à ajuster selon votre vocabulaire Jira). */
|
||||
export function isMaquetteRelated(st: JiraIssue): boolean {
|
||||
const t = `${st.fields.summary} ${st.key}`.toLowerCase()
|
||||
return /maquette|mockup|figma|wireframe|zoning|ui\s*design/i.test(t)
|
||||
}
|
||||
|
||||
/** % de maquettes validées parmi les sous-tâches identifiées comme maquettes. */
|
||||
export function designHealthPercent(groups: StoryGroup[]): number {
|
||||
const candidates = maquetteRelatedSubtasks(groups)
|
||||
if (candidates.length === 0) return 0
|
||||
const ok = candidates.filter((s) => statusToPhase(s.fields.status.name) === 'done').length
|
||||
return Math.round((ok / candidates.length) * 100)
|
||||
}
|
||||
|
||||
function textWithComponents(st: JiraIssue, story: JiraIssue): string {
|
||||
const comps = [...(st.fields.components ?? []), ...(story.fields.components ?? [])]
|
||||
.map((c) => c.name)
|
||||
.join(' ')
|
||||
return `${st.fields.summary} ${comps}`.toLowerCase()
|
||||
}
|
||||
|
||||
/** Intégration « Golden Carbon » : filtre par mot-clé ou composant. */
|
||||
function isGoldenCarbonRelated(st: JiraIssue, story: JiraIssue): boolean {
|
||||
return /golden\s*carbon|goldencarbon/i.test(textWithComponents(st, story))
|
||||
}
|
||||
|
||||
export function goldenCarbonHealthPercent(groups: StoryGroup[]): number {
|
||||
const candidates = goldenCarbonSubtasks(groups)
|
||||
if (candidates.length === 0) return 0
|
||||
const ok = candidates.filter((s) => statusToPhase(s.fields.status.name) === 'done').length
|
||||
return Math.round((ok / candidates.length) * 100)
|
||||
}
|
||||
|
||||
export function blockingTicketsCount(groups: StoryGroup[]): number {
|
||||
const issues: JiraIssue[] = [
|
||||
...groups.map((g) => g.story),
|
||||
...allSubtasks(groups),
|
||||
]
|
||||
return issues.filter((i) => isBlockingStatus(i.fields.status.name)).length
|
||||
}
|
||||
|
||||
export function blockingIssuesInGroup(group: StoryGroup): JiraIssue[] {
|
||||
return [group.story, ...group.subtasks].filter((i) =>
|
||||
isBlockingStatus(i.fields.status.name),
|
||||
)
|
||||
}
|
||||
|
||||
export function blockingSummaryForTooltip(group: StoryGroup): string {
|
||||
const list = blockingIssuesInGroup(group)
|
||||
if (list.length === 0) return 'Aucun ticket bloquant sur cette story.'
|
||||
return list.map((i) => `${i.key} — ${i.fields.status.name}: ${i.fields.summary}`).join('\n')
|
||||
}
|
||||
Reference in New Issue
Block a user