using Hl7.Fhir.Model; using FhirObservation = Hl7.Fhir.Model.Observation; public static class ObservationMapper { private static readonly Dictionary 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 VitalSignCodes = [ "HEART_RATE", "TEMP_C", "BP_SYSTOLIC", "BP_DIASTOLIC", "RESP_RATE", "SPO2" ]; /// /// 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. /// 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; } /// /// Reverse LOINC → VigilCare code mapping for FHIR search by LOINC code. /// public static IReadOnlyDictionary GetReverseLoincMapping() => LoincToVigilCare; /// /// VigilCare observation codes that represent vital signs (used for category search). /// public static IReadOnlyCollection 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); }