|
1 | | - |
2 | 1 | # This file holds the schema/base layout that maps FHIR fields to flat JSON fields |
3 | 2 | # Each entry tells the converter how to extract and transform a specific value |
| 3 | +EXTENSION_URL_VACCINATION_PRODEDURE = "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure" |
| 4 | +EXTENSION_URL_SCT_DESC_DISPLAY = "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-CodingSCTDescDisplay" |
| 5 | + |
| 6 | +CODING_SYSTEM_URL_SNOMED = "http://snomed.info/sct" |
| 7 | + |
| 8 | + |
| 9 | +def _extract_vaccination_procedure_code(immunization) -> str: |
| 10 | + extensions = immunization.get("extension", []) |
| 11 | + for ext in extensions: |
| 12 | + if ext.get("url") == EXTENSION_URL_VACCINATION_PRODEDURE: |
| 13 | + value_cc = ext.get("valueCodeableConcept", {}) |
| 14 | + return _get_first_snomed_code(value_cc) |
| 15 | + return "" |
| 16 | + |
| 17 | + |
| 18 | +def _extract_vaccine_product_code(immunization) -> str: |
| 19 | + vaccine_code = immunization.get("vaccineCode", {}) |
| 20 | + return _get_first_snomed_code(vaccine_code) |
| 21 | + |
| 22 | + |
| 23 | +# Could be merged with smt |
| 24 | +def _extract_site_of_vaccination_code(immunization) -> str: |
| 25 | + site = immunization.get("site", {}) |
| 26 | + return _get_first_snomed_code(site) |
| 27 | + |
| 28 | + |
| 29 | +def _extract_route_of_vaccination_code(immunization) -> str: |
| 30 | + route = immunization.get("route", {}) |
| 31 | + return _get_first_snomed_code(route) |
| 32 | + |
| 33 | + |
| 34 | +def _extract_indication_code(immunization) -> str: |
| 35 | + for reason in immunization.get("reasonCode", []): |
| 36 | + codings = reason.get("coding", []) |
| 37 | + for coding in codings: |
| 38 | + if coding.get("system") == CODING_SYSTEM_URL_SNOMED: |
| 39 | + return coding.get("code", "") |
| 40 | + return "" |
| 41 | + |
| 42 | + |
| 43 | +def _extract_dose_unit_code(immunization) -> str: |
| 44 | + dose_quantity = immunization.get("doseQuantity", {}) |
| 45 | + if dose_quantity.get("system") == CODING_SYSTEM_URL_SNOMED and dose_quantity.get("code"): |
| 46 | + return dose_quantity.get("code") |
| 47 | + return "" |
| 48 | + |
| 49 | +def _extract_dose_unit_term(immunization) -> str: |
| 50 | + dose_quantity = immunization.get("doseQuantity", {}) |
| 51 | + return dose_quantity.get("unit", "") |
| 52 | + |
| 53 | +def _get_first_snomed_code(coding_container: dict) -> str: |
| 54 | + codings = coding_container.get("coding", []) |
| 55 | + for coding in codings: |
| 56 | + if coding.get("system") == CODING_SYSTEM_URL_SNOMED: |
| 57 | + return coding.get("code", "") |
| 58 | + return "" |
| 59 | + |
| 60 | +def _get_term_from_codeable_concept(concept: dict) -> str: |
| 61 | + if concept.get("text"): |
| 62 | + return concept["text"] |
| 63 | + |
| 64 | + codings = concept.get("coding", []) |
| 65 | + for coding in codings: |
| 66 | + if coding.get("system") == CODING_SYSTEM_URL_SNOMED: |
| 67 | + # Try SCTDescDisplay extension first |
| 68 | + for ext in coding.get("extension", []): |
| 69 | + if ext.get("url") == EXTENSION_URL_SCT_DESC_DISPLAY: |
| 70 | + value_string = ext.get("valueString") |
| 71 | + if value_string: |
| 72 | + return value_string |
4 | 73 |
|
| 74 | + # Fallback to display |
| 75 | + return coding.get("display", "") |
| 76 | + |
| 77 | + return "" |
| 78 | + |
| 79 | +def _extract_vaccination_procedure_term(immunization) -> str: |
| 80 | + extensions = immunization.get("extension", []) |
| 81 | + for ext in extensions: |
| 82 | + if ext.get("url") == EXTENSION_URL_VACCINATION_PRODEDURE: |
| 83 | + return _get_term_from_codeable_concept(ext.get("valueCodeableConcept", {})) |
| 84 | + return "" |
| 85 | + |
| 86 | +def _extract_vaccine_product_term(immunization) -> str: |
| 87 | + return _get_term_from_codeable_concept(immunization.get("vaccineCode", {})) |
| 88 | + |
| 89 | +def _extract_site_of_vaccination_term(immunization) -> str: |
| 90 | + return _get_term_from_codeable_concept(immunization.get("site", {})) |
| 91 | + |
| 92 | +def _extract_route_of_vaccination_term(immunization) -> str: |
| 93 | + return _get_term_from_codeable_concept(immunization.get("route", {})) |
| 94 | + |
| 95 | +# TBC |
| 96 | +# - requirements: If element doseNumberPositiveInt exists and is < 10 then populate as received, else null |
| 97 | +# - path : protocolApplied.doseNumber[x] |
| 98 | +def _extract_dose_sequence(immunization) -> str: |
| 99 | + protocol_applied = immunization.get("protocolApplied", []) |
| 100 | + |
| 101 | + if protocol_applied: |
| 102 | + dose = protocol_applied[0].get("doseNumberPositiveInt", None) |
| 103 | + return str(dose) if dose else "" |
| 104 | + return "" |
| 105 | + |
5 | 106 | ConvertLayout = { |
6 | 107 | "id": "7d78e9a6-d859-45d3-bb05-df9c405acbdb", |
7 | 108 | "schemaName": "JSON Base", |
|
157 | 258 | "fieldNameFlat": "VACCINATION_PROCEDURE_CODE", |
158 | 259 | "expression": { |
159 | 260 | "expressionName": "Not Empty", |
160 | | - "expressionType": "SNOMED", |
161 | | - "expressionRule": "" |
| 261 | + "expressionType": "NORMAL", |
| 262 | + "expressionRule": _extract_vaccination_procedure_code |
162 | 263 | } |
163 | 264 | }, |
164 | 265 | { |
165 | 266 | "fieldNameFHIR": "extension|0|valueCodeableConcept|coding|0|display", |
166 | 267 | "fieldNameFlat": "VACCINATION_PROCEDURE_TERM", |
167 | 268 | "expression": { |
168 | 269 | "expressionName": "Not Empty", |
169 | | - "expressionType": "NOTEMPTY", |
170 | | - "expressionRule": "" |
| 270 | + "expressionType": "NORMAL", |
| 271 | + "expressionRule": _extract_vaccination_procedure_term |
171 | 272 | } |
172 | 273 | }, |
173 | 274 | { |
174 | 275 | "fieldNameFHIR": "protocolApplied|0|doseNumberPositiveInt", |
175 | 276 | "fieldNameFlat": "DOSE_SEQUENCE", |
176 | 277 | "expression": { |
177 | 278 | "expressionName": "Not Empty", |
178 | | - "expressionType": "DOSESEQUENCE", |
179 | | - "expressionRule": "" |
| 279 | + "expressionType": "NORMAL", |
| 280 | + "expressionRule": _extract_dose_sequence |
180 | 281 | } |
181 | 282 | }, |
182 | 283 | { |
183 | 284 | "fieldNameFHIR": "vaccineCode|coding|#:http://snomed.info/sct|code", |
184 | 285 | "fieldNameFlat": "VACCINE_PRODUCT_CODE", |
185 | 286 | "expression": { |
186 | 287 | "expressionName": "Not Empty", |
187 | | - "expressionType": "SNOMED", |
188 | | - "expressionRule": "" |
| 288 | + "expressionType": "NORMAL", |
| 289 | + "expressionRule": _extract_vaccine_product_code |
189 | 290 | } |
190 | 291 | }, |
191 | 292 | { |
192 | 293 | "fieldNameFHIR": "vaccineCode|coding|#:http://snomed.info/sct|display", |
193 | 294 | "fieldNameFlat": "VACCINE_PRODUCT_TERM", |
194 | 295 | "expression": { |
195 | 296 | "expressionName": "Not Empty", |
196 | | - "expressionType": "NOTEMPTY", |
197 | | - "expressionRule": "" |
| 297 | + "expressionType": "NORMAL", |
| 298 | + "expressionRule": _extract_vaccine_product_term |
198 | 299 | } |
199 | 300 | }, |
200 | 301 | { |
|
229 | 330 | "fieldNameFlat": "SITE_OF_VACCINATION_CODE", |
230 | 331 | "expression": { |
231 | 332 | "expressionName": "Not Empty", |
232 | | - "expressionType": "SNOMED", |
233 | | - "expressionRule": "" |
| 333 | + "expressionType": "NORMAL", |
| 334 | + "expressionRule": _extract_site_of_vaccination_code |
234 | 335 | } |
235 | 336 | }, |
236 | 337 | { |
237 | 338 | "fieldNameFHIR": "site|coding|#:http://snomed.info/sct|display", |
238 | 339 | "fieldNameFlat": "SITE_OF_VACCINATION_TERM", |
239 | 340 | "expression": { |
240 | 341 | "expressionName": "Look Up", |
241 | | - "expressionType": "LOOKUP", |
242 | | - "expressionRule": "site|coding|#:http://snomed.info/sct|code" |
| 342 | + "expressionType": "NORMAL", |
| 343 | + "expressionRule": _extract_site_of_vaccination_term |
243 | 344 | } |
244 | 345 | }, |
245 | 346 | { |
246 | 347 | "fieldNameFHIR": "route|coding|#:http://snomed.info/sct|code", |
247 | 348 | "fieldNameFlat": "ROUTE_OF_VACCINATION_CODE", |
248 | 349 | "expression": { |
249 | 350 | "expressionName": "Not Empty", |
250 | | - "expressionType": "SNOMED", |
251 | | - "expressionRule": "" |
| 351 | + "expressionType": "NORMAL", |
| 352 | + "expressionRule": _extract_route_of_vaccination_code |
252 | 353 | } |
253 | 354 | }, |
254 | 355 | { |
255 | 356 | "fieldNameFHIR": "route|coding|#:http://snomed.info/sct|display", |
256 | 357 | "fieldNameFlat": "ROUTE_OF_VACCINATION_TERM", |
257 | 358 | "expression": { |
258 | 359 | "expressionName": "Look Up", |
259 | | - "expressionType": "LOOKUP", |
260 | | - "expressionRule": "route|coding|#:http://snomed.info/sct|code" |
| 360 | + "expressionType": "NORMAL", |
| 361 | + "expressionRule": _extract_route_of_vaccination_term |
261 | 362 | } |
262 | 363 | }, |
263 | 364 | { |
|
274 | 375 | "fieldNameFlat": "DOSE_UNIT_CODE", |
275 | 376 | "expression": { |
276 | 377 | "expressionName": "Only If", |
277 | | - "expressionType": "ONLYIF", |
278 | | - "expressionRule": "doseQuantity|system|http://snomed.info/sct" |
| 378 | + "expressionType": "NORMAL", |
| 379 | + "expressionRule": _extract_dose_unit_code |
279 | 380 | } |
280 | 381 | }, |
281 | 382 | { |
282 | 383 | "fieldNameFHIR": "doseQuantity|unit", |
283 | 384 | "fieldNameFlat": "DOSE_UNIT_TERM", |
284 | 385 | "expression": { |
285 | 386 | "expressionName": "Not Empty", |
286 | | - "expressionType": "NOTEMPTY", |
287 | | - "expressionRule": "" |
| 387 | + "expressionType": "NORMAL", |
| 388 | + "expressionRule": _extract_dose_unit_term |
288 | 389 | } |
289 | 390 | }, |
290 | 391 | { |
291 | 392 | "fieldNameFHIR": "reasonCode|#:http://snomed.info/sct|coding|#:http://snomed.info/sct|code", |
292 | 393 | "fieldNameFlat": "INDICATION_CODE", |
293 | 394 | "expression": { |
294 | 395 | "expressionName": "Not Empty", |
295 | | - "expressionType": "SNOMED", |
296 | | - "expressionRule": "" |
| 396 | + "expressionType": "NORMAL", |
| 397 | + "expressionRule": _extract_indication_code |
297 | 398 | } |
298 | 399 | }, |
299 | 400 | { |
|
0 commit comments