update docs and prep for frontend
This commit is contained in:
@@ -14,8 +14,13 @@ public class EncounterService : IEncounterService
|
||||
};
|
||||
|
||||
private readonly AppDbContext _db;
|
||||
private readonly IQsofaService _qsofa;
|
||||
|
||||
public EncounterService(AppDbContext db) => _db = db;
|
||||
public EncounterService(AppDbContext db, IQsofaService qsofa)
|
||||
{
|
||||
_db = db;
|
||||
_qsofa = qsofa;
|
||||
}
|
||||
|
||||
public async Task<Encounter> GetByIdAsync(Guid id)
|
||||
{
|
||||
@@ -31,6 +36,88 @@ public class EncounterService : IEncounterService
|
||||
return encounter;
|
||||
}
|
||||
|
||||
public async Task<PagedResult<WardEncounterSummary>> ListAsync(
|
||||
EncounterStatus? status, Department? department, int page, int pageSize)
|
||||
{
|
||||
page = Math.Max(1, page);
|
||||
pageSize = Math.Clamp(pageSize, 1, 100);
|
||||
|
||||
var query = _db.Encounters
|
||||
.AsNoTracking()
|
||||
.Include(e => e.Patient)
|
||||
.AsQueryable();
|
||||
|
||||
if (status.HasValue)
|
||||
query = query.Where(e => e.Status == status.Value);
|
||||
|
||||
if (department.HasValue)
|
||||
query = query.Where(e => e.Department == department.Value);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
var encounters = await query
|
||||
.OrderByDescending(e => e.AdmittedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
if (encounters.Count == 0)
|
||||
return new PagedResult<WardEncounterSummary>([], page, pageSize, total);
|
||||
|
||||
var encounterIds = encounters.Select(e => e.Id).ToList();
|
||||
|
||||
var news2ByEncounter = (await _db.News2Scores
|
||||
.AsNoTracking()
|
||||
.Where(s => encounterIds.Contains(s.EncounterId))
|
||||
.OrderByDescending(s => s.CalculatedAt)
|
||||
.ToListAsync())
|
||||
.GroupBy(s => s.EncounterId)
|
||||
.ToDictionary(g => g.Key, g => g.First());
|
||||
|
||||
var openAlertCounts = await _db.ClinicalAlerts
|
||||
.AsNoTracking()
|
||||
.Where(a => encounterIds.Contains(a.EncounterId) && a.Status == AlertStatus.Open)
|
||||
.GroupBy(a => a.EncounterId)
|
||||
.Select(g => new { EncounterId = g.Key, Count = g.Count() })
|
||||
.ToDictionaryAsync(x => x.EncounterId, x => x.Count);
|
||||
|
||||
var bundlesByEncounter = (await _db.SepsisBundles
|
||||
.AsNoTracking()
|
||||
.Where(b => encounterIds.Contains(b.EncounterId))
|
||||
.OrderByDescending(b => b.RecognizedAt)
|
||||
.ToListAsync())
|
||||
.GroupBy(b => b.EncounterId)
|
||||
.ToDictionary(g => g.Key, g => g.First());
|
||||
|
||||
var summaries = new List<WardEncounterSummary>(encounters.Count);
|
||||
foreach (var encounter in encounters)
|
||||
{
|
||||
news2ByEncounter.TryGetValue(encounter.Id, out var news2);
|
||||
bundlesByEncounter.TryGetValue(encounter.Id, out var bundle);
|
||||
openAlertCounts.TryGetValue(encounter.Id, out var openCount);
|
||||
|
||||
var qsofaScore = await _qsofa.GetActiveCriteriaCountAsync(encounter.Id);
|
||||
|
||||
summaries.Add(new WardEncounterSummary(
|
||||
encounter.Id,
|
||||
encounter.PatientId,
|
||||
encounter.Patient.Mrn,
|
||||
encounter.Patient.FirstName,
|
||||
encounter.Patient.LastName,
|
||||
encounter.RoomBed,
|
||||
encounter.Department,
|
||||
encounter.Status,
|
||||
news2?.TotalScore,
|
||||
news2?.RiskLevel,
|
||||
qsofaScore,
|
||||
bundle is not null && bundle.ComplianceStatus != SepsisBundleComplianceStatus.Compliant,
|
||||
bundle?.ComplianceStatus,
|
||||
openCount));
|
||||
}
|
||||
|
||||
return new PagedResult<WardEncounterSummary>(summaries, page, pageSize, total);
|
||||
}
|
||||
|
||||
public async Task<EncounterStatusTransitionResult> TransitionStatusAsync(
|
||||
Guid encounterId, EncounterStatus targetStatus, string? dischargeDiagnosis = null)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
public interface IEncounterService
|
||||
{
|
||||
Task<Encounter> GetByIdAsync(Guid id);
|
||||
Task<PagedResult<WardEncounterSummary>> ListAsync(
|
||||
EncounterStatus? status, Department? department, int page, int pageSize);
|
||||
Task<EncounterStatusTransitionResult> TransitionStatusAsync(
|
||||
Guid encounterId, EncounterStatus targetStatus, string? dischargeDiagnosis = null);
|
||||
Task<object> GetTimelineAsync(Guid encounterId);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
public interface IQsofaService
|
||||
{
|
||||
Task<QsofaCurrentResponse> GetCurrentAsync(Guid encounterId);
|
||||
Task<int> GetActiveCriteriaCountAsync(Guid encounterId);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StackExchange.Redis;
|
||||
|
||||
public class QsofaService : IQsofaService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
private readonly IConnectionMultiplexer _redis;
|
||||
|
||||
public QsofaService(AppDbContext db, IConnectionMultiplexer redis)
|
||||
{
|
||||
_db = db;
|
||||
_redis = redis;
|
||||
}
|
||||
|
||||
public async Task<QsofaCurrentResponse> GetCurrentAsync(Guid encounterId)
|
||||
{
|
||||
await EnsureEncounterExistsAsync(encounterId);
|
||||
|
||||
var values = await ReadCriterionValuesAsync(encounterId);
|
||||
return BuildResponse(values);
|
||||
}
|
||||
|
||||
public async Task<int> GetActiveCriteriaCountAsync(Guid encounterId)
|
||||
{
|
||||
var values = await ReadCriterionValuesAsync(encounterId);
|
||||
return QsofaCalculator.CountActiveCriteria(values);
|
||||
}
|
||||
|
||||
private async Task EnsureEncounterExistsAsync(Guid encounterId)
|
||||
{
|
||||
var exists = await _db.Encounters.AnyAsync(e => e.Id == encounterId);
|
||||
if (!exists)
|
||||
throw new NotFoundException("Encounter not found.", "ENCOUNTER_NOT_FOUND");
|
||||
}
|
||||
|
||||
private async Task<RedisValue[]> ReadCriterionValuesAsync(Guid encounterId)
|
||||
{
|
||||
var cache = _redis.GetDatabase();
|
||||
return await cache.StringGetAsync(QsofaCalculator.AllCriterionKeys(encounterId));
|
||||
}
|
||||
|
||||
private static QsofaCurrentResponse BuildResponse(RedisValue[] values)
|
||||
{
|
||||
return new QsofaCurrentResponse(
|
||||
QsofaCalculator.CountActiveCriteria(values),
|
||||
new QsofaCriteriaState(
|
||||
ParseOptionalDecimal(values[0]),
|
||||
ParseOptionalDecimal(values[1]),
|
||||
ParseOptionalDecimal(values[2])));
|
||||
}
|
||||
|
||||
private static decimal? ParseOptionalDecimal(RedisValue value) =>
|
||||
value.HasValue ? decimal.Parse(value.ToString()) : null;
|
||||
}
|
||||
Reference in New Issue
Block a user