namespace SeaHaven.Services.Helpers { public enum SlaState { OnTrack, /// At least half of the response window has elapsed. AtRisk, /// The whole response window has elapsed: the response deadline was missed. Breached } /// Where one work order stands against its SEV response window. public sealed record SlaClock(int Severity, DateTime StartedAt, DateTime DeadlineAt, int PercentElapsed, SlaState State); /// /// Respond deadlines from the client's SEV table, the same values the severity badge tooltip shows. /// They apply to Reactive and Emergency work orders with a severity, and the clock starts when the /// work order is created, never at its scheduled date. The table's "next day if after hours" note /// is not modelled: the clock always counts the plain hours. /// public static class SlaResponseWindows { public static readonly IReadOnlyDictionary Respond = new Dictionary { [1] = TimeSpan.FromHours(2), [2] = TimeSpan.FromHours(4), [3] = TimeSpan.FromHours(8), [4] = TimeSpan.FromHours(24), [5] = TimeSpan.FromHours(72) }; /// The latest creation time at which a work order of this level has used half its window. public static DateTime AtRiskCreatedAtOrBefore(int severity, DateTime now) => now - Respond[severity] / 2; /// The latest creation time at which a work order of this level has missed its deadline. public static DateTime BreachedCreatedAtOrBefore(int severity, DateTime now) => now - Respond[severity]; /// Null when the severity is not a SEV 1–5 level. public static SlaClock? Evaluate(string? severity, DateTime createdAt, DateTime now) { var level = WorkOrderSeverityRules.ParseLevel(severity); if (level is not int sev) return null; var window = Respond[sev]; var startedAt = DateTime.SpecifyKind(createdAt, DateTimeKind.Utc); var elapsed = now - startedAt; var state = elapsed >= window ? SlaState.Breached : elapsed >= window / 2 ? SlaState.AtRisk : SlaState.OnTrack; var percent = (int)Math.Floor(Math.Max(elapsed.Ticks, 0) * 100d / window.Ticks); return new SlaClock(sev, startedAt, startedAt + window, percent, state); } } }