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 GetCurrentAsync(Guid encounterId) { await EnsureEncounterExistsAsync(encounterId); var values = await ReadCriterionValuesAsync(encounterId); return BuildResponse(values); } public async Task 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 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; }