using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; public sealed class DisconnectedMonitorsCheck { public static readonly ReconciliationCheckType CheckType = ReconciliationCheckType.ActiveInpatientNoObservation; private readonly AppDbContext _db; private readonly ReconciliationPublisher _publisher; private readonly ReconciliationJobOptions _opts; private readonly ILogger _logger; public DisconnectedMonitorsCheck( AppDbContext db, ReconciliationPublisher publisher, IOptions opts, ILogger logger) { _db = db; _publisher = publisher; _opts = opts.Value; _logger = logger; } public async Task RunAsync(CancellationToken ct) { var cutoff = DateTimeOffset.UtcNow .AddHours(-_opts.NoObservationThresholdHours); // Limitation: this check detects absence of recorded observations, not absence // of actual clinical monitoring. A nurse who took manual vitals but did not // enter them into the system would still trigger a reconciliation alert. // This is a known gap; the check errs on the side of false positives. var candidates = await _db.Encounters .Where(e => e.Status == EncounterStatus.Active && e.EncounterType == EncounterType.Inpatient) .Select(e => new { EncounterId = e.Id, e.PatientId, LastObservationAt = e.Observations .OrderByDescending(o => o.RecordedAt) .Select(o => (DateTimeOffset?)o.RecordedAt) .FirstOrDefault(), }) .ToListAsync(ct); var stale = candidates .Where(e => e.LastObservationAt == null || e.LastObservationAt < cutoff) .ToList(); if (!stale.Any()) { _logger.LogDebug("[RECONCILIATION] Check3 — all active inpatients have recent observations"); return 0; } var inserted = 0; foreach (var enc in stale) { var alreadyOpen = await _db.ReconciliationAlerts .AnyAsync(r => r.CheckType == CheckType && r.EncounterId == enc.EncounterId && r.ResolvedAt == null, ct); if (alreadyOpen) continue; var lastObs = enc.LastObservationAt.HasValue ? $"last observation {enc.LastObservationAt:u}" : "no observations ever recorded"; var details = $"Active inpatient encounter {enc.EncounterId} has no recent observations " + $"({lastObs}). Monitor may be disconnected."; var rec = new ReconciliationAlert { Id = Guid.NewGuid(), CheckType = CheckType, // ReconciliationCheckType enum EncounterId = enc.EncounterId, PatientId = enc.PatientId, Details = details, CreatedAt = DateTimeOffset.UtcNow, }; _db.ReconciliationAlerts.Add(rec); await _db.SaveChangesAsync(ct); await _publisher.PublishAsync(rec, ct); _logger.LogWarning( "[RECONCILIATION] {CheckType} — encounter {EncounterId}: {LastObs}", CheckType, enc.EncounterId, lastObs); inserted++; } return inserted; } }