feature: Medication Tracking & Vital Sign Correlation
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
public class MedicationService : IMedicationService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
private readonly MedicationCorrelationOptions _correlationOptions;
|
||||
|
||||
public MedicationService(
|
||||
AppDbContext db,
|
||||
IOptions<MedicationCorrelationOptions> correlationOptions)
|
||||
{
|
||||
_db = db;
|
||||
_correlationOptions = correlationOptions.Value;
|
||||
}
|
||||
|
||||
public async Task<MedicationAdministration> CreateAsync(
|
||||
Guid encounterId, CreateMedicationAdministrationRequest req)
|
||||
{
|
||||
var encounter = await _db.Encounters.FindAsync(encounterId);
|
||||
if (encounter is null)
|
||||
throw new NotFoundException("Encounter not found.", "ENCOUNTER_NOT_FOUND");
|
||||
|
||||
if (encounter.Status != EncounterStatus.Active)
|
||||
throw new ConflictException(
|
||||
"Cannot record medications for a non-active encounter.",
|
||||
"ENCOUNTER_NOT_ACTIVE");
|
||||
|
||||
var med = new MedicationAdministration
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
EncounterId = encounterId,
|
||||
DrugName = req.DrugName.Trim(),
|
||||
Dose = req.Dose,
|
||||
DoseUnit = req.DoseUnit.Trim(),
|
||||
Route = req.Route.Trim(),
|
||||
AdministeredAt = req.AdministeredAt ?? DateTimeOffset.UtcNow,
|
||||
AdministeredBy = req.AdministeredBy.Trim()
|
||||
};
|
||||
|
||||
_db.MedicationAdministrations.Add(med);
|
||||
await _db.SaveChangesAsync();
|
||||
return med;
|
||||
}
|
||||
|
||||
public async Task<MedicationAdministration> GetByIdAsync(Guid id)
|
||||
{
|
||||
var med = await _db.MedicationAdministrations
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Encounter)
|
||||
.FirstOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (med is null)
|
||||
throw new NotFoundException("Medication administration not found.", "MEDICATION_NOT_FOUND");
|
||||
|
||||
return med;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<MedicationAdministration>> GetRecentForEncounterAsync(
|
||||
Guid encounterId, string observationCode, int windowMinutes)
|
||||
{
|
||||
var cutoff = DateTimeOffset.UtcNow.AddMinutes(-windowMinutes);
|
||||
var mappings = _correlationOptions.DrugVitalMappings;
|
||||
|
||||
// Find drugs that affect this observation code
|
||||
var relevantDrugs = mappings
|
||||
.Where(kv => kv.Value.Contains(observationCode, StringComparer.OrdinalIgnoreCase))
|
||||
.Select(kv => kv.Key)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (relevantDrugs.Count == 0)
|
||||
return Array.Empty<MedicationAdministration>();
|
||||
|
||||
return await _db.MedicationAdministrations
|
||||
.AsNoTracking()
|
||||
.Where(m => m.EncounterId == encounterId
|
||||
&& m.AdministeredAt >= cutoff
|
||||
&& relevantDrugs.Contains(m.DrugName.ToLower()))
|
||||
.OrderByDescending(m => m.AdministeredAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<PagedResult<MedicationAdministration>> ListByEncounterAsync(
|
||||
Guid encounterId, DateTimeOffset? since, int page, int pageSize)
|
||||
{
|
||||
var query = _db.MedicationAdministrations
|
||||
.AsNoTracking()
|
||||
.Where(m => m.EncounterId == encounterId);
|
||||
|
||||
if (since.HasValue)
|
||||
query = query.Where(m => m.AdministeredAt >= since.Value);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(m => m.AdministeredAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return new PagedResult<MedicationAdministration>(items, page, pageSize, total);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user