1031 lines
39 KiB
C#
1031 lines
39 KiB
C#
using adas_core.Application.Exceptions;
|
|
using adas_core.Application.Repositories.Interfaces;
|
|
using adas_core.Domain.Enums;
|
|
using adas_core.Domain.Models;
|
|
using adas_core.Domain.Models.AppSettings;
|
|
using adas_core.Domain.Models.Filter;
|
|
using adas_core.Domain.Models.GroupedObservations;
|
|
using adas_core.Domain.Models.MongoModels;
|
|
using adas_core.Domain.Utils;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using MongoDB.Bson;
|
|
using MongoDB.Driver;
|
|
using System.Diagnostics;
|
|
using System.Text.RegularExpressions;
|
|
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
|
|
|
|
namespace adas_core.Infrastructure.Repositories;
|
|
|
|
public class ObservationRepository : MongoRepository<PatientObservation>, IObservationRepository
|
|
{
|
|
private readonly ApiSettings _apiSettings;
|
|
private readonly ILogger<ObservationRepository> _logger;
|
|
|
|
|
|
public ObservationRepository(
|
|
IOptions<ApiSettings>? apiSettings,
|
|
IMongoDatabase database,
|
|
ILogger<ObservationRepository> logger) : base(database)
|
|
{
|
|
if (apiSettings != null)
|
|
{
|
|
_logger = logger;
|
|
_apiSettings = apiSettings.Value;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentNullException(nameof(apiSettings));
|
|
}
|
|
} //For testing
|
|
|
|
|
|
public override string GetCollectionName()
|
|
{
|
|
return _apiSettings.PatientsObservations ?? "patients_observations";
|
|
}
|
|
|
|
public async Task<IEnumerable<PatientObservation>> FindLastObservations(ObjectId patientId, string codingSystem,
|
|
string code, int num = 2)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.And(
|
|
Builders<PatientObservation>.Filter.Eq(ob => ob.PatientId, patientId),
|
|
Builders<PatientObservation>.Filter.Eq(ob => ob.CodingSystem, codingSystem),
|
|
Builders<PatientObservation>.Filter.Eq(ob => ob.Code, code)
|
|
);
|
|
|
|
var result = await Collection.FindAsync(
|
|
filter,
|
|
new FindOptions<PatientObservation>
|
|
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
|
|
);
|
|
|
|
return result.ToEnumerable();
|
|
}
|
|
|
|
public async Task<List<PatientObservation>> FindLastObservationsByCodingSystem(ObjectId patientId,
|
|
string codingSystem, int num = 10)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.And(
|
|
Builders<PatientObservation>.Filter.Eq(ob => ob.PatientId, patientId),
|
|
Builders<PatientObservation>.Filter.Eq(ob => ob.CodingSystem, codingSystem)
|
|
);
|
|
|
|
var result = await Collection.FindAsync(
|
|
filter,
|
|
new FindOptions<PatientObservation>
|
|
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
|
|
);
|
|
|
|
return await result.ToListAsync();
|
|
}
|
|
|
|
public async Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num,
|
|
List<string>? filterObservations = null)
|
|
{
|
|
filterObservations = await AggregatePatientObservations(patientId, filterObservations);
|
|
var results = new List<PatientObservation>();
|
|
foreach (var obs in filterObservations)
|
|
{
|
|
var builder = Builders<PatientObservation>.Filter;
|
|
var filter = builder.And(
|
|
builder.Eq(o => o.PatientId, patientId),
|
|
builder.Eq(o => o.Name, obs)
|
|
);
|
|
|
|
var cursor = await Collection.FindAsync(filter,
|
|
new FindOptions<PatientObservation>
|
|
{
|
|
Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id"),
|
|
Limit = num
|
|
});
|
|
|
|
results.AddRange(cursor.ToEnumerable());
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
|
|
public async Task<List<PatientObservation>> AggregatedPatientLastObservationsByLastDate(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 cursor = await Collection.FindAsync(
|
|
filter,
|
|
new FindOptions<PatientObservation>
|
|
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id"), Limit = num }
|
|
);
|
|
|
|
results.AddRange(await cursor.ToListAsync());
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
|
|
public async Task<List<PatientObservation>> AggregatedPatientLastObservationsByField(ObjectId patientId,
|
|
List<Field>? filterObservations)
|
|
{
|
|
try
|
|
{
|
|
var results = new List<PatientObservation>();
|
|
IAsyncCursor<PatientObservation>? cursor;
|
|
var builder = Builders<PatientObservation>.Filter;
|
|
|
|
if (filterObservations != null)
|
|
{
|
|
foreach (var obs in filterObservations)
|
|
{
|
|
FilterDefinition<PatientObservation> filter;
|
|
|
|
if (obs is { OnlyExpired: true, Name: not null })
|
|
filter = builder.And(
|
|
builder.Eq(o => o.PatientId, patientId),
|
|
builder.Eq(o => o.Name, obs.Name),
|
|
builder.Eq(o => o.Expired, obs.OnlyExpired)
|
|
);
|
|
else
|
|
filter = builder.And(
|
|
builder.Eq(o => o.PatientId, patientId),
|
|
builder.Eq(o => o.Name, obs.Name)
|
|
);
|
|
|
|
cursor = await Collection.FindAsync(
|
|
filter,
|
|
new FindOptions<PatientObservation>
|
|
{
|
|
Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id"),
|
|
Limit = obs.Last
|
|
});
|
|
|
|
results.AddRange(cursor.ToEnumerable());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var filter = builder.Eq(o => o.PatientId, patientId);
|
|
|
|
cursor = await Collection.FindAsync(
|
|
filter,
|
|
new FindOptions<PatientObservation>
|
|
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
|
|
|
|
results.AddRange(cursor.ToEnumerable());
|
|
}
|
|
|
|
return results;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("Error aggregated patient last observations by field {exMessage}", ex.Message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
|
|
public async Task<List<BsonDocument>> AggregatedPatientGroupedObservations(ObjectId patientId,
|
|
GroupedField groupedField)
|
|
{
|
|
try
|
|
{
|
|
DateTime? lastObsTime = null;
|
|
if (groupedField.Since == GroupedObservationEnum.Since.Last && !string.IsNullOrEmpty(groupedField.Name))
|
|
{
|
|
var lastObsList =
|
|
await AggregatedPatientLastObservations(patientId, 1, [groupedField.Name]);
|
|
var lastObs = lastObsList.FirstOrDefault();
|
|
if (lastObs != null)
|
|
{
|
|
lastObsTime = lastObs.Time;
|
|
lastObsTime = DateTime.SpecifyKind(lastObsTime.Value, DateTimeKind.Utc);
|
|
}
|
|
}
|
|
|
|
|
|
var fields = new BsonArray();
|
|
|
|
var match = new BsonDocument { { "patientid", patientId } };
|
|
if (groupedField.GetNames().Count > 1)
|
|
{
|
|
groupedField.GetNames().ForEach(name => { fields.Add(new BsonDocument { { "name", name } }); });
|
|
match.Add("$or", fields);
|
|
}
|
|
else
|
|
{
|
|
match.Add("name", groupedField.Name);
|
|
}
|
|
|
|
var projectDate = new BsonDocument
|
|
{
|
|
{ "y", new BsonDocument { { "$year", "$time" } } },
|
|
{ "M", new BsonDocument { { "$month", "$time" } } },
|
|
{ "d", new BsonDocument { { "$dayOfMonth", "$time" } } }
|
|
};
|
|
var projectTimeFromParts = new BsonDocument
|
|
{
|
|
{ "year", "$_id.year" },
|
|
{ "month", "$_id.month" },
|
|
{ "day", "$_id.day" }
|
|
};
|
|
var idDocument = new BsonDocument { { "year", "$y" }, { "month", "$M" }, { "day", "$d" } };
|
|
var fromDate = DateTime.MinValue;
|
|
if (groupedField.Regularity == GroupedObservationEnum.Regularity.Day)
|
|
{
|
|
if (groupedField.Since == GroupedObservationEnum.Since.Last && lastObsTime.HasValue)
|
|
fromDate = lastObsTime.Value.AddDays(-1 * groupedField.Max);
|
|
else
|
|
fromDate = DateTime.UtcNow.AddDays(-1 * groupedField.Max);
|
|
}
|
|
|
|
if (groupedField.Regularity is GroupedObservationEnum.Regularity.Hour
|
|
or GroupedObservationEnum.Regularity.Minute or GroupedObservationEnum.Regularity.Second
|
|
or GroupedObservationEnum.Regularity.Times or GroupedObservationEnum.Regularity.Shift)
|
|
{
|
|
projectDate.Add("h", new BsonDocument { { "$hour", "$time" } });
|
|
idDocument.Add("hour", "$h");
|
|
projectTimeFromParts.Add("hour", "$_id.hour");
|
|
//fromDate = DateTime.UtcNow.AddHours(-1 * groupedField.max);
|
|
if (groupedField.Since == GroupedObservationEnum.Since.Last && lastObsTime.HasValue)
|
|
fromDate = new DateTime(lastObsTime.Value.Year, lastObsTime.Value.Month, lastObsTime.Value.Day,
|
|
lastObsTime.Value.AddHours(1).Hour, 00, 00).AddHours(-1 * groupedField.Max);
|
|
else
|
|
fromDate = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, DateTime.UtcNow.Day,
|
|
DateTime.UtcNow.Hour, 00, 00).AddHours(-1 * groupedField.Max);
|
|
}
|
|
|
|
if (groupedField.Regularity == GroupedObservationEnum.Regularity.Minute ||
|
|
groupedField.Regularity == GroupedObservationEnum.Regularity.Second ||
|
|
groupedField.Regularity == GroupedObservationEnum.Regularity.Times
|
|
//No queremos los minutos cuando pedimos por turno, en principio. Revisar si en algún caso necesitamos los minutos, quitado de momento por problemas
|
|
// a la hora de devolver el last.
|
|
//|| groupedField.regularity == Regularity.Shift
|
|
)
|
|
{
|
|
projectDate.Add("m", new BsonDocument { { "$minute", "$time" } });
|
|
idDocument.Add("minute", "$m");
|
|
projectTimeFromParts.Add("minute", "$_id.minute");
|
|
|
|
if (groupedField.Regularity != GroupedObservationEnum.Regularity.Shift)
|
|
{
|
|
if (groupedField.Since == GroupedObservationEnum.Since.Last && lastObsTime.HasValue)
|
|
fromDate = lastObsTime.Value.AddMinutes(-1 * groupedField.Max);
|
|
else
|
|
fromDate = DateTime.UtcNow.AddMinutes(-1 * groupedField.Max);
|
|
}
|
|
}
|
|
|
|
if (groupedField.Regularity == GroupedObservationEnum.Regularity.Second ||
|
|
groupedField.Regularity == GroupedObservationEnum.Regularity.Times)
|
|
{
|
|
projectDate.Add("s", new BsonDocument { { "$second", "$time" } });
|
|
idDocument.Add("second", "$s");
|
|
projectTimeFromParts.Add("second", "$_id.second");
|
|
if (groupedField.Since == GroupedObservationEnum.Since.Last && lastObsTime.HasValue)
|
|
fromDate = lastObsTime.Value.AddSeconds(-1 * groupedField.Max);
|
|
else
|
|
fromDate = DateTime.UtcNow.AddSeconds(-1 * groupedField.Max);
|
|
}
|
|
|
|
if (groupedField.Regularity == GroupedObservationEnum.Regularity.Second ||
|
|
groupedField.Regularity == GroupedObservationEnum.Regularity.Times)
|
|
{
|
|
projectDate.Add("ms", new BsonDocument { { "$millisecond", "$time" } });
|
|
idDocument.Add("millisecond ", "$ms");
|
|
projectTimeFromParts.Add("millisecond", "$_id.millisecond");
|
|
}
|
|
|
|
projectDate.Add("name", "$name");
|
|
projectDate.Add("min", "$min");
|
|
projectDate.Add("max", "$max");
|
|
projectDate.Add("time", "$time");
|
|
projectDate.Add("value", "$value");
|
|
idDocument.Add("name", "$name");
|
|
//No queremos meter el mínimo y el maximo en la agrupación.
|
|
//idDocument.Add("min", "$min");
|
|
//idDocument.Add("max", "$max");
|
|
fromDate = DateTime.SpecifyKind(fromDate, DateTimeKind.Utc);
|
|
if (groupedField.Max > 0)
|
|
{
|
|
//Add filter lte for ICCA future observations like hour balance.
|
|
if (groupedField.Since == GroupedObservationEnum.Since.Last && lastObsTime.HasValue)
|
|
match.Add("time", new BsonDocument("$gte", fromDate).Add("$lte", lastObsTime.Value));
|
|
else
|
|
match.Add("time", new BsonDocument("$gte", fromDate).Add("$lte", DateTime.UtcNow));
|
|
}
|
|
|
|
|
|
var group = new BsonDocument
|
|
{
|
|
{ "_id", idDocument }
|
|
};
|
|
if (groupedField.Result.Count == 0 || groupedField.Result.Contains(GroupedObservationEnum.Result.First))
|
|
group.Add("first",
|
|
new BsonDocument
|
|
{
|
|
{
|
|
"$first",
|
|
new BsonDocument
|
|
{ { "time", "$time" }, { "value", "$value" }, { "min", "$min" }, { "max", "$max" } }
|
|
}
|
|
});
|
|
|
|
if (groupedField.Result.Contains(GroupedObservationEnum.Result.Last))
|
|
group.Add("last",
|
|
new BsonDocument
|
|
{
|
|
{
|
|
"$last",
|
|
new BsonDocument
|
|
{ { "time", "$time" }, { "value", "$value" }, { "min", "$min" }, { "max", "$max" } }
|
|
}
|
|
});
|
|
|
|
if (groupedField.Result.Contains(GroupedObservationEnum.Result.LastFilled))
|
|
group.Add("lastfilled",
|
|
new BsonDocument
|
|
{
|
|
{
|
|
"$last",
|
|
new BsonDocument
|
|
{ { "time", "$time" }, { "value", "$value" }, { "min", "$min" }, { "max", "$max" } }
|
|
}
|
|
});
|
|
|
|
if (groupedField.Result.Contains(GroupedObservationEnum.Result.Min))
|
|
group.Add("min",
|
|
new BsonDocument
|
|
{
|
|
{
|
|
"$min",
|
|
new BsonDocument
|
|
{ { "value", "$value" }, { "time", "$time" }, { "min", "$min" }, { "max", "$max" } }
|
|
}
|
|
});
|
|
|
|
if (groupedField.Result.Contains(GroupedObservationEnum.Result.Max))
|
|
group.Add("max",
|
|
new BsonDocument
|
|
{
|
|
{
|
|
"$max",
|
|
new BsonDocument
|
|
{
|
|
{ "value", "$value" }, { "time", "$time" }, { "min", "$min" }, { "max", "$max" }
|
|
}
|
|
}
|
|
});
|
|
|
|
if (groupedField.Result.Contains(GroupedObservationEnum.Result.Sum))
|
|
group.Add("sum", new BsonDocument { { "$sum", "$value" } });
|
|
|
|
if (groupedField.Result.Contains(GroupedObservationEnum.Result.Average))
|
|
group.Add("average", new BsonDocument { { "$avg", "$value" } });
|
|
|
|
if (groupedField.Result.Contains(GroupedObservationEnum.Result.Count))
|
|
group.Add("count", new BsonDocument { { "$sum", 1 } });
|
|
|
|
if (groupedField.Result.Contains(GroupedObservationEnum.Result.HalfHour))
|
|
group.Add("all",
|
|
new BsonDocument
|
|
{
|
|
{
|
|
"$push",
|
|
new BsonDocument
|
|
{
|
|
{ "value", "$value" }, { "time", "$time" }, { "min", "$min" }, { "max", "$max" }
|
|
}
|
|
}
|
|
});
|
|
//group.Add("halfHour", new BsonDocument { { "$unset", new BsonDocument { { "time", "$time" }, { "value", "$value" }, { "min", "$min" }, { "max", "$max" } } } });
|
|
var pipeline = new List<BsonDocument>
|
|
{
|
|
new()
|
|
{
|
|
{
|
|
"$match", match
|
|
}
|
|
},
|
|
new()
|
|
{
|
|
{
|
|
"$sort", new BsonDocument { { "time", 1 } }
|
|
}
|
|
},
|
|
new()
|
|
{
|
|
{
|
|
"$project", projectDate
|
|
}
|
|
},
|
|
new()
|
|
{
|
|
{
|
|
"$group", group
|
|
}
|
|
},
|
|
new()
|
|
{
|
|
{
|
|
"$addFields",
|
|
new BsonDocument
|
|
{
|
|
{ "time", new BsonDocument { { "$dateFromParts", projectTimeFromParts } } },
|
|
{ "isFilled", false }
|
|
}
|
|
}
|
|
},
|
|
new()
|
|
{
|
|
{
|
|
"$sort", new BsonDocument { { "time", 1 } }
|
|
}
|
|
}
|
|
};
|
|
if (groupedField is { Regularity: GroupedObservationEnum.Regularity.Times, Max: > 0 })
|
|
pipeline.Add(new BsonDocument("$limit", groupedField.Max));
|
|
//System.Diagnostics.Debug.WriteLine("AggregatedPatientGroupedObservations query: \n" + pipeline.ToJson());
|
|
_logger.LogDebug("Executing AggregatedPatientGroupedObservations query: {pipeline}:", pipeline.ToJson());
|
|
var resultsCursor =
|
|
await Collection.AggregateAsync<BsonDocument>(pipeline, new AggregateOptions { AllowDiskUse = true });
|
|
var results = resultsCursor.ToList();
|
|
|
|
return results;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("Error agregated patient grouped observations {exMessage}", ex.Message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
|
|
public new async Task InsertOneAsync(PatientObservation patientObservation)
|
|
{
|
|
const int maxRetries = 2; // Número máximo de reintentos
|
|
var retryCount = 0;
|
|
|
|
while (retryCount < maxRetries)
|
|
try
|
|
{
|
|
await Collection.InsertOneAsync(patientObservation);
|
|
return;
|
|
}
|
|
catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
|
|
{
|
|
retryCount++;
|
|
_logger.LogWarning(
|
|
"Duplicate key error encountered. Retrying with new ObjectId. Attempt {attempt} of {maxRetries}",
|
|
retryCount, maxRetries);
|
|
|
|
if (retryCount < maxRetries) continue;
|
|
|
|
_logger.LogError(
|
|
"Maximum retry attempts reached. Could not insert document due to duplicate key error.");
|
|
throw; // Relanzar la excepción después de alcanzar el número máximo de reintentos
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("Error inserting patient observation: {exMessage}", ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task DeleteByPatientId(ObjectId id)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.Eq(po => po.PatientId, id);
|
|
await Collection.DeleteManyAsync(filter);
|
|
}
|
|
|
|
public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAsync(ObjectId patientId)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.Eq(ob => ob.PatientId, patientId);
|
|
return await Collection.FindAsync(filter, new FindOptions<PatientObservation> { BatchSize = 100 });
|
|
}
|
|
|
|
public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId,
|
|
string codingSystem, string name)
|
|
{
|
|
var filter =
|
|
Builders<PatientObservation>.Filter.And(
|
|
Builders<PatientObservation>.Filter.Eq(ob => ob.PatientId, patientId),
|
|
Builders<PatientObservation>.Filter.Eq(ob => ob.CodingSystem, codingSystem),
|
|
Builders<PatientObservation>.Filter.Eq(ob => ob.Name, name)
|
|
);
|
|
|
|
return await Collection.FindAsync(filter, new FindOptions<PatientObservation> { BatchSize = 100 });
|
|
}
|
|
|
|
|
|
public new async Task DeleteAsync(ObjectId id)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.Eq(obs => obs.Id, id);
|
|
await Collection.DeleteOneAsync(filter);
|
|
}
|
|
|
|
|
|
public async Task<List<PatientObservation>> DeleteOlderDaysAsync(string name, int retentionPolicyValue)
|
|
{
|
|
var dateLimit = DateTime.UtcNow.AddDays(-1 * retentionPolicyValue);
|
|
var filter = Builders<PatientObservation>.Filter.And(
|
|
Builders<PatientObservation>.Filter.Eq(obs => obs.Name, name),
|
|
Builders<PatientObservation>.Filter.Lt(obs => obs.Time, dateLimit)
|
|
);
|
|
var documentsToDelete = await Collection.Find(filter).ToListAsync();
|
|
|
|
await Collection.DeleteManyAsync(filter);
|
|
|
|
return documentsToDelete;
|
|
}
|
|
|
|
|
|
public async Task<List<PatientObservation>> DeleteOlderSecondsAsync(string name, int retentionPolicyValue)
|
|
{
|
|
var dateLimit = DateTime.UtcNow.AddSeconds(-1 * retentionPolicyValue);
|
|
var filter = Builders<PatientObservation>.Filter.And(
|
|
Builders<PatientObservation>.Filter.Eq(obs => obs.Name, name),
|
|
Builders<PatientObservation>.Filter.Lt(obs => obs.Time, dateLimit)
|
|
);
|
|
var documentsToDelete = await Collection.Find(filter).ToListAsync();
|
|
|
|
await Collection.DeleteManyAsync(filter);
|
|
|
|
return documentsToDelete;
|
|
}
|
|
|
|
|
|
public async Task<List<PatientObservation>> DeleteOlderNumberAsync(string name, int retentionPolicyValue)
|
|
{
|
|
var builder = Builders<PatientObservation>.Filter;
|
|
var idsToDelete = await Collection.Find(obs => obs.Name == name)
|
|
.Project(obs => obs.Id)
|
|
.Sort("{time: -1}")
|
|
.Skip(retentionPolicyValue)
|
|
.ToListAsync();
|
|
|
|
List<PatientObservation> documentsToDelete = [];
|
|
|
|
if (idsToDelete == null || !idsToDelete.Any()) return documentsToDelete;
|
|
|
|
|
|
var idFilter = builder.In("_id", idsToDelete);
|
|
documentsToDelete = await Collection.Find(idFilter).ToListAsync();
|
|
|
|
await Collection.DeleteManyAsync(idFilter);
|
|
|
|
return documentsToDelete;
|
|
}
|
|
|
|
|
|
public async Task<bool> ExistBySystemId(ObjectId patientid, string systemId)
|
|
{
|
|
return await Collection.Find(Builders<PatientObservation>.Filter.And(
|
|
Builders<PatientObservation>.Filter.Eq(obs => obs.PatientId, patientid),
|
|
Builders<PatientObservation>.Filter.Eq(obs => obs.SystemId, systemId)
|
|
))
|
|
.Project("{ _id: true }")
|
|
.AnyAsync();
|
|
}
|
|
|
|
|
|
public async Task<PatientObservation?> FindLastObservationBeforeDate(ObjectId patientId, string? name,
|
|
DateTime date)
|
|
{
|
|
var builder = Builders<PatientObservation>.Filter;
|
|
var filter = builder.And(
|
|
builder.Eq(obs => obs.PatientId, patientId),
|
|
builder.Eq(obs => obs.Name, name),
|
|
builder.Lt(obs => obs.Time, date)
|
|
);
|
|
|
|
var result = await Collection.FindAsync(filter,
|
|
new FindOptions<PatientObservation>
|
|
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id") });
|
|
|
|
return await result.FirstOrDefaultAsync();
|
|
}
|
|
|
|
public async Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, string? name, DateTime date)
|
|
{
|
|
var builder = Builders<PatientObservation>.Filter;
|
|
var filter = builder.Eq(obs => obs.PatientId, patientId)
|
|
& builder.Eq(obs => obs.Name, name)
|
|
& builder.Eq(obs => obs.Time, date);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
return result.ToList();
|
|
}
|
|
|
|
|
|
public async Task<List<PatientObservation>> FindAnyBeforeDate(ObjectId patientId, DateTime date)
|
|
{
|
|
var filterBuilder = Builders<PatientObservation>.Filter;
|
|
var patientIdFilter = filterBuilder.Eq(obs => obs.PatientId, patientId);
|
|
var dateFilter = filterBuilder.Lt(obs => obs.Time, date);
|
|
|
|
var filter = filterBuilder.And(patientIdFilter, dateFilter);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
return result.ToList();
|
|
}
|
|
|
|
public async Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name,
|
|
int? expires)
|
|
{
|
|
// Crear el filtro base
|
|
var matchFilter = new BsonDocument
|
|
{
|
|
{ "patientid", patientId },
|
|
{ "name", name }
|
|
};
|
|
|
|
// Agregar filtro de expiración si expires no es null
|
|
if (expires.HasValue)
|
|
{
|
|
var expirationDate = DateTime.UtcNow.AddSeconds(-expires.Value);
|
|
matchFilter.Add("time", new BsonDocument("$gte", expirationDate));
|
|
}
|
|
|
|
var pipeline = new[]
|
|
{
|
|
// Filtrar por PatientId, name y (opcionalmente) tiempo no expirado
|
|
new BsonDocument("$match", matchFilter),
|
|
|
|
// Ordenar por tiempo en orden descendente
|
|
new BsonDocument("$sort", new BsonDocument("time", -1)),
|
|
|
|
// Agrupar por el campo `value` (valores únicos)
|
|
new BsonDocument("$group", new BsonDocument
|
|
{
|
|
{ "_id", "$value" },
|
|
{ "LatestObservation", new BsonDocument("$first", "$$ROOT") }
|
|
}),
|
|
|
|
// Proyectar solo los campos originales
|
|
new BsonDocument("$replaceRoot", new BsonDocument("newRoot", "$LatestObservation"))
|
|
};
|
|
|
|
var result = await Collection.AggregateAsync<PatientObservation>(pipeline);
|
|
return result.ToList();
|
|
}
|
|
|
|
|
|
public async Task<List<PatientObservation?>> AggregatedPatientActiveIntravenousLinesObservations(ObjectId patientId)
|
|
{
|
|
var results = new List<PatientObservation>();
|
|
|
|
var cursor = await Collection.FindAsync(
|
|
o => o.PatientId == patientId && o.Name == "IntravenousLinesObs",
|
|
new FindOptions<PatientObservation>
|
|
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
|
|
|
|
results.AddRange(cursor.ToEnumerable());
|
|
|
|
|
|
//Group by tpye and location, cant be in same location two of same type.
|
|
return results
|
|
.GroupBy(obs => new
|
|
{
|
|
((PatientIntravenousLinesValue)obs.Value).Location,
|
|
((PatientIntravenousLinesValue)obs.Value).Type
|
|
})
|
|
.Select(g =>
|
|
g.OrderBy(t => t.Time).ThenBy(t => t.Id).LastOrDefault())
|
|
.ToList();
|
|
}
|
|
|
|
|
|
public async Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime()
|
|
{
|
|
var group = new BsonDocument
|
|
{
|
|
{ "_id", "$patientid" },
|
|
{ "time", new BsonDocument { { "$last", "$time" } } }
|
|
};
|
|
|
|
var pipeline = new List<BsonDocument>
|
|
{
|
|
new()
|
|
{
|
|
{
|
|
"$sort", new BsonDocument { { "time", 1 } }
|
|
}
|
|
},
|
|
new()
|
|
{
|
|
{
|
|
"$group", group
|
|
}
|
|
}
|
|
};
|
|
|
|
var cursor = await Collection.AggregateAsync<BsonDocument>(pipeline,
|
|
new AggregateOptions { AllowDiskUse = true, BatchSize = 10 });
|
|
|
|
var result = new Dictionary<ObjectId, DateTime>();
|
|
|
|
while (await cursor.MoveNextAsync())
|
|
cursor.Current
|
|
.Where(obs => obs.GetValue("_id").BsonType != BsonType.Null)
|
|
.ToList()
|
|
.ForEach(obs =>
|
|
{
|
|
if (obs.Get("_id") != null)
|
|
result.Add(obs.Get("_id")?.AsObjectId ?? new ObjectId(),
|
|
obs.Get("time")?.ToUniversalTime() ?? DateTime.MinValue);
|
|
}
|
|
);
|
|
return result;
|
|
}
|
|
|
|
public async Task<PatientObservation?> FindById(ObjectId id)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.Eq(o => o.Id, id);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.FirstOrDefaultAsync();
|
|
}
|
|
|
|
public async Task<List<PatientObservation>?> FindByPatientId(ObjectId id)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.Eq(o => o.PatientId, id);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.ToListAsync();
|
|
}
|
|
|
|
|
|
public async Task Update(PatientObservation observation)
|
|
{
|
|
await UpdateOneAsync(observation.Id, observation);
|
|
}
|
|
|
|
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
|
|
{
|
|
await UpdateManyObjectIdAsync(nameId, id, oldId);
|
|
}
|
|
|
|
public async Task UpdateExpiredObservations(List<PatientObservation> expiredObservations)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter
|
|
.In(x => x.Id, expiredObservations.Select(c => c.Id));
|
|
//var filter = Builders<PatientObservation>.Filter.In("Id", expiredObservations);
|
|
var update = Builders<PatientObservation>.Update.Set("Expired", true);
|
|
_ = await Collection.UpdateManyAsync(filter, update);
|
|
}
|
|
|
|
public async Task UpdateMany(IEnumerable<PatientObservation> patientObservations,
|
|
UpdateDefinition<PatientObservation> update)
|
|
{
|
|
var filter = Builders<PatientObservation>.Filter.In(x => x.Id, patientObservations.Select(x => x.Id));
|
|
|
|
await Collection.UpdateManyAsync(filter, update);
|
|
}
|
|
|
|
public async Task<IEnumerable<PatientObservation>> FindAll()
|
|
{
|
|
var result = await Collection.FindAsync(_ => true);
|
|
|
|
return result.ToEnumerable();
|
|
}
|
|
|
|
public async Task ExpireExpiredObservations(
|
|
List<ConfigObservation> configObservationsToExpire)
|
|
{
|
|
try
|
|
{
|
|
var builder = Builders<PatientObservation>.Filter;
|
|
var filterObs = builder.Empty;
|
|
configObservationsToExpire.ForEach(ob =>
|
|
{
|
|
var minsToAdd = ob.Expires.HasValue ? ob.Expires * -1 : 0;
|
|
if (filterObs == builder.Empty)
|
|
filterObs = builder.Where(o =>
|
|
o.Name == ob.Name && o.Time < DateTime.Now.AddMinutes(minsToAdd.Value));
|
|
else
|
|
filterObs |= builder.Where(o =>
|
|
o.Name == ob.Name && o.Time < DateTime.Now.AddMinutes(minsToAdd.Value));
|
|
});
|
|
var filterNotExpired = Builders<PatientObservation>.Filter.Where(o => !o.Expired);
|
|
var combineFilter = filterObs & filterNotExpired;
|
|
/*var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer<PatientObservation>();
|
|
var renderedFilter = combineFilter.Render(documentSerializer, BsonSerializer.SerializerRegistry);
|
|
Debug.WriteLine($"Result : {renderedFilter}");*/
|
|
|
|
var updateDefinition = Builders<PatientObservation>.Update.Set(o => o.Expired, true);
|
|
await Collection.UpdateManyAsync(combineFilter, updateDefinition);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("exception expiring observations expired: {exMessage} ", ex);
|
|
}
|
|
}
|
|
|
|
|
|
public async Task<IEnumerable<PatientObservation>> FindNotExpired(List<string?>? filterObservations)
|
|
{
|
|
var filterBuilder = Builders<PatientObservation>.Filter;
|
|
var filters = new List<FilterDefinition<PatientObservation>>();
|
|
|
|
if (filterObservations != null)
|
|
{
|
|
filters.Add(filterBuilder.Not(filterBuilder.Eq(o => o.Name, null)));
|
|
filters.Add(filterBuilder.In(o => o.Name, filterObservations));
|
|
}
|
|
|
|
filters.Add(filterBuilder.Ne(o => o.Expired, true));
|
|
|
|
var filter = filterBuilder.And(filters);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.ToListAsync();
|
|
}
|
|
|
|
|
|
public async Task<IEnumerable<PatientObservation>> FindByName(string name, DateTime? fromDate = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
|
|
|
|
if (name.Length > 100)
|
|
throw new BadRequestException("Name too long");
|
|
|
|
var safeName = Regex.Escape(name);
|
|
|
|
var filterBuilder = Builders<PatientObservation>.Filter;
|
|
|
|
var nameFilter = filterBuilder.And(
|
|
filterBuilder.Ne(o => o.Name, null),
|
|
filterBuilder.Regex(o => o.Name, new BsonRegularExpression(safeName, "i"))
|
|
);
|
|
|
|
var dateFilter = fromDate.HasValue
|
|
? filterBuilder.Gt(o => o.Time, fromDate.Value)
|
|
: filterBuilder.Empty;
|
|
|
|
var filter = filterBuilder.And(nameFilter, dateFilter);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.ToListAsync();
|
|
}
|
|
|
|
public IFindFluent<PatientObservation, PatientObservation> GetPaginatedObservations(PaginationFilter filter)
|
|
{
|
|
// Crear variable con la clase que construye los filtros
|
|
var filterBuilder = Builders<PatientObservation>.Filter;
|
|
// Crear una lista de filtros
|
|
var filters = new List<FilterDefinition<PatientObservation>>();
|
|
// Ordenar los resultados por "time" en orden descendente
|
|
var sort = Builders<PatientObservation>.Sort.Descending("time");
|
|
if (filter.FilteredRequest != null)
|
|
{
|
|
var requestFilter = filter.FilteredRequest;
|
|
if (!requestFilter.Observations.IsNullOrEmpty())
|
|
filters.Add(filterBuilder.In(o => o.Name, requestFilter.Observations));
|
|
|
|
if (requestFilter.PatientId != null)
|
|
{
|
|
var parsed = ObjectId.TryParse(requestFilter.PatientId, out var patientObjectId);
|
|
if (parsed) filters.Add(filterBuilder.Eq(o => o.PatientId, patientObjectId));
|
|
}
|
|
}
|
|
|
|
// Añadir los filtros por defecto en caso de llegar la lista de filtros vacia, si no la consulta a base de datos falla
|
|
filters.Add(filterBuilder.Gt(p => p.Time, filter.FilteredRequest?.StartDate ?? DateTime.MinValue));
|
|
filters.Add(filterBuilder.Lt(p => p.Time, filter.FilteredRequest?.EndDate ?? DateTime.MaxValue));
|
|
|
|
// Combinar los filtros en una consulta compuesta con operador "$and"
|
|
var combinedFilter = Builders<PatientObservation>.Filter.And(filters);
|
|
|
|
return Collection
|
|
.Find(combinedFilter)
|
|
.Sort(sort);
|
|
}
|
|
|
|
public async Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId,
|
|
string name, int? endAfter = null, int? num = null)
|
|
{
|
|
var sort = Builders<PatientObservation>.Sort.Descending("time");
|
|
|
|
var filterBuilder = Builders<PatientObservation>.Filter;
|
|
|
|
if (endAfter.HasValue)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
|
|
var thresholdTime = now.AddSeconds(-endAfter.Value); // Calcular la fecha límite
|
|
|
|
var dateFilter =
|
|
filterBuilder.Gte(o => o.Time, thresholdTime); // con fecha mayor que la fecha límite calculada
|
|
|
|
filterBuilder.And(dateFilter);
|
|
}
|
|
|
|
|
|
var patientFilter = filterBuilder.Eq(o => o.PatientId, patientId);
|
|
|
|
var nameFilter = filterBuilder.Eq(o => o.Name, name);
|
|
|
|
var filters = filterBuilder.And(patientFilter, nameFilter);
|
|
|
|
var result = await Collection.FindAsync(
|
|
filters,
|
|
new FindOptions<PatientObservation> { Sort = sort, Limit = num }
|
|
);
|
|
|
|
return result.ToEnumerable();
|
|
}
|
|
|
|
public override async Task CreateIndexes()
|
|
{
|
|
try
|
|
{
|
|
var options = new CreateIndexOptions { Background = true, Unique = false };
|
|
var indexes = new List<CreateIndexModel<PatientObservation>>
|
|
{
|
|
new("{ patientid: 1 }", options),
|
|
new("{ patientid: 1, name: 1, codingSystem: 1 }", options),
|
|
new("{ patientid: 1, name: 1 }", options),
|
|
new("{ name: 1, time: -1 }", options),
|
|
new("{ patientid: 1, systemId: 1 }", options),
|
|
new("{ name: 1 }", options),
|
|
new("{ patientid: 1, name: 1 , time: -1, id: -1}", options),
|
|
new("{ patientid: 1, name: 1 , time: 1}", options)
|
|
};
|
|
await MongoUtils.EnsureIndexes(Collection, indexes);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(
|
|
"error creating indexes for observation collection {eMessage} TRACE: {eStackTrace}", e.Message,
|
|
e.StackTrace);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
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("AggregatedPatientLastObservations distinct obs: \n" +
|
|
distinctPipeline.ToJson());
|
|
var resultList = await Collection.AggregateAsync<BsonDocument>(distinctPipeline,
|
|
new AggregateOptions { AllowDiskUse = true });
|
|
var result = resultList.ToList().FirstOrDefault();
|
|
|
|
if (result != null)
|
|
filterObservations = result.GetValue("obs").AsBsonArray.Select(it => it.AsString)
|
|
.ToList();
|
|
}
|
|
|
|
return filterObservations ?? [];
|
|
}
|
|
} |