feature: FHIR R4 Inbound Facade
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
using Hl7.Fhir.Model;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
public record MappedObservation(IngestObservationRequest Request, Guid EncounterId);
|
||||
|
||||
public class ObservationFhirMapper
|
||||
{
|
||||
private readonly FhirReferenceResolver _refs;
|
||||
|
||||
public ObservationFhirMapper(FhirReferenceResolver refs) => _refs = refs;
|
||||
|
||||
public async Task<IReadOnlyList<MappedObservation>> ToIngestRequestsAsync(Hl7.Fhir.Model.Observation fhir)
|
||||
{
|
||||
if (fhir.Encounter is null)
|
||||
throw new FhirMappingException("Observation encounter reference is required.", "required");
|
||||
|
||||
var encounterId = await _refs.ResolveEncounterReferenceAsync(fhir.Encounter);
|
||||
var source = MapSource(fhir);
|
||||
var recordedAt = fhir.Effective.ToUtcDateTimeOffset() ?? DateTimeOffset.UtcNow;
|
||||
var idempotencyKey = fhir.Identifier?.FirstOrDefault()?.Value ?? fhir.Id;
|
||||
|
||||
var results = new List<MappedObservation>();
|
||||
|
||||
if (fhir.Component?.Count > 0)
|
||||
{
|
||||
foreach (var component in fhir.Component)
|
||||
results.AddRange(MapSingleCoding(
|
||||
component.Code, component.Value, encounterId, source, recordedAt,
|
||||
idempotencyKey is null ? null : $"{idempotencyKey}:{component.Code?.Coding?.FirstOrDefault()?.Code}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
results.AddRange(MapSingleCoding(
|
||||
fhir.Code, fhir.Value, encounterId, source, recordedAt, idempotencyKey));
|
||||
}
|
||||
|
||||
if (results.Count == 0)
|
||||
throw new FhirMappingException("Observation contains no mappable values.", "invalid");
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static ObservationSource MapSource(Hl7.Fhir.Model.Observation fhir)
|
||||
{
|
||||
var category = fhir.Category?.FirstOrDefault()?.Coding?.FirstOrDefault()?.Code;
|
||||
return category switch
|
||||
{
|
||||
"vital-signs" => ObservationSource.Device,
|
||||
"laboratory" => ObservationSource.Lab,
|
||||
_ => ObservationSource.Manual
|
||||
};
|
||||
}
|
||||
|
||||
private static List<MappedObservation> MapSingleCoding(
|
||||
CodeableConcept? code,
|
||||
DataType? value,
|
||||
Guid encounterId,
|
||||
ObservationSource source,
|
||||
DateTimeOffset recordedAt,
|
||||
string? idempotencyKey)
|
||||
{
|
||||
var coding = code?.Coding?.FirstOrDefault(c =>
|
||||
!string.IsNullOrWhiteSpace(c.System) && !string.IsNullOrWhiteSpace(c.Code));
|
||||
|
||||
if (coding is null)
|
||||
throw new FhirMappingException("Observation code coding is required.", "required");
|
||||
|
||||
if (!LoincCodeMapper.TryMap(coding.System!, coding.Code!, out var mapping))
|
||||
throw new FhirMappingException(
|
||||
$"Unsupported observation code {coding.System}|{coding.Code}.",
|
||||
"not-supported");
|
||||
|
||||
if (value is not Quantity qty)
|
||||
throw new FhirMappingException("Observation valueQuantity is required.", "required");
|
||||
|
||||
var (normalizedValue, _) = FhirUnitConverter.Normalize(
|
||||
mapping.InternalCode, qty.Value ?? 0m, qty.Unit, mapping.AllowFahrenheit);
|
||||
|
||||
var request = new IngestObservationRequest(
|
||||
ObservationCode: mapping.InternalCode,
|
||||
Value: normalizedValue,
|
||||
Unit: mapping.ExpectedUnit,
|
||||
Source: source,
|
||||
RecordedAt: recordedAt,
|
||||
IdempotencyKey: idempotencyKey);
|
||||
|
||||
return [new MappedObservation(request, encounterId)];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user