Files
vigilcare-clinical/VigilCareClinicalAPI/BackgroundServices/SepsisEngineService.cs
T
voltsrage bf46e6554a feature: Sepsis Engine Refactor: Remove SIRS, Rewire qSOFA + Bundle
Frontend: GCS Entry, SOFA Display, Sepsis UI Refactor
2026-06-21 03:56:27 +08:00

90 lines
3.1 KiB
C#

using System.Text.Json;
using Confluent.Kafka;
using Microsoft.Extensions.Options;
public class SepsisEngineService : BackgroundService
{
private static readonly JsonSerializerOptions EventJsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
private readonly IServiceProvider _services;
private readonly KafkaOptions _kafkaOptions;
private readonly ILogger<SepsisEngineService> _logger;
public SepsisEngineService(
IServiceProvider services,
IOptions<KafkaOptions> kafkaOptions,
ILogger<SepsisEngineService> logger)
{
_services = services;
_kafkaOptions = kafkaOptions.Value;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var config = new ConsumerConfig
{
BootstrapServers = _kafkaOptions.BootstrapServers,
GroupId = "sepsis-engine",
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false
};
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe(_kafkaOptions.Topics.ObservationRecorded);
_logger.LogInformation(
"SepsisEngineService started — consumer group: sepsis-engine (qSOFA screening only)");
try
{
while (!stoppingToken.IsCancellationRequested)
{
ConsumeResult<string, string>? result = null;
try
{
result = consumer.Consume(stoppingToken);
var evt = JsonSerializer.Deserialize<SepsisObservationEvent>(
result.Message.Value, EventJsonOptions)!;
using var scope = _services.CreateScope();
var qsofaDetector = scope.ServiceProvider.GetRequiredService<QsofaDetector>();
var qsofaOutcome = await qsofaDetector.ProcessObservationAsync(
evt.EncounterId,
evt.PatientId,
evt.ObservationCode,
evt.Value,
stoppingToken);
if (qsofaOutcome.Outcome == QsofaOutcome.AlertCreated)
_logger.LogInformation(
"QSOFA_SCREEN created via SepsisEngine " +
"— encounter={EncounterId} code={Code} value={Value}",
evt.EncounterId, evt.ObservationCode, evt.Value);
consumer.Commit(result);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex,
"SepsisEngine failed on topic={Topic} offset={Offset} — not committing",
result?.Topic, result?.Offset.Value);
await Task.Delay(2000, stoppingToken);
}
}
}
finally
{
consumer.Close();
}
}
}