@the-cascade-protocol/cli 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/commands/convert.d.ts +8 -5
  2. package/dist/commands/convert.d.ts.map +1 -1
  3. package/dist/commands/convert.js +51 -7
  4. package/dist/commands/convert.js.map +1 -1
  5. package/dist/commands/pod/helpers.d.ts +2 -0
  6. package/dist/commands/pod/helpers.d.ts.map +1 -1
  7. package/dist/commands/pod/helpers.js +62 -1
  8. package/dist/commands/pod/helpers.js.map +1 -1
  9. package/dist/commands/pod/import.d.ts +20 -0
  10. package/dist/commands/pod/import.d.ts.map +1 -0
  11. package/dist/commands/pod/import.js +382 -0
  12. package/dist/commands/pod/import.js.map +1 -0
  13. package/dist/commands/pod/index.d.ts.map +1 -1
  14. package/dist/commands/pod/index.js +2 -0
  15. package/dist/commands/pod/index.js.map +1 -1
  16. package/dist/commands/pod/query.d.ts.map +1 -1
  17. package/dist/commands/pod/query.js +34 -1
  18. package/dist/commands/pod/query.js.map +1 -1
  19. package/dist/commands/reconcile.d.ts.map +1 -1
  20. package/dist/commands/reconcile.js +22 -396
  21. package/dist/commands/reconcile.js.map +1 -1
  22. package/dist/lib/fhir-converter/cascade-to-fhir-admin.d.ts +13 -0
  23. package/dist/lib/fhir-converter/cascade-to-fhir-admin.d.ts.map +1 -0
  24. package/dist/lib/fhir-converter/cascade-to-fhir-admin.js +70 -0
  25. package/dist/lib/fhir-converter/cascade-to-fhir-admin.js.map +1 -0
  26. package/dist/lib/fhir-converter/cascade-to-fhir-clinical.d.ts +36 -0
  27. package/dist/lib/fhir-converter/cascade-to-fhir-clinical.d.ts.map +1 -0
  28. package/dist/lib/fhir-converter/cascade-to-fhir-clinical.js +407 -0
  29. package/dist/lib/fhir-converter/cascade-to-fhir-clinical.js.map +1 -0
  30. package/dist/lib/fhir-converter/cascade-to-fhir-demographics.d.ts +15 -0
  31. package/dist/lib/fhir-converter/cascade-to-fhir-demographics.d.ts.map +1 -0
  32. package/dist/lib/fhir-converter/cascade-to-fhir-demographics.js +115 -0
  33. package/dist/lib/fhir-converter/cascade-to-fhir-demographics.js.map +1 -0
  34. package/dist/lib/fhir-converter/cascade-to-fhir.d.ts +9 -3
  35. package/dist/lib/fhir-converter/cascade-to-fhir.d.ts.map +1 -1
  36. package/dist/lib/fhir-converter/cascade-to-fhir.js +69 -290
  37. package/dist/lib/fhir-converter/cascade-to-fhir.js.map +1 -1
  38. package/dist/lib/fhir-converter/converters-clinical-admin.d.ts +16 -0
  39. package/dist/lib/fhir-converter/converters-clinical-admin.d.ts.map +1 -0
  40. package/dist/lib/fhir-converter/converters-clinical-admin.js +166 -0
  41. package/dist/lib/fhir-converter/converters-clinical-admin.js.map +1 -0
  42. package/dist/lib/fhir-converter/converters-clinical.d.ts +28 -0
  43. package/dist/lib/fhir-converter/converters-clinical.d.ts.map +1 -1
  44. package/dist/lib/fhir-converter/converters-clinical.js +436 -11
  45. package/dist/lib/fhir-converter/converters-clinical.js.map +1 -1
  46. package/dist/lib/fhir-converter/converters-demographics.d.ts.map +1 -1
  47. package/dist/lib/fhir-converter/converters-demographics.js +4 -5
  48. package/dist/lib/fhir-converter/converters-demographics.js.map +1 -1
  49. package/dist/lib/fhir-converter/converters-passthrough.d.ts +29 -0
  50. package/dist/lib/fhir-converter/converters-passthrough.d.ts.map +1 -0
  51. package/dist/lib/fhir-converter/converters-passthrough.js +86 -0
  52. package/dist/lib/fhir-converter/converters-passthrough.js.map +1 -0
  53. package/dist/lib/fhir-converter/fhir-to-cascade.d.ts +8 -4
  54. package/dist/lib/fhir-converter/fhir-to-cascade.d.ts.map +1 -1
  55. package/dist/lib/fhir-converter/fhir-to-cascade.js +41 -10
  56. package/dist/lib/fhir-converter/fhir-to-cascade.js.map +1 -1
  57. package/dist/lib/fhir-converter/import-manifest.d.ts +40 -0
  58. package/dist/lib/fhir-converter/import-manifest.d.ts.map +1 -0
  59. package/dist/lib/fhir-converter/import-manifest.js +66 -0
  60. package/dist/lib/fhir-converter/import-manifest.js.map +1 -0
  61. package/dist/lib/fhir-converter/index.d.ts +33 -13
  62. package/dist/lib/fhir-converter/index.d.ts.map +1 -1
  63. package/dist/lib/fhir-converter/index.js +38 -17
  64. package/dist/lib/fhir-converter/index.js.map +1 -1
  65. package/dist/lib/fhir-converter/types.d.ts +12 -1
  66. package/dist/lib/fhir-converter/types.d.ts.map +1 -1
  67. package/dist/lib/fhir-converter/types.js +67 -2
  68. package/dist/lib/fhir-converter/types.js.map +1 -1
  69. package/dist/lib/output.d.ts +5 -0
  70. package/dist/lib/output.d.ts.map +1 -1
  71. package/dist/lib/output.js +6 -1
  72. package/dist/lib/output.js.map +1 -1
  73. package/dist/lib/reconciler.d.ts +45 -0
  74. package/dist/lib/reconciler.d.ts.map +1 -0
  75. package/dist/lib/reconciler.js +397 -0
  76. package/dist/lib/reconciler.js.map +1 -0
  77. package/package.json +1 -1
@@ -7,6 +7,13 @@
7
7
  * - AllergyIntolerance -> health:AllergyRecord
8
8
  * - Observation (lab) -> health:LabResultRecord
9
9
  * - Observation (vital) -> clinical:VitalSign
10
+ * - Procedure -> clinical:Procedure
11
+ * - DocumentReference -> clinical:ClinicalDocument
12
+ * - Encounter -> clinical:Encounter
13
+ * - DiagnosticReport -> clinical:LaboratoryReport
14
+ * - MedicationAdministration -> clinical:MedicationAdministration
15
+ * - Device -> clinical:ImplantedDevice
16
+ * - ImagingStudy -> clinical:ImagingStudy
10
17
  */
11
18
  import type { Quad } from 'n3';
12
19
  import { type ConversionResult } from './types.js';
@@ -26,4 +33,25 @@ export declare function convertObservationLab(resource: any): ConversionResult &
26
33
  export declare function convertObservationVital(resource: any): ConversionResult & {
27
34
  _quads: Quad[];
28
35
  };
36
+ export declare function convertProcedure(resource: any): ConversionResult & {
37
+ _quads: Quad[];
38
+ };
39
+ export declare function convertClinicalDocument(resource: any): ConversionResult & {
40
+ _quads: Quad[];
41
+ };
42
+ export declare function convertEncounter(resource: any): ConversionResult & {
43
+ _quads: Quad[];
44
+ };
45
+ export declare function convertLaboratoryReport(resource: any): ConversionResult & {
46
+ _quads: Quad[];
47
+ };
48
+ export declare function convertMedicationAdministration(resource: any): ConversionResult & {
49
+ _quads: Quad[];
50
+ };
51
+ export declare function convertDevice(resource: any): ConversionResult & {
52
+ _quads: Quad[];
53
+ };
54
+ export declare function convertImagingStudy(resource: any): ConversionResult & {
55
+ _quads: Quad[];
56
+ };
29
57
  //# sourceMappingURL=converters-clinical.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"converters-clinical.d.ts","sourceRoot":"","sources":["../../../src/lib/fhir-converter/converters-clinical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,EACL,KAAK,gBAAgB,EAetB,MAAM,YAAY,CAAC;AAMpB,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CA2F/F;AAMD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAqDrF;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAwD9F;AAMD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAe7D;AAMD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CA4F1F;AAMD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAwE5F"}
1
+ {"version":3,"file":"converters-clinical.d.ts","sourceRoot":"","sources":["../../../src/lib/fhir-converter/converters-clinical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,EACL,KAAK,gBAAgB,EAkBtB,MAAM,YAAY,CAAC;AAMpB,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CA2F/F;AAMD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAqDrF;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAwD9F;AAMD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAoB7D;AAMD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAiH1F;AAMD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAiG5F;AAMD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAgDrF;AAMD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAgD5F;AAMD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAsErF;AAMD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CA0E5F;AAMD,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAmDpG;AAMD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CA+ClF;AAMD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB,GAAG;IAAE,MAAM,EAAE,IAAI,EAAE,CAAA;CAAE,CAuDxF"}
@@ -7,15 +7,21 @@
7
7
  * - AllergyIntolerance -> health:AllergyRecord
8
8
  * - Observation (lab) -> health:LabResultRecord
9
9
  * - Observation (vital) -> clinical:VitalSign
10
+ * - Procedure -> clinical:Procedure
11
+ * - DocumentReference -> clinical:ClinicalDocument
12
+ * - Encounter -> clinical:Encounter
13
+ * - DiagnosticReport -> clinical:LaboratoryReport
14
+ * - MedicationAdministration -> clinical:MedicationAdministration
15
+ * - Device -> clinical:ImplantedDevice
16
+ * - ImagingStudy -> clinical:ImagingStudy
10
17
  */
11
- import { randomUUID } from 'node:crypto';
12
- import { NS, CODING_SYSTEM_MAP, VITAL_LOINC_CODES, VITAL_CATEGORIES, extractCodings, codeableConceptText, tripleStr, tripleBool, tripleDouble, tripleRef, tripleType, tripleDateTime, commonTriples, quadsToJsonLd, } from './types.js';
18
+ import { NS, CODING_SYSTEM_MAP, VITAL_LOINC_CODES, VITAL_CATEGORIES, isLoincSystem, extractCodings, codeableConceptText, tripleStr, tripleBool, tripleDouble, tripleRef, tripleType, tripleDateTime, tripleTyped, commonTriples, quadsToJsonLd, mintSubjectUri, } from './types.js';
13
19
  // ---------------------------------------------------------------------------
14
20
  // Medication converter
15
21
  // ---------------------------------------------------------------------------
16
22
  export function convertMedicationStatement(resource) {
17
23
  const warnings = [];
18
- const subjectUri = `urn:uuid:${randomUUID()}`;
24
+ const subjectUri = mintSubjectUri(resource);
19
25
  const quads = [];
20
26
  quads.push(tripleType(subjectUri, NS.health + 'MedicationRecord'));
21
27
  quads.push(...commonTriples(subjectUri));
@@ -105,7 +111,7 @@ export function convertMedicationStatement(resource) {
105
111
  // ---------------------------------------------------------------------------
106
112
  export function convertCondition(resource) {
107
113
  const warnings = [];
108
- const subjectUri = `urn:uuid:${randomUUID()}`;
114
+ const subjectUri = mintSubjectUri(resource);
109
115
  const quads = [];
110
116
  quads.push(tripleType(subjectUri, NS.health + 'ConditionRecord'));
111
117
  quads.push(...commonTriples(subjectUri));
@@ -157,7 +163,7 @@ export function convertCondition(resource) {
157
163
  // ---------------------------------------------------------------------------
158
164
  export function convertAllergyIntolerance(resource) {
159
165
  const warnings = [];
160
- const subjectUri = `urn:uuid:${randomUUID()}`;
166
+ const subjectUri = mintSubjectUri(resource);
161
167
  const quads = [];
162
168
  quads.push(tripleType(subjectUri, NS.health + 'AllergyRecord'));
163
169
  quads.push(...commonTriples(subjectUri));
@@ -210,17 +216,23 @@ export function convertAllergyIntolerance(resource) {
210
216
  export function isVitalSignObservation(resource) {
211
217
  if (Array.isArray(resource.category)) {
212
218
  for (const cat of resource.category) {
219
+ // Structured coding check
213
220
  if (Array.isArray(cat.coding)) {
214
221
  for (const c of cat.coding) {
215
222
  if (VITAL_CATEGORIES.includes(c.code))
216
223
  return true;
217
224
  }
218
225
  }
226
+ // Text fallback — some systems only populate category.text
227
+ if (typeof cat.text === 'string' && /vital/i.test(cat.text))
228
+ return true;
219
229
  }
220
230
  }
231
+ // LOINC code fallback — catches observations with no/wrong category
232
+ // Accepts all known LOINC system URL variants (http, https, OID, trailing slash)
221
233
  const codings = extractCodings(resource.code);
222
234
  for (const c of codings) {
223
- if (c.system === 'http://loinc.org' && VITAL_LOINC_CODES[c.code])
235
+ if (isLoincSystem(c.system) && VITAL_LOINC_CODES[c.code])
224
236
  return true;
225
237
  }
226
238
  return false;
@@ -230,7 +242,7 @@ export function isVitalSignObservation(resource) {
230
242
  // ---------------------------------------------------------------------------
231
243
  export function convertObservationLab(resource) {
232
244
  const warnings = [];
233
- const subjectUri = `urn:uuid:${randomUUID()}`;
245
+ const subjectUri = mintSubjectUri(resource);
234
246
  const quads = [];
235
247
  quads.push(tripleType(subjectUri, NS.health + 'LabResultRecord'));
236
248
  quads.push(...commonTriples(subjectUri));
@@ -249,6 +261,29 @@ export function convertObservationLab(resource) {
249
261
  const valText = codeableConceptText(resource.valueCodeableConcept) ?? '';
250
262
  quads.push(tripleStr(subjectUri, NS.health + 'resultValue', valText));
251
263
  }
264
+ else if (Array.isArray(resource.component) && resource.component.length > 0) {
265
+ // Panel-style observation (e.g., PRAPARE survey, multi-question assessments):
266
+ // serialize component question/answer pairs into a single resultValue string.
267
+ const parts = [];
268
+ for (const comp of resource.component) {
269
+ const question = codeableConceptText(comp.code);
270
+ if (!question)
271
+ continue;
272
+ const answer = (comp.valueCodeableConcept ? codeableConceptText(comp.valueCodeableConcept) : undefined) ??
273
+ comp.valueString ??
274
+ (comp.valueQuantity !== undefined ? `${comp.valueQuantity.value} ${comp.valueQuantity.unit ?? ''}`.trim() : undefined);
275
+ if (answer !== undefined) {
276
+ parts.push(`${question}: ${answer}`);
277
+ }
278
+ }
279
+ if (parts.length > 0) {
280
+ quads.push(tripleStr(subjectUri, NS.health + 'resultValue', parts.join('; ')));
281
+ }
282
+ else {
283
+ quads.push(tripleStr(subjectUri, NS.health + 'resultValue', ''));
284
+ warnings.push('No result value found in Observation resource');
285
+ }
286
+ }
252
287
  else {
253
288
  quads.push(tripleStr(subjectUri, NS.health + 'resultValue', ''));
254
289
  warnings.push('No result value found in Observation resource');
@@ -324,14 +359,14 @@ export function convertObservationLab(resource) {
324
359
  // ---------------------------------------------------------------------------
325
360
  export function convertObservationVital(resource) {
326
361
  const warnings = [];
327
- const subjectUri = `urn:uuid:${randomUUID()}`;
362
+ const subjectUri = mintSubjectUri(resource);
328
363
  const quads = [];
329
364
  quads.push(tripleType(subjectUri, NS.clinical + 'VitalSign'));
330
365
  quads.push(...commonTriples(subjectUri));
331
366
  const codings = extractCodings(resource.code);
332
367
  let vitalInfo;
333
368
  for (const c of codings) {
334
- if (c.system === 'http://loinc.org' && VITAL_LOINC_CODES[c.code]) {
369
+ if (isLoincSystem(c.system) && VITAL_LOINC_CODES[c.code]) {
335
370
  vitalInfo = VITAL_LOINC_CODES[c.code];
336
371
  quads.push(tripleRef(subjectUri, NS.clinical + 'loincCode', NS.loinc + c.code));
337
372
  break;
@@ -343,15 +378,42 @@ export function convertObservationVital(resource) {
343
378
  quads.push(tripleRef(subjectUri, NS.clinical + 'snomedCode', NS.sct + vitalInfo.snomedCode));
344
379
  }
345
380
  else {
381
+ // No LOINC match in the map — fall back to a slug derived from the display name.
382
+ // Data is fully preserved (value + unit + name); we just lack a canonical vitalType enum.
346
383
  const name = codeableConceptText(resource.code) ?? 'Unknown Vital';
347
- quads.push(tripleStr(subjectUri, NS.clinical + 'vitalType', name.toLowerCase().replace(/\s+/g, '_')));
384
+ quads.push(tripleStr(subjectUri, NS.clinical + 'vitalType', name.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '')));
348
385
  quads.push(tripleStr(subjectUri, NS.clinical + 'vitalTypeName', name));
349
- warnings.push(`Unknown vital sign LOINC code -- using display name: ${name}`);
350
386
  }
351
387
  if (resource.valueQuantity) {
352
388
  quads.push(tripleDouble(subjectUri, NS.clinical + 'value', resource.valueQuantity.value));
353
389
  quads.push(tripleStr(subjectUri, NS.clinical + 'unit', resource.valueQuantity.unit ?? vitalInfo?.unit ?? ''));
354
390
  }
391
+ else if (Array.isArray(resource.component) && resource.component.length > 0) {
392
+ // Panel-style vital (e.g., blood pressure panel LOINC 55284-4): component children
393
+ // hold the individual readings. Emit each known component as a typed value predicate,
394
+ // e.g., clinical:bloodPressureSystolicValue / clinical:bloodPressureDiastolicValue.
395
+ let emittedCount = 0;
396
+ for (const comp of resource.component) {
397
+ if (!comp.valueQuantity)
398
+ continue;
399
+ const compCodings = extractCodings(comp.code);
400
+ let compInfo;
401
+ for (const c of compCodings) {
402
+ if (isLoincSystem(c.system) && VITAL_LOINC_CODES[c.code]) {
403
+ compInfo = VITAL_LOINC_CODES[c.code];
404
+ break;
405
+ }
406
+ }
407
+ if (compInfo) {
408
+ quads.push(tripleDouble(subjectUri, NS.clinical + compInfo.type + 'Value', comp.valueQuantity.value));
409
+ quads.push(tripleStr(subjectUri, NS.clinical + compInfo.type + 'Unit', comp.valueQuantity.unit ?? compInfo.unit ?? ''));
410
+ emittedCount++;
411
+ }
412
+ }
413
+ if (emittedCount === 0) {
414
+ warnings.push('No valueQuantity found in vital sign Observation');
415
+ }
416
+ }
355
417
  else {
356
418
  warnings.push('No valueQuantity found in vital sign Observation');
357
419
  }
@@ -388,4 +450,367 @@ export function convertObservationVital(resource) {
388
450
  _quads: quads,
389
451
  };
390
452
  }
453
+ // ---------------------------------------------------------------------------
454
+ // Procedure converter (B1)
455
+ // ---------------------------------------------------------------------------
456
+ export function convertProcedure(resource) {
457
+ const warnings = [];
458
+ const subjectUri = mintSubjectUri(resource);
459
+ const quads = [];
460
+ quads.push(tripleType(subjectUri, NS.clinical + 'Procedure'));
461
+ quads.push(...commonTriples(subjectUri));
462
+ // Procedure name
463
+ const name = codeableConceptText(resource.code) ?? 'Unknown Procedure';
464
+ quads.push(tripleStr(subjectUri, NS.clinical + 'procedureName', name));
465
+ // Procedure codes
466
+ const codings = extractCodings(resource.code);
467
+ for (const c of codings) {
468
+ if (c.system === 'http://snomed.info/sct') {
469
+ quads.push(tripleRef(subjectUri, NS.clinical + 'procedureSnomedCode', NS.sct + c.code));
470
+ }
471
+ else if (c.system === 'http://www.ama-assn.org/go/cpt' || c.system.includes('cpt')) {
472
+ quads.push(tripleStr(subjectUri, NS.clinical + 'cptCode', c.code));
473
+ }
474
+ }
475
+ // performedDate -- use performedDateTime first, fall back to performedPeriod.start
476
+ const performedDate = resource.performedDateTime ?? resource.performedPeriod?.start;
477
+ if (performedDate) {
478
+ quads.push(tripleDateTime(subjectUri, NS.clinical + 'performedDate', performedDate));
479
+ }
480
+ // Status
481
+ if (resource.status) {
482
+ quads.push(tripleStr(subjectUri, NS.clinical + 'procedureStatus', resource.status));
483
+ }
484
+ // Source record ID
485
+ if (resource.id) {
486
+ quads.push(tripleStr(subjectUri, NS.clinical + 'sourceRecordId', resource.id));
487
+ }
488
+ quads.push(tripleRef(subjectUri, NS.cascade + 'layerPromotionStatus', NS.cascade + 'FullyMapped'));
489
+ return {
490
+ turtle: '',
491
+ jsonld: quadsToJsonLd(quads, 'clinical:Procedure'),
492
+ warnings,
493
+ resourceType: 'Procedure',
494
+ cascadeType: 'clinical:Procedure',
495
+ _quads: quads,
496
+ };
497
+ }
498
+ // ---------------------------------------------------------------------------
499
+ // ClinicalDocument converter (B1)
500
+ // ---------------------------------------------------------------------------
501
+ export function convertClinicalDocument(resource) {
502
+ const warnings = [];
503
+ const subjectUri = mintSubjectUri(resource);
504
+ const quads = [];
505
+ quads.push(tripleType(subjectUri, NS.clinical + 'ClinicalDocument'));
506
+ quads.push(...commonTriples(subjectUri));
507
+ // Document type
508
+ const docType = codeableConceptText(resource.type) ?? 'Unknown Document';
509
+ quads.push(tripleStr(subjectUri, NS.clinical + 'documentType', docType));
510
+ // Date
511
+ const docDate = resource.date ?? resource.indexed;
512
+ if (docDate) {
513
+ quads.push(tripleDateTime(subjectUri, NS.clinical + 'documentDate', docDate));
514
+ }
515
+ // Content type and URL from first attachment
516
+ if (Array.isArray(resource.content) && resource.content.length > 0) {
517
+ const attachment = resource.content[0]?.attachment;
518
+ if (attachment) {
519
+ if (attachment.contentType) {
520
+ quads.push(tripleStr(subjectUri, NS.clinical + 'contentType', attachment.contentType));
521
+ }
522
+ if (attachment.url) {
523
+ quads.push(tripleStr(subjectUri, NS.clinical + 'documentUrl', attachment.url));
524
+ }
525
+ if (attachment.title) {
526
+ quads.push(tripleStr(subjectUri, NS.clinical + 'documentTitle', attachment.title));
527
+ }
528
+ }
529
+ }
530
+ if (resource.id) {
531
+ quads.push(tripleStr(subjectUri, NS.clinical + 'sourceRecordId', resource.id));
532
+ }
533
+ quads.push(tripleRef(subjectUri, NS.cascade + 'layerPromotionStatus', NS.cascade + 'FullyMapped'));
534
+ return {
535
+ turtle: '',
536
+ jsonld: quadsToJsonLd(quads, 'clinical:ClinicalDocument'),
537
+ warnings,
538
+ resourceType: 'DocumentReference',
539
+ cascadeType: 'clinical:ClinicalDocument',
540
+ _quads: quads,
541
+ };
542
+ }
543
+ // ---------------------------------------------------------------------------
544
+ // Encounter converter (B2)
545
+ // ---------------------------------------------------------------------------
546
+ export function convertEncounter(resource) {
547
+ const warnings = [];
548
+ const subjectUri = mintSubjectUri(resource);
549
+ const quads = [];
550
+ quads.push(tripleType(subjectUri, NS.clinical + 'Encounter'));
551
+ quads.push(...commonTriples(subjectUri));
552
+ // Encounter class (ambulatory, emergency, inpatient, etc.)
553
+ const encounterClass = resource.class?.code ?? resource.class?.coding?.[0]?.code;
554
+ if (encounterClass) {
555
+ quads.push(tripleStr(subjectUri, NS.clinical + 'encounterClass', encounterClass));
556
+ }
557
+ // Status
558
+ if (resource.status) {
559
+ quads.push(tripleStr(subjectUri, NS.clinical + 'encounterStatus', resource.status));
560
+ }
561
+ // Encounter type (from type[0])
562
+ if (Array.isArray(resource.type) && resource.type.length > 0) {
563
+ const typeText = codeableConceptText(resource.type[0]);
564
+ if (typeText) {
565
+ quads.push(tripleStr(subjectUri, NS.clinical + 'encounterType', typeText));
566
+ }
567
+ // SNOMED code from type
568
+ const codings = extractCodings(resource.type[0]);
569
+ for (const c of codings) {
570
+ if (c.system === 'http://snomed.info/sct') {
571
+ quads.push(tripleRef(subjectUri, NS.clinical + 'snomedCode', NS.sct + c.code));
572
+ break;
573
+ }
574
+ }
575
+ }
576
+ // Period
577
+ if (resource.period?.start) {
578
+ quads.push(tripleDateTime(subjectUri, NS.clinical + 'encounterStart', resource.period.start));
579
+ }
580
+ if (resource.period?.end) {
581
+ quads.push(tripleDateTime(subjectUri, NS.clinical + 'encounterEnd', resource.period.end));
582
+ }
583
+ // Provider from first participant
584
+ if (Array.isArray(resource.participant) && resource.participant.length > 0) {
585
+ const providerName = resource.participant[0]?.individual?.display;
586
+ if (providerName) {
587
+ quads.push(tripleStr(subjectUri, NS.clinical + 'providerName', providerName));
588
+ }
589
+ }
590
+ // Facility from serviceProvider
591
+ if (resource.serviceProvider?.display) {
592
+ quads.push(tripleStr(subjectUri, NS.clinical + 'facilityName', resource.serviceProvider.display));
593
+ }
594
+ if (resource.id) {
595
+ quads.push(tripleStr(subjectUri, NS.clinical + 'sourceRecordId', resource.id));
596
+ }
597
+ quads.push(tripleRef(subjectUri, NS.cascade + 'layerPromotionStatus', NS.cascade + 'FullyMapped'));
598
+ return {
599
+ turtle: '',
600
+ jsonld: quadsToJsonLd(quads, 'clinical:Encounter'),
601
+ warnings,
602
+ resourceType: 'Encounter',
603
+ cascadeType: 'clinical:Encounter',
604
+ _quads: quads,
605
+ };
606
+ }
607
+ // ---------------------------------------------------------------------------
608
+ // LaboratoryReport (DiagnosticReport) converter (B5)
609
+ // ---------------------------------------------------------------------------
610
+ export function convertLaboratoryReport(resource) {
611
+ const warnings = [];
612
+ const subjectUri = mintSubjectUri(resource);
613
+ const quads = [];
614
+ quads.push(tripleType(subjectUri, NS.clinical + 'LaboratoryReport'));
615
+ quads.push(...commonTriples(subjectUri));
616
+ // Panel name from code.text or first coding display
617
+ const panelName = codeableConceptText(resource.code) ?? 'Unknown Panel';
618
+ quads.push(tripleStr(subjectUri, NS.clinical + 'panelName', panelName));
619
+ // LOINC code
620
+ const codings = extractCodings(resource.code);
621
+ for (const c of codings) {
622
+ if (c.system === 'http://loinc.org') {
623
+ quads.push(tripleRef(subjectUri, NS.clinical + 'loincCode', NS.loinc + c.code));
624
+ break;
625
+ }
626
+ }
627
+ // Report category
628
+ if (Array.isArray(resource.category) && resource.category.length > 0) {
629
+ const catCode = resource.category[0]?.coding?.[0]?.code ?? codeableConceptText(resource.category[0]);
630
+ if (catCode) {
631
+ quads.push(tripleStr(subjectUri, NS.clinical + 'reportCategory', catCode));
632
+ }
633
+ }
634
+ // Effective date
635
+ const effectiveDate = resource.effectiveDateTime ?? resource.effectivePeriod?.start;
636
+ if (effectiveDate) {
637
+ quads.push(tripleDateTime(subjectUri, NS.clinical + 'performedDate', effectiveDate));
638
+ }
639
+ // Provider from first performer
640
+ if (Array.isArray(resource.performer) && resource.performer.length > 0) {
641
+ const provName = resource.performer[0]?.display;
642
+ if (provName) {
643
+ quads.push(tripleStr(subjectUri, NS.clinical + 'providerName', provName));
644
+ }
645
+ }
646
+ // Link to constituent LabResult Observations via hasLabResult
647
+ if (Array.isArray(resource.result)) {
648
+ for (const ref of resource.result) {
649
+ // Extract ID from reference like "Observation/uuid-here"
650
+ const refStr = ref.reference;
651
+ if (refStr) {
652
+ const parts = refStr.split('/');
653
+ const obsId = parts[parts.length - 1];
654
+ if (obsId) {
655
+ // Mint deterministic URI for the referenced Observation
656
+ const obsUri = `urn:uuid:${obsId}`;
657
+ quads.push(tripleRef(subjectUri, NS.clinical + 'hasLabResult', obsUri));
658
+ }
659
+ }
660
+ }
661
+ }
662
+ if (resource.id) {
663
+ quads.push(tripleStr(subjectUri, NS.clinical + 'sourceRecordId', resource.id));
664
+ }
665
+ quads.push(tripleRef(subjectUri, NS.cascade + 'layerPromotionStatus', NS.cascade + 'FullyMapped'));
666
+ return {
667
+ turtle: '',
668
+ jsonld: quadsToJsonLd(quads, 'clinical:LaboratoryReport'),
669
+ warnings,
670
+ resourceType: 'DiagnosticReport',
671
+ cascadeType: 'clinical:LaboratoryReport',
672
+ _quads: quads,
673
+ };
674
+ }
675
+ // ---------------------------------------------------------------------------
676
+ // MedicationAdministration converter (B5)
677
+ // ---------------------------------------------------------------------------
678
+ export function convertMedicationAdministration(resource) {
679
+ const warnings = [];
680
+ const subjectUri = mintSubjectUri(resource);
681
+ const quads = [];
682
+ quads.push(tripleType(subjectUri, NS.clinical + 'MedicationAdministration'));
683
+ quads.push(...commonTriples(subjectUri));
684
+ // Medication name
685
+ const medName = codeableConceptText(resource.medicationCodeableConcept)
686
+ ?? resource.medicationReference?.display
687
+ ?? 'Unknown Medication';
688
+ quads.push(tripleStr(subjectUri, NS.health + 'medicationName', medName));
689
+ // Status
690
+ if (resource.status) {
691
+ quads.push(tripleStr(subjectUri, NS.clinical + 'administrationStatus', resource.status));
692
+ }
693
+ // Administered date
694
+ const adminDate = resource.effectiveDateTime ?? resource.effectivePeriod?.start;
695
+ if (adminDate) {
696
+ quads.push(tripleDateTime(subjectUri, NS.clinical + 'administeredDate', adminDate));
697
+ }
698
+ // Dose and route from dosage
699
+ if (resource.dosage) {
700
+ if (resource.dosage.dose) {
701
+ const dose = `${resource.dosage.dose.value ?? ''} ${resource.dosage.dose.unit ?? ''}`.trim();
702
+ if (dose)
703
+ quads.push(tripleStr(subjectUri, NS.clinical + 'administeredDose', dose));
704
+ }
705
+ const route = codeableConceptText(resource.dosage.route);
706
+ if (route) {
707
+ quads.push(tripleStr(subjectUri, NS.clinical + 'administeredRoute', route));
708
+ }
709
+ }
710
+ if (resource.id) {
711
+ quads.push(tripleStr(subjectUri, NS.clinical + 'sourceRecordId', resource.id));
712
+ }
713
+ quads.push(tripleRef(subjectUri, NS.cascade + 'layerPromotionStatus', NS.cascade + 'FullyMapped'));
714
+ return {
715
+ turtle: '',
716
+ jsonld: quadsToJsonLd(quads, 'clinical:MedicationAdministration'),
717
+ warnings,
718
+ resourceType: 'MedicationAdministration',
719
+ cascadeType: 'clinical:MedicationAdministration',
720
+ _quads: quads,
721
+ };
722
+ }
723
+ // ---------------------------------------------------------------------------
724
+ // Device (ImplantedDevice) converter (B5)
725
+ // ---------------------------------------------------------------------------
726
+ export function convertDevice(resource) {
727
+ const warnings = [];
728
+ const subjectUri = mintSubjectUri(resource);
729
+ const quads = [];
730
+ quads.push(tripleType(subjectUri, NS.clinical + 'ImplantedDevice'));
731
+ quads.push(...commonTriples(subjectUri));
732
+ // Device type
733
+ const deviceType = codeableConceptText(resource.type) ?? 'Unknown Device';
734
+ quads.push(tripleStr(subjectUri, NS.clinical + 'deviceType', deviceType));
735
+ // Status
736
+ if (resource.status) {
737
+ quads.push(tripleStr(subjectUri, NS.clinical + 'deviceStatus', resource.status));
738
+ }
739
+ // Manufacturer
740
+ if (resource.manufacturer) {
741
+ quads.push(tripleStr(subjectUri, NS.clinical + 'deviceManufacturer', resource.manufacturer));
742
+ }
743
+ // UDI carrier
744
+ if (Array.isArray(resource.udiCarrier) && resource.udiCarrier.length > 0) {
745
+ const udi = resource.udiCarrier[0]?.deviceIdentifier ?? resource.udiCarrier[0]?.carrierHRF;
746
+ if (udi)
747
+ quads.push(tripleStr(subjectUri, NS.clinical + 'udiCarrier', udi));
748
+ }
749
+ // Implant date (from manufactureDate or extension)
750
+ if (resource.manufactureDate) {
751
+ quads.push(tripleDateTime(subjectUri, NS.clinical + 'implantDate', resource.manufactureDate));
752
+ }
753
+ if (resource.id) {
754
+ quads.push(tripleStr(subjectUri, NS.clinical + 'sourceRecordId', resource.id));
755
+ }
756
+ quads.push(tripleRef(subjectUri, NS.cascade + 'layerPromotionStatus', NS.cascade + 'FullyMapped'));
757
+ return {
758
+ turtle: '',
759
+ jsonld: quadsToJsonLd(quads, 'clinical:ImplantedDevice'),
760
+ warnings,
761
+ resourceType: 'Device',
762
+ cascadeType: 'clinical:ImplantedDevice',
763
+ _quads: quads,
764
+ };
765
+ }
766
+ // ---------------------------------------------------------------------------
767
+ // ImagingStudy converter (B5)
768
+ // ---------------------------------------------------------------------------
769
+ export function convertImagingStudy(resource) {
770
+ const warnings = [];
771
+ const subjectUri = mintSubjectUri(resource);
772
+ const quads = [];
773
+ quads.push(tripleType(subjectUri, NS.clinical + 'ImagingStudy'));
774
+ quads.push(...commonTriples(subjectUri));
775
+ // Modality from first series
776
+ const modality = resource.series?.[0]?.modality?.code;
777
+ if (modality) {
778
+ quads.push(tripleStr(subjectUri, NS.clinical + 'imagingModality', modality));
779
+ }
780
+ // Description
781
+ if (resource.description) {
782
+ quads.push(tripleStr(subjectUri, NS.clinical + 'studyDescription', resource.description));
783
+ }
784
+ // Number of series
785
+ if (resource.numberOfSeries !== undefined) {
786
+ quads.push(tripleTyped(subjectUri, NS.clinical + 'numberOfSeries', String(resource.numberOfSeries), NS.xsd + 'integer'));
787
+ }
788
+ // Study date
789
+ if (resource.started) {
790
+ quads.push(tripleDateTime(subjectUri, NS.clinical + 'studyDate', resource.started));
791
+ }
792
+ // DICOM Study UID from identifier[0]
793
+ if (Array.isArray(resource.identifier) && resource.identifier.length > 0) {
794
+ const uid = resource.identifier[0]?.value;
795
+ if (uid)
796
+ quads.push(tripleStr(subjectUri, NS.clinical + 'dicomStudyUid', uid));
797
+ }
798
+ // Retrieve URL from first series endpoint
799
+ const retrieveUrl = resource.series?.[0]?.endpoint?.[0]?.reference;
800
+ if (retrieveUrl) {
801
+ quads.push(tripleStr(subjectUri, NS.clinical + 'retrieveUrl', retrieveUrl));
802
+ }
803
+ if (resource.id) {
804
+ quads.push(tripleStr(subjectUri, NS.clinical + 'sourceRecordId', resource.id));
805
+ }
806
+ quads.push(tripleRef(subjectUri, NS.cascade + 'layerPromotionStatus', NS.cascade + 'FullyMapped'));
807
+ return {
808
+ turtle: '',
809
+ jsonld: quadsToJsonLd(quads, 'clinical:ImagingStudy'),
810
+ warnings,
811
+ resourceType: 'ImagingStudy',
812
+ cascadeType: 'clinical:ImagingStudy',
813
+ _quads: quads,
814
+ };
815
+ }
391
816
  //# sourceMappingURL=converters-clinical.js.map