guardia-messenger/hooks/useSLAPrediction.ts

76 lines
2.5 KiB
TypeScript

/**
* useSLAPrediction (#43) — SLA 위반 예측 훅
*
* GET /api/sla/prediction 으로 SLA 위반 위험이 있는 SR/서비스를 예측 조회.
* 폴링(기본 60초)으로 주기 갱신. 위험도(risk) 기준으로 정렬·요약 제공.
*/
import { useEffect, useState, useCallback, useRef } from 'react'
import { getSLAPrediction } from '../services/api'
export interface SLAPredictItem {
id: string | number
target_type: 'sr' | 'service'
target_name: string
sla_deadline: string // ISO
predicted_breach_at: string // ISO
risk: 'high' | 'medium' | 'low'
confidence: number // 0~1
remaining_minutes: number
}
interface SLAPredictResult {
items: SLAPredictItem[]
highRiskCount: number
loading: boolean
error: boolean
refresh: () => void
}
const RISK_ORDER: Record<string, number> = { high: 0, medium: 1, low: 2 }
export function useSLAPrediction(pollMs = 60000): SLAPredictResult {
const [items, setItems] = useState<SLAPredictItem[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
const timer = useRef<ReturnType<typeof setInterval> | null>(null)
const load = useCallback(async () => {
try {
const res = await getSLAPrediction()
const raw: any[] = res.data?.items ?? res.data?.predictions ?? res.data ?? []
const mapped: SLAPredictItem[] = raw.map((r: any) => ({
id: r.id ?? r.sr_id ?? r.target_id,
target_type: r.target_type ?? (r.sr_id ? 'sr' : 'service'),
target_name: r.target_name ?? r.title ?? r.service_name ?? `#${r.id ?? ''}`,
sla_deadline: r.sla_deadline ?? r.deadline ?? '',
predicted_breach_at: r.predicted_breach_at ?? r.eta ?? '',
risk: (r.risk ?? r.risk_level ?? 'low').toLowerCase(),
confidence: typeof r.confidence === 'number' ? r.confidence : 0,
remaining_minutes: r.remaining_minutes ?? r.remaining_min ?? 0,
}))
mapped.sort((a, b) =>
(RISK_ORDER[a.risk] ?? 9) - (RISK_ORDER[b.risk] ?? 9) ||
a.remaining_minutes - b.remaining_minutes
)
setItems(mapped)
setError(false)
} catch {
setError(true)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
load()
timer.current = setInterval(load, pollMs)
return () => { if (timer.current) clearInterval(timer.current) }
}, [load, pollMs])
const highRiskCount = items.filter(i => i.risk === 'high').length
return { items, highRiskCount, loading, error, refresh: load }
}
export default useSLAPrediction