rama creada apartir de master en j
This commit is contained in:
@@ -17,12 +17,23 @@ using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
|
||||
|
||||
namespace adas_core.Infrastructure.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository implementation for managing <see cref="PatientObservation"/> entities in MongoDB.
|
||||
/// Provides specialized query, aggregation, retention, and expiration operations for patient clinical observations.
|
||||
/// </summary>
|
||||
public class ObservationRepository : MongoRepository<PatientObservation>, IObservationRepository
|
||||
{
|
||||
private readonly ApiSettings _apiSettings;
|
||||
private readonly ILogger<ObservationRepository> _logger;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservationRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiSettings">The application API settings containing configuration values, including the collection name. Must not be <see langword="null"/>.</param>
|
||||
/// <param name="database">The MongoDB database instance used to access the collection.</param>
|
||||
/// <param name="logger">The logger used to record diagnostic and error information.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <see langword="null"/>.</exception>
|
||||
public ObservationRepository(
|
||||
IOptions<ApiSettings>? apiSettings,
|
||||
IMongoDatabase database,
|
||||
@@ -38,13 +49,27 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
throw new ArgumentNullException(nameof(apiSettings));
|
||||
}
|
||||
} //For testing
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the MongoDB collection used to store patient observations.
|
||||
/// Falls back to the default "patients_observations" collection name when not configured in the API settings.
|
||||
/// </summary>
|
||||
/// <returns>The collection name retrieved from the API settings, or "patients_observations" if not configured.</returns>
|
||||
public override string GetCollectionName()
|
||||
{
|
||||
return _apiSettings.PatientsObservations ?? "patients_observations";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds the most recent observations of a specific type (by coding system and code) for a patient.
|
||||
/// Results are sorted by time in descending order and limited to <paramref name="num"/> entries.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="codingSystem">The coding system used (for example, LOINC, SNOMED).</param>
|
||||
/// <param name="code">The code identifying the observation type within the coding system.</param>
|
||||
/// <param name="num">The maximum number of recent observations to return. Defaults to 2.</param>
|
||||
/// <returns>An <see cref="IEnumerable{PatientObservation}"/> containing the matching observations ordered from newest to oldest.</returns>
|
||||
public async Task<IEnumerable<PatientObservation>> FindLastObservations(ObjectId patientId, string codingSystem,
|
||||
string code, int num = 2)
|
||||
{
|
||||
@@ -57,12 +82,20 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
var result = await Collection.FindAsync(
|
||||
filter,
|
||||
new FindOptions<PatientObservation>
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
|
||||
);
|
||||
|
||||
return result.ToEnumerable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds the most recent observations of a specific coding system for a patient.
|
||||
/// Results are sorted by time in descending order and limited to <paramref name="num"/> entries.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="codingSystem">The coding system to filter observations by.</param>
|
||||
/// <param name="num">The maximum number of recent observations to return. Defaults to 10.</param>
|
||||
/// <returns>A <see cref="List{PatientObservation}"/> containing the matching observations ordered from newest to oldest.</returns>
|
||||
public async Task<List<PatientObservation>> FindLastObservationsByCodingSystem(ObjectId patientId,
|
||||
string codingSystem, int num = 10)
|
||||
{
|
||||
@@ -74,12 +107,20 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
var result = await Collection.FindAsync(
|
||||
filter,
|
||||
new FindOptions<PatientObservation>
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time"), Limit = num }
|
||||
);
|
||||
|
||||
return await result.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the most recent observations for a patient, optionally restricted to a list of observation names.
|
||||
/// If <paramref name="filterObservations"/> is <see langword="null"/>, all distinct observation names for the patient are used.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="num">The maximum number of observations to return per observation name.</param>
|
||||
/// <param name="filterObservations">Optional list of observation names to restrict the query to. When <see langword="null"/>, all distinct names are discovered.</param>
|
||||
/// <returns>A <see cref="List{PatientObservation}"/> containing the aggregated latest observations across all matching names.</returns>
|
||||
public async Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num,
|
||||
List<string>? filterObservations = null)
|
||||
{
|
||||
@@ -107,6 +148,15 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the most recent observations for a patient that occurred on or before a given date,
|
||||
/// optionally restricted to a list of observation names.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="num">The maximum number of observations to return per observation name.</param>
|
||||
/// <param name="lastDate">The inclusive upper bound (UTC) for the observation <c>time</c> field.</param>
|
||||
/// <param name="filterObservations">Optional list of observation names to restrict the query to. When <see langword="null"/>, all distinct names are discovered.</param>
|
||||
/// <returns>A <see cref="List{PatientObservation}"/> containing the matching observations.</returns>
|
||||
public async Task<List<PatientObservation>> AggregatedPatientLastObservationsByLastDate(ObjectId patientId, int num,
|
||||
DateTime lastDate, List<string>? filterObservations = null)
|
||||
{
|
||||
@@ -124,7 +174,7 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
var cursor = await Collection.FindAsync(
|
||||
filter,
|
||||
new FindOptions<PatientObservation>
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id"), Limit = num }
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id"), Limit = num }
|
||||
);
|
||||
|
||||
results.AddRange(await cursor.ToListAsync());
|
||||
@@ -134,6 +184,17 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the most recent observations for a patient, with per-field limits and an optional "expired" flag filter.
|
||||
/// When a <see cref="Field"/> specifies <see cref="Field.OnlyExpired"/> as <see langword="true"/>, only expired observations are returned.
|
||||
/// When <paramref name="filterObservations"/> is <see langword="null"/>, all observations for the patient are returned.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="filterObservations">Optional list of <see cref="Field"/> descriptors defining the names, limits, and expiration filter. When <see langword="null"/>, all observations for the patient are returned.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="List{PatientObservation}"/> containing the matching observations.
|
||||
/// Returns an empty list when an exception occurs while querying the database.
|
||||
/// </returns>
|
||||
public async Task<List<PatientObservation>> AggregatedPatientLastObservationsByField(ObjectId patientId,
|
||||
List<Field>? filterObservations)
|
||||
{
|
||||
@@ -179,7 +240,7 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
cursor = await Collection.FindAsync(
|
||||
filter,
|
||||
new FindOptions<PatientObservation>
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
|
||||
|
||||
results.AddRange(cursor.ToEnumerable());
|
||||
}
|
||||
@@ -194,6 +255,17 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously executes an aggregation pipeline that groups a patient's observations into time buckets
|
||||
/// (second / minute / hour / day / shift / times) and computes per-bucket results such as first, last, min, max, sum, average, or count.
|
||||
/// Supports a "since last observation" mode and a configurable look-back window based on the regularity.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="groupedField">A <see cref="GroupedField"/> descriptor containing the observation name(s), regularity, look-back window, and the result computations to perform.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="List{BsonDocument}"/> with one document per time bucket.
|
||||
/// Returns an empty list when an exception occurs while executing the pipeline.
|
||||
/// </returns>
|
||||
public async Task<List<BsonDocument>> AggregatedPatientGroupedObservations(ObjectId patientId,
|
||||
GroupedField groupedField)
|
||||
{
|
||||
@@ -267,9 +339,9 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
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
|
||||
//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" } });
|
||||
@@ -470,6 +542,14 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously inserts a single patient observation, automatically retrying with a new <see cref="ObjectId"/>
|
||||
/// when a MongoDB duplicate-key error is encountered. The retry strategy is bounded by an internal maximum.
|
||||
/// </summary>
|
||||
/// <param name="patientObservation">The <see cref="PatientObservation"/> to insert. If a duplicate-key error occurs, a new identifier is generated and the insert is retried.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous insert operation.</returns>
|
||||
/// <exception cref="MongoWriteException">Rethrown after the maximum number of retries has been reached when a duplicate-key error keeps occurring.</exception>
|
||||
/// <exception cref="Exception">Rethrown when an unexpected error occurs during the insert operation.</exception>
|
||||
public new async Task InsertOneAsync(PatientObservation patientObservation)
|
||||
{
|
||||
const int maxRetries = 2; // Número máximo de reintentos
|
||||
@@ -489,7 +569,7 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
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
|
||||
@@ -501,18 +581,40 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes all observations associated with the specified patient.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="ObjectId"/> of the patient whose observations should be removed.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous delete operation.</returns>
|
||||
public async Task DeleteByPatientId(ObjectId id)
|
||||
{
|
||||
var filter = Builders<PatientObservation>.Filter.Eq(po => po.PatientId, id);
|
||||
await Collection.DeleteManyAsync(filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously returns a cursor over all observations for a given patient, using a server-side batch size of 100.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IAsyncCursor{PatientObservation}"/> that can be enumerated to retrieve the patient's observations.
|
||||
/// </returns>
|
||||
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 });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously returns a cursor over all observations for a patient that match a given coding system and observation name.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="codingSystem">The coding system to filter by.</param>
|
||||
/// <param name="name">The observation name to filter by.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IAsyncCursor{PatientObservation}"/> containing the matching observations,
|
||||
/// retrieved with a server-side batch size of 100.
|
||||
/// </returns>
|
||||
public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId,
|
||||
string codingSystem, string name)
|
||||
{
|
||||
@@ -527,6 +629,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes a single observation by its identifier. This method hides the base
|
||||
/// <c>DeleteAsync(ObjectId)</c> defined on <see cref="MongoRepository{T}"/> because the base returns the deleted document,
|
||||
/// while this implementation is fire-and-forget.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="ObjectId"/> of the observation to delete.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous delete operation.</returns>
|
||||
public new async Task DeleteAsync(ObjectId id)
|
||||
{
|
||||
var filter = Builders<PatientObservation>.Filter.Eq(obs => obs.Id, id);
|
||||
@@ -534,6 +643,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all observations with the specified name that are older than the configured number of days.
|
||||
/// Returns the documents that were deleted for downstream processing.
|
||||
/// </summary>
|
||||
/// <param name="name">The observation name to filter by.</param>
|
||||
/// <param name="retentionPolicyValue">The retention window expressed in days. Observations older than <c>UtcNow - retentionPolicyValue</c> days are removed.</param>
|
||||
/// <returns>A <see cref="List{PatientObservation}"/> containing the documents that were deleted.</returns>
|
||||
public async Task<List<PatientObservation>> DeleteOlderDaysAsync(string name, int retentionPolicyValue)
|
||||
{
|
||||
var dateLimit = DateTime.UtcNow.AddDays(-1 * retentionPolicyValue);
|
||||
@@ -549,6 +665,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all observations with the specified name that are older than the configured number of seconds.
|
||||
/// Returns the documents that were deleted for downstream processing.
|
||||
/// </summary>
|
||||
/// <param name="name">The observation name to filter by.</param>
|
||||
/// <param name="retentionPolicyValue">The retention window expressed in seconds. Observations older than <c>UtcNow - retentionPolicyValue</c> seconds are removed.</param>
|
||||
/// <returns>A <see cref="List{PatientObservation}"/> containing the documents that were deleted.</returns>
|
||||
public async Task<List<PatientObservation>> DeleteOlderSecondsAsync(string name, int retentionPolicyValue)
|
||||
{
|
||||
var dateLimit = DateTime.UtcNow.AddSeconds(-1 * retentionPolicyValue);
|
||||
@@ -564,6 +687,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Keeps only the <paramref name="retentionPolicyValue"/> most recent observations for the specified name,
|
||||
/// deleting all older ones. Returns the documents that were deleted for downstream processing.
|
||||
/// </summary>
|
||||
/// <param name="name">The observation name to apply the retention policy to.</param>
|
||||
/// <param name="retentionPolicyValue">The maximum number of observations to retain. The remainder are deleted.</param>
|
||||
/// <returns>A <see cref="List{PatientObservation}"/> containing the documents that were deleted. Returns an empty list when nothing had to be deleted.</returns>
|
||||
public async Task<List<PatientObservation>> DeleteOlderNumberAsync(string name, int retentionPolicyValue)
|
||||
{
|
||||
var builder = Builders<PatientObservation>.Filter;
|
||||
@@ -587,6 +717,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously checks whether an observation exists for a given patient with the specified <c>SystemId</c>.
|
||||
/// Only the document identifier is projected, making the query lightweight.
|
||||
/// </summary>
|
||||
/// <param name="patientid">The unique identifier of the patient.</param>
|
||||
/// <param name="systemId">The external system identifier to look up.</param>
|
||||
/// <returns><see langword="true"/> if a matching observation exists; otherwise, <see langword="false"/>.</returns>
|
||||
public async Task<bool> ExistBySystemId(ObjectId patientid, string systemId)
|
||||
{
|
||||
return await Collection.Find(Builders<PatientObservation>.Filter.And(
|
||||
@@ -598,6 +735,16 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds the most recent observation for a patient with the given name that occurred strictly before the specified date.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="name">The observation name to search for. Can be <see langword="null"/>.</param>
|
||||
/// <param name="date">The upper-bound (exclusive) observation <c>time</c>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{PatientObservation}"/> representing the asynchronous operation.
|
||||
/// The task result contains the most recent matching observation, or <see langword="null"/> if no observation matches.
|
||||
/// </returns>
|
||||
public async Task<PatientObservation?> FindLastObservationBeforeDate(ObjectId patientId, string? name,
|
||||
DateTime date)
|
||||
{
|
||||
@@ -610,11 +757,21 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
|
||||
var result = await Collection.FindAsync(filter,
|
||||
new FindOptions<PatientObservation>
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id") });
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("_id") });
|
||||
|
||||
return await result.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves all observations for a patient with a given name whose <c>time</c> exactly matches the provided value.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="name">The observation name to match. Can be <see langword="null"/>.</param>
|
||||
/// <param name="date">The exact <c>time</c> value to match.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{List{PatientObservation}}"/> representing the asynchronous operation.
|
||||
/// The task result contains a list of matching observations, which may be empty.
|
||||
/// </returns>
|
||||
public async Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, string? name, DateTime date)
|
||||
{
|
||||
var builder = Builders<PatientObservation>.Filter;
|
||||
@@ -627,6 +784,14 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves all observations for a patient whose <c>time</c> is strictly before the specified date.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="date">The upper-bound (exclusive) observation <c>time</c>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="List{PatientObservation}"/> containing the matching observations, which may be empty.
|
||||
/// </returns>
|
||||
public async Task<List<PatientObservation>> FindAnyBeforeDate(ObjectId patientId, DateTime date)
|
||||
{
|
||||
var filterBuilder = Builders<PatientObservation>.Filter;
|
||||
@@ -639,6 +804,19 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves, for a given patient and observation name, the latest occurrence of each distinct value.
|
||||
/// Optionally restricts the result to observations not older than <paramref name="expires"/> seconds.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="name">The observation name to search for.</param>
|
||||
/// <param name="expires">
|
||||
/// Optional expiration window expressed in seconds. When provided, only observations newer than
|
||||
/// <c>UtcNow - expires</c> seconds are considered.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="List{PatientObservation}"/> containing the most recent observation for each distinct value.
|
||||
/// </returns>
|
||||
public async Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name,
|
||||
int? expires)
|
||||
{
|
||||
@@ -680,6 +858,15 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the currently active intravenous-line observations for a patient,
|
||||
/// grouping by the <c>Location</c> and <c>Type</c> of the <see cref="PatientIntravenousLinesValue"/>.
|
||||
/// Within each group, only the most recent observation (ordered by <c>time</c> and <c>id</c>) is returned.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="List{PatientObservation}"/> containing one observation per unique (Location, Type) combination.
|
||||
/// </returns>
|
||||
public async Task<List<PatientObservation?>> AggregatedPatientActiveIntravenousLinesObservations(ObjectId patientId)
|
||||
{
|
||||
var results = new List<PatientObservation>();
|
||||
@@ -687,7 +874,7 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
var cursor = await Collection.FindAsync(
|
||||
o => o.PatientId == patientId && o.Name == "IntravenousLinesObs",
|
||||
new FindOptions<PatientObservation>
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
|
||||
{ Sort = Builders<PatientObservation>.Sort.Descending("time").Descending("id") });
|
||||
|
||||
results.AddRange(cursor.ToEnumerable());
|
||||
|
||||
@@ -705,6 +892,13 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves, for every patient with observations, the timestamp of the most recent observation.
|
||||
/// Implemented as a server-side aggregation that groups by <c>patientid</c> and selects the latest <c>time</c> value.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="Dictionary{ObjectId, DateTime}"/> mapping each patient's <see cref="ObjectId"/> to the UTC timestamp of their latest observation.
|
||||
/// </returns>
|
||||
public async Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime()
|
||||
{
|
||||
var group = new BsonDocument
|
||||
@@ -739,15 +933,23 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
.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);
|
||||
}
|
||||
{
|
||||
if (obs.Get("_id") != null)
|
||||
result.Add(obs.Get("_id")?.AsObjectId ?? new ObjectId(),
|
||||
obs.Get("time")?.ToUniversalTime() ?? DateTime.MinValue);
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds an observation by its unique identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="ObjectId"/> of the observation to retrieve.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{PatientObservation}"/> representing the asynchronous operation.
|
||||
/// The task result contains the <see cref="PatientObservation"/> if found; otherwise, <see langword="null"/>.
|
||||
/// </returns>
|
||||
public async Task<PatientObservation?> FindById(ObjectId id)
|
||||
{
|
||||
var filter = Builders<PatientObservation>.Filter.Eq(o => o.Id, id);
|
||||
@@ -757,6 +959,14 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
return await result.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves all observations for a patient by the patient's identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="ObjectId"/> of the patient whose observations should be retrieved.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{List{PatientObservation}}"/> representing the asynchronous operation.
|
||||
/// The task result contains a list of observations, which may be empty.
|
||||
/// </returns>
|
||||
public async Task<List<PatientObservation>?> FindByPatientId(ObjectId id)
|
||||
{
|
||||
var filter = Builders<PatientObservation>.Filter.Eq(o => o.PatientId, id);
|
||||
@@ -767,16 +977,34 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously updates a single observation by replacing its document with the provided instance.
|
||||
/// </summary>
|
||||
/// <param name="observation">The <see cref="PatientObservation"/> whose <see cref="ObjectId"/> identifies the document to update.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
|
||||
public async Task Update(PatientObservation observation)
|
||||
{
|
||||
await UpdateOneAsync(observation.Id, observation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously updates all documents in the collection where the specified field equals <paramref name="oldId"/>,
|
||||
/// setting that field to the new <paramref name="id"/>. Thin wrapper around the protected helper on the base repository.
|
||||
/// </summary>
|
||||
/// <param name="nameId">The name of the field to match and update.</param>
|
||||
/// <param name="id">The new <see cref="ObjectId"/> value to assign.</param>
|
||||
/// <param name="oldId">The existing <see cref="ObjectId"/> value to replace.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
|
||||
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
|
||||
{
|
||||
await UpdateManyObjectIdAsync(nameId, id, oldId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously marks the supplied list of observations as expired by setting their <c>Expired</c> flag to <see langword="true"/>.
|
||||
/// </summary>
|
||||
/// <param name="expiredObservations">The list of <see cref="PatientObservation"/> instances to mark as expired. The set of identifiers is used to build the update filter.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
|
||||
public async Task UpdateExpiredObservations(List<PatientObservation> expiredObservations)
|
||||
{
|
||||
var filter = Builders<PatientObservation>.Filter
|
||||
@@ -786,6 +1014,12 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
_ = await Collection.UpdateManyAsync(filter, update);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously applies the given update definition to a set of observations identified by their identifiers.
|
||||
/// </summary>
|
||||
/// <param name="patientObservations">The observations whose identifiers form the target set of the update.</param>
|
||||
/// <param name="update">The <see cref="UpdateDefinition{PatientObservation}"/> describing the changes to apply to each matching document.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
|
||||
public async Task UpdateMany(IEnumerable<PatientObservation> patientObservations,
|
||||
UpdateDefinition<PatientObservation> update)
|
||||
{
|
||||
@@ -794,6 +1028,12 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
await Collection.UpdateManyAsync(filter, update);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously returns every observation stored in the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{PatientObservation}"/> containing all observations.
|
||||
/// </returns>
|
||||
public async Task<IEnumerable<PatientObservation>> FindAll()
|
||||
{
|
||||
var result = await Collection.FindAsync(_ => true);
|
||||
@@ -801,6 +1041,16 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
return result.ToEnumerable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously marks observations as expired when their <c>time</c> is older than the configured expiration
|
||||
/// window defined by the supplied <see cref="ConfigObservation"/> entries. Only observations that are not
|
||||
/// already marked as expired are updated. Errors are logged and swallowed.
|
||||
/// </summary>
|
||||
/// <param name="configObservationsToExpire">
|
||||
/// A list of <see cref="ConfigObservation"/> entries describing the observation names and their expiration windows
|
||||
/// (in minutes). Observations whose <c>time</c> is older than <c>DateTime.Now - expires</c> minutes are marked as expired.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous update operation.</returns>
|
||||
public async Task ExpireExpiredObservations(
|
||||
List<ConfigObservation> configObservationsToExpire)
|
||||
{
|
||||
@@ -834,6 +1084,17 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves observations that are not marked as expired, optionally restricted to a list of observation names.
|
||||
/// Observations with a <see langword="null"/> name are excluded from the result.
|
||||
/// </summary>
|
||||
/// <param name="filterObservations">
|
||||
/// Optional list of observation names to match. When <see langword="null"/>, all non-null named observations are considered
|
||||
/// (still subject to the not-expired filter).
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{PatientObservation}"/> containing the matching non-expired observations.
|
||||
/// </returns>
|
||||
public async Task<IEnumerable<PatientObservation>> FindNotExpired(List<string?>? filterObservations)
|
||||
{
|
||||
var filterBuilder = Builders<PatientObservation>.Filter;
|
||||
@@ -855,6 +1116,19 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds observations whose name matches the given pattern (case-insensitive substring/regex),
|
||||
/// optionally restricted to those with a <c>time</c> greater than <paramref name="fromDate"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The regex pattern to match against the observation name. Cannot be null, empty, whitespace, or longer than 100 characters.</param>
|
||||
/// <param name="fromDate">Optional inclusive lower bound on the observation <c>time</c>.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{PatientObservation}"/> containing the matching observations.
|
||||
/// </returns>
|
||||
/// <exception cref="BadRequestException">
|
||||
/// Thrown when <paramref name="name"/> is null, empty, or whitespace,
|
||||
/// or when its length exceeds 100 characters.
|
||||
/// </exception>
|
||||
public async Task<IEnumerable<PatientObservation>> FindByName(string name, DateTime? fromDate = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
@@ -883,6 +1157,16 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
return await result.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a paginated, sorted, and filtered query over observations.
|
||||
/// Results are sorted by <c>time</c> in descending order. When a <see cref="PaginationFilter.FilteredRequest"/>
|
||||
/// is provided, observations can be filtered by name list and patient identifier. A mandatory time window
|
||||
/// (defaulting to <c>DateTime.MinValue</c> / <c>DateTime.MaxValue</c>) is always applied.
|
||||
/// </summary>
|
||||
/// <param name="filter">The <see cref="PaginationFilter"/> containing pagination and filter criteria.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IFindFluent{PatientObservation, PatientObservation}"/> instance that can be used to further refine and execute the query.
|
||||
/// </returns>
|
||||
public IFindFluent<PatientObservation, PatientObservation> GetPaginatedObservations(PaginationFilter filter)
|
||||
{
|
||||
// Crear variable con la clase que construye los filtros
|
||||
@@ -916,6 +1200,20 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
.Sort(sort);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the most recent observations for a patient with a given name,
|
||||
/// optionally bounded to a recent time window and an explicit maximum count.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="name">The observation name to filter by.</param>
|
||||
/// <param name="endAfter">
|
||||
/// Optional look-back window in seconds. When provided, only observations whose <c>time</c> is greater than or equal to
|
||||
/// <c>UtcNow - endAfter</c> are returned.
|
||||
/// </param>
|
||||
/// <param name="num">Optional maximum number of observations to return. <see langword="null"/> means no limit.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{PatientObservation}"/> containing the matching observations ordered from newest to oldest.
|
||||
/// </returns>
|
||||
public async Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId,
|
||||
string name, int? endAfter = null, int? num = null)
|
||||
{
|
||||
@@ -950,6 +1248,12 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
return result.ToEnumerable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the indexes required by the observations collection to support the repository's query patterns.
|
||||
/// Indexes are created in the background and are non-unique. If an error occurs, it is logged and rethrown.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous index creation operation.</returns>
|
||||
/// <exception cref="Exception">Rethrown when an error occurs while creating the indexes.</exception>
|
||||
public override async Task CreateIndexes()
|
||||
{
|
||||
try
|
||||
@@ -978,6 +1282,20 @@ public class ObservationRepository : MongoRepository<PatientObservation>, IObser
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines the list of observation names to use when running a per-name aggregation for a patient.
|
||||
/// If <paramref name="filterObservations"/> is null or empty, the distinct observation names currently
|
||||
/// stored for the patient are discovered via a server-side <c>$group</c> aggregation.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="filterObservations">
|
||||
/// Optional list of observation names to use. When null or empty, the method discovers the patient's
|
||||
/// distinct observation names automatically.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="List{String}"/> containing the observation names to aggregate over.
|
||||
/// Returns an empty list if no observations exist for the patient.
|
||||
/// </returns>
|
||||
private async Task<List<string>> AggregatePatientObservations(ObjectId patientId,
|
||||
List<string>? filterObservations = null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user