210 lines
9.0 KiB
C#
210 lines
9.0 KiB
C#
using System.Diagnostics;
|
|
using adas_core.Application.Repositories.Interfaces;
|
|
using adas_core.Domain.Models;
|
|
using adas_core.Domain.Models.AppSettings;
|
|
using Microsoft.Extensions.Options;
|
|
using MongoDB.Bson;
|
|
using MongoDB.Driver;
|
|
using Serilog;
|
|
|
|
namespace adas_core.Infrastructure.Repositories;
|
|
|
|
/// <summary>
|
|
/// Repository implementation for managing PatientObservation archive entities in MongoDB.
|
|
/// Provides operations for storing and retrieving historical patient observations.
|
|
/// </summary>
|
|
public class ObservationArchiveRepository : MongoRepository<PatientObservation>, IObservationArchiveRepository
|
|
{
|
|
private readonly ApiSettings _apiSettings;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the ObservationArchiveRepository.
|
|
/// </summary>
|
|
/// <param name="apiSettings">API settings containing collection names configuration.</param>
|
|
/// <param name="database">The MongoDB database instance.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
|
|
public ObservationArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
|
|
{
|
|
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
|
|
_apiSettings = apiSettings.Value;
|
|
} //For testing
|
|
|
|
/// <summary>
|
|
/// Retrieves the aggregated last observations for a specific patient.
|
|
/// Filters observations by name and date, returning the most recent ones.
|
|
/// </summary>
|
|
/// <param name="patientId">The ObjectId of the patient.</param>
|
|
/// <param name="num">The maximum number of observations to return per observation type.</param>
|
|
/// <param name="lastDate">The cutoff date to filter observations (inclusive).</param>
|
|
/// <param name="filterObservations">Optional list of observation names to filter by. If null, retrieves all distinct observations.</param>
|
|
/// <returns>A list of PatientObservation entities sorted by time descending.</returns>
|
|
public async Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num,
|
|
DateTime lastDate, List<string>? filterObservations = null)
|
|
{
|
|
filterObservations = await AggregatePatientObservations(patientId, filterObservations);
|
|
var results = new List<PatientObservation>();
|
|
|
|
foreach (var obs in filterObservations)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.And(
|
|
Builders<PatientObservation>.Filter.Eq(o => o.PatientId, patientId),
|
|
Builders<PatientObservation>.Filter.Eq(o => o.Name, obs),
|
|
Builders<PatientObservation>.Filter.Lte(o => o.Time, lastDate)
|
|
);
|
|
|
|
var sort = Builders<PatientObservation>.Sort.Descending(o => o.Time);
|
|
|
|
results.AddRange(Collection.Find(filter).Sort(sort).Limit(num).ToEnumerable());
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of the collection for archived patient observations.
|
|
/// </summary>
|
|
/// <returns>The collection name from API settings, or default "archive_patients_observations".</returns>
|
|
public override string GetCollectionName()
|
|
{
|
|
return _apiSettings.ArchivePatientsObservations ?? "archive_patients_observations";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a new patient observation into the archive with retry logic for duplicate key errors.
|
|
/// If a duplicate key error occurs, generates a new ObjectId and retries up to maxRetries times.
|
|
/// </summary>
|
|
/// <param name="patientObservation">The PatientObservation entity to insert.</param>
|
|
/// <exception cref="MongoWriteException">Throws when duplicate key error persists after max retries.</exception>
|
|
/// <exception cref="Exception">Throws when insertion fails for reasons other than duplicate key.</exception>
|
|
public new async Task InsertOneAsync(PatientObservation patientObservation)
|
|
{
|
|
const int maxRetries = 2; // Número máximo de reintentos
|
|
var retryCount = 0;
|
|
|
|
while (true)
|
|
try
|
|
{
|
|
await Collection.InsertOneAsync(patientObservation);
|
|
return;
|
|
}
|
|
catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
|
|
{
|
|
retryCount++;
|
|
|
|
Log.Warning(
|
|
"Duplicate key error encountered. Retrying with new ObjectId. Attempt {attempt} of {maxRetries}",
|
|
retryCount, maxRetries);
|
|
|
|
patientObservation.Id = new ObjectId();
|
|
Log.Information("Generated ObjectId: {objectId}", patientObservation.Id);
|
|
|
|
if (retryCount >= maxRetries)
|
|
{
|
|
Log.Error("Maximum retry attempts reached. Could not insert document due to duplicate key error.");
|
|
throw; // Re-lanzar la excepción después de alcanzar el número máximo de reintentos
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error("Error inserting patient observation: {exMessage}", ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes all patient observations before a specified date.
|
|
/// Useful for archival cleanup operations.
|
|
/// </summary>
|
|
/// <param name="date">The cutoff date. Observations older than this date will be deleted.</param>
|
|
/// <returns>The number of deleted documents.</returns>
|
|
public async Task DeleteBeforeDate(DateTime date)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.Lt(po => po.Time, date);
|
|
await Collection.DeleteManyAsync(filter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a batch of patient observations using bulk write operation.
|
|
/// </summary>
|
|
/// <param name="observations">An enumerable of PatientObservation entities to insert.</param>
|
|
/// <returns>The count of successfully inserted documents.</returns>
|
|
public async Task<long> InsertBatch(IEnumerable<PatientObservation> observations)
|
|
{
|
|
var writes = new List<WriteModel<PatientObservation>>();
|
|
writes.AddRange(observations.Select(d => new InsertOneModel<PatientObservation>(d)));
|
|
|
|
var bulkInsert = await Collection.BulkWriteAsync(writes);
|
|
|
|
return bulkInsert.InsertedCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all archived observations for a specific patient.
|
|
/// </summary>
|
|
/// <param name="patientId">The ObjectId of the patient.</param>
|
|
/// <returns>A list of all PatientObservation entities for the patient.</returns>
|
|
public async Task<List<PatientObservation>> FindAllFromPatient(ObjectId patientId)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.Eq(p => p.PatientId, patientId);
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggregates distinct observation names for a patient using MongoDB aggregation pipeline.
|
|
/// If filterObservations is provided, returns that list; otherwise, computes distinct observations.
|
|
/// </summary>
|
|
/// <param name="patientId">The ObjectId of the patient.</param>
|
|
/// <param name="filterObservations">Optional pre-filtered list of observation names. If null or empty, computes distinct values.</param>
|
|
/// <returns>A list of distinct observation name strings.</returns>
|
|
private async Task<List<string>> AggregatePatientObservations(ObjectId patientId,
|
|
List<string>? filterObservations = null)
|
|
{
|
|
var matchPatient = new BsonDocument
|
|
{
|
|
{ "patientid", patientId },
|
|
{ "name", new BsonDocument { { "$ne", BsonNull.Value } } }
|
|
};
|
|
|
|
if (filterObservations == null || !filterObservations.Any())
|
|
{
|
|
// GET DISTINCT OBSERVATIONS
|
|
var distinctObs = new BsonDocument
|
|
{
|
|
{
|
|
"$group", new BsonDocument
|
|
{
|
|
{ "_id", "1" },
|
|
{
|
|
"obs", new BsonDocument
|
|
{
|
|
{ "$addToSet", "$name" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
var distinctPipeline = new[]
|
|
{
|
|
new()
|
|
{
|
|
{
|
|
"$match", matchPatient
|
|
}
|
|
},
|
|
distinctObs
|
|
};
|
|
Debug.WriteLine("AggregatedArchivedPatientLastObservations distinct obs: \n" + distinctPipeline.ToJson());
|
|
var resultList =
|
|
await Collection.AggregateAsync<BsonDocument>(distinctPipeline,
|
|
new AggregateOptions { AllowDiskUse = true });
|
|
var result = resultList.ToList().FirstOrDefault();
|
|
|
|
if (result != null && result.Any())
|
|
filterObservations = result.GetValue("obs").AsBsonArray.Select(it => it.AsString).ToList();
|
|
}
|
|
|
|
return filterObservations ?? [];
|
|
}
|
|
} |