|
| 1 | +/** |
| 2 | + * FHIR R4 Resource Creation Demo |
| 3 | + * |
| 4 | + * This file demonstrates how to: |
| 5 | + * 1. Create a Patient resource |
| 6 | + * 2. Create an Observation resource |
| 7 | + * 3. Create a body weight observation with profiles |
| 8 | + * 4. Use attach and extract functions for FHIR profiles |
| 9 | + * 5. Bundle them together in a FHIR Bundle |
| 10 | + */ |
| 11 | + |
| 12 | +import type { Bundle, BundleEntry } from "./fhir-types/hl7-fhir-r4-core/Bundle"; |
| 13 | +import type { CodeableConcept } from "./fhir-types/hl7-fhir-r4-core/CodeableConcept"; |
| 14 | +import type { Coding } from "./fhir-types/hl7-fhir-r4-core/Coding"; |
| 15 | +import type { Observation, ObservationReferenceRange } from "./fhir-types/hl7-fhir-r4-core/Observation"; |
| 16 | +import type { Address, ContactPoint, HumanName, Identifier, Patient } from "./fhir-types/hl7-fhir-r4-core/Patient"; |
| 17 | +import type { Quantity } from "./fhir-types/hl7-fhir-r4-core/Quantity"; |
| 18 | +import type { Reference } from "./fhir-types/hl7-fhir-r4-core/Reference"; |
| 19 | + |
| 20 | +function createPatient(): Patient { |
| 21 | + const identifier: Identifier = { |
| 22 | + system: "http://hospital.example.org/identifiers/patient", |
| 23 | + value: "12345", |
| 24 | + use: "official", |
| 25 | + }; |
| 26 | + |
| 27 | + const name: HumanName = { |
| 28 | + family: "Smith", |
| 29 | + given: ["John", "Jacob"], |
| 30 | + use: "official", |
| 31 | + prefix: ["Mr."], |
| 32 | + }; |
| 33 | + |
| 34 | + const telecom: ContactPoint[] = [ |
| 35 | + { |
| 36 | + system: "phone", |
| 37 | + value: "555-555-5555", |
| 38 | + use: "home", |
| 39 | + }, |
| 40 | + { |
| 41 | + system: "email", |
| 42 | + value: "john.smith@example.com", |
| 43 | + use: "work", |
| 44 | + }, |
| 45 | + ]; |
| 46 | + |
| 47 | + const address: Address = { |
| 48 | + line: ["123 Main St"], |
| 49 | + city: "Anytown", |
| 50 | + state: "CA", |
| 51 | + postalCode: "12345", |
| 52 | + country: "USA", |
| 53 | + use: "home", |
| 54 | + }; |
| 55 | + |
| 56 | + const patient: Patient = { |
| 57 | + resourceType: "Patient", |
| 58 | + id: "pt-1", |
| 59 | + identifier: [identifier], |
| 60 | + active: true, |
| 61 | + name: [name], |
| 62 | + telecom: telecom, |
| 63 | + gender: "male", |
| 64 | + birthDate: "1974-12-25", |
| 65 | + address: [address], |
| 66 | + }; |
| 67 | + |
| 68 | + return patient; |
| 69 | +} |
| 70 | + |
| 71 | +function createObservation(patientId: string): Observation { |
| 72 | + const glucoseCoding: Coding = { |
| 73 | + system: "http://loinc.org", |
| 74 | + code: "15074-8", |
| 75 | + display: "Glucose [Moles/volume] in Blood", |
| 76 | + }; |
| 77 | + |
| 78 | + const glucoseConcept: CodeableConcept = { |
| 79 | + coding: [glucoseCoding], |
| 80 | + text: "Blood glucose measurement", |
| 81 | + }; |
| 82 | + |
| 83 | + const referenceRange: ObservationReferenceRange = { |
| 84 | + low: { |
| 85 | + value: 3.1, |
| 86 | + unit: "mmol/L", |
| 87 | + system: "http://unitsofmeasure.org", |
| 88 | + code: "mmol/L", |
| 89 | + }, |
| 90 | + high: { |
| 91 | + value: 6.2, |
| 92 | + unit: "mmol/L", |
| 93 | + system: "http://unitsofmeasure.org", |
| 94 | + code: "mmol/L", |
| 95 | + }, |
| 96 | + text: "3.1 to 6.2 mmol/L", |
| 97 | + }; |
| 98 | + |
| 99 | + const glucoseValue: Quantity = { |
| 100 | + value: 6.3, |
| 101 | + unit: "mmol/L", |
| 102 | + system: "http://unitsofmeasure.org", |
| 103 | + code: "mmol/L", |
| 104 | + }; |
| 105 | + |
| 106 | + const patientReference: Reference<"Patient"> = { |
| 107 | + reference: `Patient/${patientId}`, |
| 108 | + display: "John Smith", |
| 109 | + }; |
| 110 | + |
| 111 | + const observation: Observation = { |
| 112 | + resourceType: "Observation", |
| 113 | + id: "glucose-obs-1", |
| 114 | + status: "final", |
| 115 | + category: [ |
| 116 | + { |
| 117 | + coding: [ |
| 118 | + { |
| 119 | + system: "http://terminology.hl7.org/CodeSystem/observation-category", |
| 120 | + code: "laboratory", |
| 121 | + display: "Laboratory", |
| 122 | + }, |
| 123 | + ], |
| 124 | + text: "Laboratory", |
| 125 | + }, |
| 126 | + ], |
| 127 | + code: glucoseConcept, |
| 128 | + subject: patientReference, |
| 129 | + effectiveDateTime: "2023-03-15T09:30:00Z", |
| 130 | + issued: "2023-03-15T10:15:00Z", |
| 131 | + valueQuantity: glucoseValue, |
| 132 | + referenceRange: [referenceRange], |
| 133 | + }; |
| 134 | + |
| 135 | + return observation; |
| 136 | +} |
| 137 | + |
| 138 | +function createBodyWeightObservation(patientId: string): Observation { |
| 139 | + const baseObservation: Observation = { |
| 140 | + resourceType: "Observation", |
| 141 | + id: "example-genetics-1", |
| 142 | + status: "final", |
| 143 | + code: { |
| 144 | + coding: [ |
| 145 | + { |
| 146 | + code: "29463-7", |
| 147 | + system: "http://loinc.org", |
| 148 | + display: "Body weight", |
| 149 | + }, |
| 150 | + ], |
| 151 | + }, |
| 152 | + }; |
| 153 | + |
| 154 | + const bodyweightProfile: observation_bodyweight = { |
| 155 | + __profileUrl: "http://hl7.org/fhir/StructureDefinition/bodyweight", |
| 156 | + status: baseObservation.status, |
| 157 | + category: [ |
| 158 | + { |
| 159 | + coding: [ |
| 160 | + { |
| 161 | + system: "http://terminology.hl7.org/CodeSystem/observation-category", |
| 162 | + code: "vital-signs", |
| 163 | + display: "Vital Signs", |
| 164 | + }, |
| 165 | + ], |
| 166 | + text: "Vital Signs", |
| 167 | + }, |
| 168 | + ], |
| 169 | + code: { |
| 170 | + coding: [ |
| 171 | + { |
| 172 | + code: "29463-7", |
| 173 | + system: "http://loinc.org", |
| 174 | + display: "Body weight", |
| 175 | + }, |
| 176 | + ], |
| 177 | + }, |
| 178 | + subject: { |
| 179 | + reference: `Patient/${patientId}`, |
| 180 | + display: "John Smith", |
| 181 | + }, |
| 182 | + effectiveDateTime: "2023-03-15T09:30:00Z", |
| 183 | + valueQuantity: { |
| 184 | + value: 75.5, |
| 185 | + unit: "kg", |
| 186 | + system: "http://unitsofmeasure.org", |
| 187 | + code: "kg", |
| 188 | + }, |
| 189 | + }; |
| 190 | + |
| 191 | + console.log("Original observation:", JSON.stringify(baseObservation, null, 2)); |
| 192 | + console.log("Body weight observation:", JSON.stringify(bodyweightProfile, null, 2)); |
| 193 | + |
| 194 | + const bodyweightObservation = attach_observation_bodyweight_to_Observation(baseObservation, bodyweightProfile); |
| 195 | + console.log("Body weight observation with profile:", JSON.stringify(bodyweightObservation, null, 2)); |
| 196 | + |
| 197 | + console.log("Validate bodyweight observation with attached profile"); |
| 198 | + const isStatusCorrect = bodyweightObservation.status === "final"; |
| 199 | + const isCodeCorrect = |
| 200 | + bodyweightObservation.code?.coding?.[0]?.code === "29463-7" && |
| 201 | + bodyweightObservation.code?.coding?.[0]?.system === "http://loinc.org"; |
| 202 | + const hasValueQuantity = bodyweightObservation.valueQuantity !== undefined; |
| 203 | + const isValueQuantityCorrect = |
| 204 | + bodyweightObservation.valueQuantity?.value === 75.5 && |
| 205 | + bodyweightObservation.valueQuantity?.unit === "kg" && |
| 206 | + bodyweightObservation.valueQuantity?.system === "http://unitsofmeasure.org"; |
| 207 | + |
| 208 | + console.log("✓ Status is 'final':", isStatusCorrect); |
| 209 | + console.log("✓ Code is LOINC 29463-7 (Body weight):", isCodeCorrect); |
| 210 | + console.log("✓ Has valueQuantity:", hasValueQuantity); |
| 211 | + console.log("✓ ValueQuantity is correct (75.5 kg):", isValueQuantityCorrect); |
| 212 | + |
| 213 | + if (!isStatusCorrect || !isCodeCorrect || !hasValueQuantity || !isValueQuantityCorrect) |
| 214 | + throw new Error("Bodyweight observation is not valid"); |
| 215 | + |
| 216 | + const extractedProfile = extract_observation_bodyweight_from_Observation(bodyweightObservation); |
| 217 | + console.log("Extracted bodyweight profile:", JSON.stringify(extractedProfile, null, 2)); |
| 218 | + |
| 219 | + console.log("\n--- Validation: extractedProfile correctness ---"); |
| 220 | + |
| 221 | + const isProfileUrlCorrect = extractedProfile.__profileUrl === "http://hl7.org/fhir/StructureDefinition/bodyweight"; |
| 222 | + const isExtractedStatusCorrect = extractedProfile.status === "final"; |
| 223 | + const isExtractedCodeCorrect = extractedProfile.code?.coding?.[0]?.code === "29463-7"; |
| 224 | + const isExtractedValueCorrect = |
| 225 | + extractedProfile.valueQuantity?.value === 75.5 && extractedProfile.valueQuantity?.unit === "kg"; |
| 226 | + |
| 227 | + const profilesMatch = JSON.stringify(bodyweightProfile) === JSON.stringify(extractedProfile); |
| 228 | + |
| 229 | + console.log("✓ Profile URL is correct:", isProfileUrlCorrect); |
| 230 | + console.log("✓ Extracted status matches:", isExtractedStatusCorrect); |
| 231 | + console.log("✓ Extracted code matches:", isExtractedCodeCorrect); |
| 232 | + console.log("✓ Extracted value matches:", isExtractedValueCorrect); |
| 233 | + console.log("✓ Original and extracted profiles match:", profilesMatch); |
| 234 | + |
| 235 | + if (!profilesMatch) throw new Error("Profile mismatch"); |
| 236 | + |
| 237 | + return bodyweightObservation; |
| 238 | +} |
| 239 | + |
| 240 | +function createBundle(patient: Patient, observation: Observation, bodyweightObs: Observation): Bundle { |
| 241 | + const patientEntry: BundleEntry = { |
| 242 | + fullUrl: `urn:uuid:${patient.id}`, |
| 243 | + resource: patient, |
| 244 | + }; |
| 245 | + |
| 246 | + const observationEntry: BundleEntry = { |
| 247 | + fullUrl: `urn:uuid:${observation.id}`, |
| 248 | + resource: observation, |
| 249 | + }; |
| 250 | + |
| 251 | + const bodyweightEntry: BundleEntry = { |
| 252 | + fullUrl: `urn:uuid:${bodyweightObs.id}`, |
| 253 | + resource: bodyweightObs, |
| 254 | + }; |
| 255 | + |
| 256 | + const bundle: Bundle = { |
| 257 | + resourceType: "Bundle", |
| 258 | + id: "bundle-1", |
| 259 | + type: "collection", |
| 260 | + entry: [patientEntry, observationEntry, bodyweightEntry], |
| 261 | + }; |
| 262 | + |
| 263 | + return bundle; |
| 264 | +} |
| 265 | + |
| 266 | +function runDemo() { |
| 267 | + console.log("=".repeat(60)); |
| 268 | + console.log("Creating FHIR R4 resources demo with bodyweight profile..."); |
| 269 | + console.log("=".repeat(60)); |
| 270 | + |
| 271 | + const patient = createPatient(); |
| 272 | + console.log("✓ Created patient:", patient.id); |
| 273 | + |
| 274 | + const observation = createObservation(patient.id!); |
| 275 | + console.log("✓ Created glucose observation:", observation.id); |
| 276 | + |
| 277 | + console.log(`\n${"=".repeat(60)}`); |
| 278 | + console.log("Bodyweight profile attach/extract demo:"); |
| 279 | + console.log("=".repeat(60)); |
| 280 | + |
| 281 | + const bodyweightObs = createBodyWeightObservation(patient.id!); |
| 282 | + console.log("✓ Created body weight observation with profile:", bodyweightObs.id); |
| 283 | + |
| 284 | + const bundle = createBundle(patient, observation, bodyweightObs); |
| 285 | + console.log("\n✓ Created bundle with", bundle.entry?.length, "resources"); |
| 286 | + |
| 287 | + console.log(`\n${"=".repeat(60)}`); |
| 288 | + console.log("Final Bundle JSON:"); |
| 289 | + console.log("=".repeat(60)); |
| 290 | + console.log(JSON.stringify(bundle, null, 2)); |
| 291 | +} |
| 292 | + |
| 293 | +runDemo(); |
0 commit comments