issue-ai/src/lib/sla.ts

97 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SLA 服务可用性档位定义
// 参考mail_monitor_wechat/README.md 服务可用性档位说明
export interface SlaTier {
name: string
availability: number
penalty: number
ratio: number | null // 当月总秒数占比,第五档 ratio 为 null无上限
}
export const SLA_TIERS: SlaTier[] = [
{ name: '第一档', availability: 0.99, penalty: 0, ratio: 0.01 },
{ name: '第二档', availability: 0.97, penalty: 0.10, ratio: 0.03 },
{ name: '第三档', availability: 0.95, penalty: 0.25, ratio: 0.05 },
{ name: '第四档', availability: 0.90, penalty: 0.50, ratio: 0.10 },
{ name: '第五档', availability: 0, penalty: 1.00, ratio: null },
]
export interface SlaStatus {
level: 'safe' | 'warning' | 'over-tier1' | 'critical' | 'excluded'
color: string
text: string
}
/**
* 返回指定年月的总秒数
*/
export function getMonthSeconds(year: number, month: number): number {
const daysInMonth = new Date(year, month, 0).getDate()
return daysInMonth * 24 * 3600
}
/**
* 返回第一档 SLA 截止时间
* 截止 = assign_time + 当月总秒数 × 1%
*/
export function getTier1Deadline(assignTime: string): Date {
const at = new Date(assignTime)
const monthSec = getMonthSeconds(at.getFullYear(), at.getMonth() + 1)
const tier1Window = monthSec * 0.01 // 1%
return new Date(at.getTime() + tier1Window * 1000)
}
/**
* 返回第二档 SLA 截止时间(用于"严重"判定)
*/
export function getTier2Deadline(assignTime: string): Date {
const at = new Date(assignTime)
const monthSec = getMonthSeconds(at.getFullYear(), at.getMonth() + 1)
const tier2Window = monthSec * 0.03 // 3%
return new Date(at.getTime() + tier2Window * 1000)
}
/**
* 根据 assign_time 和 counted_in_sla 返回 SLA 状态
*/
export function getSlaStatus(assignTime: string, countedInSla: number): SlaStatus {
if (countedInSla === 0) {
return { level: 'excluded', color: '#94a3b8', text: '不计入SLA' }
}
const now = new Date()
const tier1Deadline = getTier1Deadline(assignTime)
const tier1Window = tier1Deadline.getTime() - new Date(assignTime).getTime()
// 剩余时间(毫秒),正数表示还没超,负数表示已超
const remaining = tier1Deadline.getTime() - now.getTime()
if (remaining > 0) {
const remainingRatio = remaining / tier1Window
if (remainingRatio > 0.5) {
const h = Math.floor(remaining / 3600000)
const m = Math.floor((remaining % 3600000) / 60000)
return { level: 'safe', color: '#22c55e', text: `还剩 ${h}h ${m}m` }
} else {
const h = Math.floor(remaining / 3600000)
const m = Math.floor((remaining % 3600000) / 60000)
return { level: 'warning', color: '#eab308', text: `还剩 ${h}h ${m}m` }
}
}
// 已超第一档
const exceeded = Math.abs(remaining)
const tier2Deadline = getTier2Deadline(assignTime)
if (now < tier2Deadline) {
const h = Math.floor(exceeded / 3600000)
const m = Math.floor((exceeded % 3600000) / 60000)
return { level: 'over-tier1', color: '#f97316', text: `超时 ${h}h ${m}m` }
}
// 已超第二档(严重)
const h = Math.floor(exceeded / 3600000)
const m = Math.floor((exceeded % 3600000) / 60000)
return { level: 'critical', color: '#ef4444', text: `超时 ${h}h ${m}m` }
}