@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.
- package/dist/commands/convert.d.ts +8 -5
- package/dist/commands/convert.d.ts.map +1 -1
- package/dist/commands/convert.js +51 -7
- package/dist/commands/convert.js.map +1 -1
- package/dist/commands/pod/helpers.d.ts +2 -0
- package/dist/commands/pod/helpers.d.ts.map +1 -1
- package/dist/commands/pod/helpers.js +62 -1
- package/dist/commands/pod/helpers.js.map +1 -1
- package/dist/commands/pod/import.d.ts +20 -0
- package/dist/commands/pod/import.d.ts.map +1 -0
- package/dist/commands/pod/import.js +382 -0
- package/dist/commands/pod/import.js.map +1 -0
- package/dist/commands/pod/index.d.ts.map +1 -1
- package/dist/commands/pod/index.js +2 -0
- package/dist/commands/pod/index.js.map +1 -1
- package/dist/commands/pod/query.d.ts.map +1 -1
- package/dist/commands/pod/query.js +34 -1
- package/dist/commands/pod/query.js.map +1 -1
- package/dist/commands/reconcile.d.ts.map +1 -1
- package/dist/commands/reconcile.js +22 -396
- package/dist/commands/reconcile.js.map +1 -1
- package/dist/lib/fhir-converter/cascade-to-fhir-admin.d.ts +13 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-admin.d.ts.map +1 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-admin.js +70 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-admin.js.map +1 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-clinical.d.ts +36 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-clinical.d.ts.map +1 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-clinical.js +407 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-clinical.js.map +1 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-demographics.d.ts +15 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-demographics.d.ts.map +1 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-demographics.js +115 -0
- package/dist/lib/fhir-converter/cascade-to-fhir-demographics.js.map +1 -0
- package/dist/lib/fhir-converter/cascade-to-fhir.d.ts +9 -3
- package/dist/lib/fhir-converter/cascade-to-fhir.d.ts.map +1 -1
- package/dist/lib/fhir-converter/cascade-to-fhir.js +69 -290
- package/dist/lib/fhir-converter/cascade-to-fhir.js.map +1 -1
- package/dist/lib/fhir-converter/converters-clinical-admin.d.ts +16 -0
- package/dist/lib/fhir-converter/converters-clinical-admin.d.ts.map +1 -0
- package/dist/lib/fhir-converter/converters-clinical-admin.js +166 -0
- package/dist/lib/fhir-converter/converters-clinical-admin.js.map +1 -0
- package/dist/lib/fhir-converter/converters-clinical.d.ts +28 -0
- package/dist/lib/fhir-converter/converters-clinical.d.ts.map +1 -1
- package/dist/lib/fhir-converter/converters-clinical.js +436 -11
- package/dist/lib/fhir-converter/converters-clinical.js.map +1 -1
- package/dist/lib/fhir-converter/converters-demographics.d.ts.map +1 -1
- package/dist/lib/fhir-converter/converters-demographics.js +4 -5
- package/dist/lib/fhir-converter/converters-demographics.js.map +1 -1
- package/dist/lib/fhir-converter/converters-passthrough.d.ts +29 -0
- package/dist/lib/fhir-converter/converters-passthrough.d.ts.map +1 -0
- package/dist/lib/fhir-converter/converters-passthrough.js +86 -0
- package/dist/lib/fhir-converter/converters-passthrough.js.map +1 -0
- package/dist/lib/fhir-converter/fhir-to-cascade.d.ts +8 -4
- package/dist/lib/fhir-converter/fhir-to-cascade.d.ts.map +1 -1
- package/dist/lib/fhir-converter/fhir-to-cascade.js +41 -10
- package/dist/lib/fhir-converter/fhir-to-cascade.js.map +1 -1
- package/dist/lib/fhir-converter/import-manifest.d.ts +40 -0
- package/dist/lib/fhir-converter/import-manifest.d.ts.map +1 -0
- package/dist/lib/fhir-converter/import-manifest.js +66 -0
- package/dist/lib/fhir-converter/import-manifest.js.map +1 -0
- package/dist/lib/fhir-converter/index.d.ts +33 -13
- package/dist/lib/fhir-converter/index.d.ts.map +1 -1
- package/dist/lib/fhir-converter/index.js +38 -17
- package/dist/lib/fhir-converter/index.js.map +1 -1
- package/dist/lib/fhir-converter/types.d.ts +12 -1
- package/dist/lib/fhir-converter/types.d.ts.map +1 -1
- package/dist/lib/fhir-converter/types.js +67 -2
- package/dist/lib/fhir-converter/types.js.map +1 -1
- package/dist/lib/output.d.ts +5 -0
- package/dist/lib/output.d.ts.map +1 -1
- package/dist/lib/output.js +6 -1
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/reconciler.d.ts +45 -0
- package/dist/lib/reconciler.d.ts.map +1 -0
- package/dist/lib/reconciler.js +397 -0
- package/dist/lib/reconciler.js.map +1 -0
- 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
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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
|