using System.Text.Json; using Microsoft.Extensions.Options; using StackExchange.Redis; public class SofaLabCache { private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; private readonly IConnectionMultiplexer _redis; private readonly SofaOptions _options; public SofaLabCache(IConnectionMultiplexer redis, IOptions options) { _redis = redis; _options = options.Value; } public async Task StoreAsync( Guid encounterId, string code, decimal value, DateTimeOffset recordedAt) { var json = JsonSerializer.Serialize(new SofaCachedValue(value, recordedAt), JsonOptions); var key = SofaCalculator.CacheKey(encounterId, code); await _redis.GetDatabase().StringSetAsync( key, json, TimeSpan.FromHours(_options.LabStalenessHours)); } public async Task GetAsync(Guid encounterId, string code) { var cached = await _redis.GetDatabase() .StringGetAsync(SofaCalculator.CacheKey(encounterId, code)); if (!cached.HasValue) return null; return JsonSerializer.Deserialize(cached!, JsonOptions); } public SofaValueStatus Classify(SofaCachedValue? value) { if (value is null) return SofaValueStatus.Expired; var age = DateTimeOffset.UtcNow - value.RecordedAt; if (age.TotalHours > _options.LabStalenessHours) return SofaValueStatus.Expired; if (age.TotalHours > _options.LabWarningHours) return SofaValueStatus.Stale; return SofaValueStatus.Current; } public async Task> GetAllAsync(Guid encounterId) { var cache = _redis.GetDatabase(); var keys = SofaCalculator.SofaObservationCodes .Select(c => (RedisKey)SofaCalculator.CacheKey(encounterId, c)) .ToArray(); var values = await cache.StringGetAsync(keys); var result = new Dictionary(); for (var i = 0; i < SofaCalculator.SofaObservationCodes.Count; i++) { if (!values[i].HasValue) continue; var parsed = JsonSerializer.Deserialize(values[i]!, JsonOptions); if (parsed is not null) result[SofaCalculator.SofaObservationCodes[i]] = parsed; } return result; } }