@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.js
CHANGED
|
@@ -71,6 +71,13 @@ var PREGNANCY_LOINC_CODES = {
|
|
|
71
71
|
"33065-4": "Ectopic Pregnancy"
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
|
+
var PREGNANCY_SNOMED_CODES = [
|
|
75
|
+
// Add SNOMED CT codes for pregnancy history here, e.g.:
|
|
76
|
+
"72892002",
|
|
77
|
+
// Pregnancy (finding)
|
|
78
|
+
"77386006"
|
|
79
|
+
// History of pregnancy (situation)
|
|
80
|
+
];
|
|
74
81
|
var SOCIAL_HISTORY_LOINC_CODES = {
|
|
75
82
|
"72166-2": "Tobacco Use",
|
|
76
83
|
"74013-4": "Alcohol Use"
|
|
@@ -80,6 +87,29 @@ var BLOOD_PRESSURE_LOINC_CODES = {
|
|
|
80
87
|
SYSTOLIC: "8480-6",
|
|
81
88
|
DIASTOLIC: "8462-4"
|
|
82
89
|
};
|
|
90
|
+
var ESSENTIAL_LAB_PANELS = {
|
|
91
|
+
// Top 20 Most Ordered Panels
|
|
92
|
+
"24323-8": "Comprehensive Metabolic Panel (CMP)",
|
|
93
|
+
"24320-4": "Basic Metabolic Panel (BMP)",
|
|
94
|
+
"58410-2": "Complete Blood Count (CBC)",
|
|
95
|
+
"57021-8": "CBC with Differential",
|
|
96
|
+
"24331-1": "Lipid Panel",
|
|
97
|
+
"57698-3": "Lipid Panel with Direct LDL",
|
|
98
|
+
"24325-3": "Hepatic Function Panel",
|
|
99
|
+
"24362-6": "Renal Function Panel",
|
|
100
|
+
"24326-1": "Electrolyte Panel",
|
|
101
|
+
"24348-5": "Thyroid Panel",
|
|
102
|
+
"24356-8": "Urinalysis Complete",
|
|
103
|
+
"24352-7": "Iron Studies Panel",
|
|
104
|
+
"34714-6": "Coagulation Panel",
|
|
105
|
+
"24364-2": "Prenatal Panel",
|
|
106
|
+
"24108-3": "Acute Hepatitis Panel",
|
|
107
|
+
"24110-9": "Hepatitis B Panel",
|
|
108
|
+
"34574-4": "Arthritis Panel",
|
|
109
|
+
"24360-0": "Anemia Panel",
|
|
110
|
+
"80235-8": "Cardiac Markers Panel",
|
|
111
|
+
"69738-3": "CBC with Auto Differential"
|
|
112
|
+
};
|
|
83
113
|
|
|
84
114
|
// src/structures/ips_section_constants.ts
|
|
85
115
|
var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
@@ -104,34 +134,39 @@ var IPSSectionResourceFilters = {
|
|
|
104
134
|
// Only include completed immunizations
|
|
105
135
|
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
|
|
106
136
|
// Only include vital sign Observations (category.coding contains 'vital-signs')
|
|
107
|
-
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c
|
|
137
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, "vital-signs", c.system))),
|
|
108
138
|
// Includes DeviceUseStatement. Device is needed for linked device details
|
|
109
139
|
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
|
|
110
|
-
// Only include finalized diagnostic reports
|
|
111
|
-
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) =>
|
|
140
|
+
// Only include finalized diagnostic reports and relevant observations
|
|
141
|
+
["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))),
|
|
112
142
|
// Only include completed procedures
|
|
113
143
|
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
|
|
114
144
|
// Only include social history Observations
|
|
115
|
-
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code
|
|
116
|
-
// Only include pregnancy history Observations
|
|
117
|
-
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code
|
|
118
|
-
// Only include
|
|
119
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "
|
|
145
|
+
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
|
|
146
|
+
// Only include pregnancy history Observations or relevant Conditions
|
|
147
|
+
["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")),
|
|
148
|
+
// Only include Observations with LOINC 47420-5, category 'functional-status', or category display containing 'functional', and completed ClinicalImpressions
|
|
149
|
+
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, "47420-5", "http://loinc.org") || resource.category?.some(
|
|
150
|
+
(cat) => cat.coding?.some(
|
|
151
|
+
(c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
|
|
152
|
+
)
|
|
153
|
+
)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
|
|
120
154
|
// Only include resolved medical history Conditions
|
|
121
155
|
["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
|
|
122
156
|
// Only include active care plans
|
|
123
157
|
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
|
|
124
158
|
// Only include active advance directives (Consent resources)
|
|
125
|
-
|
|
159
|
+
// TODO: disable this until we right logic to get these
|
|
160
|
+
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
|
|
126
161
|
};
|
|
127
162
|
var IPSSectionSummaryCompositionFilter = {
|
|
128
|
-
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
129
|
-
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
130
|
-
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
131
|
-
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
132
|
-
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
163
|
+
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
164
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
165
|
+
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
166
|
+
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
167
|
+
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "medication_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
133
168
|
// [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)),
|
|
134
|
-
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c
|
|
169
|
+
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "procedure_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
|
|
135
170
|
};
|
|
136
171
|
var IPSSectionSummaryIPSCompositionFilter = {
|
|
137
172
|
["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")
|
|
@@ -152,9 +187,46 @@ var IPSSectionResourceHelper = class {
|
|
|
152
187
|
return IPSSectionSummaryIPSCompositionFilter[section];
|
|
153
188
|
}
|
|
154
189
|
};
|
|
190
|
+
function codingMatches(coding, code, system) {
|
|
191
|
+
if (!coding || !coding.system) return false;
|
|
192
|
+
if (Array.isArray(code)) {
|
|
193
|
+
return coding.system === system && code.includes(coding.code ?? "");
|
|
194
|
+
}
|
|
195
|
+
return coding.system === system && coding.code === code;
|
|
196
|
+
}
|
|
197
|
+
function codeableConceptMatches(codeableConcept, code, system) {
|
|
198
|
+
if (!codeableConcept || !Array.isArray(codeableConcept.coding)) return false;
|
|
199
|
+
return codeableConcept.coding.some((coding) => codingMatches(coding, code, system));
|
|
200
|
+
}
|
|
155
201
|
|
|
156
202
|
// src/narratives/templates/typescript/TemplateUtilities.ts
|
|
157
203
|
import { DateTime } from "luxon";
|
|
204
|
+
|
|
205
|
+
// src/structures/codingSystemDisplayNames.ts
|
|
206
|
+
var CODING_SYSTEM_DISPLAY_NAMES = {
|
|
207
|
+
"http://snomed.info/sct": "SNOMED CT",
|
|
208
|
+
"http://loinc.org": "LOINC",
|
|
209
|
+
"http://hl7.org/fhir/sid/icd-10": "ICD-10",
|
|
210
|
+
"http://hl7.org/fhir/sid/icd-10-cm": "ICD-10-CM",
|
|
211
|
+
"http://hl7.org/fhir/sid/icd-9": "ICD-9",
|
|
212
|
+
"http://hl7.org/fhir/sid/cvx": "CVX",
|
|
213
|
+
"http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
|
|
214
|
+
"http://www.ama-assn.org/go/cpt": "CPT",
|
|
215
|
+
"http://unitsofmeasure.org": "UCUM",
|
|
216
|
+
"http://e-imo.com/products/problem-it": "IMO Problem IT",
|
|
217
|
+
"2.16.840.1.113883.6.285": "HCPCS Level II",
|
|
218
|
+
"https://fhir.cerner.com/4ff3b259-e48d-4066-8b35-a6a051f2802a/codeSet/72": "Cerner Code Set 72",
|
|
219
|
+
"http://hl7.org/fhir/sid/ndc": "NDC",
|
|
220
|
+
// Added mapping for NDC
|
|
221
|
+
"urn:oid:2.16.840.1.113883.12.292": "CVX",
|
|
222
|
+
"http://terminology.hl7.org/CodeSystem/data-absent-reason": "Data Absent Reason",
|
|
223
|
+
// Added mapping for Data Absent Reason
|
|
224
|
+
"2.16.840.1.113883.6.208": "NDDF"
|
|
225
|
+
// Add more as needed
|
|
226
|
+
};
|
|
227
|
+
var codingSystemDisplayNames_default = CODING_SYSTEM_DISPLAY_NAMES;
|
|
228
|
+
|
|
229
|
+
// src/narratives/templates/typescript/TemplateUtilities.ts
|
|
158
230
|
var TemplateUtilities = class {
|
|
159
231
|
/**
|
|
160
232
|
* Constructor to initialize the TemplateUtilities with a FHIR resources
|
|
@@ -164,47 +236,63 @@ var TemplateUtilities = class {
|
|
|
164
236
|
this.resources = resources;
|
|
165
237
|
}
|
|
166
238
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* @
|
|
239
|
+
* Returns the preferred coding from a list of codings.
|
|
240
|
+
* If a coding has an extension with url 'https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence' and valueCode 'preferred', returns that coding.
|
|
241
|
+
* Otherwise, returns the first coding if it exists, else null.
|
|
242
|
+
* @param codings Array of coding objects
|
|
243
|
+
* @returns The preferred coding object or null
|
|
171
244
|
*/
|
|
172
|
-
|
|
173
|
-
if (!
|
|
174
|
-
|
|
245
|
+
getPreferredCoding(codings) {
|
|
246
|
+
if (!Array.isArray(codings) || codings.length === 0) return null;
|
|
247
|
+
for (const coding of codings) {
|
|
248
|
+
if (Array.isArray(coding.extension)) {
|
|
249
|
+
for (const ext of coding.extension) {
|
|
250
|
+
if (ext.url === "https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence" && ext.valueCode === "preferred") {
|
|
251
|
+
return coding;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
175
255
|
}
|
|
256
|
+
return codings[0] || null;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Returns the display value from a CodeableConcept
|
|
260
|
+
* @param cc - The CodeableConcept object
|
|
261
|
+
* @param field - Optional specific field to extract (e.g., 'text', 'display', 'code')
|
|
262
|
+
* @returns Display string or empty string
|
|
263
|
+
*/
|
|
264
|
+
codeableConceptDisplay(cc, field) {
|
|
265
|
+
if (!cc) return "";
|
|
176
266
|
if (field) {
|
|
177
267
|
if (cc[field]) {
|
|
178
268
|
return cc[field];
|
|
179
|
-
} else if (cc.coding && cc.coding
|
|
180
|
-
|
|
269
|
+
} else if (cc.coding && cc.coding.length > 0) {
|
|
270
|
+
const preferredCoding = this.getPreferredCoding(cc.coding);
|
|
271
|
+
if (preferredCoding && preferredCoding[field]) {
|
|
272
|
+
return preferredCoding[field];
|
|
273
|
+
}
|
|
181
274
|
}
|
|
182
275
|
}
|
|
183
|
-
if (cc.text)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
return cc.coding[0].display;
|
|
188
|
-
} else if (cc.coding[0].code) {
|
|
189
|
-
return cc.coding[0].code;
|
|
190
|
-
}
|
|
276
|
+
if (cc.text) return cc.text;
|
|
277
|
+
if (cc.coding && cc.coding.length > 0) {
|
|
278
|
+
const preferredCoding = this.getPreferredCoding(cc.coding);
|
|
279
|
+
if (preferredCoding && preferredCoding.display) return preferredCoding.display;
|
|
191
280
|
}
|
|
192
281
|
return "";
|
|
193
282
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
});
|
|
207
|
-
return resource ? resource : null;
|
|
283
|
+
/**
|
|
284
|
+
* Returns the code and system from a CodeableConcept
|
|
285
|
+
* @param cc - The CodeableConcept object
|
|
286
|
+
* @returns Object with code and system, or empty strings if not present
|
|
287
|
+
*/
|
|
288
|
+
codeableConceptCoding(cc) {
|
|
289
|
+
if (!cc || !cc.coding || !cc.coding.length) return "";
|
|
290
|
+
const preferredCoding = this.getPreferredCoding(cc.coding);
|
|
291
|
+
if (!preferredCoding) return "";
|
|
292
|
+
const code = preferredCoding.code || "";
|
|
293
|
+
const system = preferredCoding.system || "";
|
|
294
|
+
const systemDisplay = codingSystemDisplayNames_default[system] || system;
|
|
295
|
+
return code ? `${code} (${systemDisplay})` : "";
|
|
208
296
|
}
|
|
209
297
|
/**
|
|
210
298
|
* Renders a Device reference
|
|
@@ -252,7 +340,7 @@ var TemplateUtilities = class {
|
|
|
252
340
|
return "";
|
|
253
341
|
}
|
|
254
342
|
if (medicationType.medicationCodeableConcept) {
|
|
255
|
-
return this.
|
|
343
|
+
return this.codeableConceptDisplay(medicationType.medicationCodeableConcept);
|
|
256
344
|
} else if (medicationType.medicationReference) {
|
|
257
345
|
return this.renderMedicationRef(medicationType.medicationReference);
|
|
258
346
|
}
|
|
@@ -277,7 +365,7 @@ var TemplateUtilities = class {
|
|
|
277
365
|
*/
|
|
278
366
|
renderMedicationCode(medication) {
|
|
279
367
|
if (medication && medication.code) {
|
|
280
|
-
return this.renderTextAsHtml(this.
|
|
368
|
+
return this.renderTextAsHtml(this.codeableConceptDisplay(medication.code));
|
|
281
369
|
}
|
|
282
370
|
return "";
|
|
283
371
|
}
|
|
@@ -446,7 +534,7 @@ var TemplateUtilities = class {
|
|
|
446
534
|
*/
|
|
447
535
|
firstFromCodeableConceptList(list) {
|
|
448
536
|
if (list && Array.isArray(list) && list[0]) {
|
|
449
|
-
return this.renderTextAsHtml(this.
|
|
537
|
+
return this.renderTextAsHtml(this.codeableConceptDisplay(list[0]));
|
|
450
538
|
}
|
|
451
539
|
return "";
|
|
452
540
|
}
|
|
@@ -786,16 +874,28 @@ var TemplateUtilities = class {
|
|
|
786
874
|
return this.renderMedicationCode(medicationSource);
|
|
787
875
|
}
|
|
788
876
|
if (typeof medicationSource === "object" && ("coding" in medicationSource || "text" in medicationSource)) {
|
|
789
|
-
return this.
|
|
877
|
+
return this.codeableConceptDisplay(medicationSource);
|
|
790
878
|
}
|
|
791
879
|
if (typeof medicationSource === "object" && "reference" in medicationSource) {
|
|
792
880
|
const medication = this.resolveReference(medicationSource);
|
|
793
881
|
if (medication && medication.code) {
|
|
794
|
-
return this.
|
|
882
|
+
return this.codeableConceptDisplay(medication.code);
|
|
795
883
|
}
|
|
796
884
|
}
|
|
797
885
|
return "";
|
|
798
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* Returns the owner tag from the resource meta.security array.
|
|
889
|
+
* @param resource - FHIR resource with meta tag
|
|
890
|
+
* @returns The owner code if found, otherwise undefined
|
|
891
|
+
*/
|
|
892
|
+
getOwnerTag(resource) {
|
|
893
|
+
if (!resource?.meta?.security) return "";
|
|
894
|
+
const ownerEntry = resource.meta.security.find(
|
|
895
|
+
(sec) => sec.system === "https://www.icanbwell.com/owner" && !!sec.code
|
|
896
|
+
);
|
|
897
|
+
return ownerEntry?.code;
|
|
898
|
+
}
|
|
799
899
|
/**
|
|
800
900
|
* Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
|
|
801
901
|
* This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
|
|
@@ -959,6 +1059,21 @@ var TemplateUtilities = class {
|
|
|
959
1059
|
const denominatorUnit = valueRatio.denominator.unit ? ` ${valueRatio.denominator.unit}` : "";
|
|
960
1060
|
return `${numerator}${numeratorUnit} / ${denominator}${denominatorUnit}`;
|
|
961
1061
|
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Finds the resource that matches the reference
|
|
1064
|
+
* @param ref - Reference to a resource
|
|
1065
|
+
* @returns The resource or null
|
|
1066
|
+
*/
|
|
1067
|
+
resolveReference(ref) {
|
|
1068
|
+
if (!ref || !this.resources) {
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
const refId = ref.reference?.split("/")[1];
|
|
1072
|
+
const refType = ref.reference?.split("/")[0];
|
|
1073
|
+
return this.resources.find(
|
|
1074
|
+
(resource) => resource.resourceType === refType && resource.id === refId
|
|
1075
|
+
) || null;
|
|
1076
|
+
}
|
|
962
1077
|
};
|
|
963
1078
|
|
|
964
1079
|
// src/constants.ts
|
|
@@ -1292,7 +1407,8 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1292
1407
|
static generateStaticNarrative(resources, timezone) {
|
|
1293
1408
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1294
1409
|
const combinedPatient = this.combinePatients(resources);
|
|
1295
|
-
|
|
1410
|
+
let html = `<p>This section merges all Patient resources into a single combined patient record, preferring non-empty values for each field.</p>`;
|
|
1411
|
+
html += `<div>
|
|
1296
1412
|
<ul>
|
|
1297
1413
|
<li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
|
|
1298
1414
|
<li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
|
|
@@ -1304,6 +1420,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1304
1420
|
<li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
|
|
1305
1421
|
</ul>
|
|
1306
1422
|
</div>`;
|
|
1423
|
+
return html;
|
|
1307
1424
|
}
|
|
1308
1425
|
/**
|
|
1309
1426
|
* Combines multiple patient resources into a single patient object
|
|
@@ -1587,7 +1704,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1587
1704
|
const uniqueLanguages = /* @__PURE__ */ new Set();
|
|
1588
1705
|
const preferredLanguages = /* @__PURE__ */ new Set();
|
|
1589
1706
|
patient.communication.forEach((comm) => {
|
|
1590
|
-
const language = templateUtilities.renderTextAsHtml(templateUtilities.
|
|
1707
|
+
const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(comm.language));
|
|
1591
1708
|
if (language) {
|
|
1592
1709
|
if (comm.preferred) {
|
|
1593
1710
|
preferredLanguages.add(language);
|
|
@@ -1628,20 +1745,26 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1628
1745
|
generateSummaryNarrative(resources, timezone) {
|
|
1629
1746
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1630
1747
|
let isSummaryCreated = false;
|
|
1631
|
-
let html =
|
|
1748
|
+
let html = `<p>This list includes all AllergyIntolerance resources, with no additional filtering, sorted as provided.</p>
|
|
1749
|
+
`;
|
|
1750
|
+
html += `
|
|
1632
1751
|
<div>
|
|
1633
1752
|
<table>
|
|
1634
1753
|
<thead>
|
|
1635
1754
|
<tr>
|
|
1636
1755
|
<th>Allergen</th>
|
|
1756
|
+
<th>Code (System)</th>
|
|
1637
1757
|
<th>Criticality</th>
|
|
1638
1758
|
<th>Recorded Date</th>
|
|
1759
|
+
<th>Source</th>
|
|
1639
1760
|
</tr>
|
|
1640
1761
|
</thead>
|
|
1641
1762
|
<tbody>`;
|
|
1642
1763
|
for (const resourceItem of resources) {
|
|
1643
1764
|
for (const rowData of resourceItem.section ?? []) {
|
|
1765
|
+
const sectionCodeableConcept = rowData.code;
|
|
1644
1766
|
const data = {};
|
|
1767
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
1645
1768
|
for (const columnData of rowData.section ?? []) {
|
|
1646
1769
|
switch (columnData.title) {
|
|
1647
1770
|
case "Allergen Name":
|
|
@@ -1653,6 +1776,9 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1653
1776
|
case "Recorded Date":
|
|
1654
1777
|
data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1655
1778
|
break;
|
|
1779
|
+
case "Source":
|
|
1780
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1781
|
+
break;
|
|
1656
1782
|
default:
|
|
1657
1783
|
break;
|
|
1658
1784
|
}
|
|
@@ -1660,9 +1786,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1660
1786
|
isSummaryCreated = true;
|
|
1661
1787
|
html += `
|
|
1662
1788
|
<tr>
|
|
1663
|
-
<td>${data["allergen"] ?? "
|
|
1664
|
-
|
|
1665
|
-
<td>${
|
|
1789
|
+
<td>${data["allergen"] ?? ""}</td>
|
|
1790
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
1791
|
+
<td>${data["criticality"] ?? ""}</td>
|
|
1792
|
+
<td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? ""}</td>
|
|
1793
|
+
<td>${data["source"] ?? ""}</td>
|
|
1666
1794
|
</tr>`;
|
|
1667
1795
|
}
|
|
1668
1796
|
}
|
|
@@ -1701,7 +1829,8 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1701
1829
|
const dateB = b.onsetDateTime;
|
|
1702
1830
|
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
1703
1831
|
});
|
|
1704
|
-
let html =
|
|
1832
|
+
let html = `<p>This list includes all AllergyIntolerance resources, with no additional filtering, sorted as provided.</p>
|
|
1833
|
+
`;
|
|
1705
1834
|
html += `
|
|
1706
1835
|
<div class="ActiveAllergies">
|
|
1707
1836
|
<h3>Active</h3>
|
|
@@ -1710,10 +1839,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1710
1839
|
<tr>
|
|
1711
1840
|
<th>Allergen</th>
|
|
1712
1841
|
<th>Status</th>
|
|
1842
|
+
<th>Code (System)</th>
|
|
1713
1843
|
<th>Category</th>
|
|
1714
1844
|
<th>Reaction</th>
|
|
1715
1845
|
<th>Onset Date</th>
|
|
1716
1846
|
<th>Comments</th>
|
|
1847
|
+
<th>Source</th>
|
|
1717
1848
|
</tr>
|
|
1718
1849
|
</thead>
|
|
1719
1850
|
<tbody>`;
|
|
@@ -1737,11 +1868,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1737
1868
|
<tr>
|
|
1738
1869
|
<th>Allergen</th>
|
|
1739
1870
|
<th>Status</th>
|
|
1871
|
+
<th>Code (System)</th>
|
|
1740
1872
|
<th>Category</th>
|
|
1741
1873
|
<th>Reaction</th>
|
|
1742
1874
|
<th>Onset Date</th>
|
|
1743
1875
|
<th>Comments</th>
|
|
1744
1876
|
<th>Resolved Date</th>
|
|
1877
|
+
<th>Source</th>
|
|
1745
1878
|
</tr>
|
|
1746
1879
|
</thead>
|
|
1747
1880
|
<tbody>`;
|
|
@@ -1772,14 +1905,16 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1772
1905
|
for (const allergy of allergies) {
|
|
1773
1906
|
html += `
|
|
1774
1907
|
<tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
|
|
1775
|
-
<td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
1776
|
-
<td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
1777
|
-
<td class="
|
|
1778
|
-
<td class="
|
|
1779
|
-
<td class="
|
|
1780
|
-
<td class="
|
|
1908
|
+
<td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.code))}</span></td>
|
|
1909
|
+
<td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.clinicalStatus)) || ""}</td>
|
|
1910
|
+
<td class="CodeSystem">${templateUtilities.codeableConceptCoding(allergy.code)}</td>
|
|
1911
|
+
<td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || ""}</td>
|
|
1912
|
+
<td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || ""}</td>
|
|
1913
|
+
<td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || ""}</td>
|
|
1914
|
+
<td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>
|
|
1915
|
+
<td class="Source">${templateUtilities.getOwnerTag(allergy)}</td>`;
|
|
1781
1916
|
if (includeResolved) {
|
|
1782
|
-
let endDate = "
|
|
1917
|
+
let endDate = "";
|
|
1783
1918
|
if (allergy.extension && Array.isArray(allergy.extension)) {
|
|
1784
1919
|
const endDateExt = allergy.extension.find(
|
|
1785
1920
|
(ext) => ext.url === "http://hl7.org/fhir/StructureDefinition/allergyintolerance-resolutionDate"
|
|
@@ -1803,10 +1938,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1803
1938
|
* Generate HTML narrative for Medication resources
|
|
1804
1939
|
* @param resources - FHIR Medication resources
|
|
1805
1940
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1941
|
+
* @param now - Optional current date to use for calculations (defaults to new Date())
|
|
1806
1942
|
* @returns HTML string for rendering
|
|
1807
1943
|
*/
|
|
1808
|
-
generateNarrative(resources, timezone) {
|
|
1809
|
-
return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
|
|
1944
|
+
generateNarrative(resources, timezone, now) {
|
|
1945
|
+
return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone, now);
|
|
1810
1946
|
}
|
|
1811
1947
|
/**
|
|
1812
1948
|
* Generate HTML narrative for Medication resources using summary
|
|
@@ -1819,23 +1955,28 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1819
1955
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1820
1956
|
let isSummaryCreated = false;
|
|
1821
1957
|
const currentDate = now || /* @__PURE__ */ new Date();
|
|
1822
|
-
const
|
|
1823
|
-
let html =
|
|
1958
|
+
const twoYearsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 24, currentDate.getDate());
|
|
1959
|
+
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>";
|
|
1960
|
+
html += `
|
|
1824
1961
|
<div>
|
|
1825
1962
|
<table>
|
|
1826
1963
|
<thead>
|
|
1827
1964
|
<tr>
|
|
1828
1965
|
<th>Medication</th>
|
|
1966
|
+
<th>Code (System)</th>
|
|
1829
1967
|
<th>Status</th>
|
|
1830
1968
|
<th>Sig</th>
|
|
1831
1969
|
<th>Days of Supply</th>
|
|
1832
1970
|
<th>Refills</th>
|
|
1833
1971
|
<th>Start Date</th>
|
|
1972
|
+
<th>Source</th>
|
|
1834
1973
|
</tr>
|
|
1835
1974
|
</thead>
|
|
1836
1975
|
<tbody>`;
|
|
1976
|
+
let skippedMedications = 0;
|
|
1837
1977
|
for (const resourceItem of resources) {
|
|
1838
1978
|
for (const rowData of resourceItem.section ?? []) {
|
|
1979
|
+
const sectionCodeableConcept = rowData.code;
|
|
1839
1980
|
const data = {};
|
|
1840
1981
|
for (const columnData of rowData.section ?? []) {
|
|
1841
1982
|
switch (columnData.title) {
|
|
@@ -1860,6 +2001,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1860
2001
|
case "Authored On Date":
|
|
1861
2002
|
data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1862
2003
|
break;
|
|
2004
|
+
case "Source":
|
|
2005
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2006
|
+
break;
|
|
1863
2007
|
default:
|
|
1864
2008
|
break;
|
|
1865
2009
|
}
|
|
@@ -1871,16 +2015,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1871
2015
|
startDateObj = void 0;
|
|
1872
2016
|
}
|
|
1873
2017
|
}
|
|
1874
|
-
if (data["status"] === "active" || startDateObj && startDateObj >=
|
|
2018
|
+
if (!(data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo)) {
|
|
2019
|
+
skippedMedications++;
|
|
2020
|
+
}
|
|
2021
|
+
if (data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo) {
|
|
1875
2022
|
isSummaryCreated = true;
|
|
1876
2023
|
html += `
|
|
1877
2024
|
<tr>
|
|
1878
2025
|
<td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
|
|
2026
|
+
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
1879
2027
|
<td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
|
|
1880
2028
|
<td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
|
|
1881
2029
|
<td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
|
|
1882
2030
|
<td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
|
|
1883
2031
|
<td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
|
|
2032
|
+
<td>${templateUtilities.renderTextAsHtml(data["source"])}</td>
|
|
1884
2033
|
</tr>`;
|
|
1885
2034
|
}
|
|
1886
2035
|
}
|
|
@@ -1889,7 +2038,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1889
2038
|
</tbody>
|
|
1890
2039
|
</table>
|
|
1891
2040
|
</div>`;
|
|
1892
|
-
|
|
2041
|
+
if (skippedMedications > 0) {
|
|
2042
|
+
html += `
|
|
2043
|
+
<p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
|
|
2044
|
+
}
|
|
2045
|
+
if (isSummaryCreated || skippedMedications > 0) {
|
|
2046
|
+
return html;
|
|
2047
|
+
}
|
|
2048
|
+
return void 0;
|
|
1893
2049
|
}
|
|
1894
2050
|
/**
|
|
1895
2051
|
* Safely parse a date string and return a valid Date object or null
|
|
@@ -1907,21 +2063,42 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1907
2063
|
* Internal static implementation that actually generates the narrative
|
|
1908
2064
|
* @param resources - FHIR Medication resources
|
|
1909
2065
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2066
|
+
* @param now - Optional current date to use for calculations (defaults to new Date())
|
|
1910
2067
|
* @returns HTML string for rendering
|
|
1911
2068
|
*/
|
|
1912
|
-
|
|
1913
|
-
static generateStaticNarrative(resources, timezone) {
|
|
2069
|
+
static generateStaticNarrative(resources, timezone, now) {
|
|
1914
2070
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1915
2071
|
let html = "";
|
|
1916
2072
|
const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
|
|
1917
2073
|
const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
|
|
1918
2074
|
const allActiveMedications = [];
|
|
2075
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
2076
|
+
const twoYearsAgo = new Date(currentDate);
|
|
2077
|
+
twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
|
|
2078
|
+
let skippedMedications = 0;
|
|
2079
|
+
const allMedications = [];
|
|
1919
2080
|
medicationRequests.forEach((mr) => {
|
|
1920
|
-
|
|
2081
|
+
allMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
|
|
1921
2082
|
});
|
|
1922
2083
|
medicationStatements.forEach((ms) => {
|
|
1923
|
-
|
|
2084
|
+
allMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
|
|
1924
2085
|
});
|
|
2086
|
+
for (const med of allMedications) {
|
|
2087
|
+
let dateString;
|
|
2088
|
+
if (med.type === "request") {
|
|
2089
|
+
const mr = med.resource;
|
|
2090
|
+
dateString = mr.dispenseRequest?.validityPeriod?.start || mr.authoredOn;
|
|
2091
|
+
} else {
|
|
2092
|
+
const ms = med.resource;
|
|
2093
|
+
dateString = ms.effectiveDateTime || ms.effectivePeriod?.start;
|
|
2094
|
+
}
|
|
2095
|
+
const dateObj = this.parseDate(dateString);
|
|
2096
|
+
if (!dateObj || dateObj < twoYearsAgo) {
|
|
2097
|
+
skippedMedications++;
|
|
2098
|
+
} else {
|
|
2099
|
+
allActiveMedications.push(med);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
1925
2102
|
const sortMedications = (medications) => {
|
|
1926
2103
|
medications.sort((a, b) => {
|
|
1927
2104
|
let dateStringA;
|
|
@@ -1952,7 +2129,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1952
2129
|
sortMedications(allActiveMedications);
|
|
1953
2130
|
html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
|
|
1954
2131
|
}
|
|
1955
|
-
|
|
2132
|
+
if (skippedMedications > 0) {
|
|
2133
|
+
html += `
|
|
2134
|
+
<p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
|
|
2135
|
+
}
|
|
2136
|
+
if (allActiveMedications.length > 0 || skippedMedications > 0) {
|
|
2137
|
+
return html;
|
|
2138
|
+
}
|
|
2139
|
+
return "";
|
|
1956
2140
|
}
|
|
1957
2141
|
/**
|
|
1958
2142
|
* Extract MedicationRequest resources
|
|
@@ -1997,10 +2181,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1997
2181
|
<tr>
|
|
1998
2182
|
<th>Type</th>
|
|
1999
2183
|
<th>Medication</th>
|
|
2184
|
+
<th>Code (System)</th>
|
|
2000
2185
|
<th>Sig</th>
|
|
2001
2186
|
<th>Dispense Quantity</th>
|
|
2002
2187
|
<th>Refills</th>
|
|
2003
2188
|
<th>Start Date</th>
|
|
2189
|
+
<th>Source</th>
|
|
2004
2190
|
</tr>
|
|
2005
2191
|
</thead>
|
|
2006
2192
|
<tbody>`;
|
|
@@ -2009,27 +2195,31 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
2009
2195
|
let type;
|
|
2010
2196
|
let medicationName;
|
|
2011
2197
|
let sig;
|
|
2012
|
-
let dispenseQuantity = "
|
|
2013
|
-
let refills = "
|
|
2014
|
-
let startDate = "
|
|
2198
|
+
let dispenseQuantity = "";
|
|
2199
|
+
let refills = "";
|
|
2200
|
+
let startDate = "";
|
|
2201
|
+
let codeSystemDisplay = "";
|
|
2015
2202
|
if (medication.type === "request") {
|
|
2016
2203
|
const mr = medication.resource;
|
|
2017
2204
|
type = "Request";
|
|
2018
2205
|
medicationName = templateUtilities.getMedicationName(
|
|
2019
2206
|
mr.medicationReference || mr.medicationCodeableConcept
|
|
2020
2207
|
);
|
|
2021
|
-
sig = templateUtilities.concat(mr.dosageInstruction, "text") || "
|
|
2208
|
+
sig = templateUtilities.concat(mr.dosageInstruction, "text") || "";
|
|
2022
2209
|
if (mr.dispenseRequest?.quantity) {
|
|
2023
2210
|
const quantity = mr.dispenseRequest.quantity;
|
|
2024
2211
|
if (quantity.value) {
|
|
2025
2212
|
dispenseQuantity = `${quantity.value} ${quantity.unit || quantity.code || ""}`.trim();
|
|
2026
2213
|
}
|
|
2027
2214
|
}
|
|
2028
|
-
refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "
|
|
2215
|
+
refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "";
|
|
2029
2216
|
if (mr.dispenseRequest?.validityPeriod) {
|
|
2030
|
-
startDate = mr.dispenseRequest.validityPeriod.start || "
|
|
2217
|
+
startDate = mr.dispenseRequest.validityPeriod.start || "";
|
|
2031
2218
|
} else {
|
|
2032
|
-
startDate = mr.authoredOn || "
|
|
2219
|
+
startDate = mr.authoredOn || "";
|
|
2220
|
+
}
|
|
2221
|
+
if (mr.medicationCodeableConcept && mr.medicationCodeableConcept.coding && mr.medicationCodeableConcept.coding[0]) {
|
|
2222
|
+
codeSystemDisplay = templateUtilities.codeableConceptCoding(mr.medicationCodeableConcept);
|
|
2033
2223
|
}
|
|
2034
2224
|
} else {
|
|
2035
2225
|
const ms = medication.resource;
|
|
@@ -2037,21 +2227,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
2037
2227
|
medicationName = templateUtilities.getMedicationName(
|
|
2038
2228
|
ms.medicationReference || ms.medicationCodeableConcept
|
|
2039
2229
|
);
|
|
2040
|
-
sig = templateUtilities.concat(ms.dosage, "text") || "
|
|
2230
|
+
sig = templateUtilities.concat(ms.dosage, "text") || "";
|
|
2041
2231
|
if (ms.effectiveDateTime) {
|
|
2042
2232
|
startDate = ms.effectiveDateTime;
|
|
2043
2233
|
} else if (ms.effectivePeriod) {
|
|
2044
|
-
startDate = ms.effectivePeriod.start || "
|
|
2234
|
+
startDate = ms.effectivePeriod.start || "";
|
|
2235
|
+
}
|
|
2236
|
+
if (ms.medicationCodeableConcept && ms.medicationCodeableConcept.coding && ms.medicationCodeableConcept.coding[0]) {
|
|
2237
|
+
codeSystemDisplay = templateUtilities.codeableConceptCoding(ms.medicationCodeableConcept);
|
|
2045
2238
|
}
|
|
2046
2239
|
}
|
|
2047
2240
|
html += `
|
|
2048
2241
|
<tr${narrativeLinkId ? ` id="${narrativeLinkId}"` : ""}>
|
|
2049
2242
|
<td>${type}</td>
|
|
2050
2243
|
<td>${medicationName}<ul></ul></td>
|
|
2244
|
+
<td>${codeSystemDisplay}</td>
|
|
2051
2245
|
<td>${sig}</td>
|
|
2052
2246
|
<td>${dispenseQuantity}</td>
|
|
2053
2247
|
<td>${refills}</td>
|
|
2054
2248
|
<td>${startDate}</td>
|
|
2249
|
+
<td>${templateUtilities.getOwnerTag(medication.resource)}</td>
|
|
2055
2250
|
</tr>`;
|
|
2056
2251
|
}
|
|
2057
2252
|
html += `
|
|
@@ -2088,18 +2283,23 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2088
2283
|
let isSummaryCreated = false;
|
|
2089
2284
|
let html = `
|
|
2090
2285
|
<div>
|
|
2286
|
+
<p>This list includes all vaccinations, sorted by occurrence date (most recent first).</p>
|
|
2091
2287
|
<table>
|
|
2092
2288
|
<thead>
|
|
2093
2289
|
<tr>
|
|
2094
2290
|
<th>Immunization</th>
|
|
2291
|
+
<th>Code (System)</th>
|
|
2095
2292
|
<th>Status</th>
|
|
2096
2293
|
<th>Date</th>
|
|
2294
|
+
<th>Source</th>
|
|
2097
2295
|
</tr>
|
|
2098
2296
|
</thead>
|
|
2099
2297
|
<tbody>`;
|
|
2100
2298
|
for (const resourceItem of resources) {
|
|
2101
2299
|
for (const rowData of resourceItem.section ?? []) {
|
|
2300
|
+
const sectionCodeableConcept = rowData.code;
|
|
2102
2301
|
const data = {};
|
|
2302
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
2103
2303
|
for (const columnData of rowData.section ?? []) {
|
|
2104
2304
|
switch (columnData.title) {
|
|
2105
2305
|
case "Immunization Name":
|
|
@@ -2111,6 +2311,9 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2111
2311
|
case "occurrenceDateTime":
|
|
2112
2312
|
data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2113
2313
|
break;
|
|
2314
|
+
case "Source":
|
|
2315
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2316
|
+
break;
|
|
2114
2317
|
default:
|
|
2115
2318
|
break;
|
|
2116
2319
|
}
|
|
@@ -2119,9 +2322,11 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2119
2322
|
isSummaryCreated = true;
|
|
2120
2323
|
html += `
|
|
2121
2324
|
<tr>
|
|
2122
|
-
<td>${data["immunization"] ?? "
|
|
2123
|
-
<td>${data["
|
|
2124
|
-
<td>${
|
|
2325
|
+
<td>${data["immunization"] ?? ""}</td>
|
|
2326
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
2327
|
+
<td>${data["status"] ?? ""}</td>
|
|
2328
|
+
<td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? ""}</td>
|
|
2329
|
+
<td>${data["source"] ?? ""}</td>
|
|
2125
2330
|
</tr>`;
|
|
2126
2331
|
}
|
|
2127
2332
|
}
|
|
@@ -2145,12 +2350,14 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2145
2350
|
<thead>
|
|
2146
2351
|
<tr>
|
|
2147
2352
|
<th>Immunization</th>
|
|
2353
|
+
<th>Code (System)</th>
|
|
2148
2354
|
<th>Status</th>
|
|
2149
2355
|
<th>Dose Number</th>
|
|
2150
2356
|
<th>Manufacturer</th>
|
|
2151
2357
|
<th>Lot Number</th>
|
|
2152
2358
|
<th>Comments</th>
|
|
2153
2359
|
<th>Date</th>
|
|
2360
|
+
<th>Source</th>
|
|
2154
2361
|
</tr>
|
|
2155
2362
|
</thead>
|
|
2156
2363
|
<tbody>`;
|
|
@@ -2159,14 +2366,16 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
2159
2366
|
for (const resourceItem of immunizations) {
|
|
2160
2367
|
const imm = resourceItem;
|
|
2161
2368
|
html += `
|
|
2162
|
-
<tr
|
|
2163
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
2369
|
+
<tr>
|
|
2370
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(imm.vaccineCode))}</td>
|
|
2371
|
+
<td>${templateUtilities.codeableConceptCoding(imm.vaccineCode)}</td>
|
|
2164
2372
|
<td>${imm.status || ""}</td>
|
|
2165
2373
|
<td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
|
|
2166
2374
|
<td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
|
|
2167
2375
|
<td>${imm.lotNumber || ""}</td>
|
|
2168
2376
|
<td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
|
|
2169
2377
|
<td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
|
|
2378
|
+
<td>${templateUtilities.getOwnerTag(imm)}</td>
|
|
2170
2379
|
</tr>`;
|
|
2171
2380
|
}
|
|
2172
2381
|
}
|
|
@@ -2197,11 +2406,15 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
2197
2406
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2198
2407
|
static generateStaticNarrative(resources, timezone) {
|
|
2199
2408
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2200
|
-
let html =
|
|
2409
|
+
let html = `<p>This list includes patient problems, sorted by recorded date (most recent first)</p>
|
|
2410
|
+
`;
|
|
2201
2411
|
const activeConditions = resources.map((entry) => entry) || [];
|
|
2202
2412
|
activeConditions.sort((a, b) => {
|
|
2203
|
-
|
|
2204
|
-
|
|
2413
|
+
if (!a.recordedDate && b.recordedDate) return -1;
|
|
2414
|
+
if (a.recordedDate && !b.recordedDate) return 1;
|
|
2415
|
+
if (!a.recordedDate && !b.recordedDate) return 0;
|
|
2416
|
+
const dateA = new Date(a.recordedDate).getTime();
|
|
2417
|
+
const dateB = new Date(b.recordedDate).getTime();
|
|
2205
2418
|
return dateB - dateA;
|
|
2206
2419
|
});
|
|
2207
2420
|
html += `
|
|
@@ -2209,22 +2422,28 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
2209
2422
|
<thead>
|
|
2210
2423
|
<tr>
|
|
2211
2424
|
<th>Problem</th>
|
|
2425
|
+
<th>Code (System)</th>
|
|
2212
2426
|
<th>Onset Date</th>
|
|
2213
2427
|
<th>Recorded Date</th>
|
|
2428
|
+
<th>Source</th>
|
|
2214
2429
|
</tr>
|
|
2215
2430
|
</thead>
|
|
2216
2431
|
<tbody>`;
|
|
2217
|
-
const
|
|
2432
|
+
const seenCodeAndSystems = /* @__PURE__ */ new Set();
|
|
2218
2433
|
for (const cond of activeConditions) {
|
|
2219
|
-
const
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
<td class="Name">${conditionCode}</td>
|
|
2224
|
-
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2225
|
-
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2226
|
-
</tr>`;
|
|
2434
|
+
const conditionDisplay = templateUtilities.codeableConceptDisplay(cond.code);
|
|
2435
|
+
const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
|
|
2436
|
+
if (codeAndSystem && seenCodeAndSystems.has(codeAndSystem)) {
|
|
2437
|
+
continue;
|
|
2227
2438
|
}
|
|
2439
|
+
seenCodeAndSystems.add(codeAndSystem);
|
|
2440
|
+
html += `<tr>
|
|
2441
|
+
<td class="Name">${conditionDisplay}</td>
|
|
2442
|
+
<td class="CodeSystem">${codeAndSystem}</td>
|
|
2443
|
+
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2444
|
+
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2445
|
+
<td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
|
|
2446
|
+
</tr>`;
|
|
2228
2447
|
}
|
|
2229
2448
|
html += `</tbody>
|
|
2230
2449
|
</table>`;
|
|
@@ -2252,21 +2471,26 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
2252
2471
|
generateSummaryNarrative(resources, timezone) {
|
|
2253
2472
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2254
2473
|
let isSummaryCreated = false;
|
|
2255
|
-
let html =
|
|
2474
|
+
let html = `<p>This list includes the latest vital signs, sorted by effective date (most recent first).</p>
|
|
2475
|
+
`;
|
|
2476
|
+
html += `
|
|
2256
2477
|
<div>
|
|
2257
2478
|
<table>
|
|
2258
2479
|
<thead>
|
|
2259
2480
|
<tr>
|
|
2260
|
-
<th>
|
|
2481
|
+
<th>Name</th>
|
|
2482
|
+
<th>Code (System)</th>
|
|
2261
2483
|
<th>Result</th>
|
|
2262
|
-
<th>Reference Range</th>
|
|
2263
2484
|
<th>Date</th>
|
|
2485
|
+
<th>Source</th>
|
|
2264
2486
|
</tr>
|
|
2265
2487
|
</thead>
|
|
2266
2488
|
<tbody>`;
|
|
2267
2489
|
for (const resourceItem of resources) {
|
|
2268
2490
|
for (const rowData of resourceItem.section ?? []) {
|
|
2491
|
+
const sectionCodeableConcept = rowData.code;
|
|
2269
2492
|
const data = {};
|
|
2493
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
2270
2494
|
for (const columnData of rowData.section ?? []) {
|
|
2271
2495
|
const columnTitle = columnData.title;
|
|
2272
2496
|
if (columnTitle) {
|
|
@@ -2294,10 +2518,11 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
2294
2518
|
isSummaryCreated = true;
|
|
2295
2519
|
html += `
|
|
2296
2520
|
<tr>
|
|
2297
|
-
<td>${data["Vital Name"] ?? "
|
|
2298
|
-
<td>${
|
|
2299
|
-
<td>${templateUtilities.
|
|
2300
|
-
<td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "
|
|
2521
|
+
<td>${data["Vital Name"] ?? ""}</td>
|
|
2522
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
2523
|
+
<td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? ""}</td>
|
|
2524
|
+
<td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
|
|
2525
|
+
<td>${data["Source"] ?? ""}</td>
|
|
2301
2526
|
</tr>`;
|
|
2302
2527
|
}
|
|
2303
2528
|
}
|
|
@@ -2321,30 +2546,36 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
2321
2546
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
2322
2547
|
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
2323
2548
|
});
|
|
2324
|
-
let html =
|
|
2549
|
+
let html = `<p>This list includes the latest vital signs, sorted by effective date (most recent first).</p>
|
|
2550
|
+
`;
|
|
2551
|
+
html += `
|
|
2325
2552
|
<table>
|
|
2326
2553
|
<thead>
|
|
2327
2554
|
<tr>
|
|
2328
|
-
<th>
|
|
2555
|
+
<th>Name</th>
|
|
2556
|
+
<th>Code (System)</th>
|
|
2329
2557
|
<th>Result</th>
|
|
2330
2558
|
<th>Unit</th>
|
|
2331
2559
|
<th>Interpretation</th>
|
|
2332
2560
|
<th>Component(s)</th>
|
|
2333
2561
|
<th>Comments</th>
|
|
2334
2562
|
<th>Date</th>
|
|
2563
|
+
<th>Source</th>
|
|
2335
2564
|
</tr>
|
|
2336
2565
|
</thead>
|
|
2337
2566
|
<tbody>`;
|
|
2338
2567
|
for (const obs of observations) {
|
|
2339
2568
|
html += `
|
|
2340
|
-
<tr
|
|
2341
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
2569
|
+
<tr>
|
|
2570
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code, "display"))}</td>
|
|
2571
|
+
<td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
|
|
2342
2572
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2343
2573
|
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
2344
2574
|
<td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
|
|
2345
2575
|
<td>${templateUtilities.renderComponent(obs.component)}</td>
|
|
2346
2576
|
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
2347
2577
|
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
2578
|
+
<td>${templateUtilities.getOwnerTag(obs)}</td>
|
|
2348
2579
|
</tr>`;
|
|
2349
2580
|
}
|
|
2350
2581
|
html += `
|
|
@@ -2373,7 +2604,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2373
2604
|
*/
|
|
2374
2605
|
static generateStaticNarrative(resources, timezone) {
|
|
2375
2606
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2376
|
-
let html =
|
|
2607
|
+
let html = `<p>This list includes all DeviceUseStatement resources, sorted by recorded date (most recent first).</p>
|
|
2377
2608
|
<table>
|
|
2378
2609
|
<thead>
|
|
2379
2610
|
<tr>
|
|
@@ -2391,7 +2622,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2391
2622
|
});
|
|
2392
2623
|
for (const dus of deviceStatements) {
|
|
2393
2624
|
html += `
|
|
2394
|
-
<tr
|
|
2625
|
+
<tr>
|
|
2395
2626
|
<td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
|
|
2396
2627
|
<td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
|
|
2397
2628
|
<td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
|
|
@@ -2417,10 +2648,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2417
2648
|
* Generate HTML narrative for Diagnostic Results
|
|
2418
2649
|
* @param resources - FHIR resources array containing Observation and DiagnosticReport resources
|
|
2419
2650
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2651
|
+
* @param now - Optional current date for filtering
|
|
2420
2652
|
* @returns HTML string for rendering
|
|
2421
2653
|
*/
|
|
2422
|
-
generateNarrative(resources, timezone) {
|
|
2423
|
-
return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
|
|
2654
|
+
generateNarrative(resources, timezone, now) {
|
|
2655
|
+
return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone, now);
|
|
2424
2656
|
}
|
|
2425
2657
|
/**
|
|
2426
2658
|
* Helper function to format observation data fields
|
|
@@ -2633,6 +2865,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2633
2865
|
break;
|
|
2634
2866
|
case "valueRange.high.value":
|
|
2635
2867
|
targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2868
|
+
targetData["valueType"] = "valueRange";
|
|
2636
2869
|
break;
|
|
2637
2870
|
case "valueRange.high.unit":
|
|
2638
2871
|
targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
@@ -2647,6 +2880,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2647
2880
|
break;
|
|
2648
2881
|
case "valueRatio.denominator.value":
|
|
2649
2882
|
targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2883
|
+
targetData["valueType"] = "valueRatio";
|
|
2650
2884
|
break;
|
|
2651
2885
|
case "valueRatio.denominator.unit":
|
|
2652
2886
|
targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
@@ -2684,34 +2918,100 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2684
2918
|
* Generate HTML narrative for Diagnostic Results & Observation resources using summary
|
|
2685
2919
|
* @param resources - FHIR Composition resources
|
|
2686
2920
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2921
|
+
* @param now - Optional current date for filtering
|
|
2687
2922
|
* @returns HTML string for rendering
|
|
2688
2923
|
*/
|
|
2689
|
-
generateSummaryNarrative(resources, timezone) {
|
|
2924
|
+
generateSummaryNarrative(resources, timezone, now) {
|
|
2690
2925
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2691
|
-
|
|
2692
|
-
|
|
2926
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
2927
|
+
const twoYearsAgo = new Date(currentDate);
|
|
2928
|
+
twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
|
|
2929
|
+
let skippedObservations = 0;
|
|
2930
|
+
let skippedDiagnosticReports = 0;
|
|
2931
|
+
for (const resourceItem of resources) {
|
|
2932
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2933
|
+
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
2934
|
+
for (const columnData of rowData.section ?? []) {
|
|
2935
|
+
if (columnData.text?.div === "Observation.component" && columnData.section) {
|
|
2936
|
+
for (const componentSection of columnData.section) {
|
|
2937
|
+
const componentData = {};
|
|
2938
|
+
for (const nestedColumn of componentSection.section ?? []) {
|
|
2939
|
+
this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
|
|
2940
|
+
}
|
|
2941
|
+
let compDate = void 0;
|
|
2942
|
+
if (componentData["effectiveDateTime"]) {
|
|
2943
|
+
compDate = new Date(componentData["effectiveDateTime"]);
|
|
2944
|
+
} else if (componentData["effectivePeriodStart"]) {
|
|
2945
|
+
compDate = new Date(componentData["effectivePeriodStart"]);
|
|
2946
|
+
}
|
|
2947
|
+
if (compDate && compDate < twoYearsAgo) {
|
|
2948
|
+
skippedObservations++;
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
} else {
|
|
2952
|
+
const data = {};
|
|
2953
|
+
this.extractSummaryObservationFields(columnData, data, templateUtilities);
|
|
2954
|
+
let obsDate = void 0;
|
|
2955
|
+
if (data["effectiveDateTime"]) {
|
|
2956
|
+
obsDate = new Date(data["effectiveDateTime"]);
|
|
2957
|
+
} else if (data["effectivePeriodStart"]) {
|
|
2958
|
+
obsDate = new Date(data["effectivePeriodStart"]);
|
|
2959
|
+
}
|
|
2960
|
+
if (obsDate && obsDate < twoYearsAgo) {
|
|
2961
|
+
skippedObservations++;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2966
|
+
let issuedDate = void 0;
|
|
2967
|
+
let status = void 0;
|
|
2968
|
+
let reportFound = false;
|
|
2969
|
+
for (const columnData of rowData.section ?? []) {
|
|
2970
|
+
if (columnData.title === "Issued Date") {
|
|
2971
|
+
issuedDate = columnData.text?.div ? new Date(columnData.text.div) : void 0;
|
|
2972
|
+
}
|
|
2973
|
+
if (columnData.title === "Status") {
|
|
2974
|
+
status = columnData.text?.div;
|
|
2975
|
+
}
|
|
2976
|
+
if (columnData.title === "Diagnostic Report Name") {
|
|
2977
|
+
reportFound = true;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
if (status === "final" && issuedDate && issuedDate < twoYearsAgo && reportFound) {
|
|
2981
|
+
skippedDiagnosticReports++;
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
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>
|
|
2987
|
+
`;
|
|
2693
2988
|
let observationhtml = `
|
|
2694
2989
|
<div>
|
|
2695
2990
|
<h3>Observations</h3>
|
|
2991
|
+
${html}
|
|
2696
2992
|
<table>
|
|
2697
2993
|
<thead>
|
|
2698
2994
|
<tr>
|
|
2699
|
-
<th>
|
|
2995
|
+
<th>Name</th>
|
|
2996
|
+
<th>Code (System)</th>
|
|
2700
2997
|
<th>Result</th>
|
|
2701
2998
|
<th>Reference Range</th>
|
|
2702
2999
|
<th>Date</th>
|
|
3000
|
+
<th>Source</th>
|
|
2703
3001
|
</tr>
|
|
2704
3002
|
</thead>
|
|
2705
3003
|
<tbody>`;
|
|
2706
3004
|
let diagnosticReporthtml = `
|
|
2707
3005
|
<div>
|
|
2708
3006
|
<h3>Diagnostic Reports</h3>
|
|
3007
|
+
${html}
|
|
2709
3008
|
<table>
|
|
2710
3009
|
<thead>
|
|
2711
3010
|
<tr>
|
|
2712
3011
|
<th>Report</th>
|
|
2713
3012
|
<th>Performer</th>
|
|
2714
3013
|
<th>Issued</th>
|
|
3014
|
+
<th>Source</th>
|
|
2715
3015
|
</tr>
|
|
2716
3016
|
</thead>
|
|
2717
3017
|
<tbody>`;
|
|
@@ -2719,7 +3019,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2719
3019
|
const diagnosticReportAdded = /* @__PURE__ */ new Set();
|
|
2720
3020
|
for (const resourceItem of resources) {
|
|
2721
3021
|
for (const rowData of resourceItem.section ?? []) {
|
|
3022
|
+
const sectionCodeableConcept = rowData.code;
|
|
2722
3023
|
const data = {};
|
|
3024
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
2723
3025
|
const components = [];
|
|
2724
3026
|
for (const columnData of rowData.section ?? []) {
|
|
2725
3027
|
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
@@ -2729,7 +3031,13 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2729
3031
|
for (const nestedColumn of componentSection.section ?? []) {
|
|
2730
3032
|
this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
|
|
2731
3033
|
}
|
|
2732
|
-
|
|
3034
|
+
let compDate = void 0;
|
|
3035
|
+
if (componentData["effectiveDateTime"]) {
|
|
3036
|
+
compDate = new Date(componentData["effectiveDateTime"]);
|
|
3037
|
+
} else if (componentData["effectivePeriodStart"]) {
|
|
3038
|
+
compDate = new Date(componentData["effectivePeriodStart"]);
|
|
3039
|
+
}
|
|
3040
|
+
if (compDate && compDate >= twoYearsAgo && Object.keys(componentData).length > 0) {
|
|
2733
3041
|
components.push(componentData);
|
|
2734
3042
|
}
|
|
2735
3043
|
}
|
|
@@ -2750,6 +3058,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2750
3058
|
case "Status":
|
|
2751
3059
|
data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2752
3060
|
break;
|
|
3061
|
+
case "Source":
|
|
3062
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
3063
|
+
break;
|
|
2753
3064
|
default:
|
|
2754
3065
|
break;
|
|
2755
3066
|
}
|
|
@@ -2757,6 +3068,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2757
3068
|
}
|
|
2758
3069
|
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
2759
3070
|
let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
|
|
3071
|
+
let obsDate = void 0;
|
|
3072
|
+
if (data["effectiveDateTime"]) {
|
|
3073
|
+
obsDate = new Date(data["effectiveDateTime"]);
|
|
3074
|
+
} else if (data["effectivePeriodStart"]) {
|
|
3075
|
+
obsDate = new Date(data["effectivePeriodStart"]);
|
|
3076
|
+
}
|
|
2760
3077
|
if (!date && data["effectivePeriodStart"]) {
|
|
2761
3078
|
date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
|
|
2762
3079
|
if (data["effectivePeriodEnd"]) {
|
|
@@ -2773,36 +3090,47 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2773
3090
|
observationhtml += `
|
|
2774
3091
|
<tr>
|
|
2775
3092
|
<td>${componentCode}</td>
|
|
2776
|
-
<td
|
|
2777
|
-
<td>${templateUtilities.renderTextAsHtml(component["
|
|
2778
|
-
<td>${
|
|
3093
|
+
<td></td>
|
|
3094
|
+
<td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? ""}</td>
|
|
3095
|
+
<td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? ""}</td>
|
|
3096
|
+
<td>${date ?? ""}</td>
|
|
3097
|
+
<td>${data["source"] ?? ""}</td>
|
|
2779
3098
|
</tr>`;
|
|
2780
3099
|
}
|
|
2781
3100
|
}
|
|
2782
3101
|
} else {
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
observationAdded.
|
|
2786
|
-
|
|
2787
|
-
|
|
3102
|
+
if (obsDate && obsDate >= twoYearsAgo) {
|
|
3103
|
+
const code = data["code"] ?? "";
|
|
3104
|
+
if (code && !observationAdded.has(code)) {
|
|
3105
|
+
observationAdded.add(code);
|
|
3106
|
+
this.formatSummaryObservationData(data);
|
|
3107
|
+
observationhtml += `
|
|
2788
3108
|
<tr>
|
|
2789
|
-
<td>${data["code"] ?? "
|
|
2790
|
-
<td>${templateUtilities.
|
|
2791
|
-
<td>${templateUtilities.renderTextAsHtml(data["
|
|
2792
|
-
<td>${
|
|
3109
|
+
<td>${data["code"] ?? ""}</td>
|
|
3110
|
+
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
3111
|
+
<td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? ""}</td>
|
|
3112
|
+
<td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? ""}</td>
|
|
3113
|
+
<td>${date ?? ""}</td>
|
|
3114
|
+
<td>${data["source"] ?? ""}</td>
|
|
2793
3115
|
</tr>`;
|
|
3116
|
+
}
|
|
2794
3117
|
}
|
|
2795
3118
|
}
|
|
2796
3119
|
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2797
|
-
|
|
3120
|
+
let issuedDate = void 0;
|
|
3121
|
+
if (data["issued"]) {
|
|
3122
|
+
issuedDate = new Date(data["issued"]);
|
|
3123
|
+
}
|
|
3124
|
+
if (data["status"] === "final" && issuedDate && issuedDate >= twoYearsAgo) {
|
|
2798
3125
|
const reportName = data["report"] ?? "";
|
|
2799
3126
|
if (reportName && !diagnosticReportAdded.has(reportName)) {
|
|
2800
3127
|
diagnosticReportAdded.add(reportName);
|
|
2801
3128
|
diagnosticReporthtml += `
|
|
2802
3129
|
<tr>
|
|
2803
|
-
<td>${data["report"] ?? "
|
|
2804
|
-
<td>${data["performer"] ?? "
|
|
2805
|
-
<td>${templateUtilities.renderTime(data["issued"], timezone) ?? "
|
|
3130
|
+
<td>${data["report"] ?? ""}</td>
|
|
3131
|
+
<td>${data["performer"] ?? ""}</td>
|
|
3132
|
+
<td>${templateUtilities.renderTime(data["issued"], timezone) ?? ""}</td>
|
|
3133
|
+
<td>${data["source"] ?? ""}</td>
|
|
2806
3134
|
</tr>`;
|
|
2807
3135
|
}
|
|
2808
3136
|
}
|
|
@@ -2815,6 +3143,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2815
3143
|
</tbody>
|
|
2816
3144
|
</table>
|
|
2817
3145
|
</div>`;
|
|
3146
|
+
if (skippedObservations > 0) {
|
|
3147
|
+
html += `
|
|
3148
|
+
<p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
|
|
3149
|
+
}
|
|
2818
3150
|
}
|
|
2819
3151
|
if (diagnosticReportAdded.size > 0) {
|
|
2820
3152
|
html += diagnosticReporthtml;
|
|
@@ -2822,6 +3154,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2822
3154
|
</tbody>
|
|
2823
3155
|
</table>
|
|
2824
3156
|
</div>`;
|
|
3157
|
+
if (skippedDiagnosticReports > 0) {
|
|
3158
|
+
html += `
|
|
3159
|
+
<p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
|
|
3160
|
+
}
|
|
2825
3161
|
}
|
|
2826
3162
|
html += `
|
|
2827
3163
|
</div>`;
|
|
@@ -2831,12 +3167,33 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2831
3167
|
* Internal static implementation that actually generates the narrative
|
|
2832
3168
|
* @param resources - FHIR resources array containing Observation and DiagnosticReport resources
|
|
2833
3169
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3170
|
+
* @param now - Optional current date for filtering
|
|
2834
3171
|
* @returns HTML string for rendering
|
|
2835
3172
|
*/
|
|
2836
|
-
static generateStaticNarrative(resources, timezone) {
|
|
3173
|
+
static generateStaticNarrative(resources, timezone, now) {
|
|
2837
3174
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2838
|
-
|
|
2839
|
-
const
|
|
3175
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
3176
|
+
const twoYearsAgo = new Date(currentDate);
|
|
3177
|
+
twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
|
|
3178
|
+
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>
|
|
3179
|
+
`;
|
|
3180
|
+
let skippedObservations = 0;
|
|
3181
|
+
let skippedDiagnosticReports = 0;
|
|
3182
|
+
for (const resourceItem of resources) {
|
|
3183
|
+
if (resourceItem.resourceType === "Observation") {
|
|
3184
|
+
const obsDate = this.getObservationDate(resourceItem);
|
|
3185
|
+
if (obsDate && obsDate < twoYearsAgo) {
|
|
3186
|
+
skippedObservations++;
|
|
3187
|
+
}
|
|
3188
|
+
} else if (resourceItem.resourceType === "DiagnosticReport") {
|
|
3189
|
+
const issued = resourceItem.issued;
|
|
3190
|
+
const status = resourceItem.status;
|
|
3191
|
+
if (status === "final" && issued && new Date(issued) < twoYearsAgo) {
|
|
3192
|
+
skippedDiagnosticReports++;
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
const observations = this.getObservations(resources, twoYearsAgo);
|
|
2840
3197
|
if (observations.length > 0) {
|
|
2841
3198
|
observations.sort((a, b) => {
|
|
2842
3199
|
const dateA = this.getObservationDate(a);
|
|
@@ -2845,16 +3202,22 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2845
3202
|
});
|
|
2846
3203
|
this.filterObservationForLoincCodes(observations);
|
|
2847
3204
|
html += this.renderObservations(templateUtilities, observations, timezone);
|
|
3205
|
+
if (skippedObservations > 0) {
|
|
3206
|
+
html += `
|
|
3207
|
+
<p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
|
|
3208
|
+
}
|
|
2848
3209
|
}
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
3210
|
+
const diagnosticReports = this.getDiagnosticReports(resources, twoYearsAgo).filter((resource) => !this.isPanelDiagnosticReport(resource));
|
|
3211
|
+
if (diagnosticReports.length > 0) {
|
|
3212
|
+
diagnosticReports.sort((a, b) => {
|
|
3213
|
+
const dateA = a.issued;
|
|
3214
|
+
const dateB = b.issued;
|
|
3215
|
+
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3216
|
+
});
|
|
3217
|
+
html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
|
|
3218
|
+
if (skippedDiagnosticReports > 0) {
|
|
3219
|
+
html += `
|
|
3220
|
+
<p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
|
|
2858
3221
|
}
|
|
2859
3222
|
}
|
|
2860
3223
|
return html;
|
|
@@ -2899,15 +3262,16 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2899
3262
|
return obsDate;
|
|
2900
3263
|
}
|
|
2901
3264
|
/**
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3265
|
+
* Get all Observation resources from the resource array, filtered by twoYearsAgo
|
|
3266
|
+
* @param resources - FHIR resources array
|
|
3267
|
+
* @param twoYearsAgo - Date object representing the cutoff
|
|
3268
|
+
* @returns Array of Observation resources
|
|
3269
|
+
*/
|
|
3270
|
+
static getObservations(resources, twoYearsAgo) {
|
|
2907
3271
|
return resources.filter((resourceItem) => {
|
|
2908
3272
|
if (resourceItem.resourceType === "Observation") {
|
|
2909
3273
|
const obsDate = this.getObservationDate(resourceItem);
|
|
2910
|
-
if (obsDate && obsDate >=
|
|
3274
|
+
if (obsDate && obsDate >= twoYearsAgo) {
|
|
2911
3275
|
return true;
|
|
2912
3276
|
}
|
|
2913
3277
|
}
|
|
@@ -2915,12 +3279,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2915
3279
|
}).map((resourceItem) => resourceItem);
|
|
2916
3280
|
}
|
|
2917
3281
|
/**
|
|
2918
|
-
* Get all DiagnosticReport resources from the resource array
|
|
3282
|
+
* Get all DiagnosticReport resources from the resource array, filtered by twoYearsAgo
|
|
2919
3283
|
* @param resources - FHIR resources array
|
|
3284
|
+
* @param twoYearsAgo - Date object representing the cutoff
|
|
2920
3285
|
* @returns Array of DiagnosticReport resources
|
|
2921
3286
|
*/
|
|
2922
|
-
static getDiagnosticReports(resources) {
|
|
2923
|
-
return resources.filter((resourceItem) =>
|
|
3287
|
+
static getDiagnosticReports(resources, twoYearsAgo) {
|
|
3288
|
+
return resources.filter((resourceItem) => {
|
|
3289
|
+
if (resourceItem.resourceType === "DiagnosticReport") {
|
|
3290
|
+
const issued = resourceItem.issued;
|
|
3291
|
+
if (issued && new Date(issued) >= twoYearsAgo) {
|
|
3292
|
+
return true;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
return false;
|
|
3296
|
+
}).map((resourceItem) => resourceItem);
|
|
2924
3297
|
}
|
|
2925
3298
|
/**
|
|
2926
3299
|
* Render HTML table for Observation resources
|
|
@@ -2931,32 +3304,36 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2931
3304
|
*/
|
|
2932
3305
|
static renderObservations(templateUtilities, observations, timezone) {
|
|
2933
3306
|
let html = "";
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
<h3>Observations</h3>`;
|
|
2937
|
-
}
|
|
3307
|
+
html += `
|
|
3308
|
+
<h3>Observations</h3>`;
|
|
2938
3309
|
html += `
|
|
2939
3310
|
<table>
|
|
2940
3311
|
<thead>
|
|
2941
3312
|
<tr>
|
|
2942
|
-
<th>
|
|
3313
|
+
<th>Name</th>
|
|
3314
|
+
<th>Code (System)</th>
|
|
2943
3315
|
<th>Result</th>
|
|
2944
3316
|
<th>Reference Range</th>
|
|
2945
3317
|
<th>Date</th>
|
|
3318
|
+
<th>Source</th>
|
|
2946
3319
|
</tr>
|
|
2947
3320
|
</thead>
|
|
2948
3321
|
<tbody>`;
|
|
2949
3322
|
const observationAdded = /* @__PURE__ */ new Set();
|
|
2950
3323
|
for (const obs of observations) {
|
|
2951
|
-
const
|
|
2952
|
-
|
|
2953
|
-
|
|
3324
|
+
const obsCodeDisplay = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code));
|
|
3325
|
+
const obsCodeAndSystem = templateUtilities.codeableConceptCoding(obs.code);
|
|
3326
|
+
if (!observationAdded.has(obsCodeDisplay) && !observationAdded.has(obsCodeAndSystem)) {
|
|
3327
|
+
observationAdded.add(obsCodeDisplay);
|
|
3328
|
+
observationAdded.add(obsCodeAndSystem);
|
|
2954
3329
|
html += `
|
|
2955
3330
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2956
|
-
<td>${
|
|
3331
|
+
<td>${obsCodeDisplay}</td>
|
|
3332
|
+
<td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
|
|
2957
3333
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2958
3334
|
<td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
|
|
2959
3335
|
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
3336
|
+
<td>${templateUtilities.getOwnerTag(obs)}</td>
|
|
2960
3337
|
</tr>`;
|
|
2961
3338
|
}
|
|
2962
3339
|
}
|
|
@@ -2979,17 +3356,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2979
3356
|
<thead>
|
|
2980
3357
|
<tr>
|
|
2981
3358
|
<th>Report</th>
|
|
3359
|
+
<th>Code (System)</th>
|
|
2982
3360
|
<th>Category</th>
|
|
2983
3361
|
<th>Result</th>
|
|
2984
3362
|
<th>Issued</th>
|
|
3363
|
+
<th>Source</th>
|
|
2985
3364
|
</tr>
|
|
2986
3365
|
</thead>
|
|
2987
3366
|
<tbody>`;
|
|
2988
3367
|
const diagnosticReportAdded = /* @__PURE__ */ new Set();
|
|
2989
3368
|
for (const report of reports) {
|
|
2990
|
-
const reportName = templateUtilities.renderTextAsHtml(templateUtilities.
|
|
2991
|
-
|
|
3369
|
+
const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(report.code));
|
|
3370
|
+
const codeAndSystem = templateUtilities.codeableConceptCoding(report.code);
|
|
3371
|
+
if (!diagnosticReportAdded.has(reportName) && !diagnosticReportAdded.has(codeAndSystem)) {
|
|
2992
3372
|
diagnosticReportAdded.add(reportName);
|
|
3373
|
+
diagnosticReportAdded.add(codeAndSystem);
|
|
2993
3374
|
let resultCount = "";
|
|
2994
3375
|
if (report.result && Array.isArray(report.result)) {
|
|
2995
3376
|
resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
|
|
@@ -2997,9 +3378,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2997
3378
|
html += `
|
|
2998
3379
|
<tr id="${templateUtilities.narrativeLinkId(report)}">
|
|
2999
3380
|
<td>${reportName}</td>
|
|
3381
|
+
<td>${codeAndSystem}</td>
|
|
3000
3382
|
<td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
|
|
3001
3383
|
<td>${resultCount}</td>
|
|
3002
3384
|
<td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
|
|
3385
|
+
<td>${templateUtilities.getOwnerTag(report)}</td>
|
|
3003
3386
|
</tr>`;
|
|
3004
3387
|
}
|
|
3005
3388
|
}
|
|
@@ -3008,6 +3391,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
3008
3391
|
</table>`;
|
|
3009
3392
|
return html;
|
|
3010
3393
|
}
|
|
3394
|
+
/**
|
|
3395
|
+
* 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)
|
|
3396
|
+
* @returns true if the report is just a panel
|
|
3397
|
+
*/
|
|
3398
|
+
static isPanelDiagnosticReport(report) {
|
|
3399
|
+
return this.hasEssentialLabPanelLoinc(
|
|
3400
|
+
report
|
|
3401
|
+
);
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* Check if a DiagnosticReport or Observation has a LOINC code in ESSENTIAL_LAB_PANELS
|
|
3405
|
+
* @param resource - DiagnosticReport or Observation
|
|
3406
|
+
* @returns true if any LOINC code is in ESSENTIAL_LAB_PANELS, false otherwise
|
|
3407
|
+
*/
|
|
3408
|
+
static hasEssentialLabPanelLoinc(resource) {
|
|
3409
|
+
const codings = resource?.code?.coding ?? [];
|
|
3410
|
+
return codings.some(
|
|
3411
|
+
(coding) => coding && coding.system && coding.code && coding.system.toLowerCase().includes("loinc") && Object.keys(ESSENTIAL_LAB_PANELS).includes(coding.code)
|
|
3412
|
+
);
|
|
3413
|
+
}
|
|
3011
3414
|
};
|
|
3012
3415
|
|
|
3013
3416
|
// src/narratives/templates/typescript/HistoryOfProceduresTemplate.ts
|
|
@@ -3035,20 +3438,26 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
3035
3438
|
generateSummaryNarrative(resources, timezone) {
|
|
3036
3439
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3037
3440
|
let isSummaryCreated = false;
|
|
3038
|
-
let html =
|
|
3441
|
+
let html = `<p>This list includes all Procedure resources, sorted by performed date (most recent first).</p>
|
|
3442
|
+
`;
|
|
3443
|
+
html += `
|
|
3039
3444
|
<div>
|
|
3040
3445
|
<table>
|
|
3041
3446
|
<thead>
|
|
3042
3447
|
<tr>
|
|
3043
3448
|
<th>Procedure</th>
|
|
3449
|
+
<th>Code (System)</th>
|
|
3044
3450
|
<th>Performer</th>
|
|
3045
3451
|
<th>Date</th>
|
|
3452
|
+
<th>Source</th>
|
|
3046
3453
|
</tr>
|
|
3047
3454
|
</thead>
|
|
3048
3455
|
<tbody>`;
|
|
3049
3456
|
for (const resourceItem of resources) {
|
|
3050
3457
|
for (const rowData of resourceItem.section ?? []) {
|
|
3458
|
+
const sectionCodeableConcept = rowData.code;
|
|
3051
3459
|
const data = {};
|
|
3460
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
3052
3461
|
for (const columnData of rowData.section ?? []) {
|
|
3053
3462
|
switch (columnData.title) {
|
|
3054
3463
|
case "Procedure Name":
|
|
@@ -3060,6 +3469,9 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
3060
3469
|
case "Performed Date":
|
|
3061
3470
|
data["date"] = columnData.text?.div ?? "";
|
|
3062
3471
|
break;
|
|
3472
|
+
case "Source":
|
|
3473
|
+
data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
3474
|
+
break;
|
|
3063
3475
|
default:
|
|
3064
3476
|
break;
|
|
3065
3477
|
}
|
|
@@ -3067,9 +3479,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
3067
3479
|
isSummaryCreated = true;
|
|
3068
3480
|
html += `
|
|
3069
3481
|
<tr>
|
|
3070
|
-
<td>${data["procedure"] ?? "
|
|
3071
|
-
|
|
3072
|
-
<td>${
|
|
3482
|
+
<td>${data["procedure"] ?? ""}</td>
|
|
3483
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
3484
|
+
<td>${data["performer"] ?? ""}</td>
|
|
3485
|
+
<td>${templateUtilities.renderTime(data["date"], timezone) ?? ""}</td>
|
|
3486
|
+
<td>${data["source"] ?? ""}</td>
|
|
3073
3487
|
</tr>`;
|
|
3074
3488
|
}
|
|
3075
3489
|
}
|
|
@@ -3087,23 +3501,29 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
3087
3501
|
*/
|
|
3088
3502
|
static generateStaticNarrative(resources, timezone) {
|
|
3089
3503
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3090
|
-
let html =
|
|
3504
|
+
let html = `<p>This list includes all Procedure resources, sorted by performed date (most recent first).</p>
|
|
3505
|
+
`;
|
|
3506
|
+
html += `
|
|
3091
3507
|
<table>
|
|
3092
3508
|
<thead>
|
|
3093
3509
|
<tr>
|
|
3094
3510
|
<th>Procedure</th>
|
|
3511
|
+
<th>Code (System)</th>
|
|
3095
3512
|
<th>Comments</th>
|
|
3096
3513
|
<th>Date</th>
|
|
3514
|
+
<th>Source</th>
|
|
3097
3515
|
</tr>
|
|
3098
3516
|
</thead>
|
|
3099
3517
|
<tbody>`;
|
|
3100
3518
|
for (const resourceItem of resources) {
|
|
3101
3519
|
const proc = resourceItem;
|
|
3102
3520
|
html += `
|
|
3103
|
-
<tr
|
|
3104
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
3521
|
+
<tr>
|
|
3522
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(proc.code, "display"))}</td>
|
|
3523
|
+
<td>${templateUtilities.codeableConceptCoding(proc.code)}</td>
|
|
3105
3524
|
<td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
|
|
3106
|
-
<td>${
|
|
3525
|
+
<td>${templateUtilities.renderTime(proc.performedDateTime || proc.performedPeriod?.start, timezone)}</td>
|
|
3526
|
+
<td>${templateUtilities.getOwnerTag(proc)}</td>
|
|
3107
3527
|
</tr>`;
|
|
3108
3528
|
}
|
|
3109
3529
|
html += `
|
|
@@ -3138,26 +3558,32 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
|
|
|
3138
3558
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
3139
3559
|
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3140
3560
|
});
|
|
3141
|
-
let html =
|
|
3561
|
+
let html = `<p>This list includes all information about the patient's social history, sorted by effective date (most recent first).</p>
|
|
3562
|
+
`;
|
|
3563
|
+
html += `
|
|
3142
3564
|
<table>
|
|
3143
3565
|
<thead>
|
|
3144
3566
|
<tr>
|
|
3145
|
-
<th>
|
|
3567
|
+
<th>Name</th>
|
|
3568
|
+
<th>Code (System)</th>
|
|
3146
3569
|
<th>Result</th>
|
|
3147
3570
|
<th>Unit</th>
|
|
3148
3571
|
<th>Comments</th>
|
|
3149
3572
|
<th>Date</th>
|
|
3573
|
+
<th>Source</th>
|
|
3150
3574
|
</tr>
|
|
3151
3575
|
</thead>
|
|
3152
3576
|
<tbody>`;
|
|
3153
3577
|
for (const obs of observations) {
|
|
3154
3578
|
html += `
|
|
3155
|
-
<tr
|
|
3156
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
3579
|
+
<tr>
|
|
3580
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code))}</td>
|
|
3581
|
+
<td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
|
|
3157
3582
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
3158
3583
|
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
3159
3584
|
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
3160
3585
|
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
3586
|
+
<td>${templateUtilities.getOwnerTag(obs)}</td>
|
|
3161
3587
|
</tr>`;
|
|
3162
3588
|
}
|
|
3163
3589
|
html += `
|
|
@@ -3173,14 +3599,27 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
3173
3599
|
* Generate HTML narrative for Past History of Illnesses
|
|
3174
3600
|
* @param resources - FHIR Condition resources
|
|
3175
3601
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3602
|
+
* @param now - Optional current date to use for generating relative dates in the narrative
|
|
3176
3603
|
* @returns HTML string for rendering
|
|
3177
3604
|
*/
|
|
3178
|
-
|
|
3179
|
-
generateNarrative(resources, timezone) {
|
|
3605
|
+
generateNarrative(resources, timezone, now) {
|
|
3180
3606
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3181
|
-
let html =
|
|
3607
|
+
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>
|
|
3608
|
+
`;
|
|
3182
3609
|
const resolvedConditions = resources.map((entry) => entry) || [];
|
|
3183
|
-
|
|
3610
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
3611
|
+
const fiveYearsAgo = new Date(currentDate);
|
|
3612
|
+
fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
|
|
3613
|
+
let skippedConditions = 0;
|
|
3614
|
+
const filteredConditions = [];
|
|
3615
|
+
for (const cond of resolvedConditions) {
|
|
3616
|
+
if (cond.recordedDate && new Date(cond.recordedDate) >= fiveYearsAgo) {
|
|
3617
|
+
filteredConditions.push(cond);
|
|
3618
|
+
} else {
|
|
3619
|
+
skippedConditions++;
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
filteredConditions.sort((a, b) => {
|
|
3184
3623
|
const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
|
|
3185
3624
|
const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
|
|
3186
3625
|
return dateB - dateA;
|
|
@@ -3190,27 +3629,35 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
3190
3629
|
<thead>
|
|
3191
3630
|
<tr>
|
|
3192
3631
|
<th>Problem</th>
|
|
3632
|
+
<th>Code (System)</th>
|
|
3193
3633
|
<th>Onset Date</th>
|
|
3194
3634
|
<th>Recorded Date</th>
|
|
3195
3635
|
<th>Resolved Date</th>
|
|
3636
|
+
<th>Source</th>
|
|
3196
3637
|
</tr>
|
|
3197
3638
|
</thead>
|
|
3198
3639
|
<tbody>`;
|
|
3199
3640
|
const addedConditionCodes = /* @__PURE__ */ new Set();
|
|
3200
|
-
for (const cond of
|
|
3201
|
-
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.
|
|
3641
|
+
for (const cond of filteredConditions) {
|
|
3642
|
+
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(cond.code));
|
|
3202
3643
|
if (!addedConditionCodes.has(conditionCode)) {
|
|
3203
3644
|
addedConditionCodes.add(conditionCode);
|
|
3204
|
-
html += `<tr
|
|
3645
|
+
html += `<tr>
|
|
3205
3646
|
<td class="Name">${conditionCode}</td>
|
|
3647
|
+
<td class="CodeSystem">${templateUtilities.codeableConceptCoding(cond.code)}</td>
|
|
3206
3648
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
3207
3649
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
3208
3650
|
<td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
|
|
3651
|
+
<td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
|
|
3209
3652
|
</tr>`;
|
|
3210
3653
|
}
|
|
3211
3654
|
}
|
|
3212
3655
|
html += `</tbody>
|
|
3213
3656
|
</table>`;
|
|
3657
|
+
if (skippedConditions > 0) {
|
|
3658
|
+
html += `
|
|
3659
|
+
<p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
|
|
3660
|
+
}
|
|
3214
3661
|
return html;
|
|
3215
3662
|
}
|
|
3216
3663
|
};
|
|
@@ -3231,7 +3678,9 @@ var PlanOfCareTemplate = class {
|
|
|
3231
3678
|
const endB = b.period?.end ? new Date(b.period?.end).getTime() : 0;
|
|
3232
3679
|
return endB - endA;
|
|
3233
3680
|
});
|
|
3234
|
-
let html =
|
|
3681
|
+
let html = `<p>This list includes all CarePlan resources, sorted by planned end date (most recent first).</p>
|
|
3682
|
+
`;
|
|
3683
|
+
html += `
|
|
3235
3684
|
<table>
|
|
3236
3685
|
<thead>
|
|
3237
3686
|
<tr>
|
|
@@ -3240,6 +3689,7 @@ var PlanOfCareTemplate = class {
|
|
|
3240
3689
|
<th>Comments</th>
|
|
3241
3690
|
<th>Planned Start</th>
|
|
3242
3691
|
<th>Planned End</th>
|
|
3692
|
+
<th>Source</th>
|
|
3243
3693
|
</tr>
|
|
3244
3694
|
</thead>
|
|
3245
3695
|
<tbody>`;
|
|
@@ -3251,6 +3701,7 @@ var PlanOfCareTemplate = class {
|
|
|
3251
3701
|
<td>${templateUtilities.concat(cp.note, "text")}</td>
|
|
3252
3702
|
<td>${cp.period?.start ? templateUtilities.renderTime(cp.period?.start, timezone) : ""}</td>
|
|
3253
3703
|
<td>${cp.period?.end ? templateUtilities.renderTime(cp.period?.end, timezone) : ""}</td>
|
|
3704
|
+
<td>${templateUtilities.getOwnerTag(cp)}</td>
|
|
3254
3705
|
</tr>`;
|
|
3255
3706
|
}
|
|
3256
3707
|
html += `
|
|
@@ -3267,7 +3718,9 @@ var PlanOfCareTemplate = class {
|
|
|
3267
3718
|
generateSummaryNarrative(resources, timezone) {
|
|
3268
3719
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3269
3720
|
let isSummaryCreated = false;
|
|
3270
|
-
let html =
|
|
3721
|
+
let html = `<p>This list includes all CarePlan resources, sorted by planned end date (most recent first).</p>
|
|
3722
|
+
`;
|
|
3723
|
+
html += `
|
|
3271
3724
|
<div>
|
|
3272
3725
|
<table>
|
|
3273
3726
|
<thead>
|
|
@@ -3276,6 +3729,7 @@ var PlanOfCareTemplate = class {
|
|
|
3276
3729
|
<th>Created</th>
|
|
3277
3730
|
<th>Planned Start</th>
|
|
3278
3731
|
<th>Planned End</th>
|
|
3732
|
+
<th>Source</th>
|
|
3279
3733
|
</tr>
|
|
3280
3734
|
</thead>
|
|
3281
3735
|
<tbody>`;
|
|
@@ -3293,10 +3747,11 @@ var PlanOfCareTemplate = class {
|
|
|
3293
3747
|
isSummaryCreated = true;
|
|
3294
3748
|
html += `
|
|
3295
3749
|
<tr>
|
|
3296
|
-
<td>${data["CarePlan Name"] ?? "
|
|
3297
|
-
<td>${templateUtilities.renderTime(data["created"], timezone) ?? "
|
|
3298
|
-
<td>${templateUtilities.renderTime(data["period.start"], timezone) ?? "
|
|
3299
|
-
<td>${templateUtilities.renderTime(data["period.end"], timezone) ?? "
|
|
3750
|
+
<td>${data["CarePlan Name"] ?? ""}</td>
|
|
3751
|
+
<td>${templateUtilities.renderTime(data["created"], timezone) ?? ""}</td>
|
|
3752
|
+
<td>${templateUtilities.renderTime(data["period.start"], timezone) ?? ""}</td>
|
|
3753
|
+
<td>${templateUtilities.renderTime(data["period.end"], timezone) ?? ""}</td>
|
|
3754
|
+
<td>${data["source"] ?? ""}</td>
|
|
3300
3755
|
</tr>`;
|
|
3301
3756
|
}
|
|
3302
3757
|
}
|
|
@@ -3327,77 +3782,54 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
3327
3782
|
*/
|
|
3328
3783
|
static generateStaticNarrative(resources, timezone) {
|
|
3329
3784
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3330
|
-
let html =
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
|
|
3342
|
-
const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
|
|
3343
|
-
return dateB - dateA;
|
|
3785
|
+
let html = `<p>This section summarizes key observations and assessments related to the person's functional status and ability to perform daily activities.</p>`;
|
|
3786
|
+
let functionalObservations = resources.filter((r) => r.resourceType === "Observation").filter((r) => {
|
|
3787
|
+
const hasFunctionalLoinc = r.code?.coding?.some(
|
|
3788
|
+
(c) => c.system?.toLowerCase().includes("loinc") && c.code === "47420-5"
|
|
3789
|
+
);
|
|
3790
|
+
const hasFunctionalCategory = r.category?.some(
|
|
3791
|
+
(cat) => cat.coding?.some(
|
|
3792
|
+
(c) => c.code === "functional-status" || c.display?.toLowerCase().includes("functional")
|
|
3793
|
+
)
|
|
3794
|
+
);
|
|
3795
|
+
return hasFunctionalLoinc || hasFunctionalCategory;
|
|
3344
3796
|
});
|
|
3345
|
-
|
|
3346
|
-
const
|
|
3347
|
-
|
|
3348
|
-
return dateB - dateA;
|
|
3797
|
+
functionalObservations = functionalObservations.sort((a, b) => {
|
|
3798
|
+
const getObsDate = (obs) => obs.effectiveDateTime ? new Date(obs.effectiveDateTime).getTime() : obs.issued ? new Date(obs.issued).getTime() : 0;
|
|
3799
|
+
return getObsDate(b) - getObsDate(a);
|
|
3349
3800
|
});
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
const
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
}
|
|
3801
|
+
let clinicalImpressions = resources.filter((r) => r.resourceType === "ClinicalImpression").filter((r) => r.status === "completed");
|
|
3802
|
+
clinicalImpressions = clinicalImpressions.sort((a, b) => {
|
|
3803
|
+
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;
|
|
3804
|
+
return getImpressionDate(b) - getImpressionDate(a);
|
|
3805
|
+
});
|
|
3806
|
+
if (functionalObservations.length > 0) {
|
|
3807
|
+
html += `<table><thead><tr><th>Observation</th><th>Value</th><th>Date</th><th>Interpretation</th><th>Comments</th></tr></thead><tbody>`;
|
|
3808
|
+
for (const obs of functionalObservations) {
|
|
3809
|
+
const observation = obs;
|
|
3810
|
+
const obsName = templateUtilities.codeableConceptDisplay(observation.code);
|
|
3811
|
+
const value = templateUtilities.extractObservationValue(observation);
|
|
3812
|
+
const date = observation.effectiveDateTime ? templateUtilities.renderDate(observation.effectiveDateTime) : observation.issued ? templateUtilities.renderDate(observation.issued) : "";
|
|
3813
|
+
const interpretation = observation.interpretation ? templateUtilities.codeableConceptDisplay(observation.interpretation[0]) : "";
|
|
3814
|
+
const comments = observation.comment || observation.note?.map((n) => n.text).join("; ") || "";
|
|
3815
|
+
html += `<tr id="${templateUtilities.narrativeLinkId(observation)}">
|
|
3816
|
+
<td>${obsName}</td>
|
|
3817
|
+
<td>${value ?? ""}</td>
|
|
3818
|
+
<td>${date}</td>
|
|
3819
|
+
<td>${interpretation}</td>
|
|
3820
|
+
<td>${comments}</td>
|
|
3821
|
+
</tr>`;
|
|
3372
3822
|
}
|
|
3373
|
-
html += `</tbody
|
|
3374
|
-
</table>`;
|
|
3823
|
+
html += `</tbody></table>`;
|
|
3375
3824
|
}
|
|
3376
3825
|
if (clinicalImpressions.length > 0) {
|
|
3377
|
-
html += `<
|
|
3378
|
-
<table>
|
|
3379
|
-
<thead>
|
|
3380
|
-
<tr>
|
|
3381
|
-
<th>Date</th>
|
|
3382
|
-
<th>Status</th>
|
|
3383
|
-
<th>Description</th>
|
|
3384
|
-
<th>Summary</th>
|
|
3385
|
-
<th>Findings</th>
|
|
3386
|
-
</tr>
|
|
3387
|
-
</thead>
|
|
3388
|
-
<tbody>`;
|
|
3826
|
+
html += `<table><thead><tr><th>Date</th><th>Status</th><th>Description</th><th>Summary</th><th>Findings</th></tr></thead><tbody>`;
|
|
3389
3827
|
for (const impression of clinicalImpressions) {
|
|
3390
3828
|
let formattedDate = "";
|
|
3391
3829
|
if (impression.effectiveDateTime) {
|
|
3392
|
-
formattedDate = templateUtilities.renderTime(
|
|
3393
|
-
impression.effectiveDateTime,
|
|
3394
|
-
timezone
|
|
3395
|
-
);
|
|
3830
|
+
formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
|
|
3396
3831
|
} else if (impression.effectivePeriod) {
|
|
3397
|
-
formattedDate = templateUtilities.renderPeriod(
|
|
3398
|
-
impression.effectivePeriod,
|
|
3399
|
-
timezone
|
|
3400
|
-
);
|
|
3832
|
+
formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
|
|
3401
3833
|
} else if (impression.date) {
|
|
3402
3834
|
formattedDate = templateUtilities.renderDate(impression.date);
|
|
3403
3835
|
}
|
|
@@ -3405,23 +3837,24 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
3405
3837
|
if (impression.finding && impression.finding.length > 0) {
|
|
3406
3838
|
findingsHtml = "<ul>";
|
|
3407
3839
|
for (const finding of impression.finding) {
|
|
3408
|
-
const findingText = finding.itemCodeableConcept ? templateUtilities.
|
|
3840
|
+
const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
|
|
3409
3841
|
const cause = finding.basis || "";
|
|
3410
3842
|
findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
|
|
3411
3843
|
}
|
|
3412
3844
|
findingsHtml += "</ul>";
|
|
3413
3845
|
}
|
|
3414
|
-
html +=
|
|
3415
|
-
<
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
</tr>`;
|
|
3846
|
+
html += `<tr id="${templateUtilities.narrativeLinkId(impression)}">
|
|
3847
|
+
<td>${formattedDate}</td>
|
|
3848
|
+
<td>${impression.status || ""}</td>
|
|
3849
|
+
<td>${impression.description || ""}</td>
|
|
3850
|
+
<td>${impression.summary || ""}</td>
|
|
3851
|
+
<td>${findingsHtml}</td>
|
|
3852
|
+
</tr>`;
|
|
3422
3853
|
}
|
|
3423
|
-
html += `</tbody
|
|
3424
|
-
|
|
3854
|
+
html += `</tbody></table>`;
|
|
3855
|
+
}
|
|
3856
|
+
if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
|
|
3857
|
+
html += `<p>No functional status information available.</p>`;
|
|
3425
3858
|
}
|
|
3426
3859
|
return html;
|
|
3427
3860
|
}
|
|
@@ -3446,34 +3879,110 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
3446
3879
|
*/
|
|
3447
3880
|
static generateStaticNarrative(resources, timezone) {
|
|
3448
3881
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3449
|
-
const
|
|
3450
|
-
|
|
3882
|
+
const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
|
|
3883
|
+
const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
|
|
3884
|
+
const observations = filteredResources.filter((r) => r.resourceType === "Observation");
|
|
3885
|
+
const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
|
|
3886
|
+
const EDD_LOINC = "11778-8";
|
|
3887
|
+
const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
|
|
3888
|
+
const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
|
|
3889
|
+
const pregnancyStatusObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code && pregnancyStatusCodes.includes(c.code))).sort((a, b) => {
|
|
3451
3890
|
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
3452
3891
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
3453
|
-
return
|
|
3892
|
+
return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3893
|
+
})[0];
|
|
3894
|
+
const eddObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code === EDD_LOINC)).sort((a, b) => {
|
|
3895
|
+
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
3896
|
+
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
3897
|
+
return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3898
|
+
})[0];
|
|
3899
|
+
const historyObs = observations.filter(
|
|
3900
|
+
(obs) => obs.code?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code)) || obs.valueCodeableConcept?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code))
|
|
3901
|
+
).sort((a, b) => {
|
|
3902
|
+
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
3903
|
+
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
3904
|
+
return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
3454
3905
|
});
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
<
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3906
|
+
if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
|
|
3907
|
+
return `<p>No history of pregnancy found.</p>`;
|
|
3908
|
+
}
|
|
3909
|
+
let html = `<p>This list includes Observation and Condition resources relevant to pregnancy, sorted by date (most recent first).</p>`;
|
|
3910
|
+
html += `
|
|
3911
|
+
<table>
|
|
3912
|
+
<thead>
|
|
3913
|
+
<tr>
|
|
3914
|
+
<th>Result</th>
|
|
3915
|
+
<th>Code (System)</th>
|
|
3916
|
+
<th>Comments</th>
|
|
3917
|
+
<th>Date</th>
|
|
3918
|
+
<th>Source</th>
|
|
3919
|
+
</tr>
|
|
3920
|
+
</thead>
|
|
3921
|
+
<tbody>`;
|
|
3922
|
+
function renderRow({ id, result, comments, date, codeSystem, owner }) {
|
|
3467
3923
|
html += `
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3924
|
+
<tr id="${id}">
|
|
3925
|
+
<td class="Result">${result}</td>
|
|
3926
|
+
<td class="CodeSystem">${codeSystem}</td>
|
|
3927
|
+
<td class="Comments">${comments}</td>
|
|
3928
|
+
<td class="Date">${date}</td>
|
|
3929
|
+
<td class="Source">${owner}</td>
|
|
3930
|
+
</tr>`;
|
|
3931
|
+
}
|
|
3932
|
+
const rowResources = [];
|
|
3933
|
+
if (pregnancyStatusObs) {
|
|
3934
|
+
const date = pregnancyStatusObs.effectiveDateTime || pregnancyStatusObs.effectivePeriod?.start;
|
|
3935
|
+
rowResources.push({ resource: pregnancyStatusObs, date, type: "status" });
|
|
3936
|
+
}
|
|
3937
|
+
if (eddObs) {
|
|
3938
|
+
const date = eddObs.effectiveDateTime || eddObs.effectivePeriod?.start;
|
|
3939
|
+
rowResources.push({ resource: eddObs, date, type: "edd" });
|
|
3940
|
+
}
|
|
3941
|
+
for (const obs of historyObs) {
|
|
3942
|
+
const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
|
|
3943
|
+
rowResources.push({ resource: obs, date, type: "history" });
|
|
3944
|
+
}
|
|
3945
|
+
for (const cond of conditions) {
|
|
3946
|
+
const condition = cond;
|
|
3947
|
+
const date = condition.onsetDateTime || condition.onsetPeriod?.start;
|
|
3948
|
+
rowResources.push({ resource: condition, date, type: "condition" });
|
|
3949
|
+
}
|
|
3950
|
+
rowResources.sort((a, b) => {
|
|
3951
|
+
if (!a.date && !b.date) return 0;
|
|
3952
|
+
if (!a.date) return 1;
|
|
3953
|
+
if (!b.date) return -1;
|
|
3954
|
+
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
|
3955
|
+
});
|
|
3956
|
+
for (const { resource, date, type } of rowResources) {
|
|
3957
|
+
let result = "", comments = "", dateStr = "", codeSystem = "";
|
|
3958
|
+
const id = templateUtilities.narrativeLinkId(resource);
|
|
3959
|
+
if (type === "status") {
|
|
3960
|
+
result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
|
|
3961
|
+
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
3962
|
+
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
3963
|
+
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
3964
|
+
} else if (type === "edd") {
|
|
3965
|
+
result = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
|
|
3966
|
+
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
3967
|
+
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
3968
|
+
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
3969
|
+
} else if (type === "history") {
|
|
3970
|
+
result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
|
|
3971
|
+
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
3972
|
+
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
3973
|
+
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
3974
|
+
} else if (type === "condition") {
|
|
3975
|
+
result = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(resource.code));
|
|
3976
|
+
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
3977
|
+
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
3978
|
+
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
3979
|
+
}
|
|
3980
|
+
const owner = templateUtilities.getOwnerTag(resource);
|
|
3981
|
+
renderRow({ id, result, comments, date: dateStr, codeSystem, owner });
|
|
3473
3982
|
}
|
|
3474
3983
|
html += `
|
|
3475
|
-
|
|
3476
|
-
|
|
3984
|
+
</tbody>
|
|
3985
|
+
</table>`;
|
|
3477
3986
|
return html;
|
|
3478
3987
|
}
|
|
3479
3988
|
};
|
|
@@ -3503,8 +4012,9 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
3503
4012
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3504
4013
|
static generateStaticNarrative(resources, timezone) {
|
|
3505
4014
|
const templateUtilities = new TemplateUtilities(resources);
|
|
3506
|
-
let html =
|
|
3507
|
-
|
|
4015
|
+
let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
|
|
4016
|
+
`;
|
|
4017
|
+
html += `<table>
|
|
3508
4018
|
<thead>
|
|
3509
4019
|
<tr>
|
|
3510
4020
|
<th>Scope</th>
|
|
@@ -3517,8 +4027,8 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
3517
4027
|
for (const resourceItem of resources) {
|
|
3518
4028
|
const consent = resourceItem;
|
|
3519
4029
|
html += `
|
|
3520
|
-
<tr
|
|
3521
|
-
<td>${templateUtilities.renderTextAsHtml(templateUtilities.
|
|
4030
|
+
<tr>
|
|
4031
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display"))}</td>
|
|
3522
4032
|
<td>${consent.status || ""}</td>
|
|
3523
4033
|
<td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
|
|
3524
4034
|
<td>${consent.dateTime || ""}</td>
|
|
@@ -3551,7 +4061,7 @@ var TypeScriptTemplateMapper = class {
|
|
|
3551
4061
|
resources,
|
|
3552
4062
|
timezone,
|
|
3553
4063
|
now
|
|
3554
|
-
) : templateClass.generateNarrative(resources, timezone);
|
|
4064
|
+
) : templateClass.generateNarrative(resources, timezone, now);
|
|
3555
4065
|
}
|
|
3556
4066
|
};
|
|
3557
4067
|
// Map of section types to their template classes
|