using Microsoft.EntityFrameworkCore; public sealed class AlertsUnacknowledgedCollector : BackgroundService { // 5 minutes matches the DLQ TTL — an alert that survived escalation is still open. private static readonly TimeSpan UnacknowledgedThreshold = TimeSpan.FromMinutes(5); private readonly IServiceScopeFactory _scopes; private readonly ClinicalMetrics _metrics; private readonly ILogger _logger; public AlertsUnacknowledgedCollector( IServiceScopeFactory scopes, ClinicalMetrics metrics, ILogger logger) { _scopes = scopes; _metrics = metrics; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken ct) { using var timer = new PeriodicTimer(TimeSpan.FromSeconds(30)); while (await timer.WaitForNextTickAsync(ct)) await CollectAsync(ct); } private async Task CollectAsync(CancellationToken ct) { try { await using var scope = _scopes.CreateAsyncScope(); var db = scope.ServiceProvider.GetRequiredService(); var cutoff = DateTimeOffset.UtcNow - UnacknowledgedThreshold; var count = await db.ClinicalAlerts .CountAsync(a => a.Severity == AlertSeverity.Critical && a.Status == AlertStatus.Open && a.TriggeredAt < cutoff, ct); _metrics.AlertsUnacknowledgedGauge.Set(count); if (count > 0) _logger.LogWarning( "[PATIENT-SAFETY] alerts_unacknowledged_gauge={Count} " + "(CRITICAL alerts open > 5 min)", count); } catch (Exception ex) { _logger.LogError(ex, "AlertsUnacknowledgedCollector failed"); } } }