138 lines
5.4 KiB
C#
138 lines
5.4 KiB
C#
using Hl7.Fhir.Model;
|
|
using FhirObservation = Hl7.Fhir.Model.Observation;
|
|
|
|
public static class ObservationMapper
|
|
{
|
|
private static readonly Dictionary<string, string> LoincToVigilCare = new()
|
|
{
|
|
["8867-4"] = "HEART_RATE",
|
|
["8310-5"] = "TEMP_C",
|
|
["8480-6"] = "BP_SYSTOLIC",
|
|
["8462-4"] = "BP_DIASTOLIC",
|
|
["9279-1"] = "RESP_RATE",
|
|
["2708-6"] = "SPO2",
|
|
["2345-7"] = "GLUCOSE_MG_DL",
|
|
["2823-3"] = "POTASSIUM_MEQ_L",
|
|
["2951-2"] = "SODIUM_MEQ_L",
|
|
["2524-7"] = "LACTATE_MMOL_L",
|
|
["6690-2"] = "WBC_K_UL",
|
|
["718-7"] = "HEMOGLOBIN_G_DL",
|
|
["2160-0"] = "CREATININE_MG_DL",
|
|
};
|
|
|
|
private static readonly HashSet<string> VitalSignCodes =
|
|
[
|
|
"HEART_RATE", "TEMP_C", "BP_SYSTOLIC", "BP_DIASTOLIC", "RESP_RATE", "SPO2"
|
|
];
|
|
|
|
/// <summary>
|
|
/// Maps a VigilCare Observation entity to a FHIR R4 Observation resource.
|
|
///
|
|
/// Mapping decisions:
|
|
/// - ObservationCode maps to LOINC codes where a standard mapping exists.
|
|
/// Unknown codes use a local CodeSystem with the original code as display.
|
|
/// - Value + Unit maps to Observation.valueQuantity with UCUM unit codes.
|
|
/// - RecordedAt maps to effectiveDateTime (when the observation was clinically
|
|
/// relevant), not issued (when the system recorded it).
|
|
/// - Source and SourceBatchId are preserved as extensions for provenance.
|
|
/// </summary>
|
|
public static FhirObservation ToFhir(Observation entity, string baseUrl)
|
|
{
|
|
var observation = new FhirObservation
|
|
{
|
|
Id = entity.Id.ToString(),
|
|
Meta = new Meta
|
|
{
|
|
VersionId = "1",
|
|
LastUpdated = entity.CreatedAt,
|
|
},
|
|
Status = ObservationStatus.Final,
|
|
Subject = new ResourceReference($"Patient/{entity.PatientId}"),
|
|
Encounter = new ResourceReference($"Encounter/{entity.EncounterId}"),
|
|
Effective = new FhirDateTime(entity.RecordedAt),
|
|
Issued = entity.CreatedAt,
|
|
};
|
|
|
|
observation.Code = MapObservationCode(entity.ObservationCode);
|
|
|
|
observation.Value = new Quantity
|
|
{
|
|
Value = entity.Value,
|
|
Unit = entity.Unit,
|
|
System = "http://unitsofmeasure.org",
|
|
Code = MapToUcum(entity.Unit),
|
|
};
|
|
|
|
var category = IsVitalSign(entity.ObservationCode) ? "vital-signs" : "laboratory";
|
|
observation.Category.Add(new CodeableConcept(
|
|
"http://terminology.hl7.org/CodeSystem/observation-category",
|
|
category));
|
|
|
|
if (!string.IsNullOrEmpty(entity.Note))
|
|
{
|
|
observation.Note.Add(new Annotation { Text = new Markdown(entity.Note) });
|
|
}
|
|
|
|
observation.Extension.Add(new Extension(
|
|
"urn:vigilcare:source",
|
|
new FhirString(entity.Source)));
|
|
|
|
if (entity.SourceBatchId.HasValue)
|
|
{
|
|
observation.Extension.Add(new Extension(
|
|
"urn:vigilcare:source-batch-id",
|
|
new FhirString(entity.SourceBatchId.Value.ToString())));
|
|
}
|
|
|
|
return observation;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reverse LOINC → VigilCare code mapping for FHIR search by LOINC code.
|
|
/// </summary>
|
|
public static IReadOnlyDictionary<string, string> GetReverseLoincMapping() => LoincToVigilCare;
|
|
|
|
/// <summary>
|
|
/// VigilCare observation codes that represent vital signs (used for category search).
|
|
/// </summary>
|
|
public static IReadOnlyCollection<string> GetVitalSignCodes() => VitalSignCodes;
|
|
|
|
private static CodeableConcept MapObservationCode(string code) => code switch
|
|
{
|
|
"HEART_RATE" => Loinc("8867-4", "Heart rate"),
|
|
"TEMP_C" => Loinc("8310-5", "Body temperature"),
|
|
"BP_SYSTOLIC" => Loinc("8480-6", "Systolic blood pressure"),
|
|
"BP_DIASTOLIC" => Loinc("8462-4", "Diastolic blood pressure"),
|
|
"RESP_RATE" => Loinc("9279-1", "Respiratory rate"),
|
|
"SPO2" => Loinc("2708-6", "Oxygen saturation"),
|
|
"GLUCOSE_MG_DL" => Loinc("2345-7", "Glucose [Mass/volume] in Serum or Plasma"),
|
|
"POTASSIUM_MEQ_L" => Loinc("2823-3", "Potassium [Moles/volume] in Serum or Plasma"),
|
|
"SODIUM_MEQ_L" => Loinc("2951-2", "Sodium [Moles/volume] in Serum or Plasma"),
|
|
"LACTATE_MMOL_L" => Loinc("2524-7", "Lactate [Moles/volume] in Serum or Plasma"),
|
|
"WBC_K_UL" => Loinc("6690-2", "Leukocytes [#/volume] in Blood"),
|
|
"HEMOGLOBIN_G_DL" => Loinc("718-7", "Hemoglobin [Mass/volume] in Blood"),
|
|
"CREATININE_MG_DL" => Loinc("2160-0", "Creatinine [Mass/volume] in Serum or Plasma"),
|
|
_ => new CodeableConcept("urn:vigilcare:observation-code", code, code),
|
|
};
|
|
|
|
private static CodeableConcept Loinc(string code, string display) =>
|
|
new("http://loinc.org", code, display);
|
|
|
|
private static string MapToUcum(string unit) => unit switch
|
|
{
|
|
"bpm" => "/min",
|
|
"C" => "Cel",
|
|
"mmHg" => "mm[Hg]",
|
|
"breaths/min" => "/min",
|
|
"%" => "%",
|
|
"mg/dL" => "mg/dL",
|
|
"mEq/L" => "meq/L",
|
|
"mmol/L" => "mmol/L",
|
|
"K/uL" => "10*3/uL",
|
|
"g/dL" => "g/dL",
|
|
_ => unit,
|
|
};
|
|
|
|
private static bool IsVitalSign(string code) => VitalSignCodes.Contains(code);
|
|
}
|