shoc-backend/SeaHaven.Services/Helpers/SlaResponseWindows.cs

56 lines
2.6 KiB
C#
Raw Permalink Normal View History

namespace SeaHaven.Services.Helpers
{
public enum SlaState
{
OnTrack,
/// <summary>At least half of the response window has elapsed.</summary>
AtRisk,
/// <summary>The whole response window has elapsed: the response deadline was missed.</summary>
Breached
}
/// <summary>Where one work order stands against its SEV response window.</summary>
public sealed record SlaClock(int Severity, DateTime StartedAt, DateTime DeadlineAt, int PercentElapsed, SlaState State);
/// <summary>
/// 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.
/// </summary>
public static class SlaResponseWindows
{
public static readonly IReadOnlyDictionary<int, TimeSpan> Respond = new Dictionary<int, TimeSpan>
{
[1] = TimeSpan.FromHours(2),
[2] = TimeSpan.FromHours(4),
[3] = TimeSpan.FromHours(8),
[4] = TimeSpan.FromHours(24),
[5] = TimeSpan.FromHours(72)
};
/// <summary>The latest creation time at which a work order of this level has used half its window.</summary>
public static DateTime AtRiskCreatedAtOrBefore(int severity, DateTime now) => now - Respond[severity] / 2;
/// <summary>The latest creation time at which a work order of this level has missed its deadline.</summary>
public static DateTime BreachedCreatedAtOrBefore(int severity, DateTime now) => now - Respond[severity];
/// <summary>Null when the severity is not a SEV 1–5 level.</summary>
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);
}
}
}