2 * @package OpenEMR CCDAServer
3 * @link http://www.open-emr.org
5 * @author Jerry Padgett <sjpadgett@gmail.com>
6 * @copyright Copyright (c) 2016-2022 Jerry Padgett <sjpadgett@gmail.com>
7 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
12 const enableDebug = true;
14 const net = require('net');
15 const server = net.createServer();
16 const to_json = require('xmljson').to_json;
17 const bbg = require(__dirname + '/oe-blue-button-generate');
18 const fs = require('fs');
19 const { DataStack } = require('./data-stack/data-stack');
20 const { cleanCode } = require('./utils/clean-code/clean-code');
21 const { safeTrim } = require('./utils/safe-trim/safe-trim');
22 const { headReplace } = require('./utils/head-replace/head-replace');
23 const { fDate, templateDate } = require('./utils/date/date');
24 const { countEntities } = require('./utils/count-entities/count-entities');
26 var conn = ''; // make our connection scope global to script
32 var authorDateTime = '';
33 var documentLocation = '';
35 // do a recursive descent transformation of the node object populating the timezone offset value if we have
36 // a precision property (inside a date) with the value of timezone.
37 function populateTimezones(node, tzOffset, depthCheck) {
38 if (!node || typeof node !== 'object') {
41 // we should NEVER go farther than 25 recursive loops down in our heirarchy, if we do it means we have an infinite loop
42 if (depthCheck > 25) {
43 console.error("Max depth traversal reached. Potential infinite loop. Breaking out of loop")
47 if (Object.prototype.hasOwnProperty.call(node, 'precision') && node.precision == 'tz' && !Object.prototype.hasOwnProperty.call(node, 'timezoneOffset')) {
48 node.timezoneOffset = tzOffset;
50 for (const [key, value] of Object.entries(node)) {
51 node[key] = populateTimezones(value, tzOffset, depthCheck + 1);
57 function fetchPreviousAddresses(pd) {
58 let addressArray = [];
59 let pa = pd.previous_addresses.address;
60 let streetLine = [pd.street[0]];
61 if (pd.street[1].length > 0) {
62 streetLine = [pd.street[0], pd.street[1]];
66 "street_lines": streetLine,
70 "country": pd.country || "US",
72 // use current date for current residence
79 let count = countEntities(pa);
80 // how do we ever get here where we just have one object?
82 streetLine = [pa.street[0]];
83 if (pa.street[1].length > 0) {
84 streetLine = [pa.street[0], pa.street[1]];
88 "street_lines": streetLine,
92 "country": pa.country || "US",
95 "date": fDate(pa.period_start),
99 "date": fDate(pa.period_end) || fDate(""),
104 } else if (count > 1) {
106 streetLine = [pa[i].street[0]];
107 if (pa[i].street[1].length > 0) {
108 streetLine = [pa[i].street[0], pa[i].street[1]];
112 "street_lines": streetLine,
114 "state": pa[i].state,
115 "zip": pa[i].postalCode,
116 "country": pa[i].country || "US",
119 "date": fDate(pa[i].period_start),
123 "date": fDate(pa[i].period_end) || fDate(""),
133 function populateDemographic(pd, g) {
137 const names = g.display_name.split(' ');
138 if (names.length === 2) {
142 if (names.length === 3) {
147 "relation": g.relation,
149 "street_lines": [g.address],
153 "country": g.country || "US",
154 "use": "primary home"
162 "type": "primary home"
165 if (pd.race === 'Declined To Specify' || pd.race === '') {
166 pd.race = "null_flavor";
168 if (pd.race_group === 'Declined To Specify' || pd.race_group === '') {
169 pd.race_group = "null_flavor";
171 if (pd.ethnicity === 'Declined To Specify' || pd.ethnicity === '') {
172 pd.ethnicity = "null_flavor";
174 let addressArray = fetchPreviousAddresses(pd);
179 "middle": [pd.mname] || "",
184 "middle": pd.birth_mname || "",
185 "last": pd.birth_lname || "",
186 "first": pd.birth_fname || ""
190 "date": fDate(pd.dob),
194 "gender": pd.gender.toUpperCase() || "null_flavor",
196 "identifier": oidFacility || npiFacility,
199 "marital_status": pd.status.toUpperCase(),
200 "addresses": addressArray,
203 "number": pd.phone_home,
204 "type": "primary home"
206 "number": pd.phone_mobile,
207 "type": "primary mobile"
209 "number": pd.phone_work,
212 "number": pd.phone_emergency,
213 "type": "emergency contact"
216 "type": "contact_email"
219 "ethnicity": pd.ethnicity || "",
220 "race": pd.race || "null_flavor",
221 "race_additional": pd.race_group || "null_flavor",
223 "language": pd.language === 'English' ? "en-US" : pd.language === 'Spanish' ? "sp-US" : 'en-US',
225 "mode": "Expressed spoken",
226 "proficiency": "Good"
228 //"religion": pd.religion.toUpperCase() || "",
235 "attributed_provider": {
238 "root": "2.16.840.1.113883.4.6",
239 "extension": npiFacility || ""
243 "number": all.encounter_provider.facility_phone || "",
247 "full": all.encounter_provider.facility_name || ""
253 all.encounter_provider.facility_street
255 "city": all.encounter_provider.facility_city,
256 "state": all.encounter_provider.facility_state,
257 "zip": all.encounter_provider.facility_postal_code,
258 "country": all.encounter_provider.facility_country_code || "US",
263 "guardians": g.display_name ? guardian : '' //not required
267 function populateProvider(provider) {
268 // The provider role is a maybe and will only be provided for physicians as a
269 // primary care role. All other team members will id via taxonomy only and if not physicians.
271 "function_code": provider.physician_type ? "PP" : "",
274 "date": provider.provider_since ? fDate(provider.provider_since) : fDate(""),
280 "root": provider.npi ? "2.16.840.1.113883.4.6" : oidFacility,
281 "extension": provider.npi || provider.table_id || "NI"
286 "name": provider.taxonomy_description || "",
287 "code": cleanCode(provider.taxonomy) || "",
288 "code_system": "2.16.840.1.113883.6.101",
289 "code_system_name": "NUCC Health Care Provider Taxonomy"
294 "last": provider.lname || "",
295 "first": provider.fname || ""
301 all.encounter_provider.facility_street
303 "city": all.encounter_provider.facility_city,
304 "state": all.encounter_provider.facility_state,
305 "zip": all.encounter_provider.facility_postal_code,
306 "country": all.encounter_provider.facility_country_code || "US"
311 "number": all.encounter_provider.facility_phone || ""
316 function populateProviders(all) {
317 let providerArray = [];
319 let provider = populateProvider(all.primary_care_provider.provider);
320 providerArray.push(provider);
321 let count = countEntities(all.care_team.provider);
323 provider = populateProvider(all.care_team.provider);
324 providerArray.push(provider);
325 } else if (count > 1) {
326 for (let i in all.care_team.provider) {
327 provider = populateProvider(all.care_team.provider[i]);
328 providerArray.push(provider);
336 "date": fDate(all.time_start) || fDate(""),
340 "date": fDate(all.time_end) || fDate(""),
345 "name": all.primary_diagnosis.text || "",
346 "code": cleanCode(all.primary_diagnosis.code || ""),
347 "code_system_name": all.primary_diagnosis.code_type || ""
349 "provider": providerArray,
355 function populateCareTeamMember(provider) {
357 //"function_code": provider.physician_type ? "PP" : "",
359 "xmlns": "urn:hl7-org:sdtc",
360 "name": provider.taxonomy_description || "",
361 "code": cleanCode(provider.taxonomy) || "",
362 "code_system": "2.16.840.1.113883.6.101",
363 "code_system_name": "NUCC Health Care Provider Taxonomy"
368 "date": fDate(provider.provider_since) || fDate(""),
374 "identifier": provider.npi ? "2.16.840.1.113883.4.6" : oidFacility,
375 "extension": provider.npi || provider.table_id
378 "full_name": provider.fname + " " + provider.lname,
380 "last": provider.lname || "",
381 "first": provider.fname || ""
387 "city": provider.city,
388 "state": provider.state,
390 "country": all.encounter_provider.facility_country_code || "US"
394 "number": provider.telecom,
401 function populateAuthorFromAuthorContainer(pd) {
402 let author = pd.author || {};
405 "name": author.physician_type || '',
406 "code": author.physician_type_code || '',
407 "code_system": author.physician_type_system,
408 "code_system_name": author.physician_type_system_name
412 "date": fDate(author.time),
418 "identifier": author.npi ? "2.16.840.1.113883.4.6" : author.id,
419 "extension": author.npi ? author.npi : 'NI'
424 "last": author.lname || "",
425 "first": author.fname || ""
432 "root": author.facility_oid || "2.16.840.1.113883.4.6",
433 "extension": author.facility_npi || "NI"
437 author.facility_name || ""
444 function populateCareTeamMembers(pd) {
445 let providerArray = [];
447 let primaryCareProvider = pd.primary_care_provider || {provider: {}};
448 let providerSince = fDate(primaryCareProvider.provider.provider_since || '');
449 if (pd.primary_care_provider) {
450 let provider = populateCareTeamMember(pd.primary_care_provider.provider);
451 providerArray.push(provider);
452 let count = countEntities(pd.care_team.provider);
454 provider = populateCareTeamMember(pd.care_team.provider);
455 providerSince = providerSince || fDate(provider.provider_since);
456 providerArray.push(provider);
457 } else if (count > 1) {
458 for (let i in pd.care_team.provider) {
459 provider = populateCareTeamMember(pd.care_team.provider[i]);
460 providerSince = providerSince || fDate(provider.provider_since);
461 providerArray.push(provider);
468 "provider": providerArray,
473 "date": providerSince || fDate(""),
477 // we treat this author a bit differently since we are working at the main pd object instead of the sub pd.care_team
478 "author": populateAuthorFromAuthorContainer(pd.care_team)
482 function populateMedication(pd) {
483 pd.status = 'Completed'; //@todo invoke prescribed
487 "date": fDate(pd.start_date),
491 "date": fDate(pd.end_date),
496 "identifier": pd.sha_extension,
497 "extension": pd.extension || ""
503 "identifier": pd.sha_extension || "2a620155-9d11-439e-92b3-5d9815ff4ee8",
504 "extension": pd.extension + 1 || ""
506 "unencoded_name": pd.drug,
509 "code": cleanCode(pd.rxnorm),
510 "code_system_name": "RXNORM"
514 "code_system_name": "RXNORM"
521 "name": pd.author.physician_type || '',
522 "code": pd.author.physician_type_code || '',
523 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
527 "date": fDate(pd.author.time),
533 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
534 "extension": pd.author.npi ? pd.author.npi : 'NI'
539 "last": pd.author.lname,
540 "first": pd.author.fname
547 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
548 "extension": pd.author.facility_npi || "NI"
552 pd.author.facility_name
560 "date": fDate(pd.start_date),
564 "date": fDate(pd.end_date),
572 "identifier": pd.sha_extension || "2a620155-9d11-439e-92b3-5d9815ff4ee8",
573 "extension": pd.extension + 1 || ""
575 "unencoded_name": pd.drug,
578 "code": cleanCode(pd.rxnorm),
582 "code_system_name": "RXNORM"
584 "code_system_name": "RXNORM"
590 "name": all.author.physician_type || '',
591 "code": all.author.physician_type_code || '',
592 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
596 "date": authorDateTime,
602 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
603 "extension": all.author.npi ? all.author.npi : 'NI'
608 "last": all.author.lname,
609 "first": all.author.fname
616 "root": oidFacility || "2.16.840.1.113883.4.6",
617 "extension": npiFacility || ""
621 all.encounter_provider.facility_name
628 "name": "instruction",
630 "code_system_name": "SNOMED CT"
632 "free_text": pd.instructions || "No Instructions"
637 "name": pd.route || "",
638 "code": cleanCode(pd.route_code) || "",
639 "code_system_name": "Medication Route FDA"
643 "code": cleanCode(pd.form_code),
644 "code_system_name": "Medication Route FDA"
647 "value": parseFloat(pd.size),
651 "value": parseFloat(pd.dosage),
656 "value": parseFloat(pd.dosage),
664 "identifier": "2.16.840.1.113883.4.6",
665 "extension": pd.npi || ""
669 "identifier": pd.sha_extension,
670 "extension": pd.extension || ""
672 "name": [pd.performer_name]
677 "code": cleanCode(pd.form_code),
678 "code_system_name": "RXNORM"
683 "code_system_name": "ActCode"
688 "code_system_name": "SNOMED CT"
693 "identifier": "db734647-fc99-424c-a864-7e3cda82e703",
699 "code_system_name": "SNOMED CT"
703 "date": fDate(pd.start_date),
708 "name": pd.indications,
709 "code": pd.indications_code,
710 "code_system_name": "SNOMED CT"
715 "identifier": "1.2.3.4.56789.1",
716 "extension": "cb734647-fc99-424c-a864-7e3cda82e704"
720 "identifier": "2.16.840.1.113883.19.5.9999.456",
721 "extension": "2981823"
724 "street_lines": [pd.address],
732 "identifier": "2.16.840.1.113883.19.5.9999.1393"
734 "name": [pd.performer_name]
739 "identifier": "2a620155-9d11-439e-92b3-5d9815ff4ee8"
741 "unencoded_name": pd.drug,
748 "code_system_name": "RXNORM"
750 "code_system_name": "RXNORM"
758 function getFinding(pd, problem) {
761 "identifier": pd.sha_extension,
767 "code_system_name": ''
776 "reason": pd.encounter_reason,
779 "name": all.author.physician_type || '',
780 "code": all.author.physician_type_code || '',
781 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
785 "date": authorDateTime,
791 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
792 "extension": all.author.npi ? all.author.npi : 'UNK'
797 "last": all.author.lname,
798 "first": all.author.fname
805 "root": oidFacility || "2.16.840.1.113883.4.6",
806 "extension": npiFacility || ""
810 all.encounter_provider.facility_name
817 finding.identifiers["0"].extension = problem.extension;
818 finding.date_time.low.date = fDate(problem.date);
819 finding.value.name = problem.text;
820 finding.value.code = cleanCode(problem.code);
821 finding.value.code_system_name = problem.code_type;
822 finding.status = problem.status;
826 function populateEncounter(pd) {
827 // just to get diagnosis. for findings..
832 count = countEntities(pd.encounter_problems.problem);
837 for (let i in pd.encounter_problems.problem) {
838 theone[i] = getFinding(pd, pd.encounter_problems.problem[i]);
839 findingObj.push(theone[i]);
841 } else if (count !== 0 && pd.encounter_problems.problem.code > '') {
842 let finding = getFinding(pd, pd.encounter_problems.problem);
843 findingObj.push(finding);
848 "name": pd.visit_category ? (pd.visit_category + " | " + pd.encounter_reason) : pd.code_description,
849 "code": pd.code || "185347001",
850 //"code_system": "2.16.840.1.113883.6.96",
851 "code_system_name": pd.code_type || "SNOMED CT",
853 "name": "Ambulatory",
855 "code_system_name": "ActCode"
859 "identifier": pd.sha_extension,
860 "extension": pd.extension
864 "date": fDate(pd.date),
870 "identifier": "2.16.840.1.113883.4.6",
871 "extension": pd.npi || ""
874 "name": pd.physician_type,
875 "code": cleanCode(pd.physician_type_code),
876 "code_system_name": pd.physician_code_type
880 "last": pd.lname || "",
881 "first": pd.fname || ""
886 "number": pd.work_phone,
894 "name": pd.location_details,
896 "code_system_name": "HealthcareServiceLocation"
899 "street_lines": [pd.facility_address],
900 "city": pd.facility_city,
901 "state": pd.facility_state,
902 "zip": pd.facility_zip,
903 "country": pd.facility_country || "US"
907 "number": pd.facility_phone,
912 "findings": findingObj
916 function populateAllergy(pd) {
920 "no_know_allergies": "No Known Allergies",
922 "low": templateDate("", "day"),
923 //"high": templateDate(pd.enddate, "day")
927 let allergyAuthor = {
929 "name": pd.author.physician_type || '',
930 "code": pd.author.physician_type_code || '',
931 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
935 "date": fDate(pd.author.time),
941 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
942 "extension": pd.author.npi ? pd.author.npi : 'NI'
947 "last": pd.author.lname,
948 "first": pd.author.fname
955 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
956 "extension": pd.author.facility_npi || "NI"
960 pd.author.facility_name
968 "identifier": pd.sha_id,
969 "extension": pd.id || ""
972 "low": templateDate(pd.startdate, "day"),
973 //"high": templateDate(pd.enddate, "day")
975 "author": allergyAuthor,
978 "identifier": pd.sha_extension || "2a620155-9d11-439e-92b3-5d9815ff4ee8",
979 "extension": pd.id + 1 || ""
981 "author": allergyAuthor,
983 "name": pd.title || "",
984 "code": pd.rxnorm_code_text ? cleanCode(pd.rxnorm_code) : pd.snomed_code_text ? cleanCode(pd.snomed_code) : cleanCode(""),
985 "code_system_name": pd.rxnorm_code_text ? "RXNORM" : pd.snomed_code_text ? "SNOMED CT" : ""
989 "date": fDate(pd.startdate) || fDate(""),
994 "name": "Propensity to adverse reactions to drug",
996 "code_system_name": "SNOMED CT"
1000 "name": pd.outcome || "",
1001 "code": cleanCode(pd.outcome_code) || "",
1002 "code_system_name": "SNOMED CT"
1006 "name": pd.status_table || "",
1007 "code": cleanCode(pd.status_code),
1008 "code_system_name": "SNOMED CT"
1012 "identifier": "4adc1020-7b14-11db-9fe1-0800200c9a64"
1015 "low": templateDate(pd.startdate, "day"),
1016 "high": templateDate(pd.enddate, "day")
1019 "name": pd.reaction_text,
1020 "code": cleanCode(pd.reaction_code) || "",
1021 "code_system_name": pd.reaction_code_type || "SNOMED CT"
1025 "name": pd.outcome || "",
1026 "code": cleanCode(pd.outcome_code),
1027 "code_system_name": "SNOMED CT"
1035 function populateProblem(pd) {
1036 let primary_care_provider = all.primary_care_provider || {provider: {}};
1040 "date": fDate(pd.start_date_table),
1044 "date": fDate(pd.end_date),
1049 "identifier": pd.sha_extension,
1050 "extension": pd.extension || ""
1053 "name": "Condition",
1055 "code_system_name": "LOINC"
1059 "name": safeTrim(pd.title),
1060 "code": cleanCode(pd.code),
1061 "code_system_name": safeTrim(pd.code_type)
1065 "date": fDate(pd.start_date),
1069 "date": fDate(pd.end_date),
1070 "precision": getPrecision()
1076 "name": pd.author.physician_type || '',
1077 "code": pd.author.physician_type_code || '',
1078 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
1082 "date": fDate(pd.author.time),
1088 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
1089 "extension": pd.author.npi ? pd.author.npi : 'NI'
1094 "last": pd.author.lname,
1095 "first": pd.author.fname
1102 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
1103 "extension": pd.author.facility_npi || "NI"
1107 pd.author.facility_name
1116 "identifier": "2.16.840.1.113883.4.6",
1117 "extension": primary_care_provider.provider.npi || ""
1122 "last": primary_care_provider.provider.lname || "",
1123 "first": primary_care_provider.provider.fname || ""
1127 "onset_age": pd.age,
1128 "onset_age_unit": "Year",
1130 "name": pd.status_table,
1133 "date": fDate(pd.start_date),
1137 "date": fDate(pd.end_date),
1138 "precision": getPrecision()
1142 "patient_status": pd.observation,
1143 "source_list_identifiers": [{
1144 "identifier": pd.sha_extension,
1145 "extension": pd.extension || ""
1151 function populateProcedure(pd) {
1154 "name": pd.description,
1155 "code": cleanCode(pd.code),
1156 //"code_system": "2.16.840.1.113883.6.12",
1157 "code_system_name": pd.code_type
1160 "identifier": "d68b7e32-7810-4f5b-9cc2-acd54b0fd85d",
1161 "extension": pd.extension
1163 "status": "completed",
1166 "date": fDate(pd.date),
1173 "code_system_name": ""
1177 "identifier": "c2ee9ee9-ae31-4628-a919-fec1cbb58683"
1182 "code_system_name": "SNOMED CT"
1187 "identifier": "2.16.840.1.113883.4.6",
1188 "extension": pd.npi || ""
1191 "street_lines": [pd.address],
1198 "number": pd.work_phone,
1199 "type": "work place"
1203 "identifier": pd.facility_sha_extension,
1204 "extension": pd.facility_extension
1206 "name": [pd.facility_name],
1208 "street_lines": [pd.facility_address],
1209 "city": pd.facility_city,
1210 "state": pd.facility_state,
1211 "zip": pd.facility_zip,
1212 "country": pd.facility_country || "US"
1215 "number": pd.facility_phone,
1216 "type": "work place"
1220 "author": populateAuthorFromAuthorContainer(pd),
1221 "procedure_type": "procedure"
1225 function populateMedicalDevice(pd) {
1228 "identifier": pd.sha_extension,
1229 "extension": pd.extension
1233 "date": fDate(pd.start_date),
1237 "date": fDate(pd.end_date),
1241 "device_type": "UDI",
1243 "name": pd.code_text,
1244 "code": cleanCode(pd.code),
1245 "code_system_name": "SNOMED CT",
1247 "identifier": "2.16.840.1.113883.3.3719",
1250 "status": "completed",
1254 "code_system_name": ""
1260 "name": pd.author.physician_type || '',
1261 "code": pd.author.physician_type_code || '',
1262 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
1266 "date": fDate(pd.author.time),
1272 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
1273 "extension": pd.author.npi ? pd.author.npi : 'NI'
1278 "last": pd.author.lname,
1279 "first": pd.author.fname
1286 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
1287 "extension": pd.author.facility_npi || "NI"
1291 pd.author.facility_name
1299 function populateResult(pd) {
1300 let icode = pd.subtest.abnormal_flag;
1301 let value = parseFloat(pd.subtest.result_value) || pd.subtest.result_value || "";
1302 let type = isNaN(value) ? "ST" : "PQ";
1303 type = !pd.subtest.unit ? "ST" : type;
1305 let range_type = pd.subtest.range.toUpperCase() == "NEGATIVE" ? "CO" : type;
1306 type = value.toUpperCase() == "NEGATIVE" ? "CO" : type;
1308 switch (pd.subtest.abnormal_flag.toUpperCase()) {
1321 "identifier": pd.subtest.root,
1322 "extension": pd.subtest.extension
1326 "code": cleanCode(pd.subtest.result_code) || "",
1327 "code_system_name": "LOINC"
1331 "date": fDate(pd.date_ordered),
1335 "status": pd.order_status,
1336 "reference_range": {
1337 "low": pd.subtest.low,
1338 "high": pd.subtest.high,
1339 "unit": pd.subtest.unit,
1341 "range_type": range_type
1343 "value": value + "",
1344 "unit": pd.subtest.unit,
1346 "range": pd.subtest.range,
1347 "range_type": range_type
1349 // interpretation cannot be an empty value so we skip it if it is
1350 // empty as Observation.interpretationCode is [0..*]
1352 result["interpretations"] = [icode];
1357 function getResultSet(results) {
1359 if (!results) return '';
1361 // not sure if the result set should be grouped better on the backend as the author information needs to be more nuanced here
1362 let tResult = results.result[0] || results.result;
1365 "identifier": tResult.root,
1366 "extension": tResult.extension
1368 "author": populateAuthorFromAuthorContainer(tResult),
1370 "name": tResult.test_name,
1371 "code": cleanCode(tResult.test_code),
1372 "code_system_name": "LOINC"
1381 count = countEntities(results.result);
1386 for (let i in results.result) {
1387 theone[i] = populateResult(results.result[i]);
1388 many.results.push(theone[i]);
1390 } else if (count !== 0) {
1391 theone = populateResult(results.result);
1392 many.results.push(theone);
1394 rs.results = Object.assign(resultSet);
1395 rs.results.results = Object.assign(many.results);
1399 function getPlanOfCare(pd) {
1402 let code_system_name = "";
1403 let status = "Active";
1407 let planType = "observation";
1408 switch (pd.care_plan_type) {
1409 case 'plan_of_care':
1410 planType = "observation"; // mood code INT. sets code in template
1412 case 'test_or_order':
1413 planType = "observation"; // mood code RQO
1416 planType = "procedure";
1418 case 'appointments':
1419 planType = "encounter";
1421 case 'instructions':
1422 planType = "instructions";
1425 planType = ""; // for now exclude. unsure how to template.
1428 planType = "observation";
1430 if (pd.code_type === 'RXCUI') {
1431 pd.code_type = 'RXNORM';
1433 if (pd.code_type === 'RXNORM') {
1434 planType = "substanceAdministration";
1436 if (planType === "") {
1440 for (let key in all.encounter_list.encounter) {
1441 // skip loop if the property is from prototype
1442 if (!Object.prototype.hasOwnProperty.call(all.encounter_list.encounter, key)) {
1445 encounter = all.encounter_list.encounter[key];
1446 if (pd.encounter == encounter.encounter_id) {
1448 name = encounter.encounter_diagnosis.text;
1449 code = cleanCode(encounter.encounter_diagnosis.code);
1450 code_system_name = encounter.encounter_diagnosis.code_type;
1451 status = encounter.encounter_diagnosis.status;
1452 encounter = all.encounter_list.encounter[key]; // to be sure.
1458 if (all.encounter_list && all.encounter_list.encounter && all.encounter_list.encounter.encounter_diagnosis) {
1459 value = all.encounter_list.encounter.encounter_diagnosis;
1462 code = cleanCode(value.code);
1463 code_system_name = value.code_type;
1464 status = value.status;
1465 encounter = all.encounter_list.encounter;
1470 "name": pd.code_text || "",
1471 "code": cleanCode(pd.code) || "",
1472 "code_system_name": pd.code_type || "SNOMED CT"
1475 "identifier": pd.sha_extension,
1476 "extension": pd.extension || ""
1479 "code": cleanCode(pd.code) || "",
1480 "name": safeTrim(pd.description) || ""
1484 "date": fDate(pd.date),
1490 "code": cleanCode(pd.status)
1492 "author": populateAuthorFromAuthorContainer(pd),
1495 "identifier": "2.16.840.1.113883.4.6",
1496 "extension": encounter.npi || ""
1499 "name": encounter.physician_type,
1500 "code": cleanCode(encounter.physician_type_code),
1501 "code_system_name": "SNOMED CT"
1505 "last": encounter.lname || "",
1506 "first": encounter.fname || ""
1511 "number": encounter.work_phone,
1512 "type": "work place"
1517 "name": encounter.location,
1519 "name": encounter.location_details,
1521 "code_system_name": "HealthcareServiceLocation"
1524 "street_lines": [encounter.facility_address],
1525 "city": encounter.facility_city,
1526 "state": encounter.facility_state,
1527 "zip": encounter.facility_zip,
1528 "country": encounter.facility_country || "US"
1532 "number": encounter.facility_phone,
1533 "type": "work place"
1539 "identifier": encounter.sha_extension,
1540 "extension": encounter.extension
1545 "code_system_name": code_system_name
1549 "date": fDate(encounter.date),
1554 "reason": encounter.encounter_reason
1556 "name": safeTrim(pd.description),
1557 "mood_code": pd.moodCode
1561 function getGoals(pd) {
1564 "name": pd.code_text !== "NULL" ? pd.code_text : "",
1565 "code": cleanCode(pd.code) || "",
1566 "code_system_name": pd.code_type || ""
1569 "identifier": pd.sha_extension,
1570 "extension": pd.extension,
1574 "date": fDate(pd.date),
1578 "type": "observation",
1580 "code": "active", //cleanCode(pd.status)
1582 "author": populateAuthorFromAuthorContainer(pd),
1583 "name": pd.description
1587 function getFunctionalStatus(pd) {
1588 let functionalStatusAuthor = {
1590 "name": all.author.physician_type || '',
1591 "code": all.author.physician_type_code || '',
1592 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
1596 "date": authorDateTime,
1602 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
1603 "extension": all.author.npi ? all.author.npi : 'NI'
1608 "last": all.author.lname,
1609 "first": all.author.fname
1616 "root": oidFacility || "2.16.840.1.113883.4.6",
1617 "extension": npiFacility || ""
1621 all.encounter_provider.facility_name
1628 "status": "completed",
1629 "author": functionalStatusAuthor,
1631 "identifier": "9a6d1bac-17d3-4195-89a4-1121bc809000",
1632 "extension": pd.extension || '',
1637 "name": pd.code_text !== "NULL" ? safeTrim(pd.code_text) : "",
1638 "code": cleanCode(pd.code) || "",
1639 "code_system_name": pd.code_type || "SNOMED-CT"
1642 "identifier": "9a6d1bac-17d3-4195-89a4-1121bc8090ab",
1643 "extension": pd.extension || '',
1647 "date": fDate(pd.date),
1651 "status": "completed",
1652 "author": functionalStatusAuthor
1657 function getMentalStatus(pd) {
1660 "name": pd.code_text !== "NULL" ? pd.code_text : "",
1661 "code": cleanCode(pd.code) || "",
1662 "code_system_name": pd.code_type || ""
1665 "identifier": "9a6d1bac-17d3-4195-89a4-1121bc809ccc",
1666 "extension": pd.extension,
1668 "note": safeTrim(pd.description),
1670 "low": templateDate(pd.date, "day")
1671 //"high": templateDate(pd.date, "day")
1675 "name": all.author.physician_type || '',
1676 "code": all.author.physician_type_code || '',
1677 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
1681 "date": authorDateTime,
1687 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
1688 "extension": all.author.npi ? all.author.npi : 'NI'
1693 "last": all.author.lname,
1694 "first": all.author.fname
1701 "root": oidFacility || "2.16.840.1.113883.4.6",
1702 "extension": npiFacility || ""
1706 all.encounter_provider.facility_name
1714 function getAssessments(pd) {
1716 "description": safeTrim(pd.description),
1717 "author": populateAuthorFromAuthorContainer(pd)
1721 function getHealthConcerns(pd) {
1724 let problems = [], problem = {};
1725 if (countEntities(pd.issues.issue_uuid) !== 0) {
1726 for (let key in pd.issues.issue_uuid) {
1727 issue_uuid = pd.issues.issue_uuid[key];
1733 "identifier": issue_uuid
1736 problems.push(problem);
1740 if (pd.issues.issue_uuid) {
1743 "identifier": pd.issues.issue_uuid
1746 problems.push(problem);
1750 // todo need to make array of health concerns
1752 "text": safeTrim(pd.text),
1754 "name": pd.code_text || "",
1755 "code": cleanCode(pd.code) || "",
1756 "code_system_name": pd.code_type || "SNOMED CT"
1758 "author": populateAuthorFromAuthorContainer(pd),
1760 "identifier": pd.sha_extension,
1761 "extension": pd.extension,
1767 function getReferralReason(pd) {
1769 "reason": safeTrim(pd.text),
1770 "author": populateAuthorFromAuthorContainer(pd)
1774 function populateVital(pd) {
1777 "identifier": pd.sha_extension,
1778 "extension": pd.extension
1780 "status": "completed",
1783 "date": fDate(pd.effectivetime),
1787 // our list of vitals per organizer.
1790 "identifier": pd.sha_extension,
1791 "extension": pd.extension_bps
1794 "name": "Blood Pressure Systolic",
1796 "code_system_name": "LOINC"
1798 "status": "completed",
1801 "date": fDate(pd.effectivetime),
1805 "interpretations": ["Normal"],
1806 "value": parseFloat(pd.bps) || pd.bps,
1808 "author": populateAuthorFromAuthorContainer(pd),
1811 "identifier": pd.sha_extension,
1812 "extension": pd.extension_bpd
1815 "name": "Blood Pressure Diastolic",
1817 "code_system_name": "LOINC"
1819 "status": "completed",
1822 "date": fDate(pd.effectivetime),
1826 "interpretations": ["Normal"],
1827 "value": parseFloat(pd.bpd) || pd.bpd,
1829 "author": populateAuthorFromAuthorContainer(pd),
1832 "identifier": pd.sha_extension,
1833 "extension": pd.extension_height
1838 "code_system_name": "LOINC"
1840 "status": "completed",
1843 "date": fDate(pd.effectivetime),
1847 "interpretations": ["Normal"],
1848 "value": parseFloat(pd.height) || pd.height,
1849 "unit": pd.unit_height,
1850 "author": populateAuthorFromAuthorContainer(pd),
1853 "identifier": pd.sha_extension,
1854 "extension": pd.extension_weight
1857 "name": "Weight Measured",
1859 "code_system_name": "LOINC"
1861 "status": "completed",
1864 "date": fDate(pd.effectivetime),
1868 "interpretations": ["Normal"],
1869 "value": parseFloat(pd.weight) || "",
1870 "unit": pd.unit_weight,
1871 "author": populateAuthorFromAuthorContainer(pd),
1874 "identifier": pd.sha_extension,
1875 "extension": pd.extension_BMI
1878 "name": "BMI (Body Mass Index)",
1880 "code_system_name": "LOINC"
1882 "status": "completed",
1885 "date": fDate(pd.effectivetime),
1889 "interpretations": [pd.BMI_status == 'Overweight' ? 'High' : pd.BMI_status == 'Overweight' ? 'Low' : 'Normal'],
1890 "value": parseFloat(pd.BMI) || "",
1892 "author": populateAuthorFromAuthorContainer(pd),
1895 "identifier": pd.sha_extension,
1896 "extension": pd.extension_pulse
1899 "name": "Heart Rate",
1901 "code_system_name": "LOINC"
1903 "status": "completed",
1906 "date": fDate(pd.effectivetime),
1910 "interpretations": ["Normal"],
1911 "value": parseFloat(pd.pulse) || "",
1913 "author": populateAuthorFromAuthorContainer(pd),
1916 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.2",
1917 "extension": pd.extension_breath
1920 "name": "Respiratory Rate",
1922 "code_system_name": "LOINC"
1924 "status": "completed",
1927 "date": fDate(pd.effectivetime),
1931 "interpretations": ["Normal"],
1932 "value": parseFloat(pd.breath) || "",
1934 "author": populateAuthorFromAuthorContainer(pd),
1937 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.3",
1938 "extension": pd.extension_temperature
1941 "name": "Body Temperature",
1943 "code_system_name": "LOINC"
1945 "status": "completed",
1948 "date": fDate(pd.effectivetime),
1952 "interpretations": ["Normal"],
1953 "value": parseFloat(pd.temperature) || "",
1954 "unit": pd.unit_temperature,
1955 "author": populateAuthorFromAuthorContainer(pd),
1958 "identifier": pd.sha_extension,
1959 "extension": pd.extension_oxygen_saturation
1962 "name": "O2 % BldC Oximetry",
1964 "code_system_name": "LOINC"
1966 "status": "completed",
1969 "date": fDate(pd.effectivetime),
1973 "interpretations": ["Normal"],
1974 "value": parseFloat(pd.oxygen_saturation) || "",
1976 "author": populateAuthorFromAuthorContainer(pd),
1979 "identifier": pd.sha_extension,
1980 "extension": pd.extension_ped_weight_height
1982 "vital": { // --------------------------------------------------------------------------------
1983 "name": "Weight for Height Percentile",
1985 "code_system_name": "LOINC"
1987 "status": "completed",
1990 "date": fDate(pd.effectivetime),
1994 "interpretations": ["Normal"],
1995 "value": parseFloat(pd.ped_weight_height) || "",
1997 "author": populateAuthorFromAuthorContainer(pd),
2000 "identifier": pd.sha_extension,
2001 "extension": pd.extension_inhaled_oxygen_concentration
2004 "name": "Inhaled Oxygen Concentration",
2006 "code_system_name": "LOINC"
2008 "status": "completed",
2011 "date": fDate(pd.effectivetime),
2015 "interpretations": ["Normal"],
2016 "value": parseFloat(pd.inhaled_oxygen_concentration) || "",
2018 "author": populateAuthorFromAuthorContainer(pd),
2021 "identifier": pd.sha_extension,
2022 "extension": pd.extension_ped_bmi
2025 "name": "BMI Percentile",
2027 "code_system_name": "LOINC"
2029 "status": "completed",
2032 "date": fDate(pd.effectivetime),
2036 "interpretations": ["Normal"],
2037 "value": parseFloat(pd.ped_bmi) || "",
2039 "author": populateAuthorFromAuthorContainer(pd),
2042 "identifier": pd.sha_extension,
2043 "extension": pd.extension_ped_head_circ
2046 "name": "Head Occipital-frontal Circumference Percentile",
2048 "code_system_name": "LOINC"
2050 "status": "completed",
2053 "date": fDate(pd.effectivetime),
2057 "interpretations": ["Normal"],
2058 "value": parseFloat(pd.ped_head_circ) || "",
2060 "author": populateAuthorFromAuthorContainer(pd),
2066 function populateSocialHistory(pd) {
2069 "low": templateDate(pd.date, "day")
2070 //"high": templateDate(pd.date, "day")
2073 "identifier": pd.sha_extension,
2074 "extension": pd.extension
2079 "element": pd.element,
2080 "value": pd.description,
2081 "gender": all.patient.gender,
2084 "name": pd.author.physician_type || '',
2085 "code": pd.author.physician_type_code || '',
2086 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
2090 "date": fDate(pd.author.time),
2096 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
2097 "extension": pd.author.npi ? pd.author.npi : 'NI'
2102 "last": pd.author.lname,
2103 "first": pd.author.fname
2110 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
2111 "extension": pd.author.facility_npi || "NI"
2115 pd.author.facility_name
2120 , "gender_author": {
2122 "name": all.patient.author.physician_type || '',
2123 "code": all.patient.author.physician_type_code || '',
2124 "code_system": all.patient.author.physician_type_system, "code_system_name": all.patient.author.physician_type_system_name
2128 "date": fDate(all.patient.author.time),
2134 "identifier": all.patient.author.npi ? "2.16.840.1.113883.4.6" : all.patient.author.id,
2135 "extension": all.patient.author.npi ? all.patient.author.npi : 'NI'
2140 "last": all.patient.author.lname,
2141 "first": all.patient.author.fname
2148 "root": all.patient.author.facility_oid || "2.16.840.1.113883.4.6",
2149 "extension": all.patient.author.facility_npi || "NI"
2153 all.patient.author.facility_name
2161 function populateImmunization(pd) {
2165 "date": fDate(pd.administered_on),
2170 "identifier": pd.sha_extension,
2171 "extension": pd.extension || ""
2173 "status": "complete",
2176 "name": pd.code_text,
2177 "code": cleanCode(pd.cvx_code),
2178 "code_system_name": "CVX"
2179 /*"translations": [{
2182 "code_system_name": "CVX"
2190 "name": pd.route_of_administration,
2191 "code": cleanCode(pd.route_code) || "",
2192 "code_system_name": "Medication Route FDA"
2201 "identifier": "2.16.840.1.113883.4.6",
2202 "extension": pd.npi || ""
2209 "street_lines": [pd.address],
2217 "identifier": "2.16.840.1.113883.4.6",
2218 "extension": npiFacility || ""
2220 "name": [pd.facility_name]
2225 "name": "immunization education",
2226 "code": "171044003",
2227 "code_system_name": "SNOMED CT"
2229 "free_text": "Needs Attention for more data."
2233 "name": pd.author.physician_type || '',
2234 "code": pd.author.physician_type_code || '',
2235 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
2239 "date": fDate(pd.author.time),
2245 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
2246 "extension": pd.author.npi ? pd.author.npi : 'NI'
2251 "last": pd.author.lname,
2252 "first": pd.author.fname
2259 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
2260 "extension": pd.author.facility_npi || "NI"
2264 pd.author.facility_name
2272 function populatePayer(pd) {
2275 "identifier": "1fe2cdd0-7aad-11db-9fe1-0800200c9a66"
2279 "identifier": "3e676a50-7aac-11db-9fe1-0800200c9a66"
2283 "code_system_name": "HL7 RoleCode"
2288 "code_system_name": "HL7 RoleCode"
2292 "identifier": "2.16.840.1.113883.19"
2295 "street_lines": ["123 Insurance Road"],
2296 "city": "Blue Bell",
2303 "number": "(781)555-1515",
2304 "type": "work place"
2307 "name": ["Good Health Insurance"],
2309 "street_lines": ["123 Insurance Road"],
2310 "city": "Blue Bell",
2317 "number": "(781)555-1515",
2318 "type": "work place"
2323 "code_system_name": "HL7 RoleCode"
2331 "code_system_name": "HL7 Role"
2334 "identifier": "329fcdf0-7ab3-11db-9fe1-0800200c9a66"
2338 "middle": ["Frankie"],
2343 "street_lines": ["17 Daws Rd."],
2344 "city": "Blue Bell",
2348 "use": "primary home"
2351 "number": "(781)555-1212",
2352 "type": "primary home"
2359 "code_system_name": "HL7 Role"
2363 "identifier": "14d4a520-7aae-11db-9fe1-0800200c9a66",
2364 "extension": "1138345"
2367 "street_lines": ["17 Daws Rd."],
2368 "city": "Blue Bell",
2372 "use": "primary home"
2377 "code_system_name": "HL7 Role"
2390 "identifier": "2.16.840.1.113883.19",
2391 "extension": "1138345"
2394 "street_lines": ["17 Daws Rd."],
2395 "city": "Blue Bell",
2399 "use": "primary home"
2405 "identifier": "f4dce790-8328-11db-9fe1-0800200c9a66"
2409 "name": "Colonoscopy",
2411 "code_system_name": "SNOMED CT"
2418 function populateNote(pd) {
2422 "date": fDate(pd.date),
2427 code_system: "2.16.840.1.113883.6.1",
2428 code_system_name: "LOINC",
2429 code: cleanCode(pd.code),
2430 name: pd.code_text || ""
2432 "author": populateAuthorFromAuthorContainer(pd),
2433 "note": safeTrim(pd.description),
2437 function populateParticipant(participant) {
2440 "prefix": participant.prefix || "",
2441 "suffix": participant.suffix || "",
2442 "middle": [participant.mname] || "",
2443 "last": participant.lname || "",
2444 "first": participant.fname || ""
2446 "typeCode": participant.type || "",
2447 "classCode": "ASSIGNED",
2449 "name": participant.organization_taxonomy_description || "",
2450 "code": cleanCode(participant.organization_taxonomy) || "",
2451 "code_system": "2.16.840.1.113883.6.101",
2452 "code_system_name": "NUCC Health Care Provider Taxonomy"
2455 "identifier": participant.organization_npi ? "2.16.840.1.113883.4.6" : participant.organization_id,
2456 "extension": participant.organization_npi ? participant.organization_npi : ''
2460 "date": participant.date_time,
2466 "number": participant.phonew1 || "",
2475 "city": participant.city,
2476 "state": participant.state,
2477 "zip": participant.postalCode,
2478 "country": participant.country || "US",
2479 "use": participant.address_use || "WP"
2485 function populateHeader(pd) {
2486 // default doc type ToC CCD
2487 let name = "Summarization of Episode Note";
2488 let docCode = "34133-9";
2489 let docOid = "2.16.840.1.113883.10.20.22.1.2";
2490 if (pd.doc_type == 'referral') {
2491 name = "Referral Note";
2492 docCode = "57133-1";
2493 docOid = "2.16.840.1.113883.10.20.22.1.14";
2496 if (pd.doc_type == 'unstructured') {
2497 name = "Patient Documents";
2498 docCode = "34133-9";
2499 docOid = "2.16.840.1.113883.10.20.22.1.10";
2505 "identifier": oidFacility,
2506 "extension": "123456"
2512 "code_system_name": "LOINC"
2516 "extension": "2015-08-01"
2521 "date": fDate(pd.created_time_timezone),
2527 "name": all.author.physician_type || '',
2528 "code": all.author.physician_type_code || '',
2529 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
2533 "date": authorDateTime,
2539 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
2540 "extension": all.author.npi ? all.author.npi : 'NI'
2545 "last": all.author.lname,
2546 "first": all.author.fname
2552 all.author.streetAddressLine
2554 "city": all.author.city,
2555 "state": all.author.state,
2556 "zip": all.author.postalCode,
2557 "country": all.author.country || "US",
2563 "number": all.author.telecom || "",
2571 "root": oidFacility || "2.16.840.1.113883.4.6",
2572 "extension": npiFacility || ""
2576 all.encounter_provider.facility_name
2581 all.encounter_provider.facility_street
2583 "city": all.encounter_provider.facility_city,
2584 "state": all.encounter_provider.facility_state,
2585 "zip": all.encounter_provider.facility_postal_code,
2586 "country": all.encounter_provider.facility_country_code || "US",
2592 "number": all.encounter_provider.facility_phone,
2593 "type": "work primary"
2602 "root": "2.16.840.1.113883.4.6",
2603 "extension": npiFacility || ""
2607 pd.custodian.organization || pd.custodian.name
2612 pd.custodian.streetAddressLine
2614 "city": pd.custodian.city,
2615 "state": pd.custodian.state,
2616 "zip": pd.custodian.postalCode,
2617 "country": pd.custodian.country || "US"
2622 "number": pd.custodian.telecom,
2623 "type": "work primary"
2627 "information_recipient": {
2629 "prefix": pd.information_recipient.prefix || "",
2630 "suffix": pd.information_recipient.suffix || "",
2631 "middle": [pd.information_recipient.mname] || "",
2632 "last": pd.information_recipient.lname || "",
2633 "first": pd.information_recipient.fname || ""
2636 "name": pd.information_recipient.organization || "org"
2640 let participants = [];
2641 let docParticipants = pd.document_participants || {participant: []};
2644 count = countEntities(docParticipants.participant);
2649 participants = [populateParticipant(docParticipants.participant)];
2651 // grab the values of our object
2652 participants = Object.values(docParticipants.participant).filter(pcpt => pcpt.type).map(pcpt => populateParticipant(pcpt));
2654 if (participants.length) {
2655 head.participants = participants;
2658 if (countEntities(all.encounter_list.encounter) === 1) {
2659 let primary_care_provider = pd.primary_care_provider || {provider: {}};
2660 head.component_of = {
2663 "identifier": oidFacility || "",
2664 "extension": "PT-" + (pd.patient.id || "")
2668 "name": pd.primary_diagnosis.text || "",
2669 "code": pd.primary_diagnosis.code || "",
2670 "code_system_name": pd.primary_diagnosis.code_type || ""
2674 "date": pd.primary_diagnosis.encounter_date || "",
2678 "date": pd.primary_diagnosis.encounter_end_date || "",
2682 "responsible_party": {
2683 "root": oidFacility,
2685 "last": pd.author.lname,
2686 "first": pd.author.fname
2689 "encounter_participant": {
2690 "root": oidFacility,
2692 "last": primary_care_provider.provider.lname || "",
2693 "first": primary_care_provider.provider.fname || ""
2698 pd.encounter_provider.facility_street
2700 "city": pd.encounter_provider.facility_city,
2701 "state": pd.encounter_provider.facility_state,
2702 "zip": pd.encounter_provider.facility_postal_code,
2703 "country": pd.encounter_provider.facility_country_code || "US",
2709 "number": pd.encounter_provider.facility_phone,
2710 "type": "work primary"
2720 function getMeta(pd) {
2723 "type": pd.doc_type,
2726 "identifier": oidFacility || "NI",
2727 "extension": "TT988"
2730 "confidentiality": "Normal",
2732 "identifier": oidFacility || "NI",
2733 "extension": "sTT988"
2740 / * function generateCcda
2741 /* The main document builder
2742 /* pd array the xml parsed array of data sent from CCM.
2744 function generateCcda(pd) {
2751 let primary_care_provider = all.primary_care_provider || {};
2752 npiProvider = primary_care_provider.provider ? primary_care_provider.provider.npi : "NI";
2753 oidFacility = all.encounter_provider.facility_oid ? all.encounter_provider.facility_oid : "2.16.840.1.113883.19.5.99999.1";
2754 npiFacility = all.encounter_provider.facility_npi;
2755 webRoot = all.serverRoot;
2756 documentLocation = all.document_location;
2758 authorDateTime = pd.created_time_timezone;
2759 if (pd.author.time.length > 7) {
2760 authorDateTime = pd.author.time;
2761 } else if (all.encounter_list && all.encounter_list.encounter) {
2762 if (countEntities(all.encounter_list.encounter) === 1) {
2763 authorDateTime = all.encounter_list.encounter.date;
2765 authorDateTime = all.encounter_list.encounter[0].date;
2769 authorDateTime = fDate(authorDateTime);
2771 let demographic = populateDemographic(pd.patient, pd.guardian, pd);
2772 // This populates documentationOf. We are using providerOrganization also.
2773 if (pd.primary_care_provider) {
2774 Object.assign(demographic, populateProviders(pd));
2776 data.demographics = Object.assign(demographic);
2780 encs.encounters = [];
2782 count = countEntities(pd.encounter_list.encounter);
2787 for (let i in pd.encounter_list.encounter) {
2788 enc[i] = populateEncounter(pd.encounter_list.encounter[i]);
2789 encs.encounters.push(enc[i]);
2791 } else if (count !== 0) {
2792 enc = populateEncounter(pd.encounter_list.encounter);
2793 encs.encounters.push(enc);
2796 data.encounters = Object.assign(encs.encounters);
2803 count = countEntities(pd.history_physical.vitals_list.vitals);
2808 for (let i in pd.history_physical.vitals_list.vitals) {
2809 vitals[i] = populateVital(pd.history_physical.vitals_list.vitals[i]);
2810 vitals.vitals.push(vitals[i]);
2812 } else if (count !== 0) {
2813 vital = populateVital(pd.history_physical.vitals_list.vitals);
2814 vitals.vitals.push(vital);
2817 data.vitals = Object.assign(vitals.vitals);
2822 meds.medications = [];
2824 count = countEntities(pd.medications.medication);
2829 for (let i in pd.medications.medication) {
2830 m[i] = populateMedication(pd.medications.medication[i]);
2831 meds.medications.push(m[i]);
2833 } else if (count !== 0) {
2834 m = populateMedication(pd.medications.medication);
2835 meds.medications.push(m);
2838 data.medications = Object.assign(meds.medications);
2843 allergies.allergies = [];
2845 count = countEntities(pd.allergies.allergy);
2850 for (let i in pd.allergies.allergy) {
2851 allergy[i] = populateAllergy(pd.allergies.allergy[i]);
2852 allergies.allergies.push(allergy[i]);
2854 } else if (count <= 1) {
2855 allergy = populateAllergy(pd.allergies.allergy);
2856 allergies.allergies.push(allergy);
2860 data.allergies = Object.assign(allergies.allergies);
2865 problems.problems = [];
2867 count = countEntities(pd.problem_lists.problem);
2872 for (let i in pd.problem_lists.problem) {
2873 problem[i] = populateProblem(pd.problem_lists.problem[i], pd);
2874 problems.problems.push(problem[i]);
2876 } else if (count !== 0) {
2877 problem = populateProblem(pd.problem_lists.problem);
2878 problems.problems.push(problem);
2881 data.problems = Object.assign(problems.problems);
2886 many.procedures = [];
2888 count = countEntities(pd.procedures.procedure);
2893 for (let i in pd.procedures.procedure) {
2894 theone[i] = populateProcedure(pd.procedures.procedure[i]);
2895 many.procedures.push(theone[i]);
2897 } else if (count !== 0) {
2898 theone = populateProcedure(pd.procedures.procedure);
2899 many.procedures.push(theone);
2902 data.procedures = Object.assign(many.procedures);
2907 many.medical_devices = [];
2909 count = countEntities(pd.medical_devices.device);
2914 for (let i in pd.medical_devices.device) {
2915 theone[i] = populateMedicalDevice(pd.medical_devices.device[i]);
2916 many.medical_devices.push(theone[i]);
2918 } else if (count !== 0) {
2919 theone = populateMedicalDevice(pd.medical_devices.device);
2920 many.medical_devices.push(theone);
2923 data.medical_devices = Object.assign(many.medical_devices);
2927 data.results = Object.assign(getResultSet(pd.results, pd)['results']);
2930 // Referral TODO sjp I'm not happy with this.
2931 // different referral sources. 1st is dynamic with doc gen from CCM.
2932 // 2nd is the latest referral from transactions.
2933 if (pd.referral_reason[0].text !== "") {
2934 data.referral_reason = Object.assign(getReferralReason(pd.referral_reason[0], pd));
2935 } else if (pd.referral_reason[1].text !== "" && typeof pd.referral_reason[1].text !== 'undefined') {
2936 data.referral_reason = Object.assign(getReferralReason(pd.referral_reason[1], pd));
2938 data.referral_reason = {}; // leave as empty so we can get our null flavor section.
2943 many.health_concerns = [];
2945 count = countEntities(pd.health_concerns.concern);
2950 for (let i in pd.health_concerns.concern) {
2951 theone[i] = getHealthConcerns(pd.health_concerns.concern[i]);
2952 many.health_concerns.push(theone[i]);
2955 } else if (count !== 0) {
2956 theone = getHealthConcerns(pd.health_concerns.concern);
2957 many.health_concerns.push(theone);
2960 data.health_concerns = Object.assign(many.health_concerns);
2962 data.health_concerns = {"type": "act"}; // leave it as an empty section that we'll null flavor
2967 many.immunizations = [];
2969 count = countEntities(pd.immunizations.immunization);
2974 for (let i in pd.immunizations.immunization) {
2975 theone[i] = populateImmunization(pd.immunizations.immunization[i]);
2976 many.immunizations.push(theone[i]);
2978 } else if (count !== 0) {
2979 theone = populateImmunization(pd.immunizations.immunization);
2980 many.immunizations.push(theone);
2983 data.immunizations = Object.assign(many.immunizations);
2988 many.plan_of_care = [];
2990 count = countEntities(pd.planofcare.item);
2995 for (let i in pd.planofcare.item) {
2996 if (cleanCode(pd.planofcare.item[i].date) === '') {
3000 theone[i] = getPlanOfCare(pd.planofcare.item[i]);
3002 many.plan_of_care.push(theone[i]);
3005 } else if (count !== 0) {
3006 theone = getPlanOfCare(pd.planofcare.item);
3008 many.plan_of_care.push(theone);
3012 data.plan_of_care = Object.assign(many.plan_of_care);
3019 count = countEntities(pd.goals.item);
3024 for (let i in pd.goals.item) {
3025 theone[i] = getGoals(pd.goals.item[i]);
3026 many.goals.push(theone[i]);
3028 } else if (count !== 0) {
3029 theone = getGoals(pd.goals.item);
3030 many.goals.push(theone);
3033 data.goals = Object.assign(many.goals);
3038 many.clinicalNoteAssessments = [];
3040 count = countEntities(pd.clinical_notes.evaluation_note);
3045 for (let i in pd.clinical_notes.evaluation_note) {
3046 theone[i] = getAssessments(pd.clinical_notes.evaluation_note[i]);
3047 many.clinicalNoteAssessments.push(theone[i]);
3048 break; // for now only one assessment. @todo concat notes to one.
3050 } else if (count !== 0) {
3051 theone = getAssessments(pd.clinical_notes.evaluation_note);
3052 many.clinicalNoteAssessments.push(theone);
3055 data.clinicalNoteAssessments = Object.assign(many.clinicalNoteAssessments);
3058 // Functional Status.
3061 many.functional_status = [];
3063 count = countEntities(pd.functional_status.item);
3068 for (let i in pd.functional_status.item) {
3069 theone[i] = getFunctionalStatus(pd.functional_status.item[i]);
3070 many.functional_status.push(theone[i]);
3072 } else if (count !== 0) {
3073 theone = getFunctionalStatus(pd.functional_status.item);
3074 many.functional_status.push(theone);
3077 data.functional_status = Object.assign(many.functional_status);
3083 many.mental_status = [];
3085 count = countEntities(pd.mental_status.item);
3090 for (let i in pd.mental_status.item) {
3091 theone[i] = getMentalStatus(pd.mental_status.item[i]);
3092 many.mental_status.push(theone[i]);
3094 } else if (count !== 0) {
3095 theone = getMentalStatus(pd.mental_status.item);
3096 many.mental_status.push(theone);
3099 data.mental_status = Object.assign(many.mental_status);
3105 many.social_history = [];
3107 count = countEntities(pd.history_physical.social_history.history_element);
3112 for (let i in pd.history_physical.social_history.history_element) {
3114 theone[i] = populateSocialHistory(pd.history_physical.social_history.history_element[i]);
3115 many.social_history.push(theone[i]);
3117 } else if (count !== 0) {
3118 theone = populateSocialHistory(pd.history_physical.social_history.history_element);
3119 many.social_history.push(theone);
3122 data.social_history = Object.assign(many.social_history);
3125 for (let currentNote in pd.clinical_notes) {
3128 switch (pd.clinical_notes[currentNote].clinical_notes_type) {
3129 case 'evaluation_note':
3131 case 'progress_note':
3133 case 'history_physical':
3134 pd.clinical_notes[currentNote].code_text = "History and Physical";
3138 case 'general_note':
3140 case 'discharge_summary':
3142 case 'procedure_note':
3144 case 'consultation_note':
3146 case 'imaging_narrative':
3148 case 'laboratory_report_narrative':
3150 case 'pathology_report_narrative':
3156 count = countEntities(pd.clinical_notes[currentNote]);
3161 for (let i in pd.clinical_notes[currentNote]) {
3162 theone[i] = populateNote(pd.clinical_notes[currentNote]);
3163 many.push(theone[i]);
3165 } else if (count !== 0) {
3166 theone = populateNote(pd.clinical_notes[currentNote]);
3170 data[currentNote] = Object.assign(many);
3173 // Care Team and members
3174 if (pd.care_team.is_active == 'active') {
3175 data.care_team = Object.assign(populateCareTeamMembers(pd));
3178 // ------------------------------------------ End Sections ---------------------------------------- //
3180 // sections data objects
3181 doc.data = Object.assign(data);
3182 // document meta data and header objects
3183 let meta = getMeta(pd);
3184 let header = populateHeader(pd);
3185 meta.ccda_header = Object.assign(header);
3186 doc.meta = Object.assign(meta);
3188 if (pd.timezone_local_offset) {
3189 populateTimezones(doc, pd.timezone_local_offset, 0);
3192 let xml = bbg.generateCCD(doc);
3195 if (enableDebug === true) {
3196 let place = documentLocation + "/documents/temp/";
3197 if (fs.existsSync(place)) {
3198 fs.writeFile(place + "ccda.json", JSON.stringify(all, null, 4), function (err) {
3200 return console.log(err);
3203 fs.writeFile(place + "ccda.xml", xml, function (err) {
3205 return console.log(err);
3214 let unstructuredTemplate = null;
3216 function generateUnstructured(pd) {
3222 // include unstructured document type oid in header
3223 pd.doc_type = 'unstructured';
3225 let primary_care_provider = all.primary_care_provider || {};
3226 npiProvider = primary_care_provider.provider ? primary_care_provider.provider.npi : "NI";
3227 oidFacility = all.encounter_provider.facility_oid ? all.encounter_provider.facility_oid : "2.16.840.1.113883.19.5.99999.1";
3228 npiFacility = all.encounter_provider.facility_npi || "NI";
3229 webRoot = all.serverRoot;
3230 documentLocation = all.document_location;
3231 authorDateTime = pd.created_time_timezone;
3232 if (pd.author.time.length > 7) {
3233 authorDateTime = pd.author.time;
3234 } else if (all.encounter_list && all.encounter_list.encounter) {
3235 if (countEntities(all.encounter_list.encounter) === 1) {
3236 authorDateTime = all.encounter_list.encounter.date;
3238 authorDateTime = all.encounter_list.encounter[0].date;
3241 authorDateTime = fDate(authorDateTime);
3242 // Demographics is needed in unstructured
3243 let demographic = populateDemographic(pd.patient, pd.guardian, pd);
3244 data.demographics = Object.assign(demographic);
3246 if (pd.primary_care_provider) {
3247 Object.assign(demographic, populateProviders(pd));
3249 doc.data = Object.assign(data);
3251 // document meta data and header objects
3252 let meta = getMeta(pd);
3253 let header = populateHeader(pd);
3254 meta.ccda_header = Object.assign(header);
3255 doc.meta = Object.assign(meta);
3257 // set TZ offset for moment
3258 if (pd.timezone_local_offset) {
3259 populateTimezones(doc, pd.timezone_local_offset, 0);
3262 let xml = bbg.generateCCD(doc);
3263 unstructuredTemplate = unstructuredTemplate.trim();
3264 xml = xml.replace(/<\/ClinicalDocument>/g, unstructuredTemplate);
3265 xml += "</ClinicalDocument>" + "\n";
3268 if (enableDebug === true) {
3269 let place = documentLocation + "/documents/temp/";
3270 if (fs.existsSync(place)) {
3271 fs.writeFile(place + "unstructured.xml", xml, function (err) {
3273 return console.log(err);
3282 function processConnection(connection) {
3283 conn = connection; // make it global
3284 let remoteAddress = conn.remoteAddress + ':' + conn.remotePort;
3285 conn.setEncoding('utf8');
3286 //console.log('server remote address ', remoteAddress);
3287 let xml_complete = "";
3289 function eventData(xml) {
3290 xml_complete = xml.toString();
3291 // ensure we have an array start and end
3292 if (xml_complete.match(/^<CCDA/g) && xml_complete.match(/<\/CCDA>$/g)) {
3295 /* eslint-disable-next-line no-control-regex */
3296 xml_complete = xml_complete.replace(/(\u000b\u001c)/gm, "").trim();
3297 xml_complete = xml_complete.replace(/\t\s+/g, " ").trim();
3298 // convert xml data set for document to json array
3299 to_json(xml_complete, function (error, data) {
3302 "toJson error: " + error + "Len: " + xml_complete.length
3304 return "ERROR: Failed json build";
3306 let unstructured = "";
3307 let isUnstruturedData = !!data.CCDA.patient_files;
3308 // extract unstructured documents file component templates. One per file.
3309 if (isUnstruturedData) {
3310 unstructuredTemplate = xml_complete.substring(
3311 xml_complete.lastIndexOf("<patient_files>") + 15,
3312 xml_complete.lastIndexOf("</patient_files>")
3315 // create doc_type document i.e. CCD Referral etc.
3316 if (data.CCDA.doc_type !== "unstructured") {
3317 doc = generateCcda(data.CCDA);
3318 if (data.CCDA.xslUrl) {
3319 xslUrl = data.CCDA.xslUrl || "";
3321 doc = headReplace(doc, xslUrl);
3323 unstructured = generateUnstructured(data.CCDA);
3324 if (data.CCDA.xslUrl) {
3325 xslUrl = data.CCDA.xslUrl || "";
3327 doc = headReplace(unstructured, xslUrl);
3328 // combine the two documents to send back all at once.
3329 doc += unstructured;
3331 // auto build an Unstructured document of supplied embedded files.
3333 data.CCDA.doc_type !== "unstructured" &&
3336 unstructured = generateUnstructured(data.CCDA);
3337 unstructured = headReplace(unstructured, xslUrl);
3338 // combine the two documents to send back all at once.
3339 doc += unstructured;
3342 // send results back to eagerly awaiting CCM for disposal.
3345 /* eslint-disable-next-line no-control-regex */
3346 .replace(/(\u000b\u001c|\r)/gm, "")
3349 let numChunks = Math.ceil(doc.length / 1024);
3350 for (let i = 0, o = 0; i < numChunks; ++i, o += 1024) {
3351 chunk = doc.substring(o, o + 1024);
3354 conn.write(String.fromCharCode(28) + "\r\r" + "");
3359 function eventCloseConn() {
3360 //console.log('connection from %s closed', remoteAddress);
3363 function eventErrorConn(err) {
3364 console.log('Connection %s error: %s', remoteAddress, err.message);
3365 console.log(err.stack);
3369 // Connection Events //
3370 // CCM will send one File Separator characters to mark end of array.
3371 let received = new DataStack(String.fromCharCode(28));
3372 conn.on("data", data => {
3373 received.push(data);
3374 while (!received.endOfCcda() && data.length > 0) {
3376 eventData(received.returnData());
3380 conn.once('close', eventCloseConn);
3381 conn.on('error', eventErrorConn);
3384 function setUp(server) {
3385 server.on('connection', processConnection);
3386 server.listen(6661, '127.0.0.1', function () { // never change port!
3387 //console.log('server listening to ', server.address());
3391 // start up listener for requests from CCM or others.
3394 /* ---------------------------------For future use in header. Do not remove!-------------------------------------------- */
3398 "identifier": "2.16.840.1.113883.4.6",
3399 "extension": "999999943252"
3404 "last": pd.data_enterer.lname,
3405 "first": pd.data_enterer.fname
3411 pd.data_enterer.streetAddressLine
3413 "city": pd.data_enterer.city,
3414 "state": pd.data_enterer.state,
3415 "zip": pd.data_enterer.postalCode,
3416 "country": pd.data_enterer.country
3421 "number": pd.data_enterer.telecom,
3422 "type": "work place"
3429 "identifier": "2.16.840.1.113883.19.5",
3430 "extension": "KP00017"
3435 "last": pd.informer.lname || "",
3436 "first": pd.informer.fname || ""
3442 pd.informer.streetAddressLine || ""
3444 "city": pd.informer.city,
3445 "state": pd.informer.state,
3446 "zip": pd.informer.postalCode,
3447 "country": pd.informer.country
3452 "number": pd.informer.telecom || "",
3453 "type": "work place"
3457 /*"service_event": {
3461 "code_system_name": "SNOMED CT"
3465 "date": "2021-03-11",
3469 "date": pd.created_time,
3479 "identifier": "2.16.840.1.113883.4.6",
3480 "extension": npiProvider
3485 "last": pd.information_recipient.lname || "DAH",
3486 "first": pd.information_recipient.fname || "DAH"
3492 pd.information_recipient.streetAddressLine
3494 "city": pd.information_recipient.city,
3495 "state": pd.information_recipient.state,
3496 "zip": pd.information_recipient.postalCode,
3497 "country": pd.information_recipient.country || "US"
3502 "number": pd.information_recipient.telecom,
3503 "type": "work place"
3510 "identifier": "2.16.840.1.113883.19.5.9999.1393"
3514 pd.encounter_provider.facility_name
3519 pd.encounter_provider.facility_street
3521 "city": pd.encounter_provider.facility_city,
3522 "state": pd.encounter_provider.facility_state,
3523 "zip": pd.encounter_provider.facility_postal_code,
3524 "country": pd.encounter_provider.facility_country_code || "US"
3529 "number": pd.encounter_provider.facility_phone,
3530 "type": "primary work"
3539 "code_system_name": "Provider Codes"
3545 "name": "Primary Performer",
3547 "code_system_name": "Provider Role"