using Microsoft.EntityFrameworkCore; public class AlertQualityMetricsService : IAlertQualityMetricsService { private readonly AppDbContext _db; public AlertQualityMetricsService(AppDbContext db) => _db = db; public async Task> ListAsync( AlertType? alertType, DateTimeOffset from, DateTimeOffset to) { var query = _db.AlertQualityMetrics .AsNoTracking() .Where(m => m.WindowStart >= from && m.WindowEnd <= to); if (alertType.HasValue) query = query.Where(m => m.AlertType == alertType.Value); var rows = await query .OrderByDescending(m => m.WindowStart) .ThenBy(m => m.AlertType) .ToListAsync(); return rows.Select(Map).ToList(); } public async Task GetSummaryAsync( DateTimeOffset from, DateTimeOffset to) { var snapshots = await _db.AlertQualityMetrics .AsNoTracking() .Where(m => m.WindowStart >= from && m.WindowEnd <= to) .ToListAsync(); if (snapshots.Count == 0) { return new AlertQualitySummaryResponse( from, to, 0, 0, 0, 0, 0, 0, 0, 0); } var totalAlerts = snapshots.Sum(s => s.TotalAlerts); var totalFeedback = snapshots.Sum(s => s.FeedbackCount); var totalAcknowledged = snapshots.Sum(s => s.AcknowledgedCount); var totalUseful = snapshots.Sum(s => s.FeedbackUsefulCount); var totalFalsePositive = snapshots.Sum(s => s.FeedbackFalsePositiveCount); var totalWouldAct = snapshots.Sum(s => s.FeedbackWouldActCount); var weightedAckSeconds = snapshots.Sum(s => s.AvgSecondsToAcknowledge * s.TotalAlerts); var weightedResolveSeconds = snapshots.Sum(s => s.AvgSecondsToResolution * s.ResolvedCount); return new AlertQualitySummaryResponse( PeriodStart: from, PeriodEnd: to, TotalAlerts: totalAlerts, TotalFeedback: totalFeedback, AcknowledgementRate: totalAlerts == 0 ? 0 : (double)totalAcknowledged / totalAlerts, FalsePositiveRate: totalFeedback == 0 ? 0 : (double)totalFalsePositive / totalFeedback, UsefulRate: totalFeedback == 0 ? 0 : (double)totalUseful / totalFeedback, WouldActRate: totalFeedback == 0 ? 0 : (double)totalWouldAct / totalFeedback, AvgSecondsToAcknowledge: totalAlerts == 0 ? 0 : weightedAckSeconds / totalAlerts, AvgSecondsToResolution: snapshots.Sum(s => s.ResolvedCount) == 0 ? 0 : weightedResolveSeconds / snapshots.Sum(s => s.ResolvedCount)); } private static AlertQualityMetricResponse Map(AlertQualityMetric m) => new( m.Id, m.AlertType.ToString(), m.WindowStart, m.WindowEnd, m.TotalAlerts, m.AcknowledgedCount, m.ResolvedCount, m.EscalatedCount, m.FeedbackUsefulCount, m.FeedbackFalsePositiveCount, m.FeedbackWouldActCount, m.FeedbackCount, m.AcknowledgementRate, m.FalsePositiveRate, m.UsefulRate, m.WouldActRate, m.AvgSecondsToAcknowledge, m.AvgSecondsToResolution, m.ComputedAt); }