Mar 03 2026: implemented SLA issue tracking & enhanced Dashboard
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
sla.py
|
||||
------
|
||||
Issue SLA (Service Level Agreement) helpers.
|
||||
|
||||
SLA thresholds define the maximum number of hours an issue of a given
|
||||
severity may remain unresolved before it is considered breached.
|
||||
|
||||
Statuses
|
||||
--------
|
||||
ok — within the allowed window
|
||||
at_risk — past 75% of the allowed window but not yet breached
|
||||
breached — past the deadline
|
||||
None — issue is already resolved; SLA no longer applies
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
from app.utils.time_utils import now_eastern
|
||||
|
||||
# ── Configurable thresholds (hours) ──────────────────────────────────────────
|
||||
SLA_HOURS = {
|
||||
'critical': 4,
|
||||
'high': 24,
|
||||
'medium': 72,
|
||||
'low': 168, # 7 days
|
||||
}
|
||||
|
||||
# Fraction of the window at which an issue becomes "at risk"
|
||||
AT_RISK_THRESHOLD = 0.75
|
||||
|
||||
|
||||
def sla_deadline(issue):
|
||||
"""
|
||||
Return the datetime by which the issue must be resolved,
|
||||
or None if the severity is not recognised.
|
||||
"""
|
||||
hours = SLA_HOURS.get(issue.severity)
|
||||
if hours is None:
|
||||
return None
|
||||
return issue.reported_at + timedelta(hours=hours)
|
||||
|
||||
|
||||
def sla_status(issue):
|
||||
"""
|
||||
Return one of: 'ok', 'at_risk', 'breached', or None.
|
||||
|
||||
None is returned when the issue is already resolved — SLA no longer
|
||||
applies. None is also returned for unrecognised severity values.
|
||||
"""
|
||||
if issue.status == 'resolved':
|
||||
return None
|
||||
|
||||
hours = SLA_HOURS.get(issue.severity)
|
||||
if hours is None:
|
||||
return None
|
||||
|
||||
deadline = issue.reported_at + timedelta(hours=hours)
|
||||
at_risk_at = issue.reported_at + timedelta(hours=hours * AT_RISK_THRESHOLD)
|
||||
now = now_eastern()
|
||||
|
||||
if now >= deadline:
|
||||
return 'breached'
|
||||
if now >= at_risk_at:
|
||||
return 'at_risk'
|
||||
return 'ok'
|
||||
|
||||
|
||||
def sla_hours_remaining(issue):
|
||||
"""
|
||||
Return the number of hours remaining before the SLA deadline.
|
||||
Negative values indicate the deadline has already passed.
|
||||
Returns None for resolved issues or unrecognised severities.
|
||||
"""
|
||||
if issue.status == 'resolved':
|
||||
return None
|
||||
deadline = sla_deadline(issue)
|
||||
if deadline is None:
|
||||
return None
|
||||
delta = deadline - now_eastern()
|
||||
return round(delta.total_seconds() / 3600, 1)
|
||||
Reference in New Issue
Block a user