@imranq2/fhirpatientsummary 1.0.16 → 1.0.18
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/README.md +6 -6
- package/dist/index.cjs +661 -340
- package/dist/index.d.cts +74 -8
- package/dist/index.d.ts +74 -8
- package/dist/index.js +661 -340
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -82,33 +82,21 @@ var BLOOD_PRESSURE_LOINC_CODES = {
|
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
// src/structures/ips_section_resource_map.ts
|
|
85
|
-
var IPSSectionResourceMap = {
|
|
86
|
-
["Patient" /* PATIENT */]: ["Patient"],
|
|
87
|
-
["AllergyIntoleranceSection" /* ALLERGIES */]: ["AllergyIntolerance"],
|
|
88
|
-
["MedicationSummarySection" /* MEDICATIONS */]: ["MedicationRequest", "MedicationStatement", "Medication"],
|
|
89
|
-
// Medication resource is needed for identifying name of medication
|
|
90
|
-
["ProblemSection" /* PROBLEMS */]: ["Condition"],
|
|
91
|
-
["ImmunizationSection" /* IMMUNIZATIONS */]: ["Immunization", "Organization"],
|
|
92
|
-
// Immunization can include Organization as a related resource
|
|
93
|
-
["VitalSignsSection" /* VITAL_SIGNS */]: ["Observation"],
|
|
94
|
-
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: ["DeviceUseStatement", "Device"],
|
|
95
|
-
// Device resource is used for medical devices name
|
|
96
|
-
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: ["DiagnosticReport", "Observation"],
|
|
97
|
-
["HistoryOfProceduresSection" /* PROCEDURES */]: ["Procedure"],
|
|
98
|
-
["SocialHistorySection" /* SOCIAL_HISTORY */]: ["Observation"],
|
|
99
|
-
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: ["Observation"],
|
|
100
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: ["Condition", "ClinicalImpression"],
|
|
101
|
-
["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: ["Condition"],
|
|
102
|
-
["PlanOfCareSection" /* CARE_PLAN */]: ["CarePlan"],
|
|
103
|
-
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
|
|
104
|
-
};
|
|
105
85
|
var IPSSectionResourceFilters = {
|
|
86
|
+
// Patient section: only Patient resource
|
|
87
|
+
["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Patient",
|
|
88
|
+
// Only include allergies
|
|
89
|
+
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "AllergyIntolerance",
|
|
90
|
+
// includes MedicationRequest, MedicationStatement. Medication is needed for medication names
|
|
91
|
+
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement", "Medication"].includes(resource.resourceType),
|
|
106
92
|
// Only include active conditions
|
|
107
93
|
["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
|
|
108
94
|
// Only include completed immunizations
|
|
109
95
|
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
|
|
110
96
|
// Only include vital sign Observations (category.coding contains 'vital-signs')
|
|
111
97
|
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c.code === "vital-signs")),
|
|
98
|
+
// Includes DeviceUseStatement. Device is needed for linked device details
|
|
99
|
+
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
|
|
112
100
|
// Only include finalized diagnostic reports
|
|
113
101
|
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => ["DiagnosticReport", "Observation"].includes(resource.resourceType) && resource.status === "final",
|
|
114
102
|
// Only include completed procedures
|
|
@@ -124,28 +112,30 @@ var IPSSectionResourceFilters = {
|
|
|
124
112
|
// Only include active care plans
|
|
125
113
|
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
|
|
126
114
|
// Only include active advance directives (Consent resources)
|
|
127
|
-
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
|
|
128
|
-
|
|
129
|
-
|
|
115
|
+
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
|
|
116
|
+
};
|
|
117
|
+
var IPSSectionSummaryCompositionFilter = {
|
|
118
|
+
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "allergy_summary_document"),
|
|
119
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "vital_summary_document")
|
|
130
120
|
};
|
|
131
121
|
var IPSSectionResourceHelper = class {
|
|
132
|
-
static getResourceTypesForSection(section) {
|
|
133
|
-
return IPSSectionResourceMap[section] || [];
|
|
134
|
-
}
|
|
135
122
|
static getResourceFilterForSection(section) {
|
|
136
123
|
return IPSSectionResourceFilters[section];
|
|
137
124
|
}
|
|
125
|
+
static getSummaryCompositionFilterForSection(section) {
|
|
126
|
+
return IPSSectionSummaryCompositionFilter[section];
|
|
127
|
+
}
|
|
138
128
|
};
|
|
139
129
|
|
|
140
130
|
// src/narratives/templates/typescript/TemplateUtilities.ts
|
|
141
131
|
import { DateTime } from "luxon";
|
|
142
132
|
var TemplateUtilities = class {
|
|
143
133
|
/**
|
|
144
|
-
* Constructor to initialize the TemplateUtilities with a FHIR
|
|
145
|
-
* @param
|
|
134
|
+
* Constructor to initialize the TemplateUtilities with a FHIR resources
|
|
135
|
+
* @param resources - FHIR resources
|
|
146
136
|
*/
|
|
147
|
-
constructor(
|
|
148
|
-
this.
|
|
137
|
+
constructor(resources) {
|
|
138
|
+
this.resources = resources;
|
|
149
139
|
}
|
|
150
140
|
/**
|
|
151
141
|
* Formats a CodeableConcept object
|
|
@@ -176,7 +166,7 @@ var TemplateUtilities = class {
|
|
|
176
166
|
return "";
|
|
177
167
|
}
|
|
178
168
|
resolveReference(ref) {
|
|
179
|
-
if (!ref || !this.
|
|
169
|
+
if (!ref || !this.resources) {
|
|
180
170
|
return null;
|
|
181
171
|
}
|
|
182
172
|
const referenceParts = ref.reference?.split("/");
|
|
@@ -185,10 +175,10 @@ var TemplateUtilities = class {
|
|
|
185
175
|
}
|
|
186
176
|
const referenceResourceType = referenceParts[0];
|
|
187
177
|
const referenceResourceId = referenceParts[1];
|
|
188
|
-
const resource = this.
|
|
189
|
-
return entry.
|
|
178
|
+
const resource = this.resources.find((entry) => {
|
|
179
|
+
return entry.resourceType === referenceResourceType && entry.id === referenceResourceId;
|
|
190
180
|
});
|
|
191
|
-
return resource ? resource
|
|
181
|
+
return resource ? resource : null;
|
|
192
182
|
}
|
|
193
183
|
/**
|
|
194
184
|
* Renders a Device reference
|
|
@@ -584,6 +574,137 @@ var TemplateUtilities = class {
|
|
|
584
574
|
}
|
|
585
575
|
return status;
|
|
586
576
|
}
|
|
577
|
+
extractObservationSummaryValue(data, timezone) {
|
|
578
|
+
if (data["valueQuantity.value"] !== void 0) {
|
|
579
|
+
const value = data["valueQuantity.value"];
|
|
580
|
+
const unit = data["valueQuantity.unit"];
|
|
581
|
+
return unit ? `${value} ${unit}` : `${value}`;
|
|
582
|
+
}
|
|
583
|
+
if (data["valueCodeableConcept.text"] !== void 0) {
|
|
584
|
+
return data["valueCodeableConcept.text"];
|
|
585
|
+
}
|
|
586
|
+
if (data["valueCodeableConcept.coding.display"] !== void 0) {
|
|
587
|
+
return data["valueCodeableConcept.coding.display"];
|
|
588
|
+
}
|
|
589
|
+
if (data["valueString"] !== void 0) {
|
|
590
|
+
return data["valueString"];
|
|
591
|
+
}
|
|
592
|
+
if (data["valueBoolean"] !== void 0) {
|
|
593
|
+
return String(data["valueBoolean"]);
|
|
594
|
+
}
|
|
595
|
+
if (data["valueInteger"] !== void 0) {
|
|
596
|
+
return String(data["valueInteger"]);
|
|
597
|
+
}
|
|
598
|
+
if (data["valueDateTime"] !== void 0) {
|
|
599
|
+
return this.renderTime(data["valueDateTime"], timezone);
|
|
600
|
+
}
|
|
601
|
+
if (data["valuePeriod.start"] !== void 0 || data["valuePeriod.end"] !== void 0) {
|
|
602
|
+
const start = this.renderTime(data["valuePeriod.start"], timezone);
|
|
603
|
+
const end = this.renderTime(data["valuePeriod.end"], timezone);
|
|
604
|
+
if (start && end) {
|
|
605
|
+
return `${start} - ${end}`;
|
|
606
|
+
} else if (start) {
|
|
607
|
+
return `${start}`;
|
|
608
|
+
} else if (end) {
|
|
609
|
+
return `${end}`;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (data["valueTime"] !== void 0) {
|
|
613
|
+
return this.renderTime(data["valueTime"], timezone);
|
|
614
|
+
}
|
|
615
|
+
if (data["valueSampledData.origin.value"] !== void 0 || data["valueSampledData.origin.unit"] !== void 0) {
|
|
616
|
+
const originValue = data["valueSampledData.origin.value"];
|
|
617
|
+
const originUnit = data["valueSampledData.origin.unit"];
|
|
618
|
+
let result = "";
|
|
619
|
+
if (originValue !== void 0 && originUnit !== void 0) {
|
|
620
|
+
result = `${originValue} ${originUnit}`;
|
|
621
|
+
} else if (originValue !== void 0) {
|
|
622
|
+
result = `${originValue}`;
|
|
623
|
+
} else if (originUnit !== void 0) {
|
|
624
|
+
result = `${originUnit}`;
|
|
625
|
+
}
|
|
626
|
+
const period = data["valueSampledData.period"];
|
|
627
|
+
const factor = data["valueSampledData.factor"];
|
|
628
|
+
const lowerLimit = data["valueSampledData.lowerLimit"];
|
|
629
|
+
const upperLimit = data["valueSampledData.upperLimit"];
|
|
630
|
+
const sampledData = data["valueSampledData.data"];
|
|
631
|
+
const extras = [];
|
|
632
|
+
if (period !== void 0) extras.push(`period: ${period}`);
|
|
633
|
+
if (factor !== void 0) extras.push(`factor: ${factor}`);
|
|
634
|
+
if (lowerLimit !== void 0) extras.push(`lowerLimit: ${lowerLimit}`);
|
|
635
|
+
if (upperLimit !== void 0) extras.push(`upperLimit: ${upperLimit}`);
|
|
636
|
+
if (sampledData !== void 0) extras.push(`data: ${sampledData}`);
|
|
637
|
+
if (extras.length > 0) {
|
|
638
|
+
result += ` (${extras.join(", ")})`;
|
|
639
|
+
}
|
|
640
|
+
return result;
|
|
641
|
+
}
|
|
642
|
+
if (data["valueRange.low.value"] !== void 0 || data["valueRange.high.value"] !== void 0) {
|
|
643
|
+
let referenceRange = "";
|
|
644
|
+
if (data["valueRange.low.value"] !== void 0) {
|
|
645
|
+
referenceRange += `${data["valueRange.low.value"]}`;
|
|
646
|
+
if (data["valueRange.low.unit"] !== void 0) {
|
|
647
|
+
referenceRange += ` ${data["valueRange.low.unit"]}`;
|
|
648
|
+
}
|
|
649
|
+
referenceRange = referenceRange.trim();
|
|
650
|
+
if (data["valueRange.high.value"] !== void 0) {
|
|
651
|
+
referenceRange += " - ";
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (data["valueRange.high.value"] !== void 0) {
|
|
655
|
+
referenceRange += `${data["valueRange.high.value"]}`;
|
|
656
|
+
if (data["valueRange.high.unit"] !== void 0) {
|
|
657
|
+
referenceRange += ` ${data["valueRange.high.unit"]}`;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return referenceRange.trim();
|
|
661
|
+
}
|
|
662
|
+
if (data["valueRatio.numerator.value"] !== void 0 || data["valueRatio.denominator.value"] !== void 0) {
|
|
663
|
+
let ratio = "";
|
|
664
|
+
if (data["valueRatio.numerator.value"] !== void 0) {
|
|
665
|
+
ratio += `${data["valueRatio.numerator.value"]}`;
|
|
666
|
+
if (data["valueRatio.numerator.unit"] !== void 0) {
|
|
667
|
+
ratio += ` ${data["valueRatio.numerator.unit"]}`;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (data["valueRatio.denominator.value"] !== void 0) {
|
|
671
|
+
ratio += " / ";
|
|
672
|
+
ratio += `${data["valueRatio.denominator.value"]}`;
|
|
673
|
+
if (data["valueRatio.denominator.unit"] !== void 0) {
|
|
674
|
+
ratio += ` ${data["valueRatio.denominator.unit"]}`;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return ratio.trim();
|
|
678
|
+
}
|
|
679
|
+
return "";
|
|
680
|
+
}
|
|
681
|
+
extractObservationSummaryReferenceRange(data) {
|
|
682
|
+
let referenceRange = "";
|
|
683
|
+
if (data["referenceRange.low.value"]) {
|
|
684
|
+
referenceRange += `${data["referenceRange.low.value"]} ${data["referenceRange.low.unit"]}`;
|
|
685
|
+
referenceRange.trim();
|
|
686
|
+
if (data["referenceRange.high.value"]) {
|
|
687
|
+
referenceRange += " - ";
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (data["referenceRange.high.value"]) {
|
|
691
|
+
referenceRange += `${data["referenceRange.high.value"]} ${data["referenceRange.high.unit"]}`;
|
|
692
|
+
}
|
|
693
|
+
return referenceRange.trim();
|
|
694
|
+
}
|
|
695
|
+
extractObservationSummaryEffectiveTime(data, timezone) {
|
|
696
|
+
if (data["effectiveDateTime"]) {
|
|
697
|
+
return this.renderTime(data["effectiveDateTime"], timezone);
|
|
698
|
+
}
|
|
699
|
+
let effectiveTimePeriod = "";
|
|
700
|
+
if (data["effectivePeriod.start"]) {
|
|
701
|
+
effectiveTimePeriod += this.renderTime(data["effectivePeriod.start"], timezone);
|
|
702
|
+
}
|
|
703
|
+
if (data["effectivePeriod.end"]) {
|
|
704
|
+
effectiveTimePeriod += ` - ${this.renderTime(data["effectivePeriod.end"], timezone)}`;
|
|
705
|
+
}
|
|
706
|
+
return effectiveTimePeriod.trim();
|
|
707
|
+
}
|
|
587
708
|
formatQuantityValue(quantity) {
|
|
588
709
|
if (!quantity) return "";
|
|
589
710
|
const parts = [];
|
|
@@ -759,7 +880,7 @@ var TemplateUtilities = class {
|
|
|
759
880
|
if (parts.length === 2) {
|
|
760
881
|
const resourceType = parts[0];
|
|
761
882
|
const resourceId = parts[1];
|
|
762
|
-
const resource = this.
|
|
883
|
+
const resource = this.resources?.find((resource2) => resource2.resourceType === resourceType && resource2.id === resourceId);
|
|
763
884
|
if (resource) {
|
|
764
885
|
return `${resourceType}/${resourceId}`;
|
|
765
886
|
}
|
|
@@ -799,48 +920,96 @@ var TemplateUtilities = class {
|
|
|
799
920
|
// src/narratives/templates/typescript/PatientTemplate.ts
|
|
800
921
|
var PatientTemplate = class _PatientTemplate {
|
|
801
922
|
/**
|
|
802
|
-
* Generate HTML narrative for Patient
|
|
803
|
-
* @param
|
|
923
|
+
* Generate HTML narrative for Patient resources
|
|
924
|
+
* @param resources - FHIR Patient resources
|
|
804
925
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
805
926
|
* @returns HTML string for rendering
|
|
806
927
|
*/
|
|
807
|
-
generateNarrative(
|
|
808
|
-
return _PatientTemplate.generateStaticNarrative(
|
|
928
|
+
generateNarrative(resources, timezone) {
|
|
929
|
+
return _PatientTemplate.generateStaticNarrative(resources, timezone);
|
|
809
930
|
}
|
|
810
931
|
/**
|
|
811
932
|
* Internal static implementation that actually generates the narrative
|
|
812
|
-
* @param
|
|
933
|
+
* @param resources - FHIR Patient resources
|
|
813
934
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
814
935
|
* @returns HTML string for rendering
|
|
815
936
|
*/
|
|
816
937
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
817
|
-
static generateStaticNarrative(
|
|
818
|
-
const templateUtilities = new TemplateUtilities(
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
<
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
938
|
+
static generateStaticNarrative(resources, timezone) {
|
|
939
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
940
|
+
const combinedPatient = this.combinePatients(resources);
|
|
941
|
+
return `<div>
|
|
942
|
+
<ul>
|
|
943
|
+
<li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
|
|
944
|
+
<li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
|
|
945
|
+
<li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
|
|
946
|
+
<li><strong>Identifier(s):</strong>${this.renderIdentifiers(combinedPatient)}</li>
|
|
947
|
+
<li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
|
|
948
|
+
<li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
|
|
949
|
+
<li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus?.text || ""}</li>
|
|
950
|
+
<li><strong>Deceased:</strong>${this.renderDeceased(combinedPatient)}</li>
|
|
951
|
+
<li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
|
|
952
|
+
</ul>
|
|
953
|
+
</div>`;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Combines multiple patient resources into a single patient object
|
|
957
|
+
* Merges fields, preferring non-empty values
|
|
958
|
+
* @param patients - Array of patient resources
|
|
959
|
+
* @returns Combined patient resource
|
|
960
|
+
*/
|
|
961
|
+
static combinePatients(patients) {
|
|
962
|
+
if (patients.length === 1) {
|
|
963
|
+
return patients[0];
|
|
964
|
+
}
|
|
965
|
+
const combined = patients[0];
|
|
966
|
+
const allNames = [];
|
|
967
|
+
const allIdentifiers = [];
|
|
968
|
+
const allTelecom = [];
|
|
969
|
+
const allAddresses = [];
|
|
970
|
+
const allCommunication = [];
|
|
971
|
+
patients.forEach((patient) => {
|
|
972
|
+
if (patient.name) {
|
|
973
|
+
allNames.push(...patient.name);
|
|
837
974
|
}
|
|
838
|
-
|
|
839
|
-
|
|
975
|
+
if (patient.identifier) {
|
|
976
|
+
allIdentifiers.push(...patient.identifier);
|
|
977
|
+
}
|
|
978
|
+
if (patient.telecom) {
|
|
979
|
+
allTelecom.push(...patient.telecom);
|
|
980
|
+
}
|
|
981
|
+
if (patient.address) {
|
|
982
|
+
allAddresses.push(...patient.address);
|
|
983
|
+
}
|
|
984
|
+
if (patient.communication) {
|
|
985
|
+
allCommunication.push(...patient.communication);
|
|
986
|
+
}
|
|
987
|
+
if (!combined.gender && patient.gender) {
|
|
988
|
+
combined.gender = patient.gender;
|
|
989
|
+
}
|
|
990
|
+
if (!combined.birthDate && patient.birthDate) {
|
|
991
|
+
combined.birthDate = patient.birthDate;
|
|
992
|
+
}
|
|
993
|
+
if (!combined.maritalStatus && patient.maritalStatus) {
|
|
994
|
+
combined.maritalStatus = patient.maritalStatus;
|
|
995
|
+
}
|
|
996
|
+
if (!combined.deceasedBoolean && patient.deceasedBoolean !== void 0) {
|
|
997
|
+
combined.deceasedBoolean = patient.deceasedBoolean;
|
|
998
|
+
}
|
|
999
|
+
if (!combined.deceasedDateTime && patient.deceasedDateTime) {
|
|
1000
|
+
combined.deceasedDateTime = patient.deceasedDateTime;
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
combined.name = allNames;
|
|
1004
|
+
combined.identifier = allIdentifiers;
|
|
1005
|
+
combined.telecom = allTelecom;
|
|
1006
|
+
combined.address = allAddresses;
|
|
1007
|
+
combined.communication = allCommunication;
|
|
1008
|
+
return combined;
|
|
840
1009
|
}
|
|
841
1010
|
/**
|
|
842
1011
|
* Renders patient names as HTML list items
|
|
843
|
-
* @param patient - Patient
|
|
1012
|
+
* @param patient - Patient resources
|
|
844
1013
|
* @returns HTML string of list items
|
|
845
1014
|
*/
|
|
846
1015
|
static renderNames(patient) {
|
|
@@ -860,7 +1029,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
860
1029
|
}
|
|
861
1030
|
/**
|
|
862
1031
|
* Renders patient identifiers as HTML list items
|
|
863
|
-
* @param patient - Patient
|
|
1032
|
+
* @param patient - Patient resources
|
|
864
1033
|
* @returns HTML string of list items
|
|
865
1034
|
*/
|
|
866
1035
|
static renderIdentifiers(patient) {
|
|
@@ -875,7 +1044,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
875
1044
|
}
|
|
876
1045
|
/**
|
|
877
1046
|
* Renders patient telecom information grouped by system
|
|
878
|
-
* @param patient - Patient
|
|
1047
|
+
* @param patient - Patient resources
|
|
879
1048
|
* @returns HTML string grouped by system
|
|
880
1049
|
*/
|
|
881
1050
|
static renderTelecom(patient) {
|
|
@@ -934,7 +1103,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
934
1103
|
}
|
|
935
1104
|
/**
|
|
936
1105
|
* Renders patient addresses as HTML list items
|
|
937
|
-
* @param patient - Patient
|
|
1106
|
+
* @param patient - Patient resources
|
|
938
1107
|
* @returns HTML string of list items
|
|
939
1108
|
*/
|
|
940
1109
|
static renderAddresses(patient) {
|
|
@@ -956,9 +1125,18 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
956
1125
|
if (address.city) {
|
|
957
1126
|
addressArray.push(address.city);
|
|
958
1127
|
}
|
|
1128
|
+
if (address.district) {
|
|
1129
|
+
addressArray.push(address.district);
|
|
1130
|
+
}
|
|
1131
|
+
if (address.state) {
|
|
1132
|
+
addressArray.push(address.state);
|
|
1133
|
+
}
|
|
959
1134
|
if (address.country) {
|
|
960
1135
|
addressArray.push(address.country);
|
|
961
1136
|
}
|
|
1137
|
+
if (address.postalCode) {
|
|
1138
|
+
addressArray.push(address.postalCode);
|
|
1139
|
+
}
|
|
962
1140
|
}
|
|
963
1141
|
const addressText = addressArray.join(", ").trim();
|
|
964
1142
|
if (addressText) {
|
|
@@ -969,7 +1147,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
969
1147
|
}
|
|
970
1148
|
/**
|
|
971
1149
|
* Renders patient deceased status
|
|
972
|
-
* @param patient - Patient
|
|
1150
|
+
* @param patient - Patient resources
|
|
973
1151
|
* @returns HTML string for deceased status
|
|
974
1152
|
*/
|
|
975
1153
|
static renderDeceased(patient) {
|
|
@@ -984,7 +1162,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
984
1162
|
/**
|
|
985
1163
|
* Renders patient communication preferences as HTML list items
|
|
986
1164
|
* @param templateUtilities - Instance of TemplateUtilities for utility functions
|
|
987
|
-
* @param patient - Patient
|
|
1165
|
+
* @param patient - Patient resources
|
|
988
1166
|
* @returns HTML string of list items
|
|
989
1167
|
*/
|
|
990
1168
|
static renderCommunication(templateUtilities, patient) {
|
|
@@ -1019,32 +1197,81 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1019
1197
|
var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
1020
1198
|
/**
|
|
1021
1199
|
* Generate HTML narrative for AllergyIntolerance resources
|
|
1022
|
-
* @param
|
|
1200
|
+
* @param resources - FHIR resources array containing AllergyIntolerance resources
|
|
1023
1201
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1024
1202
|
* @returns HTML string for rendering
|
|
1025
1203
|
*/
|
|
1026
|
-
generateNarrative(
|
|
1027
|
-
return _AllergyIntoleranceTemplate.generateStaticNarrative(
|
|
1204
|
+
generateNarrative(resources, timezone) {
|
|
1205
|
+
return _AllergyIntoleranceTemplate.generateStaticNarrative(resources, timezone);
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Generate HTML narrative for AllergyIntolerance resources using summary
|
|
1209
|
+
* @param resources - FHIR Composition resources
|
|
1210
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1211
|
+
* @returns HTML string for rendering
|
|
1212
|
+
*/
|
|
1213
|
+
generateSummaryNarrative(resources, timezone) {
|
|
1214
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1215
|
+
let html = `
|
|
1216
|
+
<div>
|
|
1217
|
+
<table>
|
|
1218
|
+
<thead>
|
|
1219
|
+
<tr>
|
|
1220
|
+
<th>Allergen</th>
|
|
1221
|
+
<th>Criticality</th>
|
|
1222
|
+
<th>Recorded Date</th>
|
|
1223
|
+
</tr>
|
|
1224
|
+
</thead>
|
|
1225
|
+
<tbody>`;
|
|
1226
|
+
for (const resourceItem of resources) {
|
|
1227
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
1228
|
+
const data = {};
|
|
1229
|
+
for (const columnData of rowData.section ?? []) {
|
|
1230
|
+
switch (columnData.title) {
|
|
1231
|
+
case "Allergen Name":
|
|
1232
|
+
data["allergen"] = columnData.text?.div ?? "";
|
|
1233
|
+
break;
|
|
1234
|
+
case "Criticality":
|
|
1235
|
+
data["criticality"] = columnData.text?.div ?? "";
|
|
1236
|
+
break;
|
|
1237
|
+
case "Recorded Date":
|
|
1238
|
+
data["recordedDate"] = columnData.text?.div ?? "";
|
|
1239
|
+
break;
|
|
1240
|
+
default:
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
html += `
|
|
1245
|
+
<tr>
|
|
1246
|
+
<td>${data["allergen"] ?? "-"}</td>
|
|
1247
|
+
<td>${data["criticality"] ?? "-"}</td>
|
|
1248
|
+
<td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? "-"}</td>
|
|
1249
|
+
</tr>`;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
html += `
|
|
1253
|
+
</tbody>
|
|
1254
|
+
</table>
|
|
1255
|
+
</div>`;
|
|
1256
|
+
return html;
|
|
1028
1257
|
}
|
|
1029
1258
|
/**
|
|
1030
1259
|
* Internal static implementation that actually generates the narrative
|
|
1031
|
-
* @param
|
|
1260
|
+
* @param resources - FHIR resources array containing AllergyIntolerance resources
|
|
1032
1261
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1033
1262
|
* @returns HTML string for rendering
|
|
1034
1263
|
*/
|
|
1035
|
-
static generateStaticNarrative(
|
|
1036
|
-
const templateUtilities = new TemplateUtilities(
|
|
1264
|
+
static generateStaticNarrative(resources, timezone) {
|
|
1265
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1037
1266
|
const activeAllergies = [];
|
|
1038
1267
|
const resolvedAllergies = [];
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
activeAllergies.push(allergy);
|
|
1047
|
-
}
|
|
1268
|
+
for (const resourceItem of resources) {
|
|
1269
|
+
const allergy = resourceItem;
|
|
1270
|
+
const isResolved = allergy.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code));
|
|
1271
|
+
if (isResolved) {
|
|
1272
|
+
resolvedAllergies.push(allergy);
|
|
1273
|
+
} else {
|
|
1274
|
+
activeAllergies.push(allergy);
|
|
1048
1275
|
}
|
|
1049
1276
|
}
|
|
1050
1277
|
activeAllergies.sort((a, b) => {
|
|
@@ -1157,12 +1384,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1157
1384
|
var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
1158
1385
|
/**
|
|
1159
1386
|
* Generate HTML narrative for Medication resources
|
|
1160
|
-
* @param
|
|
1387
|
+
* @param resources - FHIR Medication resources
|
|
1161
1388
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1162
1389
|
* @returns HTML string for rendering
|
|
1163
1390
|
*/
|
|
1164
|
-
generateNarrative(
|
|
1165
|
-
return _MedicationSummaryTemplate.generateStaticNarrative(
|
|
1391
|
+
generateNarrative(resources, timezone) {
|
|
1392
|
+
return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
|
|
1166
1393
|
}
|
|
1167
1394
|
/**
|
|
1168
1395
|
* Safely parse a date string and return a valid Date object or null
|
|
@@ -1227,16 +1454,16 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1227
1454
|
}
|
|
1228
1455
|
/**
|
|
1229
1456
|
* Internal static implementation that actually generates the narrative
|
|
1230
|
-
* @param
|
|
1457
|
+
* @param resources - FHIR Medication resources
|
|
1231
1458
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1232
1459
|
* @returns HTML string for rendering
|
|
1233
1460
|
*/
|
|
1234
1461
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1235
|
-
static generateStaticNarrative(
|
|
1236
|
-
const templateUtilities = new TemplateUtilities(
|
|
1462
|
+
static generateStaticNarrative(resources, timezone) {
|
|
1463
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1237
1464
|
let html = "";
|
|
1238
|
-
const medicationRequests = this.getMedicationRequests(templateUtilities,
|
|
1239
|
-
const medicationStatements = this.getMedicationStatements(templateUtilities,
|
|
1465
|
+
const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
|
|
1466
|
+
const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
|
|
1240
1467
|
const allActiveMedications = [];
|
|
1241
1468
|
const allInactiveMedications = [];
|
|
1242
1469
|
medicationRequests.forEach((mr) => {
|
|
@@ -1290,33 +1517,33 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1290
1517
|
return html;
|
|
1291
1518
|
}
|
|
1292
1519
|
/**
|
|
1293
|
-
* Extract MedicationRequest resources
|
|
1520
|
+
* Extract MedicationRequest resources
|
|
1294
1521
|
* @param templateUtilities - Instance of TemplateUtilities for utility functions
|
|
1295
|
-
* @param
|
|
1522
|
+
* @param resources - FHIR Medication resources
|
|
1296
1523
|
* @returns Array of MedicationRequest resources
|
|
1297
1524
|
*/
|
|
1298
|
-
static getMedicationRequests(templateUtilities,
|
|
1299
|
-
if (
|
|
1525
|
+
static getMedicationRequests(templateUtilities, resources) {
|
|
1526
|
+
if (resources.length === 0) {
|
|
1300
1527
|
return [];
|
|
1301
1528
|
}
|
|
1302
|
-
return
|
|
1303
|
-
resource: entry
|
|
1304
|
-
extension: templateUtilities.narrativeLinkExtension(entry
|
|
1529
|
+
return resources.filter((entry) => entry.resourceType === "MedicationRequest").map((entry) => ({
|
|
1530
|
+
resource: entry,
|
|
1531
|
+
extension: templateUtilities.narrativeLinkExtension(entry)
|
|
1305
1532
|
}));
|
|
1306
1533
|
}
|
|
1307
1534
|
/**
|
|
1308
|
-
* Extract MedicationStatement resources
|
|
1535
|
+
* Extract MedicationStatement resources
|
|
1309
1536
|
* @param templateUtilities - Instance of TemplateUtilities for utility functions
|
|
1310
|
-
* @param
|
|
1537
|
+
* @param resources - FHIR Medication resources
|
|
1311
1538
|
* @returns Array of MedicationStatement resources
|
|
1312
1539
|
*/
|
|
1313
|
-
static getMedicationStatements(templateUtilities,
|
|
1314
|
-
if (
|
|
1540
|
+
static getMedicationStatements(templateUtilities, resources) {
|
|
1541
|
+
if (resources.length === 0) {
|
|
1315
1542
|
return [];
|
|
1316
1543
|
}
|
|
1317
|
-
return
|
|
1318
|
-
resource: entry
|
|
1319
|
-
extension: templateUtilities.narrativeLinkExtension(entry
|
|
1544
|
+
return resources.filter((entry) => entry.resourceType === "MedicationStatement").map((entry) => ({
|
|
1545
|
+
resource: entry,
|
|
1546
|
+
extension: templateUtilities.narrativeLinkExtension(entry)
|
|
1320
1547
|
}));
|
|
1321
1548
|
}
|
|
1322
1549
|
/**
|
|
@@ -1412,28 +1639,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1412
1639
|
var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
1413
1640
|
/**
|
|
1414
1641
|
* Generate HTML narrative for Immunization resources
|
|
1415
|
-
* @param
|
|
1642
|
+
* @param resources - FHIR resources array containing Immunization resources
|
|
1416
1643
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1417
1644
|
* @returns HTML string for rendering
|
|
1418
1645
|
*/
|
|
1419
|
-
generateNarrative(
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
}
|
|
1427
|
-
return _ImmunizationsTemplate.generateStaticNarrative(resource, timezone);
|
|
1646
|
+
generateNarrative(resources, timezone) {
|
|
1647
|
+
resources.sort((a, b) => {
|
|
1648
|
+
const dateA = a.occurrenceDateTime;
|
|
1649
|
+
const dateB = b.occurrenceDateTime;
|
|
1650
|
+
return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
1651
|
+
});
|
|
1652
|
+
return _ImmunizationsTemplate.generateStaticNarrative(resources, timezone);
|
|
1428
1653
|
}
|
|
1429
1654
|
/**
|
|
1430
1655
|
* Internal static implementation that actually generates the narrative
|
|
1431
|
-
* @param
|
|
1656
|
+
* @param resources - FHIR resources array containing Immunization resources
|
|
1432
1657
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1433
1658
|
* @returns HTML string for rendering
|
|
1434
1659
|
*/
|
|
1435
|
-
static generateStaticNarrative(
|
|
1436
|
-
const templateUtilities = new TemplateUtilities(
|
|
1660
|
+
static generateStaticNarrative(resources, timezone) {
|
|
1661
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1437
1662
|
let html = `
|
|
1438
1663
|
<table>
|
|
1439
1664
|
<thead>
|
|
@@ -1448,21 +1673,20 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
1448
1673
|
</tr>
|
|
1449
1674
|
</thead>
|
|
1450
1675
|
<tbody>`;
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
}
|
|
1676
|
+
const immunizations = resources.filter((resourceItem) => resourceItem.resourceType === "Immunization");
|
|
1677
|
+
if (immunizations.length > 0) {
|
|
1678
|
+
for (const resourceItem of immunizations) {
|
|
1679
|
+
const imm = resourceItem;
|
|
1680
|
+
html += `
|
|
1681
|
+
<tr id="${templateUtilities.narrativeLinkId(imm)}">
|
|
1682
|
+
<td>${templateUtilities.codeableConcept(imm.vaccineCode)}</td>
|
|
1683
|
+
<td>${imm.status || ""}</td>
|
|
1684
|
+
<td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
|
|
1685
|
+
<td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
|
|
1686
|
+
<td>${imm.lotNumber || ""}</td>
|
|
1687
|
+
<td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
|
|
1688
|
+
<td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
|
|
1689
|
+
</tr>`;
|
|
1466
1690
|
}
|
|
1467
1691
|
}
|
|
1468
1692
|
html += `
|
|
@@ -1476,23 +1700,23 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
1476
1700
|
var ProblemListTemplate = class _ProblemListTemplate {
|
|
1477
1701
|
/**
|
|
1478
1702
|
* Generate HTML narrative for Problem List
|
|
1479
|
-
* @param
|
|
1703
|
+
* @param resources - FHIR Condition resources
|
|
1480
1704
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1481
1705
|
* @returns HTML string for rendering
|
|
1482
1706
|
*/
|
|
1483
|
-
generateNarrative(
|
|
1484
|
-
return _ProblemListTemplate.generateStaticNarrative(
|
|
1707
|
+
generateNarrative(resources, timezone) {
|
|
1708
|
+
return _ProblemListTemplate.generateStaticNarrative(resources, timezone);
|
|
1485
1709
|
}
|
|
1486
1710
|
/**
|
|
1487
1711
|
* Internal static implementation that actually generates the narrative
|
|
1488
|
-
* @param
|
|
1712
|
+
* @param resources - FHIR Condition resources
|
|
1489
1713
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1490
1714
|
* @returns HTML string for rendering
|
|
1491
1715
|
*/
|
|
1492
|
-
static generateStaticNarrative(
|
|
1493
|
-
const templateUtilities = new TemplateUtilities(
|
|
1716
|
+
static generateStaticNarrative(resources, timezone) {
|
|
1717
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1494
1718
|
let html = ``;
|
|
1495
|
-
const activeConditions =
|
|
1719
|
+
const activeConditions = resources.map((entry) => entry) || [];
|
|
1496
1720
|
activeConditions.sort((a, b) => {
|
|
1497
1721
|
const dateA = a.onsetDateTime ? new Date(a.onsetDateTime).getTime() : 0;
|
|
1498
1722
|
const dateB = b.onsetDateTime ? new Date(b.onsetDateTime).getTime() : 0;
|
|
@@ -1523,26 +1747,95 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1523
1747
|
}
|
|
1524
1748
|
};
|
|
1525
1749
|
|
|
1750
|
+
// src/structures/ips_section_constants.ts
|
|
1751
|
+
var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
1752
|
+
"Systolic Blood Pressure": "valueRatio.numerator.value",
|
|
1753
|
+
"Diastolic Blood Pressure": "valueRatio.denominator.value",
|
|
1754
|
+
"Default": "valueString"
|
|
1755
|
+
};
|
|
1756
|
+
|
|
1526
1757
|
// src/narratives/templates/typescript/VitalSignsTemplate.ts
|
|
1527
1758
|
var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
1528
1759
|
/**
|
|
1529
1760
|
* Generate HTML narrative for Vital Signs
|
|
1530
|
-
* @param
|
|
1761
|
+
* @param resources - FHIR Observation resources
|
|
1531
1762
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1532
1763
|
* @returns HTML string for rendering
|
|
1533
1764
|
*/
|
|
1534
|
-
generateNarrative(
|
|
1535
|
-
return _VitalSignsTemplate.generateStaticNarrative(
|
|
1765
|
+
generateNarrative(resources, timezone) {
|
|
1766
|
+
return _VitalSignsTemplate.generateStaticNarrative(resources, timezone);
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Generate HTML narrative for vital signs using summary
|
|
1770
|
+
* @param resources - FHIR Composition resources
|
|
1771
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1772
|
+
* @returns HTML string for rendering
|
|
1773
|
+
*/
|
|
1774
|
+
generateSummaryNarrative(resources, timezone) {
|
|
1775
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1776
|
+
let html = `
|
|
1777
|
+
<div>
|
|
1778
|
+
<table>
|
|
1779
|
+
<thead>
|
|
1780
|
+
<tr>
|
|
1781
|
+
<th>Vital Name</th>
|
|
1782
|
+
<th>Result</th>
|
|
1783
|
+
<th>Reference Range</th>
|
|
1784
|
+
<th>Date</th>
|
|
1785
|
+
</tr>
|
|
1786
|
+
</thead>
|
|
1787
|
+
<tbody>`;
|
|
1788
|
+
for (const resourceItem of resources) {
|
|
1789
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
1790
|
+
const data = {};
|
|
1791
|
+
for (const columnData of rowData.section ?? []) {
|
|
1792
|
+
const columnTitle = columnData.title;
|
|
1793
|
+
if (columnTitle) {
|
|
1794
|
+
if (Object.keys(VITAL_SIGNS_SUMMARY_COMPONENT_MAP).includes(
|
|
1795
|
+
columnTitle
|
|
1796
|
+
)) {
|
|
1797
|
+
const vitalData = {};
|
|
1798
|
+
for (const component of columnData.section?.[0]?.section ?? []) {
|
|
1799
|
+
if (component.title) {
|
|
1800
|
+
vitalData[component.title] = component.text?.div ?? "";
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
const vitalValue = templateUtilities.extractObservationSummaryValue(
|
|
1804
|
+
vitalData,
|
|
1805
|
+
timezone
|
|
1806
|
+
);
|
|
1807
|
+
if (vitalValue) {
|
|
1808
|
+
const dataKey = VITAL_SIGNS_SUMMARY_COMPONENT_MAP[columnTitle] ?? VITAL_SIGNS_SUMMARY_COMPONENT_MAP["Default"];
|
|
1809
|
+
data[dataKey] = vitalValue;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
data[columnTitle] = columnData.text?.div ?? "";
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
html += `
|
|
1816
|
+
<tr>
|
|
1817
|
+
<td>${data["Vital Name"] ?? "-"}</td>
|
|
1818
|
+
<td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? "-"}</td>
|
|
1819
|
+
<td>${templateUtilities.extractObservationSummaryReferenceRange(data) ?? "-"}</td>
|
|
1820
|
+
<td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "-"}</td>
|
|
1821
|
+
</tr>`;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
html += `
|
|
1825
|
+
</tbody>
|
|
1826
|
+
</table>
|
|
1827
|
+
</div>`;
|
|
1828
|
+
return html;
|
|
1536
1829
|
}
|
|
1537
1830
|
/**
|
|
1538
1831
|
* Internal static implementation that actually generates the narrative
|
|
1539
|
-
* @param
|
|
1832
|
+
* @param resources - FHIR Observation resources
|
|
1540
1833
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1541
1834
|
* @returns HTML string for rendering
|
|
1542
1835
|
*/
|
|
1543
|
-
static generateStaticNarrative(
|
|
1544
|
-
const templateUtilities = new TemplateUtilities(
|
|
1545
|
-
const observations =
|
|
1836
|
+
static generateStaticNarrative(resources, timezone) {
|
|
1837
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1838
|
+
const observations = resources.map((entry) => entry) || [];
|
|
1546
1839
|
observations.sort((a, b) => {
|
|
1547
1840
|
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
1548
1841
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
@@ -1552,7 +1845,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
1552
1845
|
<table>
|
|
1553
1846
|
<thead>
|
|
1554
1847
|
<tr>
|
|
1555
|
-
<th>
|
|
1848
|
+
<th>Vital Name</th>
|
|
1556
1849
|
<th>Result</th>
|
|
1557
1850
|
<th>Unit</th>
|
|
1558
1851
|
<th>Interpretation</th>
|
|
@@ -1585,28 +1878,21 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
1585
1878
|
var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
1586
1879
|
/**
|
|
1587
1880
|
* Generate HTML narrative for Medical Device resources
|
|
1588
|
-
* @param
|
|
1881
|
+
* @param resources - FHIR resources array containing Device resources
|
|
1589
1882
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1590
1883
|
* @returns HTML string for rendering
|
|
1591
1884
|
*/
|
|
1592
|
-
generateNarrative(
|
|
1593
|
-
|
|
1594
|
-
resource.entry.sort((a, b) => {
|
|
1595
|
-
const dateA = a.resource?.recordedOn;
|
|
1596
|
-
const dateB = b.resource?.recordedOn;
|
|
1597
|
-
return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
1598
|
-
});
|
|
1599
|
-
}
|
|
1600
|
-
return _MedicalDevicesTemplate.generateStaticNarrative(resource, timezone);
|
|
1885
|
+
generateNarrative(resources, timezone) {
|
|
1886
|
+
return _MedicalDevicesTemplate.generateStaticNarrative(resources, timezone);
|
|
1601
1887
|
}
|
|
1602
1888
|
/**
|
|
1603
1889
|
* Internal static implementation that actually generates the narrative
|
|
1604
|
-
* @param
|
|
1890
|
+
* @param resources - FHIR resources array containing Device resources
|
|
1605
1891
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1606
1892
|
* @returns HTML string for rendering
|
|
1607
1893
|
*/
|
|
1608
|
-
static generateStaticNarrative(
|
|
1609
|
-
const templateUtilities = new TemplateUtilities(
|
|
1894
|
+
static generateStaticNarrative(resources, timezone) {
|
|
1895
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1610
1896
|
let html = `
|
|
1611
1897
|
<table>
|
|
1612
1898
|
<thead>
|
|
@@ -1618,19 +1904,19 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
1618
1904
|
</tr>
|
|
1619
1905
|
</thead>
|
|
1620
1906
|
<tbody>`;
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1907
|
+
const deviceStatements = resources.filter((resourceItem) => resourceItem.resourceType === "DeviceUseStatement").map((resourceItem) => resourceItem).sort((a, b) => {
|
|
1908
|
+
const dateA = a.recordedOn;
|
|
1909
|
+
const dateB = b.recordedOn;
|
|
1910
|
+
return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
1911
|
+
});
|
|
1912
|
+
for (const dus of deviceStatements) {
|
|
1913
|
+
html += `
|
|
1914
|
+
<tr id="${templateUtilities.narrativeLinkId(dus)}">
|
|
1915
|
+
<td>${templateUtilities.renderDevice(dus.device)}</td>
|
|
1916
|
+
<td>${dus.status || ""}</td>
|
|
1917
|
+
<td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
|
|
1918
|
+
<td>${templateUtilities.renderRecorded(dus.recordedOn, timezone)}</td>
|
|
1919
|
+
</tr>`;
|
|
1634
1920
|
}
|
|
1635
1921
|
html += `
|
|
1636
1922
|
</tbody>
|
|
@@ -1643,23 +1929,23 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
1643
1929
|
var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
1644
1930
|
/**
|
|
1645
1931
|
* Generate HTML narrative for Diagnostic Results
|
|
1646
|
-
* @param
|
|
1932
|
+
* @param resources - FHIR resources array containing Observation and DiagnosticReport resources
|
|
1647
1933
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1648
1934
|
* @returns HTML string for rendering
|
|
1649
1935
|
*/
|
|
1650
|
-
generateNarrative(
|
|
1651
|
-
return _DiagnosticResultsTemplate.generateStaticNarrative(
|
|
1936
|
+
generateNarrative(resources, timezone) {
|
|
1937
|
+
return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
|
|
1652
1938
|
}
|
|
1653
1939
|
/**
|
|
1654
1940
|
* Internal static implementation that actually generates the narrative
|
|
1655
|
-
* @param
|
|
1941
|
+
* @param resources - FHIR resources array containing Observation and DiagnosticReport resources
|
|
1656
1942
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1657
1943
|
* @returns HTML string for rendering
|
|
1658
1944
|
*/
|
|
1659
|
-
static generateStaticNarrative(
|
|
1660
|
-
const templateUtilities = new TemplateUtilities(
|
|
1945
|
+
static generateStaticNarrative(resources, timezone) {
|
|
1946
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1661
1947
|
let html = "";
|
|
1662
|
-
const observations = this.getObservations(
|
|
1948
|
+
const observations = this.getObservations(resources);
|
|
1663
1949
|
if (observations.length > 0) {
|
|
1664
1950
|
observations.sort((a, b) => {
|
|
1665
1951
|
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
@@ -1668,7 +1954,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
1668
1954
|
});
|
|
1669
1955
|
html += this.renderObservations(templateUtilities, observations, timezone);
|
|
1670
1956
|
}
|
|
1671
|
-
const diagnosticReports = this.getDiagnosticReports(
|
|
1957
|
+
const diagnosticReports = this.getDiagnosticReports(resources);
|
|
1672
1958
|
if (diagnosticReports.length > 0) {
|
|
1673
1959
|
diagnosticReports.sort((a, b) => {
|
|
1674
1960
|
const dateA = a.issued;
|
|
@@ -1680,26 +1966,20 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
1680
1966
|
return html;
|
|
1681
1967
|
}
|
|
1682
1968
|
/**
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
static getObservations(
|
|
1688
|
-
|
|
1689
|
-
return [];
|
|
1690
|
-
}
|
|
1691
|
-
return resource.entry.filter((entry) => entry.resource?.resourceType === "Observation").map((entry) => entry.resource);
|
|
1969
|
+
* Get all Observation resources from the resource array
|
|
1970
|
+
* @param resources - FHIR resources array
|
|
1971
|
+
* @returns Array of Observation resources
|
|
1972
|
+
*/
|
|
1973
|
+
static getObservations(resources) {
|
|
1974
|
+
return resources.filter((resourceItem) => resourceItem.resourceType === "Observation").map((resourceItem) => resourceItem);
|
|
1692
1975
|
}
|
|
1693
1976
|
/**
|
|
1694
|
-
*
|
|
1695
|
-
* @param
|
|
1977
|
+
* Get all DiagnosticReport resources from the resource array
|
|
1978
|
+
* @param resources - FHIR resources array
|
|
1696
1979
|
* @returns Array of DiagnosticReport resources
|
|
1697
1980
|
*/
|
|
1698
|
-
static getDiagnosticReports(
|
|
1699
|
-
|
|
1700
|
-
return [];
|
|
1701
|
-
}
|
|
1702
|
-
return resource.entry.filter((entry) => entry.resource?.resourceType === "DiagnosticReport").map((entry) => entry.resource);
|
|
1981
|
+
static getDiagnosticReports(resources) {
|
|
1982
|
+
return resources.filter((resourceItem) => resourceItem.resourceType === "DiagnosticReport").map((resourceItem) => resourceItem);
|
|
1703
1983
|
}
|
|
1704
1984
|
/**
|
|
1705
1985
|
* Render HTML table for Observation resources
|
|
@@ -1785,26 +2065,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
1785
2065
|
var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
1786
2066
|
/**
|
|
1787
2067
|
* Generate HTML narrative for Procedure resources
|
|
1788
|
-
* @param
|
|
2068
|
+
* @param resources - FHIR Procedure resources
|
|
1789
2069
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1790
2070
|
* @returns HTML string for rendering
|
|
1791
2071
|
*/
|
|
1792
|
-
generateNarrative(
|
|
1793
|
-
|
|
1794
|
-
const dateA = a.
|
|
1795
|
-
const dateB = b.
|
|
2072
|
+
generateNarrative(resources, timezone) {
|
|
2073
|
+
resources.sort((a, b) => {
|
|
2074
|
+
const dateA = a.performedDateTime || a.performedPeriod?.start;
|
|
2075
|
+
const dateB = b.performedDateTime || b.performedPeriod?.start;
|
|
1796
2076
|
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
1797
2077
|
});
|
|
1798
|
-
return _HistoryOfProceduresTemplate.generateStaticNarrative(
|
|
2078
|
+
return _HistoryOfProceduresTemplate.generateStaticNarrative(resources, timezone);
|
|
1799
2079
|
}
|
|
1800
2080
|
/**
|
|
1801
2081
|
* Internal static implementation that actually generates the narrative
|
|
1802
|
-
* @param
|
|
2082
|
+
* @param resources - FHIR Procedure resources
|
|
1803
2083
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1804
2084
|
* @returns HTML string for rendering
|
|
1805
2085
|
*/
|
|
1806
|
-
static generateStaticNarrative(
|
|
1807
|
-
const templateUtilities = new TemplateUtilities(
|
|
2086
|
+
static generateStaticNarrative(resources, timezone) {
|
|
2087
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1808
2088
|
let html = `
|
|
1809
2089
|
<table>
|
|
1810
2090
|
<thead>
|
|
@@ -1815,16 +2095,14 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
1815
2095
|
</tr>
|
|
1816
2096
|
</thead>
|
|
1817
2097
|
<tbody>`;
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
<
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
</tr>`;
|
|
1827
|
-
}
|
|
2098
|
+
for (const resourceItem of resources) {
|
|
2099
|
+
const proc = resourceItem;
|
|
2100
|
+
html += `
|
|
2101
|
+
<tr id="${templateUtilities.narrativeLinkId(proc)}">
|
|
2102
|
+
<td>${templateUtilities.codeableConcept(proc.code, "display")}</td>
|
|
2103
|
+
<td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
|
|
2104
|
+
<td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
|
|
2105
|
+
</tr>`;
|
|
1828
2106
|
}
|
|
1829
2107
|
html += `
|
|
1830
2108
|
</tbody>
|
|
@@ -1837,22 +2115,22 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
1837
2115
|
var SocialHistoryTemplate = class _SocialHistoryTemplate {
|
|
1838
2116
|
/**
|
|
1839
2117
|
* Generate HTML narrative for Social History
|
|
1840
|
-
* @param
|
|
2118
|
+
* @param resources - FHIR Observation resources
|
|
1841
2119
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1842
2120
|
* @returns HTML string for rendering
|
|
1843
2121
|
*/
|
|
1844
|
-
generateNarrative(
|
|
1845
|
-
return _SocialHistoryTemplate.generateStaticNarrative(
|
|
2122
|
+
generateNarrative(resources, timezone) {
|
|
2123
|
+
return _SocialHistoryTemplate.generateStaticNarrative(resources, timezone);
|
|
1846
2124
|
}
|
|
1847
2125
|
/**
|
|
1848
2126
|
* Internal static implementation that actually generates the narrative
|
|
1849
|
-
* @param
|
|
2127
|
+
* @param resources - FHIR Observation resources
|
|
1850
2128
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1851
2129
|
* @returns HTML string for rendering
|
|
1852
2130
|
*/
|
|
1853
|
-
static generateStaticNarrative(
|
|
1854
|
-
const templateUtilities = new TemplateUtilities(
|
|
1855
|
-
const observations =
|
|
2131
|
+
static generateStaticNarrative(resources, timezone) {
|
|
2132
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2133
|
+
const observations = resources.map((entry) => entry) || [];
|
|
1856
2134
|
observations.sort((a, b) => {
|
|
1857
2135
|
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
1858
2136
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
@@ -1891,14 +2169,14 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
|
|
|
1891
2169
|
var PastHistoryOfIllnessTemplate = class {
|
|
1892
2170
|
/**
|
|
1893
2171
|
* Generate HTML narrative for Past History of Illnesses
|
|
1894
|
-
* @param
|
|
2172
|
+
* @param resources - FHIR Condition resources
|
|
1895
2173
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1896
2174
|
* @returns HTML string for rendering
|
|
1897
2175
|
*/
|
|
1898
|
-
generateNarrative(
|
|
1899
|
-
const templateUtilities = new TemplateUtilities(
|
|
2176
|
+
generateNarrative(resources, timezone) {
|
|
2177
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1900
2178
|
let html = ``;
|
|
1901
|
-
const resolvedConditions =
|
|
2179
|
+
const resolvedConditions = resources.map((entry) => entry) || [];
|
|
1902
2180
|
resolvedConditions.sort((a, b) => {
|
|
1903
2181
|
const dateA = a.onsetDateTime ? new Date(a.onsetDateTime).getTime() : 0;
|
|
1904
2182
|
const dateB = b.onsetDateTime ? new Date(b.onsetDateTime).getTime() : 0;
|
|
@@ -1935,13 +2213,13 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
1935
2213
|
var PlanOfCareTemplate = class {
|
|
1936
2214
|
/**
|
|
1937
2215
|
* Generate HTML narrative for Plan of Care
|
|
1938
|
-
* @param
|
|
2216
|
+
* @param resources - FHIR CarePlan resources
|
|
1939
2217
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1940
2218
|
* @returns HTML string for rendering
|
|
1941
2219
|
*/
|
|
1942
|
-
generateNarrative(
|
|
1943
|
-
const templateUtilities = new TemplateUtilities(
|
|
1944
|
-
const carePlans =
|
|
2220
|
+
generateNarrative(resources, timezone) {
|
|
2221
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2222
|
+
const carePlans = resources.map((entry) => entry) || [];
|
|
1945
2223
|
carePlans.sort((a, b) => {
|
|
1946
2224
|
const endA = a.period?.end ? new Date(a.period?.end).getTime() : 0;
|
|
1947
2225
|
const endB = b.period?.end ? new Date(b.period?.end).getTime() : 0;
|
|
@@ -1980,37 +2258,35 @@ var PlanOfCareTemplate = class {
|
|
|
1980
2258
|
var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
1981
2259
|
/**
|
|
1982
2260
|
* Generate HTML narrative for Functional Status
|
|
1983
|
-
* @param
|
|
2261
|
+
* @param resources - FHIR resources array containing Observation resources
|
|
1984
2262
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1985
2263
|
* @returns HTML string for rendering
|
|
1986
2264
|
*/
|
|
1987
|
-
generateNarrative(
|
|
1988
|
-
return _FunctionalStatusTemplate.generateStaticNarrative(
|
|
2265
|
+
generateNarrative(resources, timezone) {
|
|
2266
|
+
return _FunctionalStatusTemplate.generateStaticNarrative(resources, timezone);
|
|
1989
2267
|
}
|
|
1990
2268
|
/**
|
|
1991
2269
|
* Internal static implementation that actually generates the narrative
|
|
1992
|
-
* @param
|
|
2270
|
+
* @param resources - FHIR resources array containing Observation resources
|
|
1993
2271
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1994
2272
|
* @returns HTML string for rendering
|
|
1995
2273
|
*/
|
|
1996
|
-
static generateStaticNarrative(
|
|
1997
|
-
const templateUtilities = new TemplateUtilities(
|
|
2274
|
+
static generateStaticNarrative(resources, timezone) {
|
|
2275
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1998
2276
|
let html = ``;
|
|
1999
2277
|
const activeConditions = [];
|
|
2000
2278
|
const clinicalImpressions = [];
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
activeConditions.push(cond);
|
|
2010
|
-
}
|
|
2011
|
-
} else if (entry.resource?.resourceType === "ClinicalImpression") {
|
|
2012
|
-
clinicalImpressions.push(entry.resource);
|
|
2279
|
+
for (const resourceItem of resources) {
|
|
2280
|
+
if (resourceItem.resourceType === "Condition") {
|
|
2281
|
+
const cond = resourceItem;
|
|
2282
|
+
const isResolved = cond.clinicalStatus?.coding?.some(
|
|
2283
|
+
(c) => c.code === "resolved" || c.code === "inactive" || c.display?.toLowerCase().includes("resolved")
|
|
2284
|
+
);
|
|
2285
|
+
if (!isResolved) {
|
|
2286
|
+
activeConditions.push(cond);
|
|
2013
2287
|
}
|
|
2288
|
+
} else if (resourceItem.resourceType === "ClinicalImpression") {
|
|
2289
|
+
clinicalImpressions.push(resourceItem);
|
|
2014
2290
|
}
|
|
2015
2291
|
}
|
|
2016
2292
|
activeConditions.sort((a, b) => {
|
|
@@ -2107,22 +2383,22 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2107
2383
|
var PregnancyTemplate = class _PregnancyTemplate {
|
|
2108
2384
|
/**
|
|
2109
2385
|
* Generate HTML narrative for Pregnancy
|
|
2110
|
-
* @param
|
|
2386
|
+
* @param resources - FHIR Observation resources
|
|
2111
2387
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2112
2388
|
* @returns HTML string for rendering
|
|
2113
2389
|
*/
|
|
2114
|
-
generateNarrative(
|
|
2115
|
-
return _PregnancyTemplate.generateStaticNarrative(
|
|
2390
|
+
generateNarrative(resources, timezone) {
|
|
2391
|
+
return _PregnancyTemplate.generateStaticNarrative(resources, timezone);
|
|
2116
2392
|
}
|
|
2117
2393
|
/**
|
|
2118
2394
|
* Internal static implementation that actually generates the narrative
|
|
2119
|
-
* @param
|
|
2395
|
+
* @param resources - FHIR Observation resources
|
|
2120
2396
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2121
2397
|
* @returns HTML string for rendering
|
|
2122
2398
|
*/
|
|
2123
|
-
static generateStaticNarrative(
|
|
2124
|
-
const templateUtilities = new TemplateUtilities(
|
|
2125
|
-
const observations =
|
|
2399
|
+
static generateStaticNarrative(resources, timezone) {
|
|
2400
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2401
|
+
const observations = resources.map((entry) => entry) || [];
|
|
2126
2402
|
observations.sort((a, b) => {
|
|
2127
2403
|
const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
|
|
2128
2404
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
@@ -2138,8 +2414,8 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
2138
2414
|
</tr>
|
|
2139
2415
|
</thead>
|
|
2140
2416
|
<tbody>`;
|
|
2141
|
-
for (const
|
|
2142
|
-
const obs =
|
|
2417
|
+
for (const resource of observations) {
|
|
2418
|
+
const obs = resource;
|
|
2143
2419
|
html += `
|
|
2144
2420
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2145
2421
|
<td>${templateUtilities.extractPregnancyStatus(obs)}</td>
|
|
@@ -2158,29 +2434,27 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
2158
2434
|
var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
2159
2435
|
/**
|
|
2160
2436
|
* Generate HTML narrative for Advance Directives
|
|
2161
|
-
* @param
|
|
2437
|
+
* @param resources - FHIR Consent resources
|
|
2162
2438
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2163
2439
|
* @returns HTML string for rendering
|
|
2164
2440
|
*/
|
|
2165
|
-
generateNarrative(
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
}
|
|
2173
|
-
return _AdvanceDirectivesTemplate.generateStaticNarrative(resource, timezone);
|
|
2441
|
+
generateNarrative(resources, timezone) {
|
|
2442
|
+
resources.sort((a, b) => {
|
|
2443
|
+
const dateA = new Date(a.dateTime || 0);
|
|
2444
|
+
const dateB = new Date(b.dateTime || 0);
|
|
2445
|
+
return dateB.getTime() - dateA.getTime();
|
|
2446
|
+
});
|
|
2447
|
+
return _AdvanceDirectivesTemplate.generateStaticNarrative(resources, timezone);
|
|
2174
2448
|
}
|
|
2175
2449
|
/**
|
|
2176
2450
|
* Internal static implementation that actually generates the narrative
|
|
2177
|
-
* @param
|
|
2451
|
+
* @param resources - FHIR Consent resources
|
|
2178
2452
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2179
2453
|
* @returns HTML string for rendering
|
|
2180
2454
|
*/
|
|
2181
2455
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2182
|
-
static generateStaticNarrative(
|
|
2183
|
-
const templateUtilities = new TemplateUtilities(
|
|
2456
|
+
static generateStaticNarrative(resources, timezone) {
|
|
2457
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2184
2458
|
let html = `
|
|
2185
2459
|
<table>
|
|
2186
2460
|
<thead>
|
|
@@ -2192,17 +2466,15 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
2192
2466
|
</tr>
|
|
2193
2467
|
</thead>
|
|
2194
2468
|
<tbody>`;
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
<
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
</tr>`;
|
|
2205
|
-
}
|
|
2469
|
+
for (const resourceItem of resources) {
|
|
2470
|
+
const consent = resourceItem;
|
|
2471
|
+
html += `
|
|
2472
|
+
<tr id="${templateUtilities.narrativeLinkId(consent)}">
|
|
2473
|
+
<td>${templateUtilities.codeableConcept(consent.scope, "display")}</td>
|
|
2474
|
+
<td>${consent.status || ""}</td>
|
|
2475
|
+
<td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
|
|
2476
|
+
<td>${consent.dateTime || ""}</td>
|
|
2477
|
+
</tr>`;
|
|
2206
2478
|
}
|
|
2207
2479
|
html += `
|
|
2208
2480
|
</tbody>
|
|
@@ -2216,16 +2488,20 @@ var TypeScriptTemplateMapper = class {
|
|
|
2216
2488
|
/**
|
|
2217
2489
|
* Generates HTML narrative for a specific IPS section
|
|
2218
2490
|
* @param section - The IPS section
|
|
2219
|
-
* @param
|
|
2491
|
+
* @param resources - FHIR resources
|
|
2220
2492
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2493
|
+
* @param useSectionSummary - Whether to use the section summary for narrative generation
|
|
2221
2494
|
* @returns HTML string for rendering
|
|
2222
2495
|
*/
|
|
2223
|
-
static generateNarrative(section,
|
|
2496
|
+
static generateNarrative(section, resources, timezone, useSectionSummary = false) {
|
|
2224
2497
|
const templateClass = this.sectionToTemplate[section];
|
|
2225
2498
|
if (!templateClass) {
|
|
2226
2499
|
throw new Error(`No template found for section: ${section}`);
|
|
2227
2500
|
}
|
|
2228
|
-
return templateClass.
|
|
2501
|
+
return useSectionSummary ? templateClass.generateSummaryNarrative(
|
|
2502
|
+
resources,
|
|
2503
|
+
timezone
|
|
2504
|
+
) : templateClass.generateNarrative(resources, timezone);
|
|
2229
2505
|
}
|
|
2230
2506
|
};
|
|
2231
2507
|
// Map of section types to their template classes
|
|
@@ -2281,21 +2557,15 @@ var NarrativeGenerator = class {
|
|
|
2281
2557
|
* @param section - IPS section type
|
|
2282
2558
|
* @param resources - Array of domain resources
|
|
2283
2559
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2560
|
+
* @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
|
|
2284
2561
|
* @returns Generated HTML content or undefined if no resources
|
|
2285
2562
|
*/
|
|
2286
|
-
static async generateNarrativeContentAsync(section, resources, timezone) {
|
|
2563
|
+
static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false) {
|
|
2287
2564
|
if (!resources || resources.length === 0) {
|
|
2288
2565
|
return void 0;
|
|
2289
2566
|
}
|
|
2290
2567
|
try {
|
|
2291
|
-
const
|
|
2292
|
-
resourceType: "Bundle",
|
|
2293
|
-
type: "collection",
|
|
2294
|
-
entry: resources.map((resource) => ({
|
|
2295
|
-
resource
|
|
2296
|
-
}))
|
|
2297
|
-
};
|
|
2298
|
-
const content = TypeScriptTemplateMapper.generateNarrative(section, bundle, timezone);
|
|
2568
|
+
const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary);
|
|
2299
2569
|
if (!content) {
|
|
2300
2570
|
return void 0;
|
|
2301
2571
|
}
|
|
@@ -2346,10 +2616,11 @@ var NarrativeGenerator = class {
|
|
|
2346
2616
|
* @param resources - Array of domain resources
|
|
2347
2617
|
* @param timezone - Optional timezone to use for date formatting
|
|
2348
2618
|
* @param minify - Whether to minify the HTML content (default: true)
|
|
2619
|
+
* @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
|
|
2349
2620
|
* @returns Promise that resolves to a FHIR Narrative object or undefined if no resources
|
|
2350
2621
|
*/
|
|
2351
|
-
static async generateNarrativeAsync(section, resources, timezone, minify = true) {
|
|
2352
|
-
const content = await this.generateNarrativeContentAsync(section, resources, timezone);
|
|
2622
|
+
static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false) {
|
|
2623
|
+
const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary);
|
|
2353
2624
|
if (!content) {
|
|
2354
2625
|
return void 0;
|
|
2355
2626
|
}
|
|
@@ -2380,22 +2651,51 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
2380
2651
|
/**
|
|
2381
2652
|
* sets the patient resource for the IPS Composition.
|
|
2382
2653
|
* This is not needed if you are calling read_bundle, but can be used to set the patient resource directly.
|
|
2383
|
-
* @param
|
|
2654
|
+
* @param patients - FHIR Patient resource to set
|
|
2384
2655
|
*/
|
|
2385
|
-
setPatient(
|
|
2386
|
-
if (!
|
|
2656
|
+
setPatient(patients) {
|
|
2657
|
+
if (!Array.isArray(patients)) {
|
|
2658
|
+
patients = [patients];
|
|
2659
|
+
}
|
|
2660
|
+
if (patients.length === 0 || !patients.every((patient) => patient.resourceType === "Patient")) {
|
|
2387
2661
|
throw new Error("Invalid Patient resource");
|
|
2388
2662
|
}
|
|
2389
|
-
this.
|
|
2663
|
+
this.patients = patients;
|
|
2390
2664
|
return this;
|
|
2391
2665
|
}
|
|
2392
2666
|
/**
|
|
2393
2667
|
* Adds a section to the composition with async HTML minification
|
|
2668
|
+
* @param narrative - Narrative content for the section
|
|
2669
|
+
* @param sectionType - IPS section type
|
|
2670
|
+
* @param validResources - Array of domain resources
|
|
2671
|
+
*/
|
|
2672
|
+
addSectionAsync(narrative, sectionType, validResources) {
|
|
2673
|
+
const sectionEntry = {
|
|
2674
|
+
title: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType,
|
|
2675
|
+
code: {
|
|
2676
|
+
coding: [{
|
|
2677
|
+
system: "http://loinc.org",
|
|
2678
|
+
code: IPS_SECTION_LOINC_CODES[sectionType],
|
|
2679
|
+
display: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType
|
|
2680
|
+
}],
|
|
2681
|
+
text: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType
|
|
2682
|
+
},
|
|
2683
|
+
text: narrative,
|
|
2684
|
+
entry: validResources.map((resource) => ({
|
|
2685
|
+
reference: `${resource.resourceType}/${resource.id}`,
|
|
2686
|
+
display: resource.resourceType
|
|
2687
|
+
}))
|
|
2688
|
+
};
|
|
2689
|
+
this.sections.push(sectionEntry);
|
|
2690
|
+
return this;
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2693
|
+
* Make and adds a section to the composition with async HTML minification
|
|
2394
2694
|
* @param sectionType - IPS section type
|
|
2395
2695
|
* @param validResources - Array of domain resources
|
|
2396
2696
|
* @param timezone - Optional timezone to use for date formatting
|
|
2397
2697
|
*/
|
|
2398
|
-
async
|
|
2698
|
+
async makeSectionAsync(sectionType, validResources, timezone) {
|
|
2399
2699
|
for (const resource of validResources) {
|
|
2400
2700
|
this.resources.add(resource);
|
|
2401
2701
|
}
|
|
@@ -2415,51 +2715,68 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
2415
2715
|
} else {
|
|
2416
2716
|
return this;
|
|
2417
2717
|
}
|
|
2418
|
-
|
|
2419
|
-
title: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType,
|
|
2420
|
-
code: {
|
|
2421
|
-
coding: [{
|
|
2422
|
-
system: "http://loinc.org",
|
|
2423
|
-
code: IPS_SECTION_LOINC_CODES[sectionType],
|
|
2424
|
-
display: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType
|
|
2425
|
-
}],
|
|
2426
|
-
text: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType
|
|
2427
|
-
},
|
|
2428
|
-
text: narrative,
|
|
2429
|
-
entry: validResources.map((resource) => ({
|
|
2430
|
-
reference: `${resource.resourceType}/${resource.id}`,
|
|
2431
|
-
display: resource.resourceType
|
|
2432
|
-
}))
|
|
2433
|
-
};
|
|
2434
|
-
this.sections.push(sectionEntry);
|
|
2718
|
+
this.addSectionAsync(narrative, sectionType, validResources);
|
|
2435
2719
|
}
|
|
2436
2720
|
return this;
|
|
2437
2721
|
}
|
|
2722
|
+
async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone) {
|
|
2723
|
+
const sectionResources = [];
|
|
2724
|
+
for (const summaryComposition of summaryCompositions) {
|
|
2725
|
+
const resourceEntries = summaryComposition?.section?.flatMap((sec) => sec.entry || []) ?? [];
|
|
2726
|
+
resources.forEach((resource) => {
|
|
2727
|
+
if (resourceEntries?.some((entry) => entry.reference === `${resource.resourceType}/${resource.id}`)) {
|
|
2728
|
+
this.resources.add(resource);
|
|
2729
|
+
sectionResources.push(resource);
|
|
2730
|
+
}
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
const narrative = await NarrativeGenerator.generateNarrativeAsync(
|
|
2734
|
+
sectionType,
|
|
2735
|
+
summaryCompositions,
|
|
2736
|
+
timezone,
|
|
2737
|
+
true,
|
|
2738
|
+
true
|
|
2739
|
+
);
|
|
2740
|
+
this.addSectionAsync(narrative, sectionType, sectionResources);
|
|
2741
|
+
return this;
|
|
2742
|
+
}
|
|
2438
2743
|
/**
|
|
2439
2744
|
* Reads a FHIR Bundle and extracts resources for each section defined in IPSSections.
|
|
2440
2745
|
* @param bundle - FHIR Bundle containing resources
|
|
2441
2746
|
* @param timezone - Optional timezone to use for date formatting
|
|
2747
|
+
* @param useSummaryCompositions - Whether to use summary compositions (default: false)
|
|
2442
2748
|
*/
|
|
2443
|
-
async readBundleAsync(bundle, timezone) {
|
|
2749
|
+
async readBundleAsync(bundle, timezone, useSummaryCompositions = false) {
|
|
2444
2750
|
if (!bundle.entry) {
|
|
2445
2751
|
return this;
|
|
2446
2752
|
}
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2753
|
+
const patientEntries = [];
|
|
2754
|
+
const resources = [];
|
|
2755
|
+
bundle.entry.forEach((e) => {
|
|
2756
|
+
if (e.resource?.resourceType === "Patient") {
|
|
2757
|
+
patientEntries.push(e.resource);
|
|
2758
|
+
this.resources.add(e.resource);
|
|
2759
|
+
} else if (e.resource) {
|
|
2760
|
+
resources.push(e.resource);
|
|
2761
|
+
}
|
|
2762
|
+
});
|
|
2763
|
+
if (patientEntries.length === 0) {
|
|
2449
2764
|
throw new Error("Patient resource not found in the bundle");
|
|
2450
2765
|
}
|
|
2451
|
-
this.
|
|
2452
|
-
const resources = bundle.entry.map((e) => e.resource);
|
|
2766
|
+
this.patients = patientEntries;
|
|
2453
2767
|
for (const sectionType of Object.values(IPSSections)) {
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
);
|
|
2459
|
-
if (
|
|
2460
|
-
|
|
2768
|
+
if (sectionType === "Patient" /* PATIENT */) {
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
const summaryCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType) : void 0;
|
|
2772
|
+
const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : void 0;
|
|
2773
|
+
if (sectionSummary) {
|
|
2774
|
+
await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone);
|
|
2775
|
+
} else {
|
|
2776
|
+
const sectionFilter = IPSSectionResourceHelper.getResourceFilterForSection(sectionType);
|
|
2777
|
+
const sectionResources = resources.filter((resource) => sectionFilter(resource));
|
|
2778
|
+
await this.makeSectionAsync(sectionType, sectionResources, timezone);
|
|
2461
2779
|
}
|
|
2462
|
-
await this.addSectionAsync(sectionType, sectionResources, timezone);
|
|
2463
2780
|
}
|
|
2464
2781
|
return this;
|
|
2465
2782
|
}
|
|
@@ -2469,16 +2786,18 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
2469
2786
|
* @param authorOrganizationName - Name of the authoring organization
|
|
2470
2787
|
* @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
|
|
2471
2788
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2789
|
+
* @param patientId - Optional patient ID to use as primary patient for composition reference
|
|
2472
2790
|
*/
|
|
2473
|
-
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone) {
|
|
2791
|
+
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId) {
|
|
2474
2792
|
if (baseUrl.endsWith("/")) {
|
|
2475
2793
|
baseUrl = baseUrl.slice(0, -1);
|
|
2476
2794
|
}
|
|
2477
|
-
if (!this.
|
|
2795
|
+
if (!this.patients) {
|
|
2478
2796
|
throw new Error("Patient resource must be set before building the bundle");
|
|
2479
2797
|
}
|
|
2798
|
+
const primaryPatientId = patientId ?? this.patients[0].id;
|
|
2480
2799
|
const composition = {
|
|
2481
|
-
id: `Composition-${
|
|
2800
|
+
id: `Composition-${primaryPatientId}`,
|
|
2482
2801
|
resourceType: "Composition",
|
|
2483
2802
|
status: "final",
|
|
2484
2803
|
type: {
|
|
@@ -2489,7 +2808,7 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
2489
2808
|
}]
|
|
2490
2809
|
},
|
|
2491
2810
|
subject: {
|
|
2492
|
-
reference: `Patient/${
|
|
2811
|
+
reference: `Patient/${primaryPatientId}`
|
|
2493
2812
|
},
|
|
2494
2813
|
author: [{
|
|
2495
2814
|
reference: `Organization/${authorOrganizationId}`,
|
|
@@ -2501,7 +2820,7 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
2501
2820
|
section: this.sections,
|
|
2502
2821
|
text: await NarrativeGenerator.generateNarrativeAsync(
|
|
2503
2822
|
"Patient" /* PATIENT */,
|
|
2504
|
-
|
|
2823
|
+
this.patients,
|
|
2505
2824
|
timezone,
|
|
2506
2825
|
true
|
|
2507
2826
|
)
|
|
@@ -2520,9 +2839,11 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
2520
2839
|
fullUrl: `${baseUrl}/Composition/${composition.id}`,
|
|
2521
2840
|
resource: composition
|
|
2522
2841
|
});
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2842
|
+
this.patients.forEach((patient) => {
|
|
2843
|
+
bundle.entry?.push({
|
|
2844
|
+
fullUrl: `${baseUrl}/Patient/${patient.id}`,
|
|
2845
|
+
resource: patient
|
|
2846
|
+
});
|
|
2526
2847
|
});
|
|
2527
2848
|
this.resources.forEach((resource) => {
|
|
2528
2849
|
if (resource.resourceType !== "Patient") {
|