@imranq2/fhirpatientsummary 1.0.29 → 1.0.31
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/index.cjs +805 -295
- package/dist/index.js +805 -295
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -99,6 +99,13 @@ var PREGNANCY_LOINC_CODES = {
|
|
|
99
99
|
"33065-4": "Ectopic Pregnancy"
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
|
+
var PREGNANCY_SNOMED_CODES = [
|
|
103
|
+
// Add SNOMED CT codes for pregnancy history here, e.g.:
|
|
104
|
+
"72892002",
|
|
105
|
+
// Pregnancy (finding)
|
|
106
|
+
"77386006"
|
|
107
|
+
// History of pregnancy (situation)
|
|
108
|
+
];
|
|
102
109
|
var SOCIAL_HISTORY_LOINC_CODES = {
|
|
103
110
|
"72166-2": "Tobacco Use",
|
|
104
111
|
"74013-4": "Alcohol Use"
|
|
@@ -108,6 +115,29 @@ var BLOOD_PRESSURE_LOINC_CODES = {
|
|
|
108
115
|
SYSTOLIC: "8480-6",
|
|
109
116
|
DIASTOLIC: "8462-4"
|
|
110
117
|
};
|
|
118
|
+
var ESSENTIAL_LAB_PANELS = {
|
|
119
|
+
// Top 20 Most Ordered Panels
|
|
120
|
+
"24323-8": "Comprehensive Metabolic Panel (CMP)",
|
|
121
|
+
"24320-4": "Basic Metabolic Panel (BMP)",
|
|
122
|
+
"58410-2": "Complete Blood Count (CBC)",
|
|
123
|
+
"57021-8": "CBC with Differential",
|
|
124
|
+
"24331-1": "Lipid Panel",
|
|
125
|
+
"57698-3": "Lipid Panel with Direct LDL",
|
|
126
|
+
"24325-3": "Hepatic Function Panel",
|
|
127
|
+
"24362-6": "Renal Function Panel",
|
|
128
|
+
"24326-1": "Electrolyte Panel",
|
|
129
|
+
"24348-5": "Thyroid Panel",
|
|
130
|
+
"24356-8": "Urinalysis Complete",
|
|
131
|
+
"24352-7": "Iron Studies Panel",
|
|
132
|
+
"34714-6": "Coagulation Panel",
|
|
133
|
+
"24364-2": "Prenatal Panel",
|
|
134
|
+
"24108-3": "Acute Hepatitis Panel",
|
|
135
|
+
"24110-9": "Hepatitis B Panel",
|
|
136
|
+
"34574-4": "Arthritis Panel",
|
|
137
|
+
"24360-0": "Anemia Panel",
|
|
138
|
+
"80235-8": "Cardiac Markers Panel",
|
|
139
|
+
"69738-3": "CBC with Auto Differential"
|
|
140
|
+
};
|
|
111
141
|
|
|
112
142
|
// src/structures/ips_section_constants.ts
|
|
113
143
|
var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
@@ -132,34 +162,39 @@ var IPSSectionResourceFilters = {
|
|
|
132
162
|
// Only include completed immunizations
|
|
133
163
|
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
|
|
134
164
|
// Only include vital sign Observations (category.coding contains 'vital-signs')
|
|
135
|
-
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c
|
|
165
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, "vital-signs", c.system))),
|
|
136
166
|
// Includes DeviceUseStatement. Device is needed for linked device details
|
|
137
167
|
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
|
|
138
|
-
// Only include finalized diagnostic reports
|
|
139
|
-
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) =>
|
|
168
|
+
// Only include finalized diagnostic reports and relevant observations
|
|
169
|
+
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "DiagnosticReport" && resource.status === "final" || resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, RESULT_SUMMARY_OBSERVATION_CATEGORIES, c.system))),
|
|
140
170
|
// Only include completed procedures
|
|
141
171
|
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
|
|
142
172
|
// Only include social history Observations
|
|
143
|
-
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code
|
|
144
|
-
// Only include pregnancy history Observations
|
|
145
|
-
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code
|
|
146
|
-
// Only include
|
|
147
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "
|
|
173
|
+
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
|
|
174
|
+
// Only include pregnancy history Observations or relevant Conditions
|
|
175
|
+
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "Condition" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")),
|
|
176
|
+
// Only include Observations with LOINC 47420-5, category 'functional-status', or category display containing 'functional', and completed ClinicalImpressions
|
|
177
|
+
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, "47420-5", "http://loinc.org") || resource.category?.some(
|
|
178
|
+
(cat) => cat.coding?.some(
|
|
179
|
+
(c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
|
|
180
|
+
)
|
|
181
|
+
)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
|
|
148
182
|
// Only include resolved medical history Conditions
|
|
149
183
|
["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
|
|
150
184
|
// Only include active care plans
|
|
151
185
|
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
|
|
152
186
|
// Only include active advance directives (Consent resources)
|
|
153
|
-
|
|
187
|
+
// TODO: disable this until we right logic to get these
|
|
188
|
+
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
|
|
154
189
|
};
|
|
155
190
|
var IPSSectionSummaryCompositionFilter = {
|
|
156
|
-
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
157
|
-
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
158
|
-
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
159
|
-
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
160
|
-
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
191
|
+
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
192
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
193
|
+
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
194
|
+
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
195
|
+
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "medication_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
161
196
|
// [IPSSections.DIAGNOSTIC_REPORTS]: (resource) => resource.resourceType === 'Composition' && resource.type?.coding?.some((c: any) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && ["lab_summary_document", "diagnosticreportlab_summary_document"].includes(c.code)),
|
|
162
|
-
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
197
|
+
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "procedure_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
|
|
163
198
|
};
|
|
164
199
|
var IPSSectionSummaryIPSCompositionFilter = {
|
|
165
200
|
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "ips_vital_summary_document")
|
|
@@ -180,9 +215,46 @@ var IPSSectionResourceHelper = class {
|
|
|
180
215
|
return IPSSectionSummaryIPSCompositionFilter[section];
|
|
181
216
|
}
|
|
182
217
|
};
|
|
218
|
+
function codingMatches(coding, code, system) {
|
|
219
|
+
if (!coding || !coding.system) return false;
|
|
220
|
+
if (Array.isArray(code)) {
|
|
221
|
+
return coding.system === system && code.includes(coding.code ?? "");
|
|
222
|
+
}
|
|
223
|
+
return coding.system === system && coding.code === code;
|
|
224
|
+
}
|
|
225
|
+
function codeableConceptMatches(codeableConcept, code, system) {
|
|
226
|
+
if (!codeableConcept || !Array.isArray(codeableConcept.coding)) return false;
|
|
227
|
+
return codeableConcept.coding.some((coding) => codingMatches(coding, code, system));
|
|
228
|
+
}
|
|
183
229
|
|
|
184
230
|
// src/narratives/templates/typescript/TemplateUtilities.ts
|
|
185
231
|
var import_luxon = require("luxon");
|
|
232
|
+
|
|
233
|
+
// src/structures/codingSystemDisplayNames.ts
|
|
234
|
+
var CODING_SYSTEM_DISPLAY_NAMES = {
|
|
235
|
+
"http://snomed.info/sct": "SNOMED CT",
|
|
236
|
+
"http://loinc.org": "LOINC",
|
|
237
|
+
"http://hl7.org/fhir/sid/icd-10": "ICD-10",
|
|
238
|
+
"http://hl7.org/fhir/sid/icd-10-cm": "ICD-10-CM",
|
|
239
|
+
"http://hl7.org/fhir/sid/icd-9": "ICD-9",
|
|
240
|
+
"http://hl7.org/fhir/sid/cvx": "CVX",
|
|
241
|
+
"http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
|
|
242
|
+
"http://www.ama-assn.org/go/cpt": "CPT",
|
|
243
|
+
"http://unitsofmeasure.org": "UCUM",
|
|
244
|
+
"http://e-imo.com/products/problem-it": "IMO Problem IT",
|
|
245
|
+
"2.16.840.1.113883.6.285": "HCPCS Level II",
|
|
246
|
+
"https://fhir.cerner.com/4ff3b259-e48d-4066-8b35-a6a051f2802a/codeSet/72": "Cerner Code Set 72",
|
|
247
|
+
"http://hl7.org/fhir/sid/ndc": "NDC",
|
|
248
|
+
// Added mapping for NDC
|
|
249
|
+
"urn:oid:2.16.840.1.113883.12.292": "CVX",
|
|
250
|
+
"http://terminology.hl7.org/CodeSystem/data-absent-reason": "Data Absent Reason",
|
|
251
|
+
// Added mapping for Data Absent Reason
|
|
252
|
+
"2.16.840.1.113883.6.208": "NDDF"
|
|
253
|
+
// Add more as needed
|
|
254
|
+
};
|
|
255
|
+
var codingSystemDisplayNames_default = CODING_SYSTEM_DISPLAY_NAMES;
|
|
256
|
+
|
|
257
|
+
// src/narratives/templates/typescript/TemplateUtilities.ts
|
|
186
258
|
var TemplateUtilities = class {
|
|
187
259
|
/**
|
|
188
260
|
* Constructor to initialize the TemplateUtilities with a FHIR resources
|
|
@@ -192,47 +264,63 @@ var TemplateUtilities = class {
|
|
|
192
264
|
this.resources = resources;
|
|
193
265
|
}
|
|
194
266
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* @
|
|
267
|
+
* Returns the preferred coding from a list of codings.
|
|
268
|
+
* If a coding has an extension with url 'https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence' and valueCode 'preferred', returns that coding.
|
|
269
|
+
* Otherwise, returns the first coding if it exists, else null.
|
|
270
|
+
* @param codings Array of coding objects
|
|
271
|
+
* @returns The preferred coding object or null
|
|
199
272
|
*/
|
|
200
|
-
|
|
201
|
-
if (!
|
|
202
|
-
|
|
273
|
+
getPreferredCoding(codings) {
|
|
274
|
+
if (!Array.isArray(codings) || codings.length === 0) return null;
|
|
275
|
+
for (const coding of codings) {
|
|
276
|
+
if (Array.isArray(coding.extension)) {
|
|
277
|
+
for (const ext of coding.extension) {
|
|
278
|
+
if (ext.url === "https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence" && ext.valueCode === "preferred") {
|
|
279
|
+
return coding;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
203
283
|
}
|
|
284
|
+
return codings[0] || null;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Returns the display value from a CodeableConcept
|
|
288
|
+
* @param cc - The CodeableConcept object
|
|
289
|
+
* @param field - Optional specific field to extract (e.g., 'text', 'display', 'code')
|
|
290
|
+
* @returns Display string or empty string
|
|
291
|
+
*/
|
|
292
|
+
codeableConceptDisplay(cc, field) {
|
|
293
|
+
if (!cc) return "";
|
|
204
294
|
if (field) {
|
|
205
295
|
if (cc[field]) {
|
|
206
296
|
return cc[field];
|
|
207
|
-
} else if (cc.coding && cc.coding
|
|
208
|
-
|
|
297
|
+
} else if (cc.coding && cc.coding.length > 0) {
|
|
298
|
+
const preferredCoding = this.getPreferredCoding(cc.coding);
|
|
299
|
+
if (preferredCoding && preferredCoding[field]) {
|
|
300
|
+
return preferredCoding[field];
|
|
301
|
+
}
|
|
209
302
|
}
|
|
210
303
|
}
|
|
211
|
-
if (cc.text)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
return cc.coding[0].display;
|
|
216
|
-
} else if (cc.coding[0].code) {
|
|
217
|
-
return cc.coding[0].code;
|
|
218
|
-
}
|
|
304
|
+
if (cc.text) return cc.text;
|
|
305
|
+
if (cc.coding && cc.coding.length > 0) {
|
|
306
|
+
const preferredCoding = this.getPreferredCoding(cc.coding);
|
|
307
|
+
if (preferredCoding && preferredCoding.display) return preferredCoding.display;
|
|
219
308
|
}
|
|
220
309
|
return "";
|
|
221
310
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
});
|
|
235
|
-
return resource ? resource : null;
|
|
311
|
+
/**
|
|
312
|
+
* Returns the code and system from a CodeableConcept
|
|
313
|
+
* @param cc - The CodeableConcept object
|
|
314
|
+
* @returns Object with code and system, or empty strings if not present
|
|
315
|
+
*/
|
|
316
|
+
codeableConceptCoding(cc) {
|
|
317
|
+
if (!cc || !cc.coding || !cc.coding.length) return "";
|
|
318
|
+
const preferredCoding = this.getPreferredCoding(cc.coding);
|
|
319
|
+
if (!preferredCoding) return "";
|
|
320
|
+
const code = preferredCoding.code || "";
|
|
321
|
+
const system = preferredCoding.system || "";
|
|
322
|
+
const systemDisplay = codingSystemDisplayNames_default[system] || system;
|
|
323
|
+
return code ? `${code} (${systemDisplay})` : "";
|
|
236
324
|
}
|
|
237
325
|
/**
|
|
238
326
|
* Renders a Device reference
|
|
@@ -280,7 +368,7 @@ var TemplateUtilities = class {
|
|
|
280
368
|
return "";
|
|
281
369
|
}
|
|
282
370
|
if (medicationType.medicationCodeableConcept) {
|
|
283
|
-
return this.
|
|
371
|
+
return this.codeableConceptDisplay(medicationType.medicationCodeableConcept);
|
|
284
372
|
} else if (medicationType.medicationReference) {
|
|
285
373
|
return this.renderMedicationRef(medicationType.medicationReference);
|
|
286
374
|
}
|
|
@@ -305,7 +393,7 @@ var TemplateUtilities = class {
|
|
|
305
393
|
*/
|
|
306
394
|
renderMedicationCode(medication) {
|
|
307
395
|
if (medication && medication.code) {
|
|
308
|
-
return this.renderTextAsHtml(this.
|
|
396
|
+
return this.renderTextAsHtml(this.codeableConceptDisplay(medication.code));
|
|
309
397
|
}
|
|
310
398
|
return "";
|
|
311
399
|
}
|
|
@@ -474,7 +562,7 @@ var TemplateUtilities = class {
|
|
|
474
562
|
*/
|
|
475
563
|
firstFromCodeableConceptList(list) {
|
|
476
564
|
if (list && Array.isArray(list) && list[0]) {
|
|
477
|
-
return this.renderTextAsHtml(this.
|
|
565
|
+
return this.renderTextAsHtml(this.codeableConceptDisplay(list[0]));
|
|
478
566
|
}
|
|
479
567
|
return "";
|
|
480
568
|
}
|
|
@@ -814,16 +902,28 @@ var TemplateUtilities = class {
|
|
|
814
902
|
return this.renderMedicationCode(medicationSource);
|
|
815
903
|
}
|
|
816
904
|
if (typeof medicationSource === "object" && ("coding" in medicationSource || "text" in medicationSource)) {
|
|
817
|
-
return this.
|
|
905
|
+
return this.codeableConceptDisplay(medicationSource);
|
|
818
906
|
}
|
|
819
907
|
if (typeof medicationSource === "object" && "reference" in medicationSource) {
|
|
820
908
|
const medication = this.resolveReference(medicationSource);
|
|
821
909
|
if (medication && medication.code) {
|
|
822
|
-
return this.
|
|
910
|
+
return this.codeableConceptDisplay(medication.code);
|
|
823
911
|
}
|
|
824
912
|
}
|
|
825
913
|
return "";
|
|
826
914
|
}
|
|
915
|
+
/**
|
|
916
|
+
* Returns the owner tag from the resource meta.security array.
|
|
917
|
+
* @param resource - FHIR resource with meta tag
|
|
918
|
+
* @returns The owner code if found, otherwise undefined
|
|
919
|
+
*/
|
|
920
|
+
getOwnerTag(resource) {
|
|
921
|
+
if (!resource?.meta?.security) return "";
|
|
922
|
+
const ownerEntry = resource.meta.security.find(
|
|
923
|
+
(sec) => sec.system === "https://www.icanbwell.com/owner" && !!sec.code
|
|
924
|
+
);
|
|
925
|
+
return ownerEntry?.code;
|
|
926
|
+
}
|
|
827
927
|
/**
|
|
828
928
|
* Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
|
|
829
929
|
* This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
|
|
@@ -987,6 +1087,21 @@ var TemplateUtilities = class {
|
|
|
987
1087
|
const denominatorUnit = valueRatio.denominator.unit ? ` ${valueRatio.denominator.unit}` : "";
|
|
988
1088
|
return `${numerator}${numeratorUnit} / ${denominator}${denominatorUnit}`;
|
|
989
1089
|
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Finds the resource that matches the reference
|
|
1092
|
+
* @param ref - Reference to a resource
|
|
1093
|
+
* @returns The resource or null
|
|
1094
|
+
*/
|
|
1095
|
+
resolveReference(ref) {
|
|
1096
|
+
if (!ref || !this.resources) {
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
const refId = ref.reference?.split("/")[1];
|
|
1100
|
+
const refType = ref.reference?.split("/")[0];
|
|
1101
|
+
return this.resources.find(
|
|
1102
|
+
(resource) => resource.resourceType === refType && resource.id === refId
|
|
1103
|
+
) || null;
|
|
1104
|
+
}
|
|
990
1105
|
};
|
|
991
1106
|
|
|
992
1107
|
// src/constants.ts
|
|
@@ -1320,7 +1435,8 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1320
1435
|
static generateStaticNarrative(resources, timezone) {
|
|
1321
1436
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1322
1437
|
const combinedPatient = this.combinePatients(resources);
|
|
1323
|
-
|
|
1438
|
+
let html = `<p>This section merges all Patient resources into a single combined patient record, preferring non-empty values for each field.</p>`;
|
|
1439
|
+
html += `<div>
|
|
1324
1440
|
<ul>
|
|
1325
1441
|
<li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
|
|
1326
1442
|
<li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
|
|
@@ -1332,6 +1448,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1332
1448
|
<li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
|
|
1333
1449
|
</ul>
|
|
1334
1450
|
</div>`;
|
|
1451
|
+
return html;
|
|
1335
1452
|
}
|
|
1336
1453
|
/**
|
|
1337
1454
|
* Combines multiple patient resources into a single patient object
|
|
@@ -1615,7 +1732,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1615
1732
|
const uniqueLanguages = /* @__PURE__ */ new Set();
|
|
1616
1733
|
const preferredLanguages = /* @__PURE__ */ new Set();
|
|
1617
1734
|
patient.communication.forEach((comm) => {
|
|
1618
|
-
const language = templateUtilities.renderTextAsHtml(templateUtilities.
|
|
1735
|
+
const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(comm.language));
|
|
1619
1736
|
if (language) {
|
|
1620
1737
|
if (comm.preferred) {
|
|
1621
1738
|
preferredLanguages.add(language);
|
|
@@ -1656,20 +1773,26 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1656
1773
|
generateSummaryNarrative(resources, timezone) {
|
|
1657
1774
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1658
1775
|
let isSummaryCreated = false;
|
|
1659
|
-
let html =
|
|
1776
|
+
let html = `<p>This list includes all AllergyIntolerance resources, with no additional filtering, sorted as provided.</p>
|
|
1777
|
+
`;
|
|
1778
|
+
html += `
|
|
1660
1779
|
<div>
|
|
1661
1780
|
<table>
|
|
1662
1781
|
<thead>
|
|
1663
1782
|
<tr>
|
|
1664
1783
|
<th>Allergen</th>
|
|
1784
|
+
<th>Code (System)</th>
|
|
1665
1785
|
<th>Criticality</th>
|
|
1666
1786
|
<th>Recorded Date</th>
|
|
1787
|
+
<th>Source</th>
|
|
1667
1788
|
</tr>
|
|
1668
1789
|
</thead>
|
|
1669
1790
|
<tbody>`;
|
|
1670
1791
|
for (const resourceItem of resources) {
|
|
1671
1792
|
for (const rowData of resourceItem.section ?? []) {
|
|
1793
|
+
const sectionCodeableConcept = rowData.code;
|
|
1672
1794
|
const data = {};
|
|
1795
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
1673
1796
|
for (const columnData of rowData.section ?? []) {
|
|
1674
1797
|
switch (columnData.title) {
|
|
1675
1798
|
case "Allergen Name":
|
|
@@ -1681,6 +1804,9 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1681
1804
|
case "Recorded Date":
|
|
1682
1805
|
data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1683
1806
|
break;
|
|
1807
|
+
case "Source":
|
|
1808
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1809
|
+
break;
|
|
1684
1810
|
default:
|
|
1685
1811
|
break;
|
|
1686
1812
|
}
|
|
@@ -1688,9 +1814,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1688
1814
|
isSummaryCreated = true;
|
|
1689
1815
|
html += `
|
|
1690
1816
|
<tr>
|
|
1691
|
-
<td>${data["allergen"] ?? "
|
|
1692
|
-
|
|
1693
|
-
<td>${
|
|
1817
|
+
<td>${data["allergen"] ?? ""}</td>
|
|
1818
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
1819
|
+
<td>${data["criticality"] ?? ""}</td>
|
|
1820
|
+
<td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? ""}</td>
|
|
1821
|
+
<td>${data["source"] ?? ""}</td>
|
|
1694
1822
|
</tr>`;
|
|
1695
1823
|
}
|
|
1696
1824
|
}
|
|
@@ -1729,7 +1857,8 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1729
1857
|
const dateB = b.onsetDateTime;
|
|
1730
1858
|
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
1731
1859
|
});
|
|
1732
|
-
let html =
|
|
1860
|
+
let html = `<p>This list includes all AllergyIntolerance resources, with no additional filtering, sorted as provided.</p>
|
|
1861
|
+
`;
|
|
1733
1862
|
html += `
|
|
1734
1863
|
<div class="ActiveAllergies">
|
|
1735
1864
|
<h3>Active</h3>
|
|
@@ -1738,10 +1867,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1738
1867
|
<tr>
|
|
1739
1868
|
<th>Allergen</th>
|
|
1740
1869
|
<th>Status</th>
|
|
1870
|
+
<th>Code (System)</th>
|
|
1741
1871
|
<th>Category</th>
|
|
1742
1872
|
<th>Reaction</th>
|
|
1743
1873
|
<th>Onset Date</th>
|
|
1744
1874
|
<th>Comments</th>
|
|
1875
|
+
<th>Source</th>
|
|
1745
1876
|
</tr>
|
|
1746
1877
|
</thead>
|
|
1747
1878
|
<tbody>`;
|
|
@@ -1765,11 +1896,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1765
1896
|
<tr>
|
|
1766
1897
|
<th>Allergen</th>
|
|
1767
1898
|
<th>Status</th>
|
|
1899
|
+
<th>Code (System)</th>
|
|
1768
1900
|
<th>Category</th>
|
|
1769
1901
|
<th>Reaction</th>
|
|
1770
1902
|
<th>Onset Date</th>
|
|
1771
1903
|
<th>Comments</th>
|
|
1772
1904
|
<th>Resolved Date</th>
|
|
1905
|
+
<th>Source</th>
|
|
1773
1906
|
</tr>
|
|
1774
1907
|
</thead>
|
|
1775
1908
|
<tbody>`;
|
|
@@ -1800,14 +1933,16 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1800
1933
|
for (const allergy of allergies) {
|
|
1801
1934
|
html += `
|
|
1802
1935
|
<tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
|
|
1803
|
-
<td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
1804
|
-
<td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
1805
|
-
<td class="
|
|
1806
|
-
<td class="
|
|
1807
|
-
<td class="
|
|
1808
|
-
<td class="
|
|
1936
|
+
<td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.code))}</span></td>
|
|
1937
|
+
<td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.clinicalStatus)) || ""}</td>
|
|
1938
|
+
<td class="CodeSystem">${templateUtilities.codeableConceptCoding(allergy.code)}</td>
|
|
1939
|
+
<td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || ""}</td>
|
|
1940
|
+
<td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || ""}</td>
|
|
1941
|
+
<td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || ""}</td>
|
|
1942
|
+
<td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>
|
|
1943
|
+
<td class="Source">${templateUtilities.getOwnerTag(allergy)}</td>`;
|
|
1809
1944
|
if (includeResolved) {
|
|
1810
|
-
let endDate = "
|
|
1945
|
+
let endDate = "";
|
|
1811
1946
|
if (allergy.extension && Array.isArray(allergy.extension)) {
|
|
1812
1947
|
const endDateExt = allergy.extension.find(
|
|
1813
1948
|
(ext) => ext.url === "http://hl7.org/fhir/StructureDefinition/allergyintolerance-resolutionDate"
|
|
@@ -1831,10 +1966,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1831
1966
|
* Generate HTML narrative for Medication resources
|
|
1832
1967
|
* @param resources - FHIR Medication resources
|
|
1833
1968
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1969
|
+
* @param now - Optional current date to use for calculations (defaults to new Date())
|
|
1834
1970
|
* @returns HTML string for rendering
|
|
1835
1971
|
*/
|
|
1836
|
-
generateNarrative(resources, timezone) {
|
|
1837
|
-
return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
|
|
1972
|
+
generateNarrative(resources, timezone, now) {
|
|
1973
|
+
return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone, now);
|
|
1838
1974
|
}
|
|
1839
1975
|
/**
|
|
1840
1976
|
* Generate HTML narrative for Medication resources using summary
|
|
@@ -1847,23 +1983,28 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1847
1983
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1848
1984
|
let isSummaryCreated = false;
|
|
1849
1985
|
const currentDate = now || /* @__PURE__ */ new Date();
|
|
1850
|
-
const
|
|
1851
|
-
let html =
|
|
1986
|
+
const twoYearsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 24, currentDate.getDate());
|
|
1987
|
+
let html = "<p>This list includes all the medications that were ordered for the patient, the medications they filled from the pharmacy and the medications they reported, filtered to past 2 years and sorted by start date.</p>";
|
|
1988
|
+
html += `
|
|
1852
1989
|
<div>
|
|
1853
1990
|
<table>
|
|
1854
1991
|
<thead>
|
|
1855
1992
|
<tr>
|
|
1856
1993
|
<th>Medication</th>
|
|
1994
|
+
<th>Code (System)</th>
|
|
1857
1995
|
<th>Status</th>
|
|
1858
1996
|
<th>Sig</th>
|
|
1859
1997
|
<th>Days of Supply</th>
|
|
1860
1998
|
<th>Refills</th>
|
|
1861
1999
|
<th>Start Date</th>
|
|
2000
|
+
<th>Source</th>
|
|
1862
2001
|
</tr>
|
|
1863
2002
|
</thead>
|
|
1864
2003
|
<tbody>`;
|
|
2004
|
+
let skippedMedications = 0;
|
|
1865
2005
|
for (const resourceItem of resources) {
|
|
1866
2006
|
for (const rowData of resourceItem.section ?? []) {
|
|
2007
|
+
const sectionCodeableConcept = rowData.code;
|
|
1867
2008
|
const data = {};
|
|
1868
2009
|
for (const columnData of rowData.section ?? []) {
|
|
1869
2010
|
switch (columnData.title) {
|
|
@@ -1888,6 +2029,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1888
2029
|
case "Authored On Date":
|
|
1889
2030
|
data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1890
2031
|
break;
|
|
2032
|
+
case "Source":
|
|
2033
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2034
|
+
break;
|
|
1891
2035
|
default:
|
|
1892
2036
|
break;
|
|
1893
2037
|
}
|
|
@@ -1899,16 +2043,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1899
2043
|
startDateObj = void 0;
|
|
1900
2044
|
}
|
|
1901
2045
|
}
|
|
1902
|
-
if (data["status"] === "active" || startDateObj && startDateObj >=
|
|
2046
|
+
if (!(data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo)) {
|
|
2047
|
+
skippedMedications++;
|
|
2048
|
+
}
|
|
2049
|
+
if (data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo) {
|
|
1903
2050
|
isSummaryCreated = true;
|
|
1904
2051
|
html += `
|
|
1905
2052
|
<tr>
|
|
1906
2053
|
<td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
|
|
2054
|
+
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
1907
2055
|
<td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
|
|
1908
2056
|
<td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
|
|
1909
2057
|
<td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
|
|
1910
2058
|
<td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
|
|
1911
2059
|
<td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
|
|
2060
|
+
<td>${templateUtilities.renderTextAsHtml(data["source"])}</td>
|
|
1912
2061
|
</tr>`;
|
|
1913
2062
|
}
|
|
1914
2063
|
}
|
|
@@ -1917,7 +2066,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1917
2066
|
</tbody>
|
|
1918
2067
|
</table>
|
|
1919
2068
|
</div>`;
|
|
1920
|
-
|
|
2069
|
+
if (skippedMedications > 0) {
|
|
2070
|
+
html += `
|
|
2071
|
+
<p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
|
|
2072
|
+
}
|
|
2073
|
+
if (isSummaryCreated || skippedMedications > 0) {
|
|
2074
|
+
return html;
|
|
2075
|
+
}
|
|
2076
|
+
return void 0;
|
|
1921
2077
|
}
|
|
1922
2078
|
/**
|
|
1923
2079
|
* Safely parse a date string and return a valid Date object or null
|
|
@@ -1935,21 +2091,42 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1935
2091
|
* Internal static implementation that actually generates the narrative
|
|
1936
2092
|
* @param resources - FHIR Medication resources
|
|
1937
2093
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2094
|
+
* @param now - Optional current date to use for calculations (defaults to new Date())
|
|
1938
2095
|
* @returns HTML string for rendering
|
|
1939
2096
|
*/
|
|
1940
|
-
|
|
1941
|
-
static generateStaticNarrative(resources, timezone) {
|
|
2097
|
+
static generateStaticNarrative(resources, timezone, now) {
|
|
1942
2098
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1943
2099
|
let html = "";
|
|
1944
2100
|
const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
|
|
1945
2101
|
const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
|
|
1946
2102
|
const allActiveMedications = [];
|
|
2103
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
2104
|
+
const twoYearsAgo = new Date(currentDate);
|
|
2105
|
+
twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
|
|
2106
|
+
let skippedMedications = 0;
|
|
2107
|
+
const allMedications = [];
|
|
1947
2108
|
medicationRequests.forEach((mr) => {
|
|
1948
|
-
|
|
2109
|
+
allMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
|
|
1949
2110
|
});
|
|
1950
2111
|
medicationStatements.forEach((ms) => {
|
|
1951
|
-
|
|
2112
|
+
allMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
|
|
1952
2113
|
});
|
|
2114
|
+
for (const med of allMedications) {
|
|
2115
|
+
let dateString;
|
|
2116
|
+
if (med.type === "request") {
|
|
2117
|
+
const mr = med.resource;
|
|
2118
|
+
dateString = mr.dispenseRequest?.validityPeriod?.start || mr.authoredOn;
|
|
2119
|
+
} else {
|
|
2120
|
+
const ms = med.resource;
|
|
2121
|
+
dateString = ms.effectiveDateTime || ms.effectivePeriod?.start;
|
|
2122
|
+
}
|
|
2123
|
+
const dateObj = this.parseDate(dateString);
|
|
2124
|
+
if (!dateObj || dateObj < twoYearsAgo) {
|
|
2125
|
+
skippedMedications++;
|
|
2126
|
+
} else {
|
|
2127
|
+
allActiveMedications.push(med);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
1953
2130
|
const sortMedications = (medications) => {
|
|
1954
2131
|
medications.sort((a, b) => {
|
|
1955
2132
|
let dateStringA;
|
|
@@ -1980,7 +2157,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1980
2157
|
sortMedications(allActiveMedications);
|
|
1981
2158
|
html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
|
|
1982
2159
|
}
|
|
1983
|
-
|
|
2160
|
+
if (skippedMedications > 0) {
|
|
2161
|
+
html += `
|
|
2162
|
+
<p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
|
|
2163
|
+
}
|
|
2164
|
+
if (allActiveMedications.length > 0 || skippedMedications > 0) {
|
|
2165
|
+
return html;
|
|
2166
|
+
}
|
|
2167
|
+
return "";
|
|
1984
2168
|
}
|
|
1985
2169
|
/**
|
|
1986
2170
|
* Extract MedicationRequest resources
|
|
@@ -2025,10 +2209,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
2025
2209
|
<tr>
|
|
2026
2210
|
<th>Type</th>
|
|
2027
2211
|
<th>Medication</th>
|
|
2212
|
+
<th>Code (System)</th>
|
|
2028
2213
|
<th>Sig</th>
|
|
2029
2214
|
<th>Dispense Quantity</th>
|
|
2030
2215
|
<th>Refills</th>
|
|
2031
2216
|
<th>Start Date</th>
|
|
2217
|
+
<th>Source</th>
|
|
2032
2218
|
</tr>
|
|
2033
2219
|
</thead>
|
|
2034
2220
|
<tbody>`;
|
|
@@ -2037,27 +2223,31 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
2037
2223
|
let type;
|
|
2038
2224
|
let medicationName;
|
|
2039
2225
|
let sig;
|
|
2040
|
-
let dispenseQuantity = "
|
|
2041
|
-
let refills = "
|
|
2042
|
-
let startDate = "
|
|
2226
|
+
let dispenseQuantity = "";
|
|
2227
|
+
let refills = "";
|
|
2228
|
+
let startDate = "";
|
|
2229
|
+
let codeSystemDisplay = "";
|
|
2043
2230
|
if (medication.type === "request") {
|
|
2044
2231
|
const mr = medication.resource;
|
|
2045
2232
|
type = "Request";
|
|
2046
2233
|
medicationName = templateUtilities.getMedicationName(
|
|
2047
2234
|
mr.medicationReference || mr.medicationCodeableConcept
|
|
2048
2235
|
);
|
|
2049
|
-
sig = templateUtilities.concat(mr.dosageInstruction, "text") || "
|
|
2236
|
+
sig = templateUtilities.concat(mr.dosageInstruction, "text") || "";
|
|
2050
2237
|
if (mr.dispenseRequest?.quantity) {
|
|
2051
2238
|
const quantity = mr.dispenseRequest.quantity;
|
|
2052
2239
|
if (quantity.value) {
|
|
2053
2240
|
dispenseQuantity = `${quantity.value} ${quantity.unit || quantity.code || ""}`.trim();
|
|
2054
2241
|
}
|
|
2055
2242
|
}
|
|
2056
|
-
refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "
|
|
2243
|
+
refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "";
|
|
2057
2244
|
if (mr.dispenseRequest?.validityPeriod) {
|
|
2058
|
-
startDate = mr.dispenseRequest.validityPeriod.start || "
|
|
2245
|
+
startDate = mr.dispenseRequest.validityPeriod.start || "";
|
|
2059
2246
|
} else {
|
|
2060
|
-
startDate = mr.authoredOn || "
|
|
2247
|
+
startDate = mr.authoredOn || "";
|
|
2248
|
+
}
|
|
2249
|
+
if (mr.medicationCodeableConcept && mr.medicationCodeableConcept.coding && mr.medicationCodeableConcept.coding[0]) {
|
|
2250
|
+
codeSystemDisplay = templateUtilities.codeableConceptCoding(mr.medicationCodeableConcept);
|
|
2061
2251
|
}
|
|
2062
2252
|
} else {
|
|
2063
2253
|
const ms = medication.resource;
|
|
@@ -2065,21 +2255,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
2065
2255
|
medicationName = templateUtilities.getMedicationName(
|
|
2066
2256
|
ms.medicationReference || ms.medicationCodeableConcept
|
|
2067
2257
|
);
|
|
2068
|
-
sig = templateUtilities.concat(ms.dosage, "text") || "
|
|
2258
|
+
sig = templateUtilities.concat(ms.dosage, "text") || "";
|
|
2069
2259
|
if (ms.effectiveDateTime) {
|
|
2070
2260
|
startDate = ms.effectiveDateTime;
|
|
2071
2261
|
} else if (ms.effectivePeriod) {
|
|
2072
|
-
startDate = ms.effectivePeriod.start || "
|
|
2262
|
+
startDate = ms.effectivePeriod.start || "";
|
|
2263
|
+
}
|
|
2264
|
+
if (ms.medicationCodeableConcept && ms.medicationCodeableConcept.coding && ms.medicationCodeableConcept.coding[0]) {
|
|
2265
|
+
codeSystemDisplay = templateUtilities.codeableConceptCoding(ms.medicationCodeableConcept);
|
|
2073
2266
|
}
|
|
2074
2267
|
}
|
|
2075
2268
|
html += `
|
|
2076
2269
|
<tr${narrativeLinkId ? ` id="${narrativeLinkId}"` : ""}>
|
|
2077
2270
|
<td>${type}</td>
|
|
2078
2271
|
<td>${medicationName}<ul></ul></td>
|
|
2272
|
+
<td>${codeSystemDisplay}</td>
|
|
2079
2273
|
<td>${sig}</td>
|
|
2080
2274
|
<td>${dispenseQuantity}</td>
|
|
2081
2275
|
<td>${refills}</td>
|
|
2082
2276
|
<td>${startDate}</td>
|
|
2277
|
+
<td>${templateUtilities.getOwnerTag(medication.resource)}</td>
|
|
2083
2278
|
</tr>`;
|
|
2084
2279
|
}
|
|
2085
2280
|
html += `
|
|
@@ -2116,18 +2311,23 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2116
2311
|
let isSummaryCreated = false;
|
|
2117
2312
|
let html = `
|
|
2118
2313
|
<div>
|
|
2314
|
+
<p>This list includes all vaccinations, sorted by occurrence date (most recent first).</p>
|
|
2119
2315
|
<table>
|
|
2120
2316
|
<thead>
|
|
2121
2317
|
<tr>
|
|
2122
2318
|
<th>Immunization</th>
|
|
2319
|
+
<th>Code (System)</th>
|
|
2123
2320
|
<th>Status</th>
|
|
2124
2321
|
<th>Date</th>
|
|
2322
|
+
<th>Source</th>
|
|
2125
2323
|
</tr>
|
|
2126
2324
|
</thead>
|
|
2127
2325
|
<tbody>`;
|
|
2128
2326
|
for (const resourceItem of resources) {
|
|
2129
2327
|
for (const rowData of resourceItem.section ?? []) {
|
|
2328
|
+
const sectionCodeableConcept = rowData.code;
|
|
2130
2329
|
const data = {};
|
|
2330
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
2131
2331
|
for (const columnData of rowData.section ?? []) {
|
|
2132
2332
|
switch (columnData.title) {
|
|
2133
2333
|
case "Immunization Name":
|
|
@@ -2139,6 +2339,9 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2139
2339
|
case "occurrenceDateTime":
|
|
2140
2340
|
data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2141
2341
|
break;
|
|
2342
|
+
case "Source":
|
|
2343
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2344
|
+
break;
|
|
2142
2345
|
default:
|
|
2143
2346
|
break;
|
|
2144
2347
|
}
|
|
@@ -2147,9 +2350,11 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2147
2350
|
isSummaryCreated = true;
|
|
2148
2351
|
html += `
|
|
2149
2352
|
<tr>
|
|
2150
|
-
<td>${data["immunization"] ?? "
|
|
2151
|
-
<td>${data["
|
|
2152
|
-
<td>${
|
|
2353
|
+
<td>${data["immunization"] ?? ""}</td>
|
|
2354
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
2355
|
+
<td>${data["status"] ?? ""}</td>
|
|
2356
|
+
<td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? ""}</td>
|
|
2357
|
+
<td>${data["source"] ?? ""}</td>
|
|
2153
2358
|
</tr>`;
|
|
2154
2359
|
}
|
|
2155
2360
|
}
|
|
@@ -2173,12 +2378,14 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2173
2378
|
<thead>
|
|
2174
2379
|
<tr>
|
|
2175
2380
|
<th>Immunization</th>
|
|
2381
|
+
<th>Code (System)</th>
|
|
2176
2382
|
<th>Status</th>
|
|
2177
2383
|
<th>Dose Number</th>
|
|
2178
2384
|
<th>Manufacturer</th>
|
|
2179
2385
|
<th>Lot Number</th>
|
|
2180
2386
|
<th>Comments</th>
|
|
2181
2387
|
<th>Date</th>
|
|
2388
|
+
<th>Source</th>
|
|
2182
2389
|
</tr>
|
|
2183
2390
|
</thead>
|
|
2184
2391
|
<tbody>`;
|
|
@@ -2187,14 +2394,16 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2187
2394
|
for (const resourceItem of immunizations) {
|
|
2188
2395
|
const imm = resourceItem;
|
|
2189
2396
|
html += `
|
|
2190
|
-
<tr
|
|
2191
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
2397
|
+
<tr>
|
|
2398
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(imm.vaccineCode))}</td>
|
|
2399
|
+
<td>${templateUtilities.codeableConceptCoding(imm.vaccineCode)}</td>
|
|
2192
2400
|
<td>${imm.status || ""}</td>
|
|
2193
2401
|
<td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
|
|
2194
2402
|
<td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
|
|
2195
2403
|
<td>${imm.lotNumber || ""}</td>
|
|
2196
2404
|
<td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
|
|
2197
2405
|
<td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
|
|
2406
|
+
<td>${templateUtilities.getOwnerTag(imm)}</td>
|
|
2198
2407
|
</tr>`;
|
|
2199
2408
|
}
|
|
2200
2409
|
}
|
|
@@ -2225,11 +2434,15 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
2225
2434
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2226
2435
|
static generateStaticNarrative(resources, timezone) {
|
|
2227
2436
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2228
|
-
let html =
|
|
2437
|
+
let html = `<p>This list includes patient problems, sorted by recorded date (most recent first)</p>
|
|
2438
|
+
`;
|
|
2229
2439
|
const activeConditions = resources.map((entry) => entry) || [];
|
|
2230
2440
|
activeConditions.sort((a, b) => {
|
|
2231
|
-
|
|
2232
|
-
|
|
2441
|
+
if (!a.recordedDate && b.recordedDate) return -1;
|
|
2442
|
+
if (a.recordedDate && !b.recordedDate) return 1;
|
|
2443
|
+
if (!a.recordedDate && !b.recordedDate) return 0;
|
|
2444
|
+
const dateA = new Date(a.recordedDate).getTime();
|
|
2445
|
+
const dateB = new Date(b.recordedDate).getTime();
|
|
2233
2446
|
return dateB - dateA;
|
|
2234
2447
|
});
|
|
2235
2448
|
html += `
|
|
@@ -2237,22 +2450,28 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
2237
2450
|
<thead>
|
|
2238
2451
|
<tr>
|
|
2239
2452
|
<th>Problem</th>
|
|
2453
|
+
<th>Code (System)</th>
|
|
2240
2454
|
<th>Onset Date</th>
|
|
2241
2455
|
<th>Recorded Date</th>
|
|
2456
|
+
<th>Source</th>
|
|
2242
2457
|
</tr>
|
|
2243
2458
|
</thead>
|
|
2244
2459
|
<tbody>`;
|
|
2245
|
-
const
|
|
2460
|
+
const seenCodeAndSystems = /* @__PURE__ */ new Set();
|
|
2246
2461
|
for (const cond of activeConditions) {
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
<td class="Name">${conditionCode}</td>
|
|
2252
|
-
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2253
|
-
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2254
|
-
</tr>`;
|
|
2462
|
+
const conditionDisplay = templateUtilities.codeableConceptDisplay(cond.code);
|
|
2463
|
+
const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
|
|
2464
|
+
if (codeAndSystem && seenCodeAndSystems.has(codeAndSystem)) {
|
|
2465
|
+
continue;
|
|
2255
2466
|
}
|
|
2467
|
+
seenCodeAndSystems.add(codeAndSystem);
|
|
2468
|
+
html += `<tr>
|
|
2469
|
+
<td class="Name">${conditionDisplay}</td>
|
|
2470
|
+
<td class="CodeSystem">${codeAndSystem}</td>
|
|
2471
|
+
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2472
|
+
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2473
|
+
<td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
|
|
2474
|
+
</tr>`;
|
|
2256
2475
|
}
|
|
2257
2476
|
html += `</tbody>
|
|
2258
2477
|
</table>`;
|
|
@@ -2280,21 +2499,26 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
2280
2499
|
generateSummaryNarrative(resources, timezone) {
|
|
2281
2500
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2282
2501
|
let isSummaryCreated = false;
|
|
2283
|
-
let html =
|
|
2502
|
+
let html = `<p>This list includes the latest vital signs, sorted by effective date (most recent first).</p>
|
|
2503
|
+
`;
|
|
2504
|
+
html += `
|
|
2284
2505
|
<div>
|
|
2285
2506
|
<table>
|
|
2286
2507
|
<thead>
|
|
2287
2508
|
<tr>
|
|
2288
|
-
<th>
|
|
2509
|
+
<th>Name</th>
|
|
2510
|
+
<th>Code (System)</th>
|
|
2289
2511
|
<th>Result</th>
|
|
2290
|
-
<th>Reference Range</th>
|
|
2291
2512
|
<th>Date</th>
|
|
2513
|
+
<th>Source</th>
|
|
2292
2514
|
</tr>
|
|
2293
2515
|
</thead>
|
|
2294
2516
|
<tbody>`;
|
|
2295
2517
|
for (const resourceItem of resources) {
|
|
2296
2518
|
for (const rowData of resourceItem.section ?? []) {
|
|
2519
|
+
const sectionCodeableConcept = rowData.code;
|
|
2297
2520
|
const data = {};
|
|
2521
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
2298
2522
|
for (const columnData of rowData.section ?? []) {
|
|
2299
2523
|
const columnTitle = columnData.title;
|
|
2300
2524
|
if (columnTitle) {
|
|
@@ -2322,10 +2546,11 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
2322
2546
|
isSummaryCreated = true;
|
|
2323
2547
|
html += `
|
|
2324
2548
|
<tr>
|
|
2325
|
-
<td>${data["Vital Name"] ?? "
|
|
2326
|
-
<td>${
|
|
2327
|
-
<td>${templateUtilities.
|
|
2328
|
-
<td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "
|
|
2549
|
+
<td>${data["Vital Name"] ?? ""}</td>
|
|
2550
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
2551
|
+
<td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? ""}</td>
|
|
2552
|
+
<td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
|
|
2553
|
+
<td>${data["Source"] ?? ""}</td>
|
|
2329
2554
|
</tr>`;
|
|
2330
2555
|
}
|
|
2331
2556
|
}
|
|
@@ -2349,30 +2574,36 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
2349
2574
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
2350
2575
|
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
2351
2576
|
});
|
|
2352
|
-
let html =
|
|
2577
|
+
let html = `<p>This list includes the latest vital signs, sorted by effective date (most recent first).</p>
|
|
2578
|
+
`;
|
|
2579
|
+
html += `
|
|
2353
2580
|
<table>
|
|
2354
2581
|
<thead>
|
|
2355
2582
|
<tr>
|
|
2356
|
-
<th>
|
|
2583
|
+
<th>Name</th>
|
|
2584
|
+
<th>Code (System)</th>
|
|
2357
2585
|
<th>Result</th>
|
|
2358
2586
|
<th>Unit</th>
|
|
2359
2587
|
<th>Interpretation</th>
|
|
2360
2588
|
<th>Component(s)</th>
|
|
2361
2589
|
<th>Comments</th>
|
|
2362
2590
|
<th>Date</th>
|
|
2591
|
+
<th>Source</th>
|
|
2363
2592
|
</tr>
|
|
2364
2593
|
</thead>
|
|
2365
2594
|
<tbody>`;
|
|
2366
2595
|
for (const obs of observations) {
|
|
2367
2596
|
html += `
|
|
2368
|
-
<tr
|
|
2369
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
2597
|
+
<tr>
|
|
2598
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code, "display"))}</td>
|
|
2599
|
+
<td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
|
|
2370
2600
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2371
2601
|
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
2372
2602
|
<td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
|
|
2373
2603
|
<td>${templateUtilities.renderComponent(obs.component)}</td>
|
|
2374
2604
|
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
2375
2605
|
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
2606
|
+
<td>${templateUtilities.getOwnerTag(obs)}</td>
|
|
2376
2607
|
</tr>`;
|
|
2377
2608
|
}
|
|
2378
2609
|
html += `
|
|
@@ -2401,7 +2632,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2401
2632
|
*/
|
|
2402
2633
|
static generateStaticNarrative(resources, timezone) {
|
|
2403
2634
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2404
|
-
let html =
|
|
2635
|
+
let html = `<p>This list includes all DeviceUseStatement resources, sorted by recorded date (most recent first).</p>
|
|
2405
2636
|
<table>
|
|
2406
2637
|
<thead>
|
|
2407
2638
|
<tr>
|
|
@@ -2419,7 +2650,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2419
2650
|
});
|
|
2420
2651
|
for (const dus of deviceStatements) {
|
|
2421
2652
|
html += `
|
|
2422
|
-
<tr
|
|
2653
|
+
<tr>
|
|
2423
2654
|
<td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
|
|
2424
2655
|
<td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
|
|
2425
2656
|
<td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
|
|
@@ -2445,10 +2676,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2445
2676
|
* Generate HTML narrative for Diagnostic Results
|
|
2446
2677
|
* @param resources - FHIR resources array containing Observation and DiagnosticReport resources
|
|
2447
2678
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2679
|
+
* @param now - Optional current date for filtering
|
|
2448
2680
|
* @returns HTML string for rendering
|
|
2449
2681
|
*/
|
|
2450
|
-
generateNarrative(resources, timezone) {
|
|
2451
|
-
return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
|
|
2682
|
+
generateNarrative(resources, timezone, now) {
|
|
2683
|
+
return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone, now);
|
|
2452
2684
|
}
|
|
2453
2685
|
/**
|
|
2454
2686
|
* Helper function to format observation data fields
|
|
@@ -2661,6 +2893,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2661
2893
|
break;
|
|
2662
2894
|
case "valueRange.high.value":
|
|
2663
2895
|
targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2896
|
+
targetData["valueType"] = "valueRange";
|
|
2664
2897
|
break;
|
|
2665
2898
|
case "valueRange.high.unit":
|
|
2666
2899
|
targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
@@ -2675,6 +2908,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2675
2908
|
break;
|
|
2676
2909
|
case "valueRatio.denominator.value":
|
|
2677
2910
|
targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2911
|
+
targetData["valueType"] = "valueRatio";
|
|
2678
2912
|
break;
|
|
2679
2913
|
case "valueRatio.denominator.unit":
|
|
2680
2914
|
targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
@@ -2712,34 +2946,100 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2712
2946
|
* Generate HTML narrative for Diagnostic Results & Observation resources using summary
|
|
2713
2947
|
* @param resources - FHIR Composition resources
|
|
2714
2948
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2949
|
+
* @param now - Optional current date for filtering
|
|
2715
2950
|
* @returns HTML string for rendering
|
|
2716
2951
|
*/
|
|
2717
|
-
generateSummaryNarrative(resources, timezone) {
|
|
2952
|
+
generateSummaryNarrative(resources, timezone, now) {
|
|
2718
2953
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2719
|
-
|
|
2720
|
-
|
|
2954
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
2955
|
+
const twoYearsAgo = new Date(currentDate);
|
|
2956
|
+
twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
|
|
2957
|
+
let skippedObservations = 0;
|
|
2958
|
+
let skippedDiagnosticReports = 0;
|
|
2959
|
+
for (const resourceItem of resources) {
|
|
2960
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2961
|
+
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
2962
|
+
for (const columnData of rowData.section ?? []) {
|
|
2963
|
+
if (columnData.text?.div === "Observation.component" && columnData.section) {
|
|
2964
|
+
for (const componentSection of columnData.section) {
|
|
2965
|
+
const componentData = {};
|
|
2966
|
+
for (const nestedColumn of componentSection.section ?? []) {
|
|
2967
|
+
this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
|
|
2968
|
+
}
|
|
2969
|
+
let compDate = void 0;
|
|
2970
|
+
if (componentData["effectiveDateTime"]) {
|
|
2971
|
+
compDate = new Date(componentData["effectiveDateTime"]);
|
|
2972
|
+
} else if (componentData["effectivePeriodStart"]) {
|
|
2973
|
+
compDate = new Date(componentData["effectivePeriodStart"]);
|
|
2974
|
+
}
|
|
2975
|
+
if (compDate && compDate < twoYearsAgo) {
|
|
2976
|
+
skippedObservations++;
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
} else {
|
|
2980
|
+
const data = {};
|
|
2981
|
+
this.extractSummaryObservationFields(columnData, data, templateUtilities);
|
|
2982
|
+
let obsDate = void 0;
|
|
2983
|
+
if (data["effectiveDateTime"]) {
|
|
2984
|
+
obsDate = new Date(data["effectiveDateTime"]);
|
|
2985
|
+
} else if (data["effectivePeriodStart"]) {
|
|
2986
|
+
obsDate = new Date(data["effectivePeriodStart"]);
|
|
2987
|
+
}
|
|
2988
|
+
if (obsDate && obsDate < twoYearsAgo) {
|
|
2989
|
+
skippedObservations++;
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2994
|
+
let issuedDate = void 0;
|
|
2995
|
+
let status = void 0;
|
|
2996
|
+
let reportFound = false;
|
|
2997
|
+
for (const columnData of rowData.section ?? []) {
|
|
2998
|
+
if (columnData.title === "Issued Date") {
|
|
2999
|
+
issuedDate = columnData.text?.div ? new Date(columnData.text.div) : void 0;
|
|
3000
|
+
}
|
|
3001
|
+
if (columnData.title === "Status") {
|
|
3002
|
+
status = columnData.text?.div;
|
|
3003
|
+
}
|
|
3004
|
+
if (columnData.title === "Diagnostic Report Name") {
|
|
3005
|
+
reportFound = true;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
if (status === "final" && issuedDate && issuedDate < twoYearsAgo && reportFound) {
|
|
3009
|
+
skippedDiagnosticReports++;
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
let html = `<p>This section includes Observations from the last 2 years (one per lab name, using code mapping) and DiagnosticReports with status 'final' issued in the last 2 years. Both are sorted by date (most recent first). Older results are counted and noted below the tables.</p>
|
|
3015
|
+
`;
|
|
2721
3016
|
let observationhtml = `
|
|
2722
3017
|
<div>
|
|
2723
3018
|
<h3>Observations</h3>
|
|
3019
|
+
${html}
|
|
2724
3020
|
<table>
|
|
2725
3021
|
<thead>
|
|
2726
3022
|
<tr>
|
|
2727
|
-
<th>
|
|
3023
|
+
<th>Name</th>
|
|
3024
|
+
<th>Code (System)</th>
|
|
2728
3025
|
<th>Result</th>
|
|
2729
3026
|
<th>Reference Range</th>
|
|
2730
3027
|
<th>Date</th>
|
|
3028
|
+
<th>Source</th>
|
|
2731
3029
|
</tr>
|
|
2732
3030
|
</thead>
|
|
2733
3031
|
<tbody>`;
|
|
2734
3032
|
let diagnosticReporthtml = `
|
|
2735
3033
|
<div>
|
|
2736
3034
|
<h3>Diagnostic Reports</h3>
|
|
3035
|
+
${html}
|
|
2737
3036
|
<table>
|
|
2738
3037
|
<thead>
|
|
2739
3038
|
<tr>
|
|
2740
3039
|
<th>Report</th>
|
|
2741
3040
|
<th>Performer</th>
|
|
2742
3041
|
<th>Issued</th>
|
|
3042
|
+
<th>Source</th>
|
|
2743
3043
|
</tr>
|
|
2744
3044
|
</thead>
|
|
2745
3045
|
<tbody>`;
|
|
@@ -2747,7 +3047,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2747
3047
|
const diagnosticReportAdded = /* @__PURE__ */ new Set();
|
|
2748
3048
|
for (const resourceItem of resources) {
|
|
2749
3049
|
for (const rowData of resourceItem.section ?? []) {
|
|
3050
|
+
const sectionCodeableConcept = rowData.code;
|
|
2750
3051
|
const data = {};
|
|
3052
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
2751
3053
|
const components = [];
|
|
2752
3054
|
for (const columnData of rowData.section ?? []) {
|
|
2753
3055
|
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
@@ -2757,7 +3059,13 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2757
3059
|
for (const nestedColumn of componentSection.section ?? []) {
|
|
2758
3060
|
this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
|
|
2759
3061
|
}
|
|
2760
|
-
|
|
3062
|
+
let compDate = void 0;
|
|
3063
|
+
if (componentData["effectiveDateTime"]) {
|
|
3064
|
+
compDate = new Date(componentData["effectiveDateTime"]);
|
|
3065
|
+
} else if (componentData["effectivePeriodStart"]) {
|
|
3066
|
+
compDate = new Date(componentData["effectivePeriodStart"]);
|
|
3067
|
+
}
|
|
3068
|
+
if (compDate && compDate >= twoYearsAgo && Object.keys(componentData).length > 0) {
|
|
2761
3069
|
components.push(componentData);
|
|
2762
3070
|
}
|
|
2763
3071
|
}
|
|
@@ -2778,6 +3086,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2778
3086
|
case "Status":
|
|
2779
3087
|
data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2780
3088
|
break;
|
|
3089
|
+
case "Source":
|
|
3090
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
3091
|
+
break;
|
|
2781
3092
|
default:
|
|
2782
3093
|
break;
|
|
2783
3094
|
}
|
|
@@ -2785,6 +3096,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2785
3096
|
}
|
|
2786
3097
|
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
2787
3098
|
let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
|
|
3099
|
+
let obsDate = void 0;
|
|
3100
|
+
if (data["effectiveDateTime"]) {
|
|
3101
|
+
obsDate = new Date(data["effectiveDateTime"]);
|
|
3102
|
+
} else if (data["effectivePeriodStart"]) {
|
|
3103
|
+
obsDate = new Date(data["effectivePeriodStart"]);
|
|
3104
|
+
}
|
|
2788
3105
|
if (!date && data["effectivePeriodStart"]) {
|
|
2789
3106
|
date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
|
|
2790
3107
|
if (data["effectivePeriodEnd"]) {
|
|
@@ -2801,36 +3118,47 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2801
3118
|
observationhtml += `
|
|
2802
3119
|
<tr>
|
|
2803
3120
|
<td>${componentCode}</td>
|
|
2804
|
-
<td
|
|
2805
|
-
<td>${templateUtilities.renderTextAsHtml(component["
|
|
2806
|
-
<td>${
|
|
3121
|
+
<td></td>
|
|
3122
|
+
<td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? ""}</td>
|
|
3123
|
+
<td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? ""}</td>
|
|
3124
|
+
<td>${date ?? ""}</td>
|
|
3125
|
+
<td>${data["source"] ?? ""}</td>
|
|
2807
3126
|
</tr>`;
|
|
2808
3127
|
}
|
|
2809
3128
|
}
|
|
2810
3129
|
} else {
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
observationAdded.
|
|
2814
|
-
|
|
2815
|
-
|
|
3130
|
+
if (obsDate && obsDate >= twoYearsAgo) {
|
|
3131
|
+
const code = data["code"] ?? "";
|
|
3132
|
+
if (code && !observationAdded.has(code)) {
|
|
3133
|
+
observationAdded.add(code);
|
|
3134
|
+
this.formatSummaryObservationData(data);
|
|
3135
|
+
observationhtml += `
|
|
2816
3136
|
<tr>
|
|
2817
|
-
<td>${data["code"] ?? "
|
|
2818
|
-
<td>${templateUtilities.
|
|
2819
|
-
<td>${templateUtilities.renderTextAsHtml(data["
|
|
2820
|
-
<td>${
|
|
3137
|
+
<td>${data["code"] ?? ""}</td>
|
|
3138
|
+
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
3139
|
+
<td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? ""}</td>
|
|
3140
|
+
<td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? ""}</td>
|
|
3141
|
+
<td>${date ?? ""}</td>
|
|
3142
|
+
<td>${data["source"] ?? ""}</td>
|
|
2821
3143
|
</tr>`;
|
|
3144
|
+
}
|
|
2822
3145
|
}
|
|
2823
3146
|
}
|
|
2824
3147
|
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2825
|
-
|
|
3148
|
+
let issuedDate = void 0;
|
|
3149
|
+
if (data["issued"]) {
|
|
3150
|
+
issuedDate = new Date(data["issued"]);
|
|
3151
|
+
}
|
|
3152
|
+
if (data["status"] === "final" && issuedDate && issuedDate >= twoYearsAgo) {
|
|
2826
3153
|
const reportName = data["report"] ?? "";
|
|
2827
3154
|
if (reportName && !diagnosticReportAdded.has(reportName)) {
|
|
2828
3155
|
diagnosticReportAdded.add(reportName);
|
|
2829
3156
|
diagnosticReporthtml += `
|
|
2830
3157
|
<tr>
|
|
2831
|
-
<td>${data["report"] ?? "
|
|
2832
|
-
<td>${data["performer"] ?? "
|
|
2833
|
-
<td>${templateUtilities.renderTime(data["issued"], timezone) ?? "
|
|
3158
|
+
<td>${data["report"] ?? ""}</td>
|
|
3159
|
+
<td>${data["performer"] ?? ""}</td>
|
|
3160
|
+
<td>${templateUtilities.renderTime(data["issued"], timezone) ?? ""}</td>
|
|
3161
|
+
<td>${data["source"] ?? ""}</td>
|
|
2834
3162
|
</tr>`;
|
|
2835
3163
|
}
|
|
2836
3164
|
}
|
|
@@ -2843,6 +3171,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2843
3171
|
</tbody>
|
|
2844
3172
|
</table>
|
|
2845
3173
|
</div>`;
|
|
3174
|
+
if (skippedObservations > 0) {
|
|
3175
|
+
html += `
|
|
3176
|
+
<p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
|
|
3177
|
+
}
|
|
2846
3178
|
}
|
|
2847
3179
|
if (diagnosticReportAdded.size > 0) {
|
|
2848
3180
|
html += diagnosticReporthtml;
|
|
@@ -2850,6 +3182,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2850
3182
|
</tbody>
|
|
2851
3183
|
</table>
|
|
2852
3184
|
</div>`;
|
|
3185
|
+
if (skippedDiagnosticReports > 0) {
|
|
3186
|
+
html += `
|
|
3187
|
+
<p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
|
|
3188
|
+
}
|
|
2853
3189
|
}
|
|
2854
3190
|
html += `
|
|
2855
3191
|
</div>`;
|
|
@@ -2859,12 +3195,33 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2859
3195
|
* Internal static implementation that actually generates the narrative
|
|
2860
3196
|
* @param resources - FHIR resources array containing Observation and DiagnosticReport resources
|
|
2861
3197
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3198
|
+
* @param now - Optional current date for filtering
|
|
2862
3199
|
* @returns HTML string for rendering
|
|
2863
3200
|
*/
|
|
2864
|
-
static generateStaticNarrative(resources, timezone) {
|
|
3201
|
+
static generateStaticNarrative(resources, timezone, now) {
|
|
2865
3202
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2866
|
-
|
|
2867
|
-
const
|
|
3203
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
3204
|
+
const twoYearsAgo = new Date(currentDate);
|
|
3205
|
+
twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
|
|
3206
|
+
let html = `<p>This section includes Observations from the last 2 years (one per lab name, using code mapping) and DiagnosticReports with status 'final' issued in the last 2 years. Both are sorted by date (most recent first). Older results are counted and noted below the tables.</p>
|
|
3207
|
+
`;
|
|
3208
|
+
let skippedObservations = 0;
|
|
3209
|
+
let skippedDiagnosticReports = 0;
|
|
3210
|
+
for (const resourceItem of resources) {
|
|
3211
|
+
if (resourceItem.resourceType === "Observation") {
|
|
3212
|
+
const obsDate = this.getObservationDate(resourceItem);
|
|
3213
|
+
if (obsDate && obsDate < twoYearsAgo) {
|
|
3214
|
+
skippedObservations++;
|
|
3215
|
+
}
|
|
3216
|
+
} else if (resourceItem.resourceType === "DiagnosticReport") {
|
|
3217
|
+
const issued = resourceItem.issued;
|
|
3218
|
+
const status = resourceItem.status;
|
|
3219
|
+
if (status === "final" && issued && new Date(issued) < twoYearsAgo) {
|
|
3220
|
+
skippedDiagnosticReports++;
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
const observations = this.getObservations(resources, twoYearsAgo);
|
|
2868
3225
|
if (observations.length > 0) {
|
|
2869
3226
|
observations.sort((a, b) => {
|
|
2870
3227
|
const dateA = this.getObservationDate(a);
|
|
@@ -2873,16 +3230,22 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2873
3230
|
});
|
|
2874
3231
|
this.filterObservationForLoincCodes(observations);
|
|
2875
3232
|
html += this.renderObservations(templateUtilities, observations, timezone);
|
|
3233
|
+
if (skippedObservations > 0) {
|
|
3234
|
+
html += `
|
|
3235
|
+
<p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
|
|
3236
|
+
}
|
|
2876
3237
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
3238
|
+
const diagnosticReports = this.getDiagnosticReports(resources, twoYearsAgo).filter((resource) => !this.isPanelDiagnosticReport(resource));
|
|
3239
|
+
if (diagnosticReports.length > 0) {
|
|
3240
|
+
diagnosticReports.sort((a, b) => {
|
|
3241
|
+
const dateA = a.issued;
|
|
3242
|
+
const dateB = b.issued;
|
|
3243
|
+
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3244
|
+
});
|
|
3245
|
+
html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
|
|
3246
|
+
if (skippedDiagnosticReports > 0) {
|
|
3247
|
+
html += `
|
|
3248
|
+
<p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
|
|
2886
3249
|
}
|
|
2887
3250
|
}
|
|
2888
3251
|
return html;
|
|
@@ -2927,15 +3290,16 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2927
3290
|
return obsDate;
|
|
2928
3291
|
}
|
|
2929
3292
|
/**
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3293
|
+
* Get all Observation resources from the resource array, filtered by twoYearsAgo
|
|
3294
|
+
* @param resources - FHIR resources array
|
|
3295
|
+
* @param twoYearsAgo - Date object representing the cutoff
|
|
3296
|
+
* @returns Array of Observation resources
|
|
3297
|
+
*/
|
|
3298
|
+
static getObservations(resources, twoYearsAgo) {
|
|
2935
3299
|
return resources.filter((resourceItem) => {
|
|
2936
3300
|
if (resourceItem.resourceType === "Observation") {
|
|
2937
3301
|
const obsDate = this.getObservationDate(resourceItem);
|
|
2938
|
-
if (obsDate && obsDate >=
|
|
3302
|
+
if (obsDate && obsDate >= twoYearsAgo) {
|
|
2939
3303
|
return true;
|
|
2940
3304
|
}
|
|
2941
3305
|
}
|
|
@@ -2943,12 +3307,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2943
3307
|
}).map((resourceItem) => resourceItem);
|
|
2944
3308
|
}
|
|
2945
3309
|
/**
|
|
2946
|
-
* Get all DiagnosticReport resources from the resource array
|
|
3310
|
+
* Get all DiagnosticReport resources from the resource array, filtered by twoYearsAgo
|
|
2947
3311
|
* @param resources - FHIR resources array
|
|
3312
|
+
* @param twoYearsAgo - Date object representing the cutoff
|
|
2948
3313
|
* @returns Array of DiagnosticReport resources
|
|
2949
3314
|
*/
|
|
2950
|
-
static getDiagnosticReports(resources) {
|
|
2951
|
-
return resources.filter((resourceItem) =>
|
|
3315
|
+
static getDiagnosticReports(resources, twoYearsAgo) {
|
|
3316
|
+
return resources.filter((resourceItem) => {
|
|
3317
|
+
if (resourceItem.resourceType === "DiagnosticReport") {
|
|
3318
|
+
const issued = resourceItem.issued;
|
|
3319
|
+
if (issued && new Date(issued) >= twoYearsAgo) {
|
|
3320
|
+
return true;
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
return false;
|
|
3324
|
+
}).map((resourceItem) => resourceItem);
|
|
2952
3325
|
}
|
|
2953
3326
|
/**
|
|
2954
3327
|
* Render HTML table for Observation resources
|
|
@@ -2959,32 +3332,36 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2959
3332
|
*/
|
|
2960
3333
|
static renderObservations(templateUtilities, observations, timezone) {
|
|
2961
3334
|
let html = "";
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
<h3>Observations</h3>`;
|
|
2965
|
-
}
|
|
3335
|
+
html += `
|
|
3336
|
+
<h3>Observations</h3>`;
|
|
2966
3337
|
html += `
|
|
2967
3338
|
<table>
|
|
2968
3339
|
<thead>
|
|
2969
3340
|
<tr>
|
|
2970
|
-
<th>
|
|
3341
|
+
<th>Name</th>
|
|
3342
|
+
<th>Code (System)</th>
|
|
2971
3343
|
<th>Result</th>
|
|
2972
3344
|
<th>Reference Range</th>
|
|
2973
3345
|
<th>Date</th>
|
|
3346
|
+
<th>Source</th>
|
|
2974
3347
|
</tr>
|
|
2975
3348
|
</thead>
|
|
2976
3349
|
<tbody>`;
|
|
2977
3350
|
const observationAdded = /* @__PURE__ */ new Set();
|
|
2978
3351
|
for (const obs of observations) {
|
|
2979
|
-
const
|
|
2980
|
-
|
|
2981
|
-
|
|
3352
|
+
const obsCodeDisplay = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code));
|
|
3353
|
+
const obsCodeAndSystem = templateUtilities.codeableConceptCoding(obs.code);
|
|
3354
|
+
if (!observationAdded.has(obsCodeDisplay) && !observationAdded.has(obsCodeAndSystem)) {
|
|
3355
|
+
observationAdded.add(obsCodeDisplay);
|
|
3356
|
+
observationAdded.add(obsCodeAndSystem);
|
|
2982
3357
|
html += `
|
|
2983
3358
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2984
|
-
<td>${
|
|
3359
|
+
<td>${obsCodeDisplay}</td>
|
|
3360
|
+
<td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
|
|
2985
3361
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2986
3362
|
<td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
|
|
2987
3363
|
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
3364
|
+
<td>${templateUtilities.getOwnerTag(obs)}</td>
|
|
2988
3365
|
</tr>`;
|
|
2989
3366
|
}
|
|
2990
3367
|
}
|
|
@@ -3007,17 +3384,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
3007
3384
|
<thead>
|
|
3008
3385
|
<tr>
|
|
3009
3386
|
<th>Report</th>
|
|
3387
|
+
<th>Code (System)</th>
|
|
3010
3388
|
<th>Category</th>
|
|
3011
3389
|
<th>Result</th>
|
|
3012
3390
|
<th>Issued</th>
|
|
3391
|
+
<th>Source</th>
|
|
3013
3392
|
</tr>
|
|
3014
3393
|
</thead>
|
|
3015
3394
|
<tbody>`;
|
|
3016
3395
|
const diagnosticReportAdded = /* @__PURE__ */ new Set();
|
|
3017
3396
|
for (const report of reports) {
|
|
3018
|
-
const reportName = templateUtilities.renderTextAsHtml(templateUtilities.
|
|
3019
|
-
|
|
3397
|
+
const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(report.code));
|
|
3398
|
+
const codeAndSystem = templateUtilities.codeableConceptCoding(report.code);
|
|
3399
|
+
if (!diagnosticReportAdded.has(reportName) && !diagnosticReportAdded.has(codeAndSystem)) {
|
|
3020
3400
|
diagnosticReportAdded.add(reportName);
|
|
3401
|
+
diagnosticReportAdded.add(codeAndSystem);
|
|
3021
3402
|
let resultCount = "";
|
|
3022
3403
|
if (report.result && Array.isArray(report.result)) {
|
|
3023
3404
|
resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
|
|
@@ -3025,9 +3406,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
3025
3406
|
html += `
|
|
3026
3407
|
<tr id="${templateUtilities.narrativeLinkId(report)}">
|
|
3027
3408
|
<td>${reportName}</td>
|
|
3409
|
+
<td>${codeAndSystem}</td>
|
|
3028
3410
|
<td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
|
|
3029
3411
|
<td>${resultCount}</td>
|
|
3030
3412
|
<td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
|
|
3413
|
+
<td>${templateUtilities.getOwnerTag(report)}</td>
|
|
3031
3414
|
</tr>`;
|
|
3032
3415
|
}
|
|
3033
3416
|
}
|
|
@@ -3036,6 +3419,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
3036
3419
|
</table>`;
|
|
3037
3420
|
return html;
|
|
3038
3421
|
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Helper to determine if a DiagnosticReport is just a panel (i.e., only references Observations, has no conclusion, and code is a known panel code)
|
|
3424
|
+
* @returns true if the report is just a panel
|
|
3425
|
+
*/
|
|
3426
|
+
static isPanelDiagnosticReport(report) {
|
|
3427
|
+
return this.hasEssentialLabPanelLoinc(
|
|
3428
|
+
report
|
|
3429
|
+
);
|
|
3430
|
+
}
|
|
3431
|
+
/**
|
|
3432
|
+
* Check if a DiagnosticReport or Observation has a LOINC code in ESSENTIAL_LAB_PANELS
|
|
3433
|
+
* @param resource - DiagnosticReport or Observation
|
|
3434
|
+
* @returns true if any LOINC code is in ESSENTIAL_LAB_PANELS, false otherwise
|
|
3435
|
+
*/
|
|
3436
|
+
static hasEssentialLabPanelLoinc(resource) {
|
|
3437
|
+
const codings = resource?.code?.coding ?? [];
|
|
3438
|
+
return codings.some(
|
|
3439
|
+
(coding) => coding && coding.system && coding.code && coding.system.toLowerCase().includes("loinc") && Object.keys(ESSENTIAL_LAB_PANELS).includes(coding.code)
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3039
3442
|
};
|
|
3040
3443
|
|
|
3041
3444
|
// src/narratives/templates/typescript/HistoryOfProceduresTemplate.ts
|
|
@@ -3063,20 +3466,26 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
3063
3466
|
generateSummaryNarrative(resources, timezone) {
|
|
3064
3467
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3065
3468
|
let isSummaryCreated = false;
|
|
3066
|
-
let html =
|
|
3469
|
+
let html = `<p>This list includes all Procedure resources, sorted by performed date (most recent first).</p>
|
|
3470
|
+
`;
|
|
3471
|
+
html += `
|
|
3067
3472
|
<div>
|
|
3068
3473
|
<table>
|
|
3069
3474
|
<thead>
|
|
3070
3475
|
<tr>
|
|
3071
3476
|
<th>Procedure</th>
|
|
3477
|
+
<th>Code (System)</th>
|
|
3072
3478
|
<th>Performer</th>
|
|
3073
3479
|
<th>Date</th>
|
|
3480
|
+
<th>Source</th>
|
|
3074
3481
|
</tr>
|
|
3075
3482
|
</thead>
|
|
3076
3483
|
<tbody>`;
|
|
3077
3484
|
for (const resourceItem of resources) {
|
|
3078
3485
|
for (const rowData of resourceItem.section ?? []) {
|
|
3486
|
+
const sectionCodeableConcept = rowData.code;
|
|
3079
3487
|
const data = {};
|
|
3488
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
3080
3489
|
for (const columnData of rowData.section ?? []) {
|
|
3081
3490
|
switch (columnData.title) {
|
|
3082
3491
|
case "Procedure Name":
|
|
@@ -3088,6 +3497,9 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
3088
3497
|
case "Performed Date":
|
|
3089
3498
|
data["date"] = columnData.text?.div ?? "";
|
|
3090
3499
|
break;
|
|
3500
|
+
case "Source":
|
|
3501
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
3502
|
+
break;
|
|
3091
3503
|
default:
|
|
3092
3504
|
break;
|
|
3093
3505
|
}
|
|
@@ -3095,9 +3507,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
3095
3507
|
isSummaryCreated = true;
|
|
3096
3508
|
html += `
|
|
3097
3509
|
<tr>
|
|
3098
|
-
<td>${data["procedure"] ?? "
|
|
3099
|
-
|
|
3100
|
-
<td>${
|
|
3510
|
+
<td>${data["procedure"] ?? ""}</td>
|
|
3511
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
3512
|
+
<td>${data["performer"] ?? ""}</td>
|
|
3513
|
+
<td>${templateUtilities.renderTime(data["date"], timezone) ?? ""}</td>
|
|
3514
|
+
<td>${data["source"] ?? ""}</td>
|
|
3101
3515
|
</tr>`;
|
|
3102
3516
|
}
|
|
3103
3517
|
}
|
|
@@ -3115,23 +3529,29 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
3115
3529
|
*/
|
|
3116
3530
|
static generateStaticNarrative(resources, timezone) {
|
|
3117
3531
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3118
|
-
let html =
|
|
3532
|
+
let html = `<p>This list includes all Procedure resources, sorted by performed date (most recent first).</p>
|
|
3533
|
+
`;
|
|
3534
|
+
html += `
|
|
3119
3535
|
<table>
|
|
3120
3536
|
<thead>
|
|
3121
3537
|
<tr>
|
|
3122
3538
|
<th>Procedure</th>
|
|
3539
|
+
<th>Code (System)</th>
|
|
3123
3540
|
<th>Comments</th>
|
|
3124
3541
|
<th>Date</th>
|
|
3542
|
+
<th>Source</th>
|
|
3125
3543
|
</tr>
|
|
3126
3544
|
</thead>
|
|
3127
3545
|
<tbody>`;
|
|
3128
3546
|
for (const resourceItem of resources) {
|
|
3129
3547
|
const proc = resourceItem;
|
|
3130
3548
|
html += `
|
|
3131
|
-
<tr
|
|
3132
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
3549
|
+
<tr>
|
|
3550
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(proc.code, "display"))}</td>
|
|
3551
|
+
<td>${templateUtilities.codeableConceptCoding(proc.code)}</td>
|
|
3133
3552
|
<td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
|
|
3134
|
-
<td>${
|
|
3553
|
+
<td>${templateUtilities.renderTime(proc.performedDateTime || proc.performedPeriod?.start, timezone)}</td>
|
|
3554
|
+
<td>${templateUtilities.getOwnerTag(proc)}</td>
|
|
3135
3555
|
</tr>`;
|
|
3136
3556
|
}
|
|
3137
3557
|
html += `
|
|
@@ -3166,26 +3586,32 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
|
|
|
3166
3586
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
3167
3587
|
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3168
3588
|
});
|
|
3169
|
-
let html =
|
|
3589
|
+
let html = `<p>This list includes all information about the patient's social history, sorted by effective date (most recent first).</p>
|
|
3590
|
+
`;
|
|
3591
|
+
html += `
|
|
3170
3592
|
<table>
|
|
3171
3593
|
<thead>
|
|
3172
3594
|
<tr>
|
|
3173
|
-
<th>
|
|
3595
|
+
<th>Name</th>
|
|
3596
|
+
<th>Code (System)</th>
|
|
3174
3597
|
<th>Result</th>
|
|
3175
3598
|
<th>Unit</th>
|
|
3176
3599
|
<th>Comments</th>
|
|
3177
3600
|
<th>Date</th>
|
|
3601
|
+
<th>Source</th>
|
|
3178
3602
|
</tr>
|
|
3179
3603
|
</thead>
|
|
3180
3604
|
<tbody>`;
|
|
3181
3605
|
for (const obs of observations) {
|
|
3182
3606
|
html += `
|
|
3183
|
-
<tr
|
|
3184
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
3607
|
+
<tr>
|
|
3608
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code))}</td>
|
|
3609
|
+
<td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
|
|
3185
3610
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
3186
3611
|
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
3187
3612
|
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
3188
3613
|
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
3614
|
+
<td>${templateUtilities.getOwnerTag(obs)}</td>
|
|
3189
3615
|
</tr>`;
|
|
3190
3616
|
}
|
|
3191
3617
|
html += `
|
|
@@ -3201,14 +3627,27 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
3201
3627
|
* Generate HTML narrative for Past History of Illnesses
|
|
3202
3628
|
* @param resources - FHIR Condition resources
|
|
3203
3629
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3630
|
+
* @param now - Optional current date to use for generating relative dates in the narrative
|
|
3204
3631
|
* @returns HTML string for rendering
|
|
3205
3632
|
*/
|
|
3206
|
-
|
|
3207
|
-
generateNarrative(resources, timezone) {
|
|
3633
|
+
generateNarrative(resources, timezone, now) {
|
|
3208
3634
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3209
|
-
let html =
|
|
3635
|
+
let html = `<p>This list includes past problems for the patient with a recorded date within the last 5 years, sorted by recorded date (most recent first).</p>
|
|
3636
|
+
`;
|
|
3210
3637
|
const resolvedConditions = resources.map((entry) => entry) || [];
|
|
3211
|
-
|
|
3638
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
3639
|
+
const fiveYearsAgo = new Date(currentDate);
|
|
3640
|
+
fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
|
|
3641
|
+
let skippedConditions = 0;
|
|
3642
|
+
const filteredConditions = [];
|
|
3643
|
+
for (const cond of resolvedConditions) {
|
|
3644
|
+
if (cond.recordedDate && new Date(cond.recordedDate) >= fiveYearsAgo) {
|
|
3645
|
+
filteredConditions.push(cond);
|
|
3646
|
+
} else {
|
|
3647
|
+
skippedConditions++;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
filteredConditions.sort((a, b) => {
|
|
3212
3651
|
const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
|
|
3213
3652
|
const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
|
|
3214
3653
|
return dateB - dateA;
|
|
@@ -3218,27 +3657,35 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
3218
3657
|
<thead>
|
|
3219
3658
|
<tr>
|
|
3220
3659
|
<th>Problem</th>
|
|
3660
|
+
<th>Code (System)</th>
|
|
3221
3661
|
<th>Onset Date</th>
|
|
3222
3662
|
<th>Recorded Date</th>
|
|
3223
3663
|
<th>Resolved Date</th>
|
|
3664
|
+
<th>Source</th>
|
|
3224
3665
|
</tr>
|
|
3225
3666
|
</thead>
|
|
3226
3667
|
<tbody>`;
|
|
3227
3668
|
const addedConditionCodes = /* @__PURE__ */ new Set();
|
|
3228
|
-
for (const cond of
|
|
3229
|
-
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.
|
|
3669
|
+
for (const cond of filteredConditions) {
|
|
3670
|
+
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(cond.code));
|
|
3230
3671
|
if (!addedConditionCodes.has(conditionCode)) {
|
|
3231
3672
|
addedConditionCodes.add(conditionCode);
|
|
3232
|
-
html += `<tr
|
|
3673
|
+
html += `<tr>
|
|
3233
3674
|
<td class="Name">${conditionCode}</td>
|
|
3675
|
+
<td class="CodeSystem">${templateUtilities.codeableConceptCoding(cond.code)}</td>
|
|
3234
3676
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
3235
3677
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
3236
3678
|
<td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
|
|
3679
|
+
<td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
|
|
3237
3680
|
</tr>`;
|
|
3238
3681
|
}
|
|
3239
3682
|
}
|
|
3240
3683
|
html += `</tbody>
|
|
3241
3684
|
</table>`;
|
|
3685
|
+
if (skippedConditions > 0) {
|
|
3686
|
+
html += `
|
|
3687
|
+
<p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
|
|
3688
|
+
}
|
|
3242
3689
|
return html;
|
|
3243
3690
|
}
|
|
3244
3691
|
};
|
|
@@ -3259,7 +3706,9 @@ var PlanOfCareTemplate = class {
|
|
|
3259
3706
|
const endB = b.period?.end ? new Date(b.period?.end).getTime() : 0;
|
|
3260
3707
|
return endB - endA;
|
|
3261
3708
|
});
|
|
3262
|
-
let html =
|
|
3709
|
+
let html = `<p>This list includes all CarePlan resources, sorted by planned end date (most recent first).</p>
|
|
3710
|
+
`;
|
|
3711
|
+
html += `
|
|
3263
3712
|
<table>
|
|
3264
3713
|
<thead>
|
|
3265
3714
|
<tr>
|
|
@@ -3268,6 +3717,7 @@ var PlanOfCareTemplate = class {
|
|
|
3268
3717
|
<th>Comments</th>
|
|
3269
3718
|
<th>Planned Start</th>
|
|
3270
3719
|
<th>Planned End</th>
|
|
3720
|
+
<th>Source</th>
|
|
3271
3721
|
</tr>
|
|
3272
3722
|
</thead>
|
|
3273
3723
|
<tbody>`;
|
|
@@ -3279,6 +3729,7 @@ var PlanOfCareTemplate = class {
|
|
|
3279
3729
|
<td>${templateUtilities.concat(cp.note, "text")}</td>
|
|
3280
3730
|
<td>${cp.period?.start ? templateUtilities.renderTime(cp.period?.start, timezone) : ""}</td>
|
|
3281
3731
|
<td>${cp.period?.end ? templateUtilities.renderTime(cp.period?.end, timezone) : ""}</td>
|
|
3732
|
+
<td>${templateUtilities.getOwnerTag(cp)}</td>
|
|
3282
3733
|
</tr>`;
|
|
3283
3734
|
}
|
|
3284
3735
|
html += `
|
|
@@ -3295,7 +3746,9 @@ var PlanOfCareTemplate = class {
|
|
|
3295
3746
|
generateSummaryNarrative(resources, timezone) {
|
|
3296
3747
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3297
3748
|
let isSummaryCreated = false;
|
|
3298
|
-
let html =
|
|
3749
|
+
let html = `<p>This list includes all CarePlan resources, sorted by planned end date (most recent first).</p>
|
|
3750
|
+
`;
|
|
3751
|
+
html += `
|
|
3299
3752
|
<div>
|
|
3300
3753
|
<table>
|
|
3301
3754
|
<thead>
|
|
@@ -3304,6 +3757,7 @@ var PlanOfCareTemplate = class {
|
|
|
3304
3757
|
<th>Created</th>
|
|
3305
3758
|
<th>Planned Start</th>
|
|
3306
3759
|
<th>Planned End</th>
|
|
3760
|
+
<th>Source</th>
|
|
3307
3761
|
</tr>
|
|
3308
3762
|
</thead>
|
|
3309
3763
|
<tbody>`;
|
|
@@ -3321,10 +3775,11 @@ var PlanOfCareTemplate = class {
|
|
|
3321
3775
|
isSummaryCreated = true;
|
|
3322
3776
|
html += `
|
|
3323
3777
|
<tr>
|
|
3324
|
-
<td>${data["CarePlan Name"] ?? "
|
|
3325
|
-
<td>${templateUtilities.renderTime(data["created"], timezone) ?? "
|
|
3326
|
-
<td>${templateUtilities.renderTime(data["period.start"], timezone) ?? "
|
|
3327
|
-
<td>${templateUtilities.renderTime(data["period.end"], timezone) ?? "
|
|
3778
|
+
<td>${data["CarePlan Name"] ?? ""}</td>
|
|
3779
|
+
<td>${templateUtilities.renderTime(data["created"], timezone) ?? ""}</td>
|
|
3780
|
+
<td>${templateUtilities.renderTime(data["period.start"], timezone) ?? ""}</td>
|
|
3781
|
+
<td>${templateUtilities.renderTime(data["period.end"], timezone) ?? ""}</td>
|
|
3782
|
+
<td>${data["source"] ?? ""}</td>
|
|
3328
3783
|
</tr>`;
|
|
3329
3784
|
}
|
|
3330
3785
|
}
|
|
@@ -3355,77 +3810,54 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
3355
3810
|
*/
|
|
3356
3811
|
static generateStaticNarrative(resources, timezone) {
|
|
3357
3812
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3358
|
-
let html =
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
|
|
3370
|
-
const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
|
|
3371
|
-
return dateB - dateA;
|
|
3813
|
+
let html = `<p>This section summarizes key observations and assessments related to the person's functional status and ability to perform daily activities.</p>`;
|
|
3814
|
+
let functionalObservations = resources.filter((r) => r.resourceType === "Observation").filter((r) => {
|
|
3815
|
+
const hasFunctionalLoinc = r.code?.coding?.some(
|
|
3816
|
+
(c) => c.system?.toLowerCase().includes("loinc") && c.code === "47420-5"
|
|
3817
|
+
);
|
|
3818
|
+
const hasFunctionalCategory = r.category?.some(
|
|
3819
|
+
(cat) => cat.coding?.some(
|
|
3820
|
+
(c) => c.code === "functional-status" || c.display?.toLowerCase().includes("functional")
|
|
3821
|
+
)
|
|
3822
|
+
);
|
|
3823
|
+
return hasFunctionalLoinc || hasFunctionalCategory;
|
|
3372
3824
|
});
|
|
3373
|
-
|
|
3374
|
-
const
|
|
3375
|
-
|
|
3376
|
-
return dateB - dateA;
|
|
3825
|
+
functionalObservations = functionalObservations.sort((a, b) => {
|
|
3826
|
+
const getObsDate = (obs) => obs.effectiveDateTime ? new Date(obs.effectiveDateTime).getTime() : obs.issued ? new Date(obs.issued).getTime() : 0;
|
|
3827
|
+
return getObsDate(b) - getObsDate(a);
|
|
3377
3828
|
});
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
const
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
}
|
|
3829
|
+
let clinicalImpressions = resources.filter((r) => r.resourceType === "ClinicalImpression").filter((r) => r.status === "completed");
|
|
3830
|
+
clinicalImpressions = clinicalImpressions.sort((a, b) => {
|
|
3831
|
+
const getImpressionDate = (ci) => ci.effectiveDateTime ? new Date(ci.effectiveDateTime).getTime() : ci.effectivePeriod?.end ? new Date(ci.effectivePeriod.end).getTime() : ci.date ? new Date(ci.date).getTime() : 0;
|
|
3832
|
+
return getImpressionDate(b) - getImpressionDate(a);
|
|
3833
|
+
});
|
|
3834
|
+
if (functionalObservations.length > 0) {
|
|
3835
|
+
html += `<table><thead><tr><th>Observation</th><th>Value</th><th>Date</th><th>Interpretation</th><th>Comments</th></tr></thead><tbody>`;
|
|
3836
|
+
for (const obs of functionalObservations) {
|
|
3837
|
+
const observation = obs;
|
|
3838
|
+
const obsName = templateUtilities.codeableConceptDisplay(observation.code);
|
|
3839
|
+
const value = templateUtilities.extractObservationValue(observation);
|
|
3840
|
+
const date = observation.effectiveDateTime ? templateUtilities.renderDate(observation.effectiveDateTime) : observation.issued ? templateUtilities.renderDate(observation.issued) : "";
|
|
3841
|
+
const interpretation = observation.interpretation ? templateUtilities.codeableConceptDisplay(observation.interpretation[0]) : "";
|
|
3842
|
+
const comments = observation.comment || observation.note?.map((n) => n.text).join("; ") || "";
|
|
3843
|
+
html += `<tr id="${templateUtilities.narrativeLinkId(observation)}">
|
|
3844
|
+
<td>${obsName}</td>
|
|
3845
|
+
<td>${value ?? ""}</td>
|
|
3846
|
+
<td>${date}</td>
|
|
3847
|
+
<td>${interpretation}</td>
|
|
3848
|
+
<td>${comments}</td>
|
|
3849
|
+
</tr>`;
|
|
3400
3850
|
}
|
|
3401
|
-
html += `</tbody
|
|
3402
|
-
</table>`;
|
|
3851
|
+
html += `</tbody></table>`;
|
|
3403
3852
|
}
|
|
3404
3853
|
if (clinicalImpressions.length > 0) {
|
|
3405
|
-
html += `<
|
|
3406
|
-
<table>
|
|
3407
|
-
<thead>
|
|
3408
|
-
<tr>
|
|
3409
|
-
<th>Date</th>
|
|
3410
|
-
<th>Status</th>
|
|
3411
|
-
<th>Description</th>
|
|
3412
|
-
<th>Summary</th>
|
|
3413
|
-
<th>Findings</th>
|
|
3414
|
-
</tr>
|
|
3415
|
-
</thead>
|
|
3416
|
-
<tbody>`;
|
|
3854
|
+
html += `<table><thead><tr><th>Date</th><th>Status</th><th>Description</th><th>Summary</th><th>Findings</th></tr></thead><tbody>`;
|
|
3417
3855
|
for (const impression of clinicalImpressions) {
|
|
3418
3856
|
let formattedDate = "";
|
|
3419
3857
|
if (impression.effectiveDateTime) {
|
|
3420
|
-
formattedDate = templateUtilities.renderTime(
|
|
3421
|
-
impression.effectiveDateTime,
|
|
3422
|
-
timezone
|
|
3423
|
-
);
|
|
3858
|
+
formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
|
|
3424
3859
|
} else if (impression.effectivePeriod) {
|
|
3425
|
-
formattedDate = templateUtilities.renderPeriod(
|
|
3426
|
-
impression.effectivePeriod,
|
|
3427
|
-
timezone
|
|
3428
|
-
);
|
|
3860
|
+
formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
|
|
3429
3861
|
} else if (impression.date) {
|
|
3430
3862
|
formattedDate = templateUtilities.renderDate(impression.date);
|
|
3431
3863
|
}
|
|
@@ -3433,23 +3865,24 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
3433
3865
|
if (impression.finding && impression.finding.length > 0) {
|
|
3434
3866
|
findingsHtml = "<ul>";
|
|
3435
3867
|
for (const finding of impression.finding) {
|
|
3436
|
-
const findingText = finding.itemCodeableConcept ? templateUtilities.
|
|
3868
|
+
const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
|
|
3437
3869
|
const cause = finding.basis || "";
|
|
3438
3870
|
findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
|
|
3439
3871
|
}
|
|
3440
3872
|
findingsHtml += "</ul>";
|
|
3441
3873
|
}
|
|
3442
|
-
html +=
|
|
3443
|
-
<
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
</tr>`;
|
|
3874
|
+
html += `<tr id="${templateUtilities.narrativeLinkId(impression)}">
|
|
3875
|
+
<td>${formattedDate}</td>
|
|
3876
|
+
<td>${impression.status || ""}</td>
|
|
3877
|
+
<td>${impression.description || ""}</td>
|
|
3878
|
+
<td>${impression.summary || ""}</td>
|
|
3879
|
+
<td>${findingsHtml}</td>
|
|
3880
|
+
</tr>`;
|
|
3450
3881
|
}
|
|
3451
|
-
html += `</tbody
|
|
3452
|
-
|
|
3882
|
+
html += `</tbody></table>`;
|
|
3883
|
+
}
|
|
3884
|
+
if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
|
|
3885
|
+
html += `<p>No functional status information available.</p>`;
|
|
3453
3886
|
}
|
|
3454
3887
|
return html;
|
|
3455
3888
|
}
|
|
@@ -3474,34 +3907,110 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
3474
3907
|
*/
|
|
3475
3908
|
static generateStaticNarrative(resources, timezone) {
|
|
3476
3909
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3477
|
-
const
|
|
3478
|
-
|
|
3910
|
+
const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
|
|
3911
|
+
const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
|
|
3912
|
+
const observations = filteredResources.filter((r) => r.resourceType === "Observation");
|
|
3913
|
+
const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
|
|
3914
|
+
const EDD_LOINC = "11778-8";
|
|
3915
|
+
const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
|
|
3916
|
+
const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
|
|
3917
|
+
const pregnancyStatusObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code && pregnancyStatusCodes.includes(c.code))).sort((a, b) => {
|
|
3479
3918
|
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
3480
3919
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
3481
|
-
return
|
|
3920
|
+
return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3921
|
+
})[0];
|
|
3922
|
+
const eddObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code === EDD_LOINC)).sort((a, b) => {
|
|
3923
|
+
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
3924
|
+
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
3925
|
+
return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3926
|
+
})[0];
|
|
3927
|
+
const historyObs = observations.filter(
|
|
3928
|
+
(obs) => obs.code?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code)) || obs.valueCodeableConcept?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code))
|
|
3929
|
+
).sort((a, b) => {
|
|
3930
|
+
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
3931
|
+
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
3932
|
+
return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3482
3933
|
});
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
<
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3934
|
+
if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
|
|
3935
|
+
return `<p>No history of pregnancy found.</p>`;
|
|
3936
|
+
}
|
|
3937
|
+
let html = `<p>This list includes Observation and Condition resources relevant to pregnancy, sorted by date (most recent first).</p>`;
|
|
3938
|
+
html += `
|
|
3939
|
+
<table>
|
|
3940
|
+
<thead>
|
|
3941
|
+
<tr>
|
|
3942
|
+
<th>Result</th>
|
|
3943
|
+
<th>Code (System)</th>
|
|
3944
|
+
<th>Comments</th>
|
|
3945
|
+
<th>Date</th>
|
|
3946
|
+
<th>Source</th>
|
|
3947
|
+
</tr>
|
|
3948
|
+
</thead>
|
|
3949
|
+
<tbody>`;
|
|
3950
|
+
function renderRow({ id, result, comments, date, codeSystem, owner }) {
|
|
3495
3951
|
html += `
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3952
|
+
<tr id="${id}">
|
|
3953
|
+
<td class="Result">${result}</td>
|
|
3954
|
+
<td class="CodeSystem">${codeSystem}</td>
|
|
3955
|
+
<td class="Comments">${comments}</td>
|
|
3956
|
+
<td class="Date">${date}</td>
|
|
3957
|
+
<td class="Source">${owner}</td>
|
|
3958
|
+
</tr>`;
|
|
3959
|
+
}
|
|
3960
|
+
const rowResources = [];
|
|
3961
|
+
if (pregnancyStatusObs) {
|
|
3962
|
+
const date = pregnancyStatusObs.effectiveDateTime || pregnancyStatusObs.effectivePeriod?.start;
|
|
3963
|
+
rowResources.push({ resource: pregnancyStatusObs, date, type: "status" });
|
|
3964
|
+
}
|
|
3965
|
+
if (eddObs) {
|
|
3966
|
+
const date = eddObs.effectiveDateTime || eddObs.effectivePeriod?.start;
|
|
3967
|
+
rowResources.push({ resource: eddObs, date, type: "edd" });
|
|
3968
|
+
}
|
|
3969
|
+
for (const obs of historyObs) {
|
|
3970
|
+
const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
|
|
3971
|
+
rowResources.push({ resource: obs, date, type: "history" });
|
|
3972
|
+
}
|
|
3973
|
+
for (const cond of conditions) {
|
|
3974
|
+
const condition = cond;
|
|
3975
|
+
const date = condition.onsetDateTime || condition.onsetPeriod?.start;
|
|
3976
|
+
rowResources.push({ resource: condition, date, type: "condition" });
|
|
3977
|
+
}
|
|
3978
|
+
rowResources.sort((a, b) => {
|
|
3979
|
+
if (!a.date && !b.date) return 0;
|
|
3980
|
+
if (!a.date) return 1;
|
|
3981
|
+
if (!b.date) return -1;
|
|
3982
|
+
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
|
3983
|
+
});
|
|
3984
|
+
for (const { resource, date, type } of rowResources) {
|
|
3985
|
+
let result = "", comments = "", dateStr = "", codeSystem = "";
|
|
3986
|
+
const id = templateUtilities.narrativeLinkId(resource);
|
|
3987
|
+
if (type === "status") {
|
|
3988
|
+
result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
|
|
3989
|
+
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
3990
|
+
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
3991
|
+
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
3992
|
+
} else if (type === "edd") {
|
|
3993
|
+
result = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
|
|
3994
|
+
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
3995
|
+
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
3996
|
+
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
3997
|
+
} else if (type === "history") {
|
|
3998
|
+
result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
|
|
3999
|
+
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
4000
|
+
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4001
|
+
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
4002
|
+
} else if (type === "condition") {
|
|
4003
|
+
result = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(resource.code));
|
|
4004
|
+
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
4005
|
+
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4006
|
+
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
4007
|
+
}
|
|
4008
|
+
const owner = templateUtilities.getOwnerTag(resource);
|
|
4009
|
+
renderRow({ id, result, comments, date: dateStr, codeSystem, owner });
|
|
3501
4010
|
}
|
|
3502
4011
|
html += `
|
|
3503
|
-
|
|
3504
|
-
|
|
4012
|
+
</tbody>
|
|
4013
|
+
</table>`;
|
|
3505
4014
|
return html;
|
|
3506
4015
|
}
|
|
3507
4016
|
};
|
|
@@ -3531,8 +4040,9 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
3531
4040
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3532
4041
|
static generateStaticNarrative(resources, timezone) {
|
|
3533
4042
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3534
|
-
let html =
|
|
3535
|
-
|
|
4043
|
+
let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
|
|
4044
|
+
`;
|
|
4045
|
+
html += `<table>
|
|
3536
4046
|
<thead>
|
|
3537
4047
|
<tr>
|
|
3538
4048
|
<th>Scope</th>
|
|
@@ -3545,8 +4055,8 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
3545
4055
|
for (const resourceItem of resources) {
|
|
3546
4056
|
const consent = resourceItem;
|
|
3547
4057
|
html += `
|
|
3548
|
-
<tr
|
|
3549
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
4058
|
+
<tr>
|
|
4059
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display"))}</td>
|
|
3550
4060
|
<td>${consent.status || ""}</td>
|
|
3551
4061
|
<td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
|
|
3552
4062
|
<td>${consent.dateTime || ""}</td>
|
|
@@ -3579,7 +4089,7 @@ var TypeScriptTemplateMapper = class {
|
|
|
3579
4089
|
resources,
|
|
3580
4090
|
timezone,
|
|
3581
4091
|
now
|
|
3582
|
-
) : templateClass.generateNarrative(resources, timezone);
|
|
4092
|
+
) : templateClass.generateNarrative(resources, timezone, now);
|
|
3583
4093
|
}
|
|
3584
4094
|
};
|
|
3585
4095
|
// Map of section types to their template classes
|