Documentation modifications

This commit is contained in:
julian
2026-06-27 15:23:26 -07:00
parent a633fe6c06
commit a19fb90902
218 changed files with 2882 additions and 0 deletions
@@ -13,12 +13,20 @@ namespace adas_core.Infrastructure.Migrations.MongoMigrations;
/// </remarks>
public class U_0_1_0_UpdateDataPatien : Migration
{
/// <summary>
/// Initializes a new instance of the <see cref="U_0_1_0_UpdateDataPatien"/> migration, passing the version <c>10</c> to the base constructor and assigning the Description that explains how person historical identifiers and locations are transformed into date-indexed arrays and merged into patients.
/// </summary>
/// <!-- aidoc:v1 sig=ccd8376 body=50d3df0 -->
public U_0_1_0_UpdateDataPatien() : base(10)
{
Description =
"Transforma person.historicalIds y historicalLocations en arrays con fechas y realiza merge sobre patients.";
}
/// <summary>
/// Migrates <c>person.historicalIds</c> and <c>historicalLocations</c> fields in the <c>patients</c>, <c>admissions</c>, and <c>archive_patient</c> MongoDB collections from a document representation to an array of objects, converting each key (a date string) into a <c>Date</c> value via <c>$dateFromString</c>. If a field is already an array it is preserved unchanged; otherwise it is reshaped using <c>$objectToArray</c> and <c>$map</c>, and the result is merged back into the same collection with <c>$merge</c>.
/// </summary>
/// <!-- aidoc:v1 sig=2ec5b05 body=7693e10 -->
public override void Update()
{
var collectionPatient = Database.GetCollection<BsonDocument>("patients");
@@ -231,6 +239,10 @@ public class U_0_1_0_UpdateDataPatien : Migration
// NO usado por MongoMigrations.Core
// Solo para ejecución manual si hiciera falta
/// <summary>
/// Reverts a schema migration on the <c>patients</c>, <c>admissions</c>, and <c>archive_patient</c> MongoDB collections by converting array representations of <c>person.historicalIds</c> and <c>historicalLocations</c> back into documents keyed by an ISO-8601 timestamp. When the target fields are already documents they are left unchanged via a <c>$cond</c> guard, and each aggregation result is persisted back to its source collection using <c>$merge</c> with <c>whenMatched: replace</c>.
/// </summary>
/// <!-- aidoc:v1 sig=6a4f4d3 body=e02e3dc -->
public void Down()
{
var collectionPatient = Database.GetCollection<BsonDocument>("patients");
@@ -12,6 +12,10 @@ namespace adas_core.Infrastructure.Migrations.MongoMigrations;
/// </remarks>
public class U_0_1_2_UpdatePointOfCareConfig : Migration
{
/// <summary>
/// Initializes a new instance of <see cref="U_0_1_2_UpdatePointOfCareConfig"/>, assigning a Description stating that it removes the PointOfCare configuration elements for poc, relay, camera, and beacon while keeping the placeholder for the new model, and passing 12 to the base constructor.
/// </summary>
/// <!-- aidoc:v1 sig=9495cc3 body=582e2e0 -->
public U_0_1_2_UpdatePointOfCareConfig() : base(12)
{
Description =
@@ -9,12 +9,20 @@ namespace adas_core.Infrastructure.Migrations.MongoMigrations;
/// </summary>
public class U_0_1_3_UpdateLanguageBarrier : Migration
{
/// <summary>
/// Initializes a new instance of the <see cref="U_0_1_3_UpdateLanguageBarrier"/> class, passing identifier <c>13</c> to the base constructor and assigning the Description property to describe the removal of poc, relay, camera and beacon configuration elements for the new model.
/// </summary>
/// <!-- aidoc:v1 sig=5e97941 body=582e2e0 -->
public U_0_1_3_UpdateLanguageBarrier() : base(13)
{
Description =
"Borra los elementos de configuracion de poc, relay, camera y beacon y deja el place holder del nuevo modelo";
}
/// <summary>
/// Converts the <c>languageBarrier</c> field in the <c>patients</c> and <c>admissions</c> collections from a scalar value into a <see cref="MongoDB.Bson.BsonArray"/>, preserving the original value as the first element when it is not null. Documents where the field is already an array are left untouched, and null values are replaced with an empty array.
/// </summary>
/// <!-- aidoc:v1 sig=2ec5b05 body=932d7f9 -->
public override void Update()
{
string[] collectionsToUpdate = { "patients", "admissions" };
@@ -23,6 +23,13 @@ public class AdmissionRepository : MongoRepository<Admission>, IAdmissionReposit
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="AdmissionRepository"/> class, passing the MongoDB database to the base repository and storing the API configuration values used by the repository.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing the <see cref="ApiSettings"/> values required by the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base repository constructor.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is null.</exception>
/// <!-- aidoc:v1 sig=b1a21dd body=d2b18a3 -->
public AdmissionRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -13,6 +13,12 @@ public class PoCMappingRepository : MongoRepository<PoCMapping>, IPoCMappingRepo
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="PoCMappingRepository"/> class, storing the resolved <see cref="ApiSettings"/> and forwarding the supplied <see cref="IMongoDatabase"/> to the base repository to support persistence of PoC mappings.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> wrapper that exposes the application's <see cref="ApiSettings"/>.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> instance passed to the base class to establish the MongoDB connection.</param>
/// <!-- aidoc:v1 sig=443b0db body=12fddac -->
public PoCMappingRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
_apiSettings = apiSettings.Value;
@@ -39,6 +39,12 @@ public class PatientCarePlanRepository : MongoRepository<PatientCarePlan>, IPati
/// <summary>
/// Initializes a new instance of the <see cref="PatientCarePlanRepository"/> class with the supplied API configuration and MongoDB database connection, forwarding the database to the base repository.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing the <see cref="ApiSettings"/> assigned to the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> connection passed to the base class for data access.</param>
/// <!-- aidoc:v1 sig=ab4ee14 body=12fddac -->
public PatientCarePlanRepository(
IOptions<ApiSettings> apiSettings,
IMongoDatabase database
@@ -22,6 +22,13 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of <see cref="PatientRepository"/>, a MongoDB-backed data repository, capturing API configuration from <see cref="IOptions{ApiSettings}"/> and forwarding the <see cref="IMongoDatabase"/> to the base repository constructor.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> whose value supplies the repository's API configuration.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> connection passed to the base class constructor.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is null.</exception>
/// <!-- aidoc:v1 sig=113519f body=d2b18a3 -->
public PatientRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -279,6 +286,12 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
}
/// <summary>
/// Finds a <see cref="Patient"/> by <paramref name="patientNumber"/>, preferring the most recently admitted active patient (one whose <see cref="Patient.DisTime"/> is null) and falling back to the <see cref="Patient"/> record with the most recent non-null <see cref="Patient.DisTime"/> when no active admission exists. Returns null when <paramref name="patientNumber"/> is null, empty, or whitespace, or when no matching record is found.
/// </summary>
/// <param name="patientNumber">The patient number used to locate the <see cref="Patient"/> record.</param>
/// <returns>A <see cref="Task{Patient}"/> that resolves to the matching <see cref="Patient"/>, or null when no record is found.</returns>
/// <!-- aidoc:v1 sig=09c89ab body=91862bd -->
public async Task<Patient?> FindByPatientNumber(string patientNumber)
{
if (string.IsNullOrWhiteSpace(patientNumber)) return null;
@@ -305,6 +318,13 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
return patient;
}
/// <summary>
/// Searches for a <see cref="Patient"/> by <paramref name="patientNumber"/> whose <see cref="Patient.UnitId"/> differs from <paramref name="unitId"/>, intended to locate a patient identified at a Point of Care but registered in another unit. Returns <c>null</c> when the patient number is blank, when multiple matches are found (since the patient number may be incomplete), or when no match exists; exceptions are logged and also surface as <c>null</c>.
/// </summary>
/// <param name="patientNumber">The patient number used to look up the <see cref="Patient"/>.</param>
/// <param name="unitId">The <see cref="ObjectId"/> of the unit that must be excluded from the match.</param>
/// <returns>A <see cref="Task{Patient}"/> resolving to the matching <see cref="Patient"/>, or <c>null</c> when there is no unique match.</returns>
/// <!-- aidoc:v1 sig=382b7c8 body=f2b7752 -->
public async Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId)
{
try
@@ -328,6 +348,12 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
}
}
/// <summary>
/// Retrieves all <see cref="Patient"/> documents that contain at least one procedure considered finished and eligible for archival. A procedure qualifies when its <c>EndDate</c> is not null and the time elapsed since that <c>EndDate</c> exceeds the supplied grace period of <paramref name="archiveProcedureEndDateAfterMinutes"/> minutes relative to the current UTC time.
/// </summary>
/// <param name="archiveProcedureEndDateAfterMinutes">The grace period, in minutes, added to a procedure's <c>EndDate</c>; the procedure is treated as finished only when the resulting timestamp is earlier than <see cref="DateTime.UtcNow"/>.</param>
/// <returns>A <see cref="Task{List{Patient}}"/> containing the patients matching the finished-procedure criteria, or an empty list when no patient has a procedure whose archival grace period has elapsed.</returns>
/// <!-- aidoc:v1 sig=8527a2e body=acd9bfe -->
public async Task<List<Patient>> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes)
{
var currentDateTime = DateTime.UtcNow;
@@ -359,6 +385,13 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
return patientsWithFinishedProcedures;
}
/// <summary>
/// Retrieves all <see cref="Patient"/> records whose tests have finished and whose end date, offset by the specified archive threshold, is earlier than the current UTC time.
/// The initial MongoDB filter keeps tests with a non-null EndDate, and the in-memory filter then retains only those whose EndDate plus <paramref name="archiveTestEndDateAfterMinutes"/> minutes is before <see cref="DateTime.UtcNow"/>.
/// </summary>
/// <param name="archiveTestEndDateAfterMinutes">The number of minutes added to each test's EndDate to determine whether the test is eligible for archival.</param>
/// <returns>A <see cref="Task{List{Patient}}"/> containing the patients whose tests meet the finished and archive criteria.</returns>
/// <!-- aidoc:v1 sig=c7d3a85 body=8559f5d -->
public async Task<List<Patient>> FindAllPatientWithFinishedTests(int archiveTestEndDateAfterMinutes)
{
var currentDateTime = DateTime.UtcNow;
@@ -389,6 +422,12 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
return patientsWithFinishedTests;
}
/// <summary>
/// Asynchronously retrieves all patients that have at least one finished <see cref="Patient.Treatment"/> whose <see cref="Treatment.EndDate"/> is older than <paramref name="archiveTreatmentEndDateAfterMinutes"/> minutes relative to the current UTC time, using a MongoDB query combined with an in-memory time threshold filter.
/// </summary>
/// <param name="archiveTreatmentEndDateAfterMinutes">The grace period in minutes that must elapse after a treatment's <see cref="Treatment.EndDate"/> before the patient qualifies for retrieval.</param>
/// <returns>A <see cref="Task{List{Patient}}"/> that resolves to the list of <see cref="Patient"/> records whose treatments satisfy the finished-treatment time threshold.</returns>
/// <!-- aidoc:v1 sig=6b43cdf body=429c102 -->
public async Task<List<Patient>> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes)
{
var currentDateTime = DateTime.UtcNow;
@@ -414,6 +453,19 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
return patientsWithFinishedTreatments;
}
/// <summary>
/// Updates a master list option for all patients belonging to the specified <paramref name="unitIds"/>,
/// handling <see cref="MasterListType.DiagnosisList"/>, <see cref="MasterListType.DoctorList"/>,
/// and <see cref="MasterListType.OriginList"/> cases by updating the relevant fields and auxiliary fields,
/// and returning the updated <see cref="Patient"/> documents. If <paramref name="typeName"/> cannot be parsed
/// as a <see cref="MasterListType"/> or the type is not implemented, an empty list is returned.
/// </summary>
/// <param name="unitIds">The collection of unit identifiers used to scope the update to the affected patients.</param>
/// <param name="opt">The DTO containing the existing option and the replacement option values to apply.</param>
/// <param name="typeName">The textual name of the <see cref="MasterListType"/> that determines which update path is executed.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the updated <see cref="List{Patient}"/> documents,
/// or an empty list when no update was performed.</returns>
/// <!-- aidoc:v1 sig=afe83cc body=25f24cf -->
public async Task<List<Patient>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt,
string typeName)
{
@@ -593,6 +645,14 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
}
/// <summary>
/// Deletes a master list option from <see cref="Patient"/> documents belonging to the specified units, applying the appropriate update logic based on the resolved <see cref="MasterListType"/>. Returns the affected patients for the supported types (<see cref="MasterListType.DiagnosisList"/>, <see cref="MasterListType.DoctorList"/>, and <see cref="MasterListType.OriginList"/>), or an empty collection when <paramref name="typeName"/> cannot be parsed or the type has no handling logic.
/// </summary>
/// <param name="unitIds">The collection of <see cref="ObjectId"/> values identifying the units whose patients will be affected by the deletion.</param>
/// <param name="opt">The <see cref="OptionList"/> option to remove, matched by its <see cref="OptionList.Name"/> (for diagnosis and origin lists) or its <see cref="OptionList.Id"/> (for the doctor list).</param>
/// <param name="typeName">The textual name of the master list type, parsed via <see cref="Enum.TryParse{T}"/> with <typeparamref name="T"/> = <see cref="MasterListType"/> to select the update strategy.</param>
/// <returns>A <see cref="Task"/> that yields the <see cref="Patient"/> documents modified for the supported <see cref="MasterListType"/> values, or an empty <see cref="List{Patient}"/> when no updates are performed.</returns>
/// <!-- aidoc:v1 sig=800d406 body=d26e8f1 -->
public async Task<IEnumerable<Patient>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt,
string typeName)
{
@@ -714,6 +774,12 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
return new List<Patient>();
}
/// <summary>
/// Finds the <see cref="Patient"/> associated with the specified <see cref="Patient.PatientId"/>, preferring an active admission (no <see cref="Patient.DisTime"/>) sorted by most recent <see cref="Patient.AdmTime"/>, and falling back to the most recently discharged record when no active admission exists.
/// </summary>
/// <param name="patientId">The identifier of the patient to locate.</param>
/// <returns>A <see cref="Patient"/> instance if found; otherwise, <see langword="null"/> when <paramref name="patientId"/> is null, empty, or whitespace, or when no matching record exists.</returns>
/// <!-- aidoc:v1 sig=fca9190 body=71f3c84 -->
public async Task<Patient?> FindByPatientId(string patientId)
{
if (string.IsNullOrWhiteSpace(patientId)) return null;
@@ -729,6 +795,12 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
return patient;
}
/// <summary>
/// Retrieves the most relevant <see cref="Patient"/> record for the specified identifier, prioritizing an active admission (no discharge time) ordered by the latest <see cref="Patient.AdmTime"/>, and falling back to the most recently discharged patient ordered by <see cref="Patient.DisTime"/> when no active admission exists.
/// </summary>
/// <param name="patientId">The <see cref="ObjectId"/> of the <see cref="Patient"/> to look up.</param>
/// <returns>The matching <see cref="Patient"/> if one is found; otherwise, <see langword="null"/>.</returns>
/// <!-- aidoc:v1 sig=b9e9549 body=8f9c8ea -->
public async Task<Patient?> FindByPatientId(ObjectId patientId)
{
//Último paciente admitido
@@ -782,6 +854,11 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
return await result.ToListAsync();
}
/// <summary>
/// Asynchronously retrieves all <see cref="Patient"/> records whose associated point of care is not a virtual <see cref="VirtualPointOfCare"/>, by performing a lookup against the <c>pointOfCares</c> collection and excluding any bed whose value matches a virtual point of care.
/// </summary>
/// <returns>A <see cref="Task"/> that resolves to a <see cref="List{Patient}"/> containing the patients located in active (non-virtual) points of care.</returns>
/// <!-- aidoc:v1 sig=2479e43 body=f5b7981 -->
public async Task<List<Patient>> FindInActivePoC()
{
var virtualPointOfCareValues = Enum.GetValues(typeof(VirtualPointOfCare)).Cast<VirtualPointOfCare>()
@@ -812,6 +889,12 @@ public class PatientRepository : MongoRepository<Patient>, IPatientRepository
}
/// <summary>
/// Asynchronously retrieves the <see cref="Patient"/> records whose associated point of care has a bed value matching one of the <see cref="VirtualPointOfCare"/> enum values.
/// The lookup is performed through a MongoDB aggregation pipeline that joins the patients collection with the point of care collection and filters by the <c>pointOfCareInfo.bed</c> field.
/// </summary>
/// <returns>A <see cref="Task"/> that yields a <see cref="List{Patient}"/> containing the patients linked to a point of care whose bed matches any <see cref="VirtualPointOfCare"/> value.</returns>
/// <!-- aidoc:v1 sig=1a67064 body=3dc3451 -->
public async Task<List<Patient>> FindInInactivePoC()
{
var virtualPointOfCareValues = Enum.GetValues(typeof(VirtualPointOfCare)).Cast<VirtualPointOfCare>()
@@ -18,6 +18,13 @@ public class PoCSettingsRepository : MongoRepository<PoCSettings>, IPoCSettingsR
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of <see cref="PoCSettingsRepository"/>, capturing the API configuration from <paramref name="apiSettings"/> and forwarding the MongoDB database to the base repository.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> wrapping the <see cref="ApiSettings"/> configuration values.</param>
/// <param name="database">The <see cref="MongoDB.Driver.IMongoDatabase"/> passed to the base repository for data access.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <see langword="null"/>.</exception>
/// <!-- aidoc:v1 sig=6bccd95 body=d2b18a3 -->
public PoCSettingsRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -20,6 +20,13 @@ public class PointOfCareRepository : MongoRepository<PointOfCare>, IPointOfCareR
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="PointOfCareRepository"/> repository, capturing the configured <see cref="ApiSettings"/> and delegating the <see cref="IMongoDatabase"/> to the base class.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing access to the configured <see cref="ApiSettings"/>.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> forwarded to the base constructor.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <c>null</c>.</exception>
/// <!-- aidoc:v1 sig=5b54ed9 body=99d85d9 -->
public PointOfCareRepository(IOptions<ApiSettings>? apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings != null)
@@ -242,6 +249,14 @@ public class PointOfCareRepository : MongoRepository<PointOfCare>, IPointOfCareR
}
}
/// <summary>
/// Asynchronously finds a <see cref="PointOfCare"/> matching the specified <paramref name="bed"/> and <paramref name="unitId"/>.
/// Returns <see langword="null"/> when the bed is null or empty, when no matching document is found, or when an error occurs.
/// </summary>
/// <param name="bed">The bed identifier to search for. If null or empty, the method returns <see langword="null"/> without querying.</param>
/// <param name="unitId">The unit identifier used to filter the <see cref="PointOfCare"/> documents.</param>
/// <returns>A task containing the first matching <see cref="PointOfCare"/>, or <see langword="null"/> if no match is found.</returns>
/// <!-- aidoc:v1 sig=03d5e9a body=92ba139 -->
public async Task<PointOfCare?> FindByBedAndUnitId(string? bed, ObjectId unitId)
{
try
@@ -386,6 +401,12 @@ public class PointOfCareRepository : MongoRepository<PointOfCare>, IPointOfCareR
throw;
}
}
/// <summary>
/// Asynchronously counts the <see cref="PointOfCare"/> records associated with the supplied <paramref name="unitId"/>, applying a filter that targets documents whose Bed value corresponds to one of the inactive <see cref="VirtualPointOfCare"/> states such as Pushed, Unknown, Deleted, NoBed, Cancelled, Recovered, UnitData, or Moved.
/// </summary>
/// <param name="unitId">The identifier of the unit whose associated <see cref="PointOfCare"/> documents are filtered and counted.</param>
/// <returns>A <see cref="Task{Int64}"/> representing the asynchronous operation, with the result being the number of matching <see cref="PointOfCare"/> documents.</returns>
/// <!-- aidoc:v1 sig=040b525 body=8d8d306 -->
public async Task<long> CountVirtualsByUnitId(ObjectId unitId)
{
try
@@ -680,6 +701,12 @@ public class PointOfCareRepository : MongoRepository<PointOfCare>, IPointOfCareR
}
//Deprecated PatientLocation by UnitName
/// <summary>
/// Asynchronously searches for the first <see cref="PointOfCare"/> matching the supplied <paramref name="patientLocation"/> criteria, combining equality filters for <see cref="PatientLocation.UnitName"/>, <see cref="PatientLocation.Bed"/> and <see cref="PatientLocation.Room"/> when their values are provided; if no criteria are provided an empty filter is used.
/// </summary>
/// <param name="patientLocation">The <see cref="PatientLocation"/> whose non-empty fields (<see cref="PatientLocation.UnitName"/>, <see cref="PatientLocation.Bed"/>, <see cref="PatientLocation.Room"/>) are used to build the search filters.</param>
/// <returns>A <see cref="Task{PointOfCare}"/> that yields the first matching <see cref="PointOfCare"/>, or <see langword="null"/> when no document matches or when an error is caught and logged.</returns>
/// <!-- aidoc:v1 sig=ee884e8 body=c1e5afa -->
public async Task<PointOfCare?> FindByPatientLocation(PatientLocation patientLocation)
{
try
@@ -17,6 +17,12 @@ public class PumpAlarmEventRepository : MongoRepository<PumpAlarmEvent>, IPumpAl
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="PumpAlarmEventRepository"/> repository, capturing the configured <see cref="ApiSettings"/> from <paramref name="apiSettings"/> and forwarding <paramref name="database"/> to the base repository for persistence operations.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> wrapper that exposes the application's <see cref="ApiSettings"/>.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base constructor and used to perform MongoDB operations.</param>
/// <!-- aidoc:v1 sig=cfddddb body=12fddac -->
public PumpAlarmEventRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database)
: base(database)
{
@@ -25,11 +31,20 @@ public class PumpAlarmEventRepository : MongoRepository<PumpAlarmEvent>, IPumpAl
/// <summary>
/// Retrieves the collection name used for pump alarm events, returning the configured value from _apiSettings.PumpAlarmEvent when set, or falling back to the default "pump_alarm_event".
/// </summary>
/// <returns>The configured pump alarm event collection name, or the default "pump_alarm_event" when no configuration value is available.</returns>
/// <!-- aidoc:v1 sig=94e22ff body=9e4a133 -->
public override string GetCollectionName()
{
return _apiSettings.PumpAlarmEvent ?? "pump_alarm_event";
}
/// <summary>
/// Creates the MongoDB indexes for the PumpAlarmEvent collection, optimizing the most common query patterns: device lookup with reverse-chronological time ordering, time-only range queries, patient lookup, and device queries filtered by alarm type.
/// </summary>
/// <!-- aidoc:v1 sig=4955da2 body=fae571b -->
public override async Task CreateIndexes()
{
var indexModels = new List<CreateIndexModel<PumpAlarmEvent>>
@@ -63,6 +78,11 @@ public class PumpAlarmEventRepository : MongoRepository<PumpAlarmEvent>, IPumpAl
}
/// <summary>
/// Asynchronously inserts a new <see cref="PumpAlarmEvent"/> document into the underlying MongoDB collection.
/// </summary>
/// <param name="alarmEvent">The <see cref="PumpAlarmEvent"/> record to persist.</param>
/// <!-- aidoc:v1 sig=5de5fec body=d69d42c -->
public async Task InsertAsync(PumpAlarmEvent alarmEvent)
{
await Collection.InsertOneAsync(alarmEvent);
@@ -85,6 +105,12 @@ public class PumpAlarmEventRepository : MongoRepository<PumpAlarmEvent>, IPumpAl
return await find.ToListAsync();
}
/// <summary>
/// Asynchronously retrieves the most recent <see cref="PumpAlarmEvent"/> for the device identified by <paramref name="deviceId"/>, returning the entry with the latest time stamp, or <c>null</c> if no event exists.
/// </summary>
/// <param name="deviceId">The identifier of the device whose latest alarm event is being retrieved.</param>
/// <returns>A <see cref="Task{PumpAlarmEvent}"/> that yields the latest <see cref="PumpAlarmEvent"/> associated with <paramref name="deviceId"/>, or <c>null</c> if no matching event is found.</returns>
/// <!-- aidoc:v1 sig=f0fbfb3 body=088b34a -->
public async Task<PumpAlarmEvent?> FindLastByDeviceIdAsync(string deviceId)
{
return await Collection
@@ -93,12 +119,25 @@ public class PumpAlarmEventRepository : MongoRepository<PumpAlarmEvent>, IPumpAl
.FirstOrDefaultAsync();
}
/// <summary>
/// Asynchronously removes all documents linked to the specified patient by deleting every record whose PatientId matches the supplied <paramref name="patientId"/>.
/// </summary>
/// <param name="patientId">The <see cref="ObjectId"/> of the patient whose associated documents should be deleted.</param>
/// <!-- aidoc:v1 sig=4776e51 body=395df12 -->
public async Task DeleteByPatientId(ObjectId patientId)
{
await Collection.DeleteManyAsync(x => x.PatientId == patientId);
}
/// <summary>
/// Asynchronously updates the value of the specified field across multiple <see cref="PumpAlarmEvent"/> documents, replacing occurrences of <paramref name="oldId"/> with <paramref name="newId"/>, and returns the number of documents that were modified.
/// </summary>
/// <param name="fieldName">The name of the <see cref="MongoDB.Bson.ObjectId"/> field on <see cref="PumpAlarmEvent"/> whose value should be replaced.</param>
/// <param name="newId">The new <see cref="MongoDB.Bson.ObjectId"/> value to assign to the field in matching documents.</param>
/// <param name="oldId">The existing <see cref="MongoDB.Bson.ObjectId"/> value used to identify documents to be updated.</param>
/// <returns>The number of <see cref="PumpAlarmEvent"/> documents modified by the bulk update.</returns>
/// <!-- aidoc:v1 sig=207e960 body=64489e6 -->
public async Task<long> UpdateManyObjectIdByFiledNameAsync(string fieldName, ObjectId newId, ObjectId oldId)
{
var filter = Builders<PumpAlarmEvent>.Filter.Eq(fieldName, oldId);
@@ -15,6 +15,12 @@ public class PumpAlarmStateRepository : MongoRepository<PumpAlarmState>, IPumpAl
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="PumpAlarmStateRepository"/> class, which persists pump alarm state data, by storing the resolved <see cref="ApiSettings"/> and delegating MongoDB initialization to the base repository.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> instance whose <see cref="IOptions{TOptions}.Value"/> supplies the <see cref="ApiSettings"/> used by the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base constructor to provide the underlying MongoDB connection.</param>
/// <!-- aidoc:v1 sig=df28b3e body=12fddac -->
public PumpAlarmStateRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database)
: base(database)
{
@@ -32,6 +38,10 @@ public class PumpAlarmStateRepository : MongoRepository<PumpAlarmState>, IPumpAl
return _apiSettings.PumpAlarmState ?? "pump_alarm_state";
}
/// <summary>
/// Creates the MongoDB indexes required by the <see cref="PumpAlarmState"/> collection: a unique compound index on <see cref="PumpAlarmState.DeviceId"/>, <see cref="PumpAlarmState.AlarmType"/>, and <see cref="PumpAlarmState.AlarmCodeMdc"/> (named <c>ux_device_alarm</c>), plus supporting indexes on <see cref="PumpAlarmState.DeviceId"/> and <see cref="PumpAlarmState.PatientId"/>.
/// </summary>
/// <!-- aidoc:v1 sig=4955da2 body=cfe5f4a -->
public override async Task CreateIndexes()
{
var indexModels = new List<CreateIndexModel<PumpAlarmState>>
@@ -80,6 +90,15 @@ public class PumpAlarmStateRepository : MongoRepository<PumpAlarmState>, IPumpAl
return await Collection.Find(filter).FirstOrDefaultAsync();
}
/// <summary>
/// Inserts or updates an active <see cref="PumpAlarmState"/> document, reusing the identifier of an
/// existing record that already matches the same <see cref="PumpAlarmState.DeviceId"/>, <see cref="PumpAlarmState.AlarmType"/>
/// and <see cref="PumpAlarmState.AlarmCodeMdc"/> combination, or generating a new <see cref="ObjectId"/> when
/// <paramref name="state"/> does not yet carry one.
/// </summary>
/// <param name="state">The <see cref="PumpAlarmState"/> to persist; its <see cref="PumpAlarmState.Id"/> is
/// populated from the matching document when one is found, or newly generated when it is currently <see cref="ObjectId.Empty"/>.</param>
/// <!-- aidoc:v1 sig=9119854 body=4bd6c92 -->
public async Task UpsertActiveAsync(PumpAlarmState state)
{
var filter =
@@ -19,6 +19,12 @@ namespace adas_core.Infrastructure.Repositories
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="PumpArchiveRepository"/> class, forwarding <paramref name="database"/> to the base repository and storing the resolved <see cref="ApiSettings"/> configuration for subsequent operations.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> wrapper whose <see cref="IOptions{T}.Value"/> supplies the current <see cref="ApiSettings"/>.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> connection passed to the base class constructor.</param>
/// <!-- aidoc:v1 sig=875c7ca body=12fddac -->
public PumpArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database)
: base(database)
{
@@ -26,12 +32,21 @@ namespace adas_core.Infrastructure.Repositories
}
/// <summary>
/// Returns the agreed MongoDB collection name used to store archived pump observations for patients, falling back to the default value when the corresponding setting is not configured.
/// </summary>
/// <returns>The collection name to use, either the value configured in <c>ArchivePatientsPumpobservations</c> or the default <c>archive_pumpobservations</c>.</returns>
/// <!-- aidoc:v1 sig=94e22ff body=09ed313 -->
public override string GetCollectionName()
{
// Nombre de colección pactado: "archive_pumpobservations"
return _apiSettings.ArchivePatientsPumpobservations ?? "archive_pumpobservations";
}
/// <summary>
/// Creates the MongoDB indexes required by the <see cref="PumpObservation"/> collection: an ascending index on <see cref="PumpObservation.PatientId"/> for patient-based lookups and audits, a compound index on <see cref="PumpObservation.DeviceId"/> (ascending) and <see cref="PumpObservation.Time"/> (descending) for per-device timelines, and a descending index on <see cref="PumpObservation.Time"/> for chronological ordering.
/// </summary>
/// <!-- aidoc:v1 sig=4955da2 body=2c1d18d -->
public override async Task CreateIndexes()
{
var indexModels = new List<CreateIndexModel<PumpObservation>>
@@ -23,6 +23,12 @@ namespace adas_core.Infrastructure.Repositories
/// <summary>
/// Initializes a new instance of the <see cref="PumpObservationRepository"/> class, capturing the <see cref="ApiSettings"/> configuration and forwarding the <see cref="IMongoDatabase"/> to the base repository to back pump observation data.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing API configuration values assigned to the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base class to supply the MongoDB connection.</param>
/// <!-- aidoc:v1 sig=8a1d3fa body=12fddac -->
public PumpObservationRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
_apiSettings = apiSettings.Value;
@@ -37,6 +43,10 @@ namespace adas_core.Infrastructure.Repositories
return _apiSettings.PumpObservations ?? "pump_observations";
}
/// <summary>
/// Creates the MongoDB indexes for the <see cref="PumpObservation"/> collection, defining a compound index on <see cref="PumpObservation.DeviceId"/> (ascending) and <see cref="PumpObservation.Time"/> (descending) to optimize per-pump timeline queries and a single-field index on <see cref="PumpObservation.PatientId"/> for patient-scoped lookups. A commented TTL index on <see cref="PumpObservation.Time"/> is included as a reference for enabling direct MongoDB retention when needed.
/// </summary>
/// <!-- aidoc:v1 sig=4955da2 body=e820feb -->
public override async Task CreateIndexes()
{
var indexModels = new List<CreateIndexModel<PumpObservation>>
@@ -149,6 +159,12 @@ namespace adas_core.Infrastructure.Repositories
return await query.ToListAsync();
}
/// <summary>
/// Asynchronously retrieves the most recent observation time for every patient that has at least one observation with a non-null patient identifier, by running a MongoDB aggregation that groups observations by patient and selects the maximum time per group.
/// Documents whose grouped identifier is not an <see cref="ObjectId"/> or whose maximum time is not a valid <see cref="DateTime"/> are skipped from the result, and the remaining timestamps are normalized to UTC.
/// </summary>
/// <returns>A <see cref="Task{Dictionary}"/> that yields a dictionary keyed by the patient's <see cref="ObjectId"/> with the latest observation <see cref="DateTime"/> as the value.</returns>
/// <!-- aidoc:v1 sig=9cf32b6 body=db998dd -->
public async Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTimeAsync()
{
// Pipeline:
@@ -321,6 +337,14 @@ namespace adas_core.Infrastructure.Repositories
}
/// <summary>
/// Deletes older <see cref="PumpObservation"/> entries for the specified <paramref name="name"/>, keeping only the <paramref name="maxCount"/> most recent records ordered by <see cref="PumpObservation.Time"/>.
/// Returns <c>0</c> when <paramref name="name"/> is null or whitespace, when the existing count does not exceed <paramref name="maxCount"/>, or when there is nothing left to delete after skipping the most recent items.
/// </summary>
/// <param name="name">Name used to filter the <see cref="PumpObservation"/> documents to be considered for deletion.</param>
/// <param name="maxCount">Maximum number of most recent <see cref="PumpObservation"/> entries to retain; any additional older entries will be removed.</param>
/// <returns>The number of <see cref="PumpObservation"/> documents deleted, or <c>0</c> when no deletion was required.</returns>
/// <!-- aidoc:v1 sig=ae5008a body=6960e61 -->
public async Task<long> DeleteOlderNumberAsync(string name, int maxCount)
{
if (string.IsNullOrWhiteSpace(name))
@@ -15,6 +15,12 @@ namespace adas_core.Infrastructure.Repositories
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of <see cref="PumpStateRepository"/>, storing the resolved <see cref="ApiSettings"/> from <paramref name="apiSettings"/> and passing the <see cref="IMongoDatabase"/> to the base class constructor.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing the <see cref="ApiSettings"/> configuration values used by the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> forwarded to the base class to establish the underlying data connection.</param>
/// <!-- aidoc:v1 sig=48961b9 body=12fddac -->
public PumpStateRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database)
: base(database)
{
@@ -31,6 +37,10 @@ namespace adas_core.Infrastructure.Repositories
return _apiSettings.PumpStates ?? "pump_states";
}
/// <summary>
/// Creates the MongoDB indexes required by the <see cref="PumpState"/> collection, including a unique index on <see cref="PumpState.DeviceId"/>, a compound index on <see cref="PumpState.DeviceId"/> and <see cref="PumpState.LastUpdated"/> for time-based queries, and an index on <see cref="PumpState.PatientId"/> for patient-scoped lookups.
/// </summary>
/// <!-- aidoc:v1 sig=4955da2 body=fd5fa60 -->
public override async Task CreateIndexes()
{
var indexModels = new List<CreateIndexModel<PumpState>>
@@ -14,6 +14,13 @@ public class RecordingAlertArchiveRepository : MongoRepository<PatientRecordingA
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="RecordingAlertArchiveRepository"/> class, which provides repository access to recording alert archive data, by storing the resolved <see cref="ApiSettings"/> configuration and forwarding the supplied <see cref="IMongoDatabase"/> to the base repository.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> containing the API configuration values assigned to the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base class for persistence operations.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is null.</exception>
/// <!-- aidoc:v1 sig=63386fa body=d2b18a3 -->
public RecordingAlertArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -18,6 +18,13 @@ public class RecordingAlertRepository : MongoRepository<PatientRecordingAlert>,
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="RecordingAlertRepository"/> class, capturing the API configuration and forwarding the MongoDB database to the base repository to support recording-alert persistence operations.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing access to the <see cref="ApiSettings"/> values.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base class constructor.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <see langword="null"/>.</exception>
/// <!-- aidoc:v1 sig=a457f4a body=d2b18a3 -->
public RecordingAlertRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -23,6 +23,12 @@ public class RelayRepository : MongoRepository<Relay>, IRelayRepository
/// <summary>
/// Initializes a new instance of the <see cref="RelayRepository"/> class, a MongoDB-backed repository that depends on <see cref="ApiSettings"/>. It forwards the <paramref name="database"/> to the base constructor and stores the configuration obtained from <paramref name="apiSettings"/>.
/// </summary>
/// <param name="database">The <see cref="IMongoDatabase"/> instance passed to the base class.</param>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> wrapper supplying the active <see cref="ApiSettings"/> configuration.</param>
/// <!-- aidoc:v1 sig=f9dbbda body=12fddac -->
public RelayRepository(IMongoDatabase database, IOptions<ApiSettings> apiSettings) : base(database)
{
_apiSettings = apiSettings.Value;
@@ -18,6 +18,13 @@ public class SectionRepository : MongoRepository<Section>, ISectionRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="SectionRepository"/> class, capturing the configured <see cref="ApiSettings"/> and forwarding the <see cref="IMongoDatabase"/> to the base repository.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing the resolved <see cref="ApiSettings"/> used by the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base constructor to enable MongoDB data access.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is null.</exception>
/// <!-- aidoc:v1 sig=02bfd10 body=d2b18a3 -->
public SectionRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -14,6 +14,13 @@ public class ServiceConfigRepository : MongoRepository<ServiceConfig>, IServiceC
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="ServiceConfigRepository"/> class, capturing the <see cref="ApiSettings"/> from the supplied <see cref="IOptions{ApiSettings}"/> and forwarding the <paramref name="database"/> to the base repository for MongoDB-backed operations.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing access to the API configuration.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base class.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <c>null</c>.</exception>
/// <!-- aidoc:v1 sig=e674d73 body=d2b18a3 -->
public ServiceConfigRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -15,6 +15,13 @@ public class TreatmentArchiveRepository : MongoRepository<PatientTreatment>, ITr
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="TreatmentArchiveRepository"/> class, which manages treatment archive records persisted in MongoDB. The constructor stores the API settings and forwards the <see cref="IMongoDatabase"/> connection to the base repository.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> wrapper exposing the application API settings required by the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> instance passed to the base constructor to provide the storage context.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <see langword="null"/>.</exception>
/// <!-- aidoc:v1 sig=cb353ae body=d2b18a3 -->
public TreatmentArchiveRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -19,6 +19,13 @@ public class TreatmentRepository : MongoRepository<PatientTreatment>, ITreatment
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="TreatmentRepository"/> class by forwarding the <paramref name="database"/> to the base constructor and caching the <see cref="ApiSettings"/> resolved from <paramref name="apiSettings"/>.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> providing the <see cref="ApiSettings"/> instance stored by the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> passed to the base class for MongoDB access.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is null.</exception>
/// <!-- aidoc:v1 sig=3b30d15 body=d2b18a3 -->
public TreatmentRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -203,6 +210,12 @@ public class TreatmentRepository : MongoRepository<PatientTreatment>, ITreatment
return result.ToEnumerable();
}
/// <summary>
/// Retrieves a paginated, filterable query of <see cref="PatientTreatment"/> records sorted by <see cref="PatientTreatment.OrderTime"/> in descending order. When <paramref name="filter"/> carries a <c>FilteredRequest</c>, the query is narrowed by <see cref="PatientTreatment.PatientId"/>, an optional date range, and the active-treatments flag; otherwise default time-based filters are applied.
/// </summary>
/// <param name="filter">The pagination and search criteria used to build the MongoDB filter pipeline.</param>
/// <returns>An <see cref="IFindFluent{TSource, TDocument}"/> of <see cref="PatientTreatment"/> that can be paged or iterated.</returns>
/// <!-- aidoc:v1 sig=4a06ded body=ea0fa2f -->
public IFindFluent<PatientTreatment, PatientTreatment> GetPaginatedTreatments(PaginationFilter filter)
{
var filterBuilder = Builders<PatientTreatment>.Filter;
@@ -28,6 +28,12 @@ public class UnitRepository : MongoRepository<Unit>, IUnitRepository
/// <summary>
/// Initializes a new instance of the <see cref="UnitRepository"/> class by forwarding the <see cref="IMongoDatabase"/> to the base constructor and storing the <see cref="ApiSettings"/> obtained from <paramref name="apiSettings"/>.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> wrapper whose <see cref="IOptions{T}.Value"/> provides the <see cref="ApiSettings"/> configuration used by the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> connection passed to the base class constructor.</param>
/// <!-- aidoc:v1 sig=303625e body=12fddac -->
public UnitRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
_apiSettings = apiSettings.Value;
@@ -105,6 +111,13 @@ public class UnitRepository : MongoRepository<Unit>, IUnitRepository
throw new NotImplementedException();
}
/// <summary>
/// Finds all <see cref="Unit"/> records linked to the supplied master list identifier, dynamically resolving the property to filter on based on <paramref name="masterListType"/>. Any failure encountered while querying is logged and rethrown.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the master list whose related units should be retrieved.</param>
/// <param name="masterListType">The <see cref="MasterListType"/> that selects which property of <see cref="Unit"/> is matched against <paramref name="id"/>.</param>
/// <returns>A task yielding an <see cref="IEnumerable{Unit}"/> with the units that satisfy the resolved filter.</returns>
/// <!-- aidoc:v1 sig=ec8caf6 body=2a1b3b7 -->
public async Task<IEnumerable<Unit>> FindByMasterListId(ObjectId id, MasterListType masterListType)
{
try
@@ -20,6 +20,13 @@ public class UserRepository : MongoRepository<User>, IUserRepository
{
private readonly ApiSettings _apiSettings;
/// <summary>
/// Initializes a new instance of the <see cref="UserRepository"/> class, passing the MongoDB database to the base constructor and storing the API settings required by the repository.
/// </summary>
/// <param name="apiSettings">The <see cref="IOptions{ApiSettings}"/> containing the API settings used by the repository.</param>
/// <param name="database">The <see cref="IMongoDatabase"/> supplied to the base constructor for MongoDB data access.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="apiSettings"/> is <see langword="null"/>.</exception>
/// <!-- aidoc:v1 sig=4f76e46 body=d2b18a3 -->
public UserRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
@@ -18,6 +18,12 @@ public class PublisherService : IPublisherService
private readonly HashSet<string> _queues = new();
private readonly IBus? _bus;
/// <summary>
/// Initializes a new instance of the <see cref="PublisherService"/> class, a RabbitMQ publisher service, by configuring the message bus from <paramref name="rabbitMqSettings"/> and storing <paramref name="logger"/> for diagnostic logging.
/// </summary>
/// <param name="rabbitMqSettings">The RabbitMQ configuration whose <see cref="RabbitMqSettings.ConnectionString"/> is used to initialize the message bus.</param>
/// <param name="logger">The logger used to record initialization status and errors.</param>
/// <!-- aidoc:v1 sig=e60da0f body=9c07ebc -->
public PublisherService(
IOptions<RabbitMqSettings> rabbitMqSettings,
ILogger<PublisherService> logger)
@@ -48,6 +54,17 @@ public class PublisherService : IPublisherService
}
}
/// <summary>
/// Registers a queue with the specified <paramref name="queueName"/> for tracking purposes,
/// while the actual queue creation is delegated to EasyNetQ. Returns false when the name is
/// null, empty, or whitespace, and returns true when the queue is newly added or already
/// registered (idempotent behavior).
/// </summary>
/// <param name="queueName">The name of the queue to register.</param>
/// <returns>A <see cref="Task{Boolean}"/> that resolves to true when the queue is successfully
/// registered (either newly added or already present), or false when <paramref name="queueName"/>
/// is null, empty, or whitespace.</returns>
/// <!-- aidoc:v1 sig=980f114 body=a6676db -->
public Task<bool> CreateQueue(string queueName)
{
if (string.IsNullOrWhiteSpace(queueName))
@@ -31,6 +31,20 @@ public class ReceiverService
private IBus? _bus;
/// <summary>
/// Initializes a new instance of <see cref="ReceiverService"/> by assigning the injected domain services, RabbitMQ settings and logger to backing fields. When <see cref="RabbitMqSettings.ConnectionString"/> is non-empty, it invokes <see cref="ReceiverService.SetQueues"/> and <see cref="ReceiverService.TryToConnect"/>; otherwise it logs an error.
/// </summary>
/// <param name="observationService">The observation service exposed by the receiver.</param>
/// <param name="treatmentService">The treatment service exposed by the receiver.</param>
/// <param name="patientsService">The patient service exposed by the receiver.</param>
/// <param name="pumpService">The pump service exposed by the receiver.</param>
/// <param name="recordingAlertService">The recording alert service exposed by the receiver.</param>
/// <param name="recordingService">The recording service exposed by the receiver.</param>
/// <param name="appointmentService">The appointment service exposed by the receiver.</param>
/// <param name="alarmService">The alarm service exposed by the receiver.</param>
/// <param name="settings">The <see cref="IOptions{TOptions}"/> wrapper providing the <see cref="RabbitMqSettings"/> configuration.</param>
/// <param name="logger">The <see cref="ILogger{TCategoryName}"/> used for diagnostic logging.</param>
/// <!-- aidoc:v1 sig=fcc45e1 body=ab58f48 -->
public ReceiverService(
IObservationService observationService,
ITreatmentService treatmentService,
@@ -139,6 +153,11 @@ public class ReceiverService
}
}
/// <summary>
/// Registers an asynchronous consumer for the specified <paramref name="queue"/> that receives messages, resolves the mapped service, and dispatches the payload for parsing and processing. When no service is mapped for the queue, the message is skipped with a warning; otherwise processing errors are logged and rethrown to enable the retry mechanism.
/// </summary>
/// <param name="queue">The name of the queue whose incoming messages will be consumed.</param>
/// <!-- aidoc:v1 sig=db55582 body=6d1e9a6 -->
private void RegisterConsumer(string queue)
{
_bus!.SendReceive.ReceiveAsync<string>(queue, async payload =>
@@ -41,6 +41,15 @@ public class RelayService : IRelayService
private readonly string _url;
private UriBuilder? _builder;
/// <summary>
/// Initializes a new instance of the <see cref="RelayService"/>, storing its logging, HTTP, configuration, point-of-care, and repository dependencies, resolving the relay endpoint from configuration, and asynchronously invoking <see cref="RelayService.InitRelayWithStatus"/>.
/// </summary>
/// <param name="logger">The <see cref="ILogger{RelayService}"/> used to record diagnostic and operational messages.</param>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> used to create HTTP clients for outbound calls.</param>
/// <param name="relaySettings">The <see cref="IOptions{RelaySettings}"/> providing the relay configuration, including the <see cref="RelaySettings.RecordingOrApiUrl"/> used to compute the relay endpoint.</param>
/// <param name="pointOfCareService">The <see cref="IPointOfCareService"/> used to interact with point-of-care data.</param>
/// <param name="relayRepository">The <see cref="IRelayRepository"/> used to persist and retrieve relay state.</param>
/// <!-- aidoc:v1 sig=6febd55 body=1386dc6 -->
public RelayService(
ILogger<RelayService> logger,
IHttpClientFactory httpClientFactory,
@@ -277,6 +286,12 @@ public class RelayService : IRelayService
return _relayRepository.GetRelayByTypeInList(configurationRelayList, type);
}
/// <summary>
/// Retrieves a paginated list of <see cref="Relay"/> entities, optionally filtered by whether each relay is currently in use by the point of care service.
/// </summary>
/// <param name="filter">The <see cref="PaginationFilter"/> containing paging parameters and optional filter criteria, including the <see cref="FilteredRequest.InUse"/> flag.</param>
/// <returns>A <see cref="Task"/> containing a <see cref="PaginationResponse{T}"/> of <see cref="Relay"/> with the requested page data, the current page number, page size, and total document count.</returns>
/// <!-- aidoc:v1 sig=54c231c body=89d28fb -->
public async Task<PaginationResponse<Relay>> GetPaginatedRelays(PaginationFilter filter)
{
var usedRelayIds = await _pointOfCareService.FindAllIdRelaysInUse();
@@ -343,6 +358,11 @@ public class RelayService : IRelayService
public event EventHandler<Tuple<RelayDevice, int>>? RelayStatusChanged;
/// <summary>
/// Asynchronously initializes the in-memory relay status cache by iterating the point-of-care configurations returned by the service and querying the status of each configured relay.
/// Configurations whose <c>RelayList</c> is null are skipped, and only relays flagged with the cache option have their status stored in the <c>_relayWithStatus</c> dictionary keyed by IP, port, and relay number; per-relay and overall failures are logged instead of being rethrown.
/// </summary>
/// <!-- aidoc:v1 sig=036fb02 body=082292a -->
private async Task InitRelayWithStatus()
{
try
@@ -9,6 +9,12 @@ using System.Diagnostics;
namespace adas_core.Infrastructure.Services;
/// <summary>
/// Implements <see cref="ISendAlertService"/> to publish alert messages to a RabbitMQ
/// broker, using the configuration provided by <see cref="IOptions{RabbitMqSettings}"/>
/// and the logging facility provided by <see cref="ILogger{SendAlertService}"/>.
/// </summary>
/// <!-- aidoc:v1 sig=e283346 -->
public class SendAlertService(
IOptions<RabbitMqSettings> rabbitMqSettings,
ILogger<SendAlertService> logger)
@@ -13,6 +13,12 @@ namespace adas_core.Infrastructure.Utils
/// </summary>
public static class CacheHostBuilderExtension
{
/// <summary>
/// Registers the cache infrastructure on the host, exposing <see cref="ICacheService"/> through a <see cref="CacheDispatcher"/> that can route to either an in-memory or Redis-backed implementation based on <see cref="CacheSettings"/>. <see cref="RedisService"/> is wired lazily so that its <see cref="RedisService.Database"/> is only resolved once the underlying connection has been established asynchronously, while <see cref="CacheService"/> is provided with an <see cref="InMemoryLockProvider"/> and <see cref="NoCacheService"/> is registered as a no-op fallback.
/// </summary>
/// <param name="hostBuilder">The <see cref="IHostBuilder"/> to extend with the cache service registrations.</param>
/// <returns>The same <paramref name="hostBuilder"/> instance, configured with the cache services for fluent chaining.</returns>
/// <!-- aidoc:v1 sig=06693b5 body=f80dd06 -->
public static IHostBuilder UseCache(this IHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices((context, services) =>
@@ -22,6 +22,13 @@ public class CustomPointOfCareConverter : JsonConverter
return objectType == typeof(PointOfCare);
}
/// <summary>
/// Serializes an object to JSON, converting all enum-typed properties to their string representation rather than their underlying integer value. If <paramref name="value"/> is <c>null</c>, a JSON null token is written and the method returns immediately.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> that receives the serialized JSON output.</param>
/// <param name="value">The object to serialize. Enum properties on this instance are written as their string names.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> used to convert the value to a <see cref="JObject"/>.</param>
/// <!-- aidoc:v1 sig=0bafa81 body=109184b -->
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
@@ -45,6 +52,16 @@ public class CustomPointOfCareConverter : JsonConverter
jo.WriteTo(writer);
}
/// <summary>
/// Reads and deserializes a JSON value into an object of the target <paramref name="objectType"/>. This override is not implemented and serves as a placeholder.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> used to read the incoming JSON tokens.</param>
/// <param name="objectType">The <see cref="Type"/> of the object to deserialize into.</param>
/// <param name="existingValue">An existing value to reuse during deserialization, or <see langword="null"/> if none is available.</param>
/// <param name="serializer">The <see cref="JsonSerializer"/> controlling the deserialization process.</param>
/// <returns>An <see cref="object"/> instance populated from the JSON data.</returns>
/// <exception cref="NotImplementedException">Thrown in all cases because the method body has not been implemented.</exception>
/// <!-- aidoc:v1 sig=c91c541 body=bfa6f2f -->
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue,
JsonSerializer serializer)
{
@@ -81,6 +81,10 @@ public static class MongoDbHostBuilderExtension
return mongoDb;
}
/// <summary>
/// Discovers all concrete types in the current <see cref="System.Reflection.Assembly"/> that implement <see cref="IEntityMapContributor"/>, instantiates each one, and invokes its <see cref="IEntityMapContributor.RegisterMaps"/> method to register entity mappings.
/// </summary>
/// <!-- aidoc:v1 sig=e89a195 body=9c8e4ad -->
public static void ConfigureRegisterMapClass()
{
var assembly = Assembly.GetExecutingAssembly();
@@ -15,6 +15,10 @@ namespace adas_core.Infrastructure.Utils.MongoMaps;
/// </summary>
public class BoxConfigMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers the BSON class maps used to control how <see cref="Box"/> and <see cref="Sensor"/> instances are serialized to and from MongoDB. Each registration is only performed when no class map is already registered for the target type, and the mappings exclude properties that should not be persisted, rename persisted members where needed, apply <see cref="DictionaryBsonConverter"/> to the <c>Configuration</c> member of <see cref="Box"/>, and configure null/default-value handling for <see cref="Sensor"/> members.
/// </summary>
/// <!-- aidoc:v1 sig=d4dea7a body=eb04ab5 -->
public void RegisterMaps()
{
if (!BsonClassMap.IsClassMapRegistered(typeof(Box)))
@@ -13,6 +13,10 @@ namespace adas_core.Infrastructure.Utils.MongoMaps
/// </summary>
public class PumpMapContributor : IEntityMapContributor
{
/// <summary>
/// Registers BSON class maps for the pump-related domain models (<see cref="CommonPumpTypes.PumpValue"/>, <see cref="CommonPumpTypes.SyringeDetails"/>, <see cref="PumpObservation"/>, <see cref="PumpState"/>, <see cref="PumpAlarmEvent"/>, <see cref="PumpAlarmState"/>, <see cref="ConfigPumps"/>, and <see cref="ConfigPumpItem"/>) used by the MongoDB driver. Each map is registered only when no prior registration exists for the type, optional members are configured to be ignored when null, enum properties are serialized as strings, and the <c>Id</c> member is mapped to the <c>_id</c> element where applicable.
/// </summary>
/// <!-- aidoc:v1 sig=d4dea7a body=345f688 -->
public void RegisterMaps()
{
// ============================================================
@@ -9,6 +9,12 @@ namespace adas_core.Infrastructure.Utils;
/// </summary>
public class MongoUtils
{
/// <summary>
/// Ensures that the indexes defined in <paramref name="expectedIndexes"/> are present on the <see cref="IMongoCollection{TDocument}"/> supplied via <paramref name="collection"/>. For each expected index, an existing index with the same key specification is left unchanged when its options match, or dropped and recreated when they differ (except for the built-in <c>_id_</c> index, which is never dropped); if no matching index exists the index is created, and MongoDB conflicts reported with code 85 are logged and tolerated.
/// </summary>
/// <param name="collection">The <see cref="IMongoCollection{TDocument}"/> whose indexes are inspected and reconciled.</param>
/// <param name="expectedIndexes">The set of <see cref="CreateIndexModel{TDocument}"/> definitions that the collection should contain.</param>
/// <!-- aidoc:v1 sig=e61054a body=29c4862 -->
public static async Task EnsureIndexes<TDocument>(IMongoCollection<TDocument> collection,
List<CreateIndexModel<TDocument>> expectedIndexes)
{
@@ -74,6 +80,16 @@ public class MongoUtils
}
}
/// <summary>
/// Determines whether the options of an existing index match the expected <see cref="CreateIndexModel{TDocument}"/> options by comparing the <c>unique</c>, <c>background</c>, and <c>partialFilterExpression</c> values.
/// Missing boolean fields in <paramref name="existingIndex"/> are treated as <c>false</c>.
/// </summary>
/// <typeparam name="TDocument">The type of the document the index is defined on.</typeparam>
/// <param name="expectedIndexModel">The <see cref="CreateIndexModel{TDocument}"/> containing the expected <c>Unique</c>, <c>Background</c>, and <c>PartialFilterExpression</c> values.</param>
/// <param name="existingIndex">The <see cref="BsonDocument"/> representing the existing index whose options should be checked.</param>
/// <param name="renderArgs">The <see cref="RenderArgs{TDocument}"/> used to render the expected partial filter expression for comparison.</param>
/// <returns><c>true</c> if the unique flag, background flag, and rendered partial filter expression all match the expected values; otherwise <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=04d4638 body=0088af8 -->
private static bool IndexOptionsMatch<TDocument>(CreateIndexModel<TDocument> expectedIndexModel,
BsonDocument existingIndex, RenderArgs<TDocument> renderArgs)
{
@@ -48,6 +48,15 @@ public class RabbitConsumerErrorHandler(IPublisherService publisherService)
}
}
/// <summary>
/// Handles retry processing for a received message, either forwarding it to an error queue when the maximum retry count is exceeded or republishing it to the original queue with an incremented retry counter.
/// </summary>
/// <param name="receivedInfo">Metadata about the message origin, used to determine the source queue and to build the error message.</param>
/// <param name="properties">The current <see cref="MessageProperties"/> of the message; its headers are copied so the retry count can be updated without mutating the original instance.</param>
/// <param name="body">The raw message payload that will be resent to the queue or forwarded to the error queue.</param>
/// <param name="exception">The <see cref="Exception"/> that triggered the retry, included when building the error message.</param>
/// <exception cref="Exception">Thrown when the republish to the original queue fails (i.e. <see cref="publisherService"/>.SendMessage returns <c>false</c>). The exception is caught and logged internally.</exception>
/// <!-- aidoc:v1 sig=d9b4e83 body=c5b7128 -->
private void HandleRetries(
MessageReceivedInfo receivedInfo,
MessageProperties properties,
@@ -25,6 +25,15 @@ public static class TypesUtils
};
}
/// <summary>
/// Resolves the <see cref="System.Type"/> of a device driver class by locating a loaded assembly whose name contains <paramref name="typeName"/> and then loading the type at the path formed by replacing hyphens in <paramref name="typeName"/> with underscores and appending <c>.Devices.</c>, <paramref name="device"/>, and <paramref name="deviceType"/>.
/// </summary>
/// <param name="typeName">Substring used to match the loaded assembly and to build the namespace portion of the target type name.</param>
/// <param name="device">Device identifier segment combined into the target class name.</param>
/// <param name="deviceType">Suffix appended to <paramref name="device"/> to form the final class name.</param>
/// <returns>The resolved <see cref="System.Type"/> representing the device driver class.</returns>
/// <exception cref="AdasException">Thrown when no loaded assembly matches <paramref name="typeName"/> or when the driver type cannot be resolved within the matched assembly.</exception>
/// <!-- aidoc:v1 sig=3e9123d body=6bc0688 -->
private static Type GetType(string typeName, string device, string deviceType)
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();