using adas_core.Application.Exceptions; using adas_core.Application.Repositories.Interfaces; using adas_core.Application.Services.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Models; using adas_core.Domain.Models.MongoModels; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using MongoDB.Bson; namespace adas_core.Application.Services; public class RecordingAlertService( Lazy patientService, IConfigObservationService configObservationService, IRecordingAlertRepository recordingAlertRepository, IRecordingAlertArchiveRepository recordingAlertArchiveRepository, ILogger logger, IClientMessageService clientMessageService, ISubscribersService subscribersService, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService) : IRecordingAlertService { public async Task Archive(Patient patient) { await ArchiveByPatientId(patient.Id); } public async Task ArchiveByPatientId(ObjectId id) { logger.LogDebug("Archive Recording Alerts by Patient Id {id}", id); using (var cursor = await recordingAlertRepository.FindByPatientIdAsync(id)) { while (await cursor.MoveNextAsync()) foreach (var current in cursor.Current) await recordingAlertArchiveRepository.InsertOneAsync(current); } await DeleteByPatientId(id); } public async Task DeleteByPatientId(ObjectId id) { logger.LogDebug("Delete Recording Alerts by Patient Id {id}", id); await recordingAlertRepository.DeleteByPatientId(id); } public async Task> FindLastRecordingAlert(ObjectId patientId, int num = 2) { return await recordingAlertRepository.AggregatedPatientLastObservations(patientId, num); } public Task SaveRequestAsync(ApiRequest apiRequest) { return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); })); } public async Task SaveRequest(ApiRequest apiRequest) { if (string.IsNullOrEmpty(apiRequest.PatientNumber) && string.IsNullOrEmpty(apiRequest.Location?.UnitName) && string.IsNullOrEmpty(apiRequest.Location?.Bed)) { logger.LogDebug("Patient and Location are nulls in apiRequest RecordingAlerts"); return; } logger.LogDebug("patientNumber: {apiRequestpatientNumber} location: {apiRequestlocation}", apiRequest.PatientNumber, apiRequest.Location); logger.LogDebug("RequestType: {apiRequesttype}", apiRequest.Type); switch (apiRequest.Type) { /* * ORU_R01 - Unsolicited transmission of an observation message * ORU_R40 - Unsolicited transmission of an alert observation message */ case "ORU_R01": case "ORU_R40": case "RecordingAlert": var patient = await patientService.Value.FindPatientByApiRequest(apiRequest); if (patient == null) { // NO PATIENTS OR LOCATIONS WERE FOUND logger.LogWarning( "Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring", apiRequest.PatientNumber, apiRequest.Location); return; } // Recording Alerts if (apiRequest.RecordingAlert != null && apiRequest.RecordingAlerts?.FirstOrDefault() == null) apiRequest.RecordingAlerts = [apiRequest.RecordingAlert]; if (apiRequest.RecordingAlerts != null) { logger.LogDebug("INSERT {apiRequestrecordingAlerts} OBSERVATIONS", apiRequest.RecordingAlerts.Count); foreach (var obs in apiRequest.RecordingAlerts) { obs.PatientId = patient.Id; obs.Id = ObjectId.GenerateNewId(); if (obs.Time == DateTime.MinValue) obs.Time = DateTime.UtcNow; await InsertRecordingAlert(obs); } } break; default: logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Observations", apiRequest.Type); throw new ApiRequestException("ApiRequest type " + apiRequest.Type + " is not valid for Observations"); } } public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId) { await recordingAlertRepository.UpdateManyObjectId(nameId, id, oldId); } private async Task InsertRecordingAlert(PatientRecordingAlert recAlert) { logger.LogDebug("Insert {recAlert}", recAlert); await recordingAlertRepository.InsertOneAsync(recAlert); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, recAlert); await SendObsBroadcast(recAlert); await DoRetentionActions(recAlert); } private async Task SendObsBroadcast(BasePatientObservation recAlert) { if (recAlert.Name == null) return; var type = recAlert is PatientRecordingAlert ? OperationType.RecordingAlert : OperationType.Observation; var patient = await patientService.Value.FindById(recAlert.PatientId); if (patient?.PointOfCareId == null) return; var subscribers = subscribersService.GetSubscribers() .Where(s => s.LocationIds.Contains(patient.PointOfCareId.Value)).ToList(); foreach (var subscriber in subscribers) await clientMessageService.SendAsync(subscriber.Id, type, recAlert); } private async Task DoRetentionActions(PatientRecordingAlert recAlert) { var result = await configObservationService.RetentionActions(recAlert); if (result is not { RetentionPolicyValue: not null } || recAlert.Name == null) return; switch (result.RetentionPolicy) { case RetentionPolicy.DeleteOlderDays: await recordingAlertRepository.DeleteOlderDaysAsync(recAlert.Name, result.RetentionPolicyValue.Value); break; case RetentionPolicy.DeleteOlderNumber: await recordingAlertRepository.DeleteOlderNumberAsync(recAlert.Name, result.RetentionPolicyValue.Value); break; default: throw new ArgumentOutOfRangeException(); } } }