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').DataStack;
20 const cleanCode = require('./utils/clean-code/clean-code').cleanCode;
21 const safeTrim = require('./utils/safe-trim/safe-trim').safeTrim;
23 var conn = ''; // make our connection scope global to script
29 var authorDateTime = '';
30 var documentLocation = '';
32 // do a recursive descent transformation of the node object populating the timezone offset value if we have
33 // a precision property (inside a date) with the value of timezone.
34 function populateTimezones(node, tzOffset, depthCheck) {
35 if (!node || typeof node !== 'object') {
38 // we should NEVER go farther than 25 recursive loops down in our heirarchy, if we do it means we have an infinite loop
39 if (depthCheck > 25) {
40 console.error("Max depth traversal reached. Potential infinite loop. Breaking out of loop")
44 if (Object.prototype.hasOwnProperty.call(node, 'precision') && node.precision == 'tz' && !Object.prototype.hasOwnProperty.call(node, 'timezoneOffset')) {
45 node.timezoneOffset = tzOffset;
47 for (const [key, value] of Object.entries(node)) {
48 node[key] = populateTimezones(value, tzOffset, depthCheck + 1);
54 function fDate(str, lim8 = false) {
57 let rtn = str.substring(0, 8);
60 if (Number(str) === 0) {
61 return (new Date()).toISOString();
63 if (str.length === 1 || str === "0000-00-00") return (new Date()).toISOString();
64 if (str.length === 8 || (str.length === 14 && (1 * str.substring(12, 14)) === 0)) {
65 return [str.slice(0, 4), str.slice(4, 6), str.slice(6, 8)].join('-');
66 } else if (str.length === 10 && (1 * str.substring(0, 2)) <= 12) {
67 // case mm/dd/yyyy or mm-dd-yyyy
68 return [str.slice(6, 10), str.slice(0, 2), str.slice(3, 5)].join('-');
69 } else if (str.length === 17) {
71 str = [str[0].slice(0, 4), str[0].slice(4, 6), str[0].slice(6, 8)].join('-') + ' ' + str[1];
73 } else if (str.length === 19 && (str.substring(14, 15)) == '-') {
74 let strZone = str.split('-');
75 let strDate = [strZone[0].substring(0, 4), strZone[0].substring(4, 6), strZone[0].substring(6, 8)].join('-');
76 let strTime = [str.substring(8, 10), str.substring(10, 12), str.substring(12, 14)].join(':');
78 let str1 = strDate + ' ' + strTime + '-' + strZone[1];
84 function getPrecision(str) {
88 if (Number(str) === 0) {
94 if (str.length > 12) {
97 if (str.length > 23) {
104 function templateDate(date, precision) {
105 return {'date': fDate(date), 'precision': precision}
108 function isOne(who) {
110 if (who !== null && typeof who === 'object') {
111 return (Object.prototype.hasOwnProperty.call(who, 'npi')
112 || Object.prototype.hasOwnProperty.call(who, 'code')
113 || Object.prototype.hasOwnProperty.call(who, 'extension')
114 || Object.prototype.hasOwnProperty.call(who, 'id')
115 || Object.prototype.hasOwnProperty.call(who, 'date')
116 || Object.prototype.hasOwnProperty.call(who, 'use')
117 || Object.prototype.hasOwnProperty.call(who, 'type')
118 ) ? 1 : Object.keys(who).length;
126 function headReplace(content, xslUrl = "") {
129 if (typeof xslUrl == "string" && xslUrl.trim() != "") {
133 let r = '<?xml version="1.0" encoding="UTF-8"?>' + "\n" +
134 '<?xml-stylesheet type="text/xsl" href="' + xsl + '"?>';
135 r += "\n" + content.substring(content.search(/<ClinicalDocument/i));
139 function fetchPreviousAddresses(pd) {
140 let addressArray = [];
141 let pa = pd.previous_addresses.address;
142 let streetLine = [pd.street[0]];
143 if (pd.street[1].length > 0) {
144 streetLine = [pd.street[0], pd.street[1]];
148 "street_lines": streetLine,
151 "zip": pd.postalCode,
152 "country": pd.country || "US",
154 // use current date for current residence
161 let count = isOne(pa);
162 // how do we ever get here where we just have one object?
164 streetLine = [pa.street[0]];
165 if (pa.street[1].length > 0) {
166 streetLine = [pa.street[0], pa.street[1]];
170 "street_lines": streetLine,
173 "zip": pa.postalCode,
174 "country": pa.country || "US",
177 "date": fDate(pa.period_start),
181 "date": fDate(pa.period_end) || fDate(""),
186 } else if (count > 1) {
188 streetLine = [pa[i].street[0]];
189 if (pa[i].street[1].length > 0) {
190 streetLine = [pa[i].street[0], pa[i].street[1]];
194 "street_lines": streetLine,
196 "state": pa[i].state,
197 "zip": pa[i].postalCode,
198 "country": pa[i].country || "US",
201 "date": fDate(pa[i].period_start),
205 "date": fDate(pa[i].period_end) || fDate(""),
215 function populateDemographic(pd, g) {
219 const names = g.display_name.split(' ');
220 if (names.length === 2) {
224 if (names.length === 3) {
229 "relation": g.relation,
231 "street_lines": [g.address],
235 "country": g.country || "US",
236 "use": "primary home"
244 "type": "primary home"
247 if (pd.race === 'Declined To Specify' || pd.race === '') {
248 pd.race = "null_flavor";
250 if (pd.race_group === 'Declined To Specify' || pd.race_group === '') {
251 pd.race_group = "null_flavor";
253 if (pd.ethnicity === 'Declined To Specify' || pd.ethnicity === '') {
254 pd.ethnicity = "null_flavor";
256 let addressArray = fetchPreviousAddresses(pd);
261 "middle": [pd.mname] || "",
266 "middle": pd.birth_mname || "",
267 "last": pd.birth_lname || "",
268 "first": pd.birth_fname || ""
272 "date": fDate(pd.dob),
276 "gender": pd.gender.toUpperCase() || "null_flavor",
278 "identifier": oidFacility || npiFacility,
281 "marital_status": pd.status.toUpperCase(),
282 "addresses": addressArray,
285 "number": pd.phone_home,
286 "type": "primary home"
288 "number": pd.phone_mobile,
289 "type": "primary mobile"
291 "number": pd.phone_work,
294 "number": pd.phone_emergency,
295 "type": "emergency contact"
298 "type": "contact_email"
301 "ethnicity": pd.ethnicity || "",
302 "race": pd.race || "null_flavor",
303 "race_additional": pd.race_group || "null_flavor",
305 "language": pd.language === 'English' ? "en-US" : pd.language === 'Spanish' ? "sp-US" : 'en-US',
307 "mode": "Expressed spoken",
308 "proficiency": "Good"
310 //"religion": pd.religion.toUpperCase() || "",
317 "attributed_provider": {
320 "root": "2.16.840.1.113883.4.6",
321 "extension": npiFacility || ""
325 "number": all.encounter_provider.facility_phone || "",
329 "full": all.encounter_provider.facility_name || ""
335 all.encounter_provider.facility_street
337 "city": all.encounter_provider.facility_city,
338 "state": all.encounter_provider.facility_state,
339 "zip": all.encounter_provider.facility_postal_code,
340 "country": all.encounter_provider.facility_country_code || "US",
345 "guardians": g.display_name ? guardian : '' //not required
349 function populateProvider(provider) {
350 // The provider role is a maybe and will only be provided for physicians as a
351 // primary care role. All other team members will id via taxonomy only and if not physicians.
353 "function_code": provider.physician_type ? "PP" : "",
356 "date": provider.provider_since ? fDate(provider.provider_since) : fDate(""),
362 "root": provider.npi ? "2.16.840.1.113883.4.6" : oidFacility,
363 "extension": provider.npi || provider.table_id || "NI"
368 "name": provider.taxonomy_description || "",
369 "code": cleanCode(provider.taxonomy) || "",
370 "code_system": "2.16.840.1.113883.6.101",
371 "code_system_name": "NUCC Health Care Provider Taxonomy"
376 "last": provider.lname || "",
377 "first": provider.fname || ""
383 all.encounter_provider.facility_street
385 "city": all.encounter_provider.facility_city,
386 "state": all.encounter_provider.facility_state,
387 "zip": all.encounter_provider.facility_postal_code,
388 "country": all.encounter_provider.facility_country_code || "US"
393 "number": all.encounter_provider.facility_phone || ""
398 function populateProviders(all) {
399 let providerArray = [];
401 let provider = populateProvider(all.primary_care_provider.provider);
402 providerArray.push(provider);
403 let count = isOne(all.care_team.provider);
405 provider = populateProvider(all.care_team.provider);
406 providerArray.push(provider);
407 } else if (count > 1) {
408 for (let i in all.care_team.provider) {
409 provider = populateProvider(all.care_team.provider[i]);
410 providerArray.push(provider);
418 "date": fDate(all.time_start) || fDate(""),
422 "date": fDate(all.time_end) || fDate(""),
427 "name": all.primary_diagnosis.text || "",
428 "code": cleanCode(all.primary_diagnosis.code || ""),
429 "code_system_name": all.primary_diagnosis.code_type || ""
431 "provider": providerArray,
437 function populateCareTeamMember(provider) {
439 //"function_code": provider.physician_type ? "PP" : "",
441 "xmlns": "urn:hl7-org:sdtc",
442 "name": provider.taxonomy_description || "",
443 "code": cleanCode(provider.taxonomy) || "",
444 "code_system": "2.16.840.1.113883.6.101",
445 "code_system_name": "NUCC Health Care Provider Taxonomy"
450 "date": fDate(provider.provider_since) || fDate(""),
456 "identifier": provider.npi ? "2.16.840.1.113883.4.6" : oidFacility,
457 "extension": provider.npi || provider.table_id
460 "full_name": provider.fname + " " + provider.lname,
462 "last": provider.lname || "",
463 "first": provider.fname || ""
469 "city": provider.city,
470 "state": provider.state,
472 "country": all.encounter_provider.facility_country_code || "US"
476 "number": provider.telecom,
483 function populateAuthorFromAuthorContainer(pd) {
484 let author = pd.author || {};
487 "name": author.physician_type || '',
488 "code": author.physician_type_code || '',
489 "code_system": author.physician_type_system,
490 "code_system_name": author.physician_type_system_name
494 "date": fDate(author.time),
500 "identifier": author.npi ? "2.16.840.1.113883.4.6" : author.id,
501 "extension": author.npi ? author.npi : 'NI'
506 "last": author.lname || "",
507 "first": author.fname || ""
514 "root": author.facility_oid || "2.16.840.1.113883.4.6",
515 "extension": author.facility_npi || "NI"
519 author.facility_name || ""
526 function populateCareTeamMembers(pd) {
527 let providerArray = [];
529 let primaryCareProvider = pd.primary_care_provider || {provider: {}};
530 let providerSince = fDate(primaryCareProvider.provider.provider_since || '');
531 if (pd.primary_care_provider) {
532 let provider = populateCareTeamMember(pd.primary_care_provider.provider);
533 providerArray.push(provider);
534 let count = isOne(pd.care_team.provider);
536 provider = populateCareTeamMember(pd.care_team.provider);
537 providerSince = providerSince || fDate(provider.provider_since);
538 providerArray.push(provider);
539 } else if (count > 1) {
540 for (let i in pd.care_team.provider) {
541 provider = populateCareTeamMember(pd.care_team.provider[i]);
542 providerSince = providerSince || fDate(provider.provider_since);
543 providerArray.push(provider);
550 "provider": providerArray,
555 "date": providerSince || fDate(""),
559 // we treat this author a bit differently since we are working at the main pd object instead of the sub pd.care_team
560 "author": populateAuthorFromAuthorContainer(pd.care_team)
564 function populateMedication(pd) {
565 pd.status = 'Completed'; //@todo invoke prescribed
569 "date": fDate(pd.start_date),
573 "date": fDate(pd.end_date),
578 "identifier": pd.sha_extension,
579 "extension": pd.extension || ""
585 "identifier": pd.sha_extension || "2a620155-9d11-439e-92b3-5d9815ff4ee8",
586 "extension": pd.extension + 1 || ""
588 "unencoded_name": pd.drug,
591 "code": cleanCode(pd.rxnorm),
592 "code_system_name": "RXNORM"
596 "code_system_name": "RXNORM"
603 "name": pd.author.physician_type || '',
604 "code": pd.author.physician_type_code || '',
605 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
609 "date": fDate(pd.author.time),
615 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
616 "extension": pd.author.npi ? pd.author.npi : 'NI'
621 "last": pd.author.lname,
622 "first": pd.author.fname
629 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
630 "extension": pd.author.facility_npi || "NI"
634 pd.author.facility_name
642 "date": fDate(pd.start_date),
646 "date": fDate(pd.end_date),
654 "identifier": pd.sha_extension || "2a620155-9d11-439e-92b3-5d9815ff4ee8",
655 "extension": pd.extension + 1 || ""
657 "unencoded_name": pd.drug,
660 "code": cleanCode(pd.rxnorm),
664 "code_system_name": "RXNORM"
666 "code_system_name": "RXNORM"
672 "name": all.author.physician_type || '',
673 "code": all.author.physician_type_code || '',
674 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
678 "date": authorDateTime,
684 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
685 "extension": all.author.npi ? all.author.npi : 'NI'
690 "last": all.author.lname,
691 "first": all.author.fname
698 "root": oidFacility || "2.16.840.1.113883.4.6",
699 "extension": npiFacility || ""
703 all.encounter_provider.facility_name
710 "name": "instruction",
712 "code_system_name": "SNOMED CT"
714 "free_text": pd.instructions || "No Instructions"
719 "name": pd.route || "",
720 "code": cleanCode(pd.route_code) || "",
721 "code_system_name": "Medication Route FDA"
725 "code": cleanCode(pd.form_code),
726 "code_system_name": "Medication Route FDA"
729 "value": parseFloat(pd.size),
733 "value": parseFloat(pd.dosage),
738 "value": parseFloat(pd.dosage),
746 "identifier": "2.16.840.1.113883.4.6",
747 "extension": pd.npi || ""
751 "identifier": pd.sha_extension,
752 "extension": pd.extension || ""
754 "name": [pd.performer_name]
759 "code": cleanCode(pd.form_code),
760 "code_system_name": "RXNORM"
765 "code_system_name": "ActCode"
770 "code_system_name": "SNOMED CT"
775 "identifier": "db734647-fc99-424c-a864-7e3cda82e703",
781 "code_system_name": "SNOMED CT"
785 "date": fDate(pd.start_date),
790 "name": pd.indications,
791 "code": pd.indications_code,
792 "code_system_name": "SNOMED CT"
797 "identifier": "1.2.3.4.56789.1",
798 "extension": "cb734647-fc99-424c-a864-7e3cda82e704"
802 "identifier": "2.16.840.1.113883.19.5.9999.456",
803 "extension": "2981823"
806 "street_lines": [pd.address],
814 "identifier": "2.16.840.1.113883.19.5.9999.1393"
816 "name": [pd.performer_name]
821 "identifier": "2a620155-9d11-439e-92b3-5d9815ff4ee8"
823 "unencoded_name": pd.drug,
830 "code_system_name": "RXNORM"
832 "code_system_name": "RXNORM"
840 function getFinding(pd, problem) {
843 "identifier": pd.sha_extension,
849 "code_system_name": ''
858 "reason": pd.encounter_reason,
861 "name": all.author.physician_type || '',
862 "code": all.author.physician_type_code || '',
863 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
867 "date": authorDateTime,
873 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
874 "extension": all.author.npi ? all.author.npi : 'UNK'
879 "last": all.author.lname,
880 "first": all.author.fname
887 "root": oidFacility || "2.16.840.1.113883.4.6",
888 "extension": npiFacility || ""
892 all.encounter_provider.facility_name
899 finding.identifiers["0"].extension = problem.extension;
900 finding.date_time.low.date = fDate(problem.date);
901 finding.value.name = problem.text;
902 finding.value.code = cleanCode(problem.code);
903 finding.value.code_system_name = problem.code_type;
904 finding.status = problem.status;
908 function populateEncounter(pd) {
909 // just to get diagnosis. for findings..
914 count = isOne(pd.encounter_problems.problem);
919 for (let i in pd.encounter_problems.problem) {
920 theone[i] = getFinding(pd, pd.encounter_problems.problem[i]);
921 findingObj.push(theone[i]);
923 } else if (count !== 0 && pd.encounter_problems.problem.code > '') {
924 let finding = getFinding(pd, pd.encounter_problems.problem);
925 findingObj.push(finding);
930 "name": pd.visit_category ? (pd.visit_category + " | " + pd.encounter_reason) : pd.code_description,
931 "code": pd.code || "185347001",
932 //"code_system": "2.16.840.1.113883.6.96",
933 "code_system_name": pd.code_type || "SNOMED CT",
935 "name": "Ambulatory",
937 "code_system_name": "ActCode"
941 "identifier": pd.sha_extension,
942 "extension": pd.extension
946 "date": fDate(pd.date),
952 "identifier": "2.16.840.1.113883.4.6",
953 "extension": pd.npi || ""
956 "name": pd.physician_type,
957 "code": cleanCode(pd.physician_type_code),
958 "code_system_name": pd.physician_code_type
962 "last": pd.lname || "",
963 "first": pd.fname || ""
968 "number": pd.work_phone,
976 "name": pd.location_details,
978 "code_system_name": "HealthcareServiceLocation"
981 "street_lines": [pd.facility_address],
982 "city": pd.facility_city,
983 "state": pd.facility_state,
984 "zip": pd.facility_zip,
985 "country": pd.facility_country || "US"
989 "number": pd.facility_phone,
994 "findings": findingObj
998 function populateAllergy(pd) {
1002 "no_know_allergies": "No Known Allergies",
1004 "low": templateDate("", "day"),
1005 //"high": templateDate(pd.enddate, "day")
1009 let allergyAuthor = {
1011 "name": pd.author.physician_type || '',
1012 "code": pd.author.physician_type_code || '',
1013 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
1017 "date": fDate(pd.author.time),
1023 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
1024 "extension": pd.author.npi ? pd.author.npi : 'NI'
1029 "last": pd.author.lname,
1030 "first": pd.author.fname
1037 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
1038 "extension": pd.author.facility_npi || "NI"
1042 pd.author.facility_name
1050 "identifier": pd.sha_id,
1051 "extension": pd.id || ""
1054 "low": templateDate(pd.startdate, "day"),
1055 //"high": templateDate(pd.enddate, "day")
1057 "author": allergyAuthor,
1060 "identifier": pd.sha_extension || "2a620155-9d11-439e-92b3-5d9815ff4ee8",
1061 "extension": pd.id + 1 || ""
1063 "author": allergyAuthor,
1065 "name": pd.title || "",
1066 "code": pd.rxnorm_code_text ? cleanCode(pd.rxnorm_code) : pd.snomed_code_text ? cleanCode(pd.snomed_code) : cleanCode(""),
1067 "code_system_name": pd.rxnorm_code_text ? "RXNORM" : pd.snomed_code_text ? "SNOMED CT" : ""
1071 "date": fDate(pd.startdate) || fDate(""),
1076 "name": "Propensity to adverse reactions to drug",
1077 "code": "420134006",
1078 "code_system_name": "SNOMED CT"
1082 "name": pd.outcome || "",
1083 "code": cleanCode(pd.outcome_code) || "",
1084 "code_system_name": "SNOMED CT"
1088 "name": pd.status_table || "",
1089 "code": cleanCode(pd.status_code),
1090 "code_system_name": "SNOMED CT"
1094 "identifier": "4adc1020-7b14-11db-9fe1-0800200c9a64"
1097 "low": templateDate(pd.startdate, "day"),
1098 "high": templateDate(pd.enddate, "day")
1101 "name": pd.reaction_text,
1102 "code": cleanCode(pd.reaction_code) || "",
1103 "code_system_name": pd.reaction_code_type || "SNOMED CT"
1107 "name": pd.outcome || "",
1108 "code": cleanCode(pd.outcome_code),
1109 "code_system_name": "SNOMED CT"
1117 function populateProblem(pd) {
1118 let primary_care_provider = all.primary_care_provider || {provider: {}};
1122 "date": fDate(pd.start_date_table),
1126 "date": fDate(pd.end_date),
1131 "identifier": pd.sha_extension,
1132 "extension": pd.extension || ""
1135 "name": "Condition",
1137 "code_system_name": "LOINC"
1141 "name": safeTrim(pd.title),
1142 "code": cleanCode(pd.code),
1143 "code_system_name": safeTrim(pd.code_type)
1147 "date": fDate(pd.start_date),
1151 "date": fDate(pd.end_date),
1152 "precision": getPrecision()
1158 "name": pd.author.physician_type || '',
1159 "code": pd.author.physician_type_code || '',
1160 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
1164 "date": fDate(pd.author.time),
1170 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
1171 "extension": pd.author.npi ? pd.author.npi : 'NI'
1176 "last": pd.author.lname,
1177 "first": pd.author.fname
1184 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
1185 "extension": pd.author.facility_npi || "NI"
1189 pd.author.facility_name
1198 "identifier": "2.16.840.1.113883.4.6",
1199 "extension": primary_care_provider.provider.npi || ""
1204 "last": primary_care_provider.provider.lname || "",
1205 "first": primary_care_provider.provider.fname || ""
1209 "onset_age": pd.age,
1210 "onset_age_unit": "Year",
1212 "name": pd.status_table,
1215 "date": fDate(pd.start_date),
1219 "date": fDate(pd.end_date),
1220 "precision": getPrecision()
1224 "patient_status": pd.observation,
1225 "source_list_identifiers": [{
1226 "identifier": pd.sha_extension,
1227 "extension": pd.extension || ""
1233 function populateProcedure(pd) {
1236 "name": pd.description,
1237 "code": cleanCode(pd.code),
1238 //"code_system": "2.16.840.1.113883.6.12",
1239 "code_system_name": pd.code_type
1242 "identifier": "d68b7e32-7810-4f5b-9cc2-acd54b0fd85d",
1243 "extension": pd.extension
1245 "status": "completed",
1248 "date": fDate(pd.date),
1255 "code_system_name": ""
1259 "identifier": "c2ee9ee9-ae31-4628-a919-fec1cbb58683"
1264 "code_system_name": "SNOMED CT"
1269 "identifier": "2.16.840.1.113883.4.6",
1270 "extension": pd.npi || ""
1273 "street_lines": [pd.address],
1280 "number": pd.work_phone,
1281 "type": "work place"
1285 "identifier": pd.facility_sha_extension,
1286 "extension": pd.facility_extension
1288 "name": [pd.facility_name],
1290 "street_lines": [pd.facility_address],
1291 "city": pd.facility_city,
1292 "state": pd.facility_state,
1293 "zip": pd.facility_zip,
1294 "country": pd.facility_country || "US"
1297 "number": pd.facility_phone,
1298 "type": "work place"
1302 "author": populateAuthorFromAuthorContainer(pd),
1303 "procedure_type": "procedure"
1307 function populateMedicalDevice(pd) {
1310 "identifier": pd.sha_extension,
1311 "extension": pd.extension
1315 "date": fDate(pd.start_date),
1319 "date": fDate(pd.end_date),
1323 "device_type": "UDI",
1325 "name": pd.code_text,
1326 "code": cleanCode(pd.code),
1327 "code_system_name": "SNOMED CT",
1329 "identifier": "2.16.840.1.113883.3.3719",
1332 "status": "completed",
1336 "code_system_name": ""
1342 "name": pd.author.physician_type || '',
1343 "code": pd.author.physician_type_code || '',
1344 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
1348 "date": fDate(pd.author.time),
1354 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
1355 "extension": pd.author.npi ? pd.author.npi : 'NI'
1360 "last": pd.author.lname,
1361 "first": pd.author.fname
1368 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
1369 "extension": pd.author.facility_npi || "NI"
1373 pd.author.facility_name
1381 function populateResult(pd) {
1382 let icode = pd.subtest.abnormal_flag;
1383 let value = parseFloat(pd.subtest.result_value) || pd.subtest.result_value || "";
1384 let type = isNaN(value) ? "ST" : "PQ";
1385 type = !pd.subtest.unit ? "ST" : type;
1387 let range_type = pd.subtest.range.toUpperCase() == "NEGATIVE" ? "CO" : type;
1388 type = value.toUpperCase() == "NEGATIVE" ? "CO" : type;
1390 switch (pd.subtest.abnormal_flag.toUpperCase()) {
1403 "identifier": pd.subtest.root,
1404 "extension": pd.subtest.extension
1408 "code": cleanCode(pd.subtest.result_code) || "",
1409 "code_system_name": "LOINC"
1413 "date": fDate(pd.date_ordered),
1417 "status": pd.order_status,
1418 "reference_range": {
1419 "low": pd.subtest.low,
1420 "high": pd.subtest.high,
1421 "unit": pd.subtest.unit,
1423 "range_type": range_type
1425 "value": value + "",
1426 "unit": pd.subtest.unit,
1428 "range": pd.subtest.range,
1429 "range_type": range_type
1431 // interpretation cannot be an empty value so we skip it if it is
1432 // empty as Observation.interpretationCode is [0..*]
1434 result["interpretations"] = [icode];
1439 function getResultSet(results) {
1441 if (!results) return '';
1443 // not sure if the result set should be grouped better on the backend as the author information needs to be more nuanced here
1444 let tResult = results.result[0] || results.result;
1447 "identifier": tResult.root,
1448 "extension": tResult.extension
1450 "author": populateAuthorFromAuthorContainer(tResult),
1452 "name": tResult.test_name,
1453 "code": cleanCode(tResult.test_code),
1454 "code_system_name": "LOINC"
1463 count = isOne(results.result);
1468 for (let i in results.result) {
1469 theone[i] = populateResult(results.result[i]);
1470 many.results.push(theone[i]);
1472 } else if (count !== 0) {
1473 theone = populateResult(results.result);
1474 many.results.push(theone);
1476 rs.results = Object.assign(resultSet);
1477 rs.results.results = Object.assign(many.results);
1481 function getPlanOfCare(pd) {
1484 let code_system_name = "";
1485 let status = "Active";
1489 let planType = "observation";
1490 switch (pd.care_plan_type) {
1491 case 'plan_of_care':
1492 planType = "observation"; // mood code INT. sets code in template
1494 case 'test_or_order':
1495 planType = "observation"; // mood code RQO
1498 planType = "procedure";
1500 case 'appointments':
1501 planType = "encounter";
1503 case 'instructions':
1504 planType = "instructions";
1507 planType = ""; // for now exclude. unsure how to template.
1510 planType = "observation";
1512 if (pd.code_type === 'RXCUI') {
1513 pd.code_type = 'RXNORM';
1515 if (pd.code_type === 'RXNORM') {
1516 planType = "substanceAdministration";
1518 if (planType === "") {
1522 for (let key in all.encounter_list.encounter) {
1523 // skip loop if the property is from prototype
1524 if (!Object.prototype.hasOwnProperty.call(all.encounter_list.encounter, key)) {
1527 encounter = all.encounter_list.encounter[key];
1528 if (pd.encounter == encounter.encounter_id) {
1530 name = encounter.encounter_diagnosis.text;
1531 code = cleanCode(encounter.encounter_diagnosis.code);
1532 code_system_name = encounter.encounter_diagnosis.code_type;
1533 status = encounter.encounter_diagnosis.status;
1534 encounter = all.encounter_list.encounter[key]; // to be sure.
1540 if (all.encounter_list && all.encounter_list.encounter && all.encounter_list.encounter.encounter_diagnosis) {
1541 value = all.encounter_list.encounter.encounter_diagnosis;
1544 code = cleanCode(value.code);
1545 code_system_name = value.code_type;
1546 status = value.status;
1547 encounter = all.encounter_list.encounter;
1552 "name": pd.code_text || "",
1553 "code": cleanCode(pd.code) || "",
1554 "code_system_name": pd.code_type || "SNOMED CT"
1557 "identifier": pd.sha_extension,
1558 "extension": pd.extension || ""
1561 "code": cleanCode(pd.code) || "",
1562 "name": safeTrim(pd.description) || ""
1566 "date": fDate(pd.date),
1572 "code": cleanCode(pd.status)
1574 "author": populateAuthorFromAuthorContainer(pd),
1577 "identifier": "2.16.840.1.113883.4.6",
1578 "extension": encounter.npi || ""
1581 "name": encounter.physician_type,
1582 "code": cleanCode(encounter.physician_type_code),
1583 "code_system_name": "SNOMED CT"
1587 "last": encounter.lname || "",
1588 "first": encounter.fname || ""
1593 "number": encounter.work_phone,
1594 "type": "work place"
1599 "name": encounter.location,
1601 "name": encounter.location_details,
1603 "code_system_name": "HealthcareServiceLocation"
1606 "street_lines": [encounter.facility_address],
1607 "city": encounter.facility_city,
1608 "state": encounter.facility_state,
1609 "zip": encounter.facility_zip,
1610 "country": encounter.facility_country || "US"
1614 "number": encounter.facility_phone,
1615 "type": "work place"
1621 "identifier": encounter.sha_extension,
1622 "extension": encounter.extension
1627 "code_system_name": code_system_name
1631 "date": fDate(encounter.date),
1636 "reason": encounter.encounter_reason
1638 "name": safeTrim(pd.description),
1639 "mood_code": pd.moodCode
1643 function getGoals(pd) {
1646 "name": pd.code_text !== "NULL" ? pd.code_text : "",
1647 "code": cleanCode(pd.code) || "",
1648 "code_system_name": pd.code_type || ""
1651 "identifier": pd.sha_extension,
1652 "extension": pd.extension,
1656 "date": fDate(pd.date),
1660 "type": "observation",
1662 "code": "active", //cleanCode(pd.status)
1664 "author": populateAuthorFromAuthorContainer(pd),
1665 "name": pd.description
1669 function getFunctionalStatus(pd) {
1670 let functionalStatusAuthor = {
1672 "name": all.author.physician_type || '',
1673 "code": all.author.physician_type_code || '',
1674 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
1678 "date": authorDateTime,
1684 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
1685 "extension": all.author.npi ? all.author.npi : 'NI'
1690 "last": all.author.lname,
1691 "first": all.author.fname
1698 "root": oidFacility || "2.16.840.1.113883.4.6",
1699 "extension": npiFacility || ""
1703 all.encounter_provider.facility_name
1710 "status": "completed",
1711 "author": functionalStatusAuthor,
1713 "identifier": "9a6d1bac-17d3-4195-89a4-1121bc809000",
1714 "extension": pd.extension || '',
1719 "name": pd.code_text !== "NULL" ? safeTrim(pd.code_text) : "",
1720 "code": cleanCode(pd.code) || "",
1721 "code_system_name": pd.code_type || "SNOMED-CT"
1724 "identifier": "9a6d1bac-17d3-4195-89a4-1121bc8090ab",
1725 "extension": pd.extension || '',
1729 "date": fDate(pd.date),
1733 "status": "completed",
1734 "author": functionalStatusAuthor
1739 function getMentalStatus(pd) {
1742 "name": pd.code_text !== "NULL" ? pd.code_text : "",
1743 "code": cleanCode(pd.code) || "",
1744 "code_system_name": pd.code_type || ""
1747 "identifier": "9a6d1bac-17d3-4195-89a4-1121bc809ccc",
1748 "extension": pd.extension,
1750 "note": safeTrim(pd.description),
1752 "low": templateDate(pd.date, "day")
1753 //"high": templateDate(pd.date, "day")
1757 "name": all.author.physician_type || '',
1758 "code": all.author.physician_type_code || '',
1759 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
1763 "date": authorDateTime,
1769 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
1770 "extension": all.author.npi ? all.author.npi : 'NI'
1775 "last": all.author.lname,
1776 "first": all.author.fname
1783 "root": oidFacility || "2.16.840.1.113883.4.6",
1784 "extension": npiFacility || ""
1788 all.encounter_provider.facility_name
1796 function getAssessments(pd) {
1798 "description": safeTrim(pd.description),
1799 "author": populateAuthorFromAuthorContainer(pd)
1803 function getHealthConcerns(pd) {
1806 let problems = [], problem = {};
1807 if (isOne(pd.issues.issue_uuid) !== 0) {
1808 for (let key in pd.issues.issue_uuid) {
1809 issue_uuid = pd.issues.issue_uuid[key];
1815 "identifier": issue_uuid
1818 problems.push(problem);
1822 if (pd.issues.issue_uuid) {
1825 "identifier": pd.issues.issue_uuid
1828 problems.push(problem);
1832 // todo need to make array of health concerns
1834 "text": safeTrim(pd.text),
1836 "name": pd.code_text || "",
1837 "code": cleanCode(pd.code) || "",
1838 "code_system_name": pd.code_type || "SNOMED CT"
1840 "author": populateAuthorFromAuthorContainer(pd),
1842 "identifier": pd.sha_extension,
1843 "extension": pd.extension,
1849 function getReferralReason(pd) {
1851 "reason": safeTrim(pd.text),
1852 "author": populateAuthorFromAuthorContainer(pd)
1856 function populateVital(pd) {
1859 "identifier": pd.sha_extension,
1860 "extension": pd.extension
1862 "status": "completed",
1865 "date": fDate(pd.effectivetime),
1869 // our list of vitals per organizer.
1872 "identifier": pd.sha_extension,
1873 "extension": pd.extension_bps
1876 "name": "Blood Pressure Systolic",
1878 "code_system_name": "LOINC"
1880 "status": "completed",
1883 "date": fDate(pd.effectivetime),
1887 "interpretations": ["Normal"],
1888 "value": parseFloat(pd.bps) || pd.bps,
1890 "author": populateAuthorFromAuthorContainer(pd),
1893 "identifier": pd.sha_extension,
1894 "extension": pd.extension_bpd
1897 "name": "Blood Pressure Diastolic",
1899 "code_system_name": "LOINC"
1901 "status": "completed",
1904 "date": fDate(pd.effectivetime),
1908 "interpretations": ["Normal"],
1909 "value": parseFloat(pd.bpd) || pd.bpd,
1911 "author": populateAuthorFromAuthorContainer(pd),
1914 "identifier": pd.sha_extension,
1915 "extension": pd.extension_height
1920 "code_system_name": "LOINC"
1922 "status": "completed",
1925 "date": fDate(pd.effectivetime),
1929 "interpretations": ["Normal"],
1930 "value": parseFloat(pd.height) || pd.height,
1931 "unit": pd.unit_height,
1932 "author": populateAuthorFromAuthorContainer(pd),
1935 "identifier": pd.sha_extension,
1936 "extension": pd.extension_weight
1939 "name": "Weight Measured",
1941 "code_system_name": "LOINC"
1943 "status": "completed",
1946 "date": fDate(pd.effectivetime),
1950 "interpretations": ["Normal"],
1951 "value": parseFloat(pd.weight) || "",
1952 "unit": pd.unit_weight,
1953 "author": populateAuthorFromAuthorContainer(pd),
1956 "identifier": pd.sha_extension,
1957 "extension": pd.extension_BMI
1960 "name": "BMI (Body Mass Index)",
1962 "code_system_name": "LOINC"
1964 "status": "completed",
1967 "date": fDate(pd.effectivetime),
1971 "interpretations": [pd.BMI_status == 'Overweight' ? 'High' : pd.BMI_status == 'Overweight' ? 'Low' : 'Normal'],
1972 "value": parseFloat(pd.BMI) || "",
1974 "author": populateAuthorFromAuthorContainer(pd),
1977 "identifier": pd.sha_extension,
1978 "extension": pd.extension_pulse
1981 "name": "Heart Rate",
1983 "code_system_name": "LOINC"
1985 "status": "completed",
1988 "date": fDate(pd.effectivetime),
1992 "interpretations": ["Normal"],
1993 "value": parseFloat(pd.pulse) || "",
1995 "author": populateAuthorFromAuthorContainer(pd),
1998 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.2",
1999 "extension": pd.extension_breath
2002 "name": "Respiratory Rate",
2004 "code_system_name": "LOINC"
2006 "status": "completed",
2009 "date": fDate(pd.effectivetime),
2013 "interpretations": ["Normal"],
2014 "value": parseFloat(pd.breath) || "",
2016 "author": populateAuthorFromAuthorContainer(pd),
2019 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.3",
2020 "extension": pd.extension_temperature
2023 "name": "Body Temperature",
2025 "code_system_name": "LOINC"
2027 "status": "completed",
2030 "date": fDate(pd.effectivetime),
2034 "interpretations": ["Normal"],
2035 "value": parseFloat(pd.temperature) || "",
2036 "unit": pd.unit_temperature,
2037 "author": populateAuthorFromAuthorContainer(pd),
2040 "identifier": pd.sha_extension,
2041 "extension": pd.extension_oxygen_saturation
2044 "name": "O2 % BldC Oximetry",
2046 "code_system_name": "LOINC"
2048 "status": "completed",
2051 "date": fDate(pd.effectivetime),
2055 "interpretations": ["Normal"],
2056 "value": parseFloat(pd.oxygen_saturation) || "",
2058 "author": populateAuthorFromAuthorContainer(pd),
2061 "identifier": pd.sha_extension,
2062 "extension": pd.extension_ped_weight_height
2064 "vital": { // --------------------------------------------------------------------------------
2065 "name": "Weight for Height Percentile",
2067 "code_system_name": "LOINC"
2069 "status": "completed",
2072 "date": fDate(pd.effectivetime),
2076 "interpretations": ["Normal"],
2077 "value": parseFloat(pd.ped_weight_height) || "",
2079 "author": populateAuthorFromAuthorContainer(pd),
2082 "identifier": pd.sha_extension,
2083 "extension": pd.extension_inhaled_oxygen_concentration
2086 "name": "Inhaled Oxygen Concentration",
2088 "code_system_name": "LOINC"
2090 "status": "completed",
2093 "date": fDate(pd.effectivetime),
2097 "interpretations": ["Normal"],
2098 "value": parseFloat(pd.inhaled_oxygen_concentration) || "",
2100 "author": populateAuthorFromAuthorContainer(pd),
2103 "identifier": pd.sha_extension,
2104 "extension": pd.extension_ped_bmi
2107 "name": "BMI Percentile",
2109 "code_system_name": "LOINC"
2111 "status": "completed",
2114 "date": fDate(pd.effectivetime),
2118 "interpretations": ["Normal"],
2119 "value": parseFloat(pd.ped_bmi) || "",
2121 "author": populateAuthorFromAuthorContainer(pd),
2124 "identifier": pd.sha_extension,
2125 "extension": pd.extension_ped_head_circ
2128 "name": "Head Occipital-frontal Circumference Percentile",
2130 "code_system_name": "LOINC"
2132 "status": "completed",
2135 "date": fDate(pd.effectivetime),
2139 "interpretations": ["Normal"],
2140 "value": parseFloat(pd.ped_head_circ) || "",
2142 "author": populateAuthorFromAuthorContainer(pd),
2148 function populateSocialHistory(pd) {
2151 "low": templateDate(pd.date, "day")
2152 //"high": templateDate(pd.date, "day")
2155 "identifier": pd.sha_extension,
2156 "extension": pd.extension
2161 "element": pd.element,
2162 "value": pd.description,
2163 "gender": all.patient.gender,
2166 "name": pd.author.physician_type || '',
2167 "code": pd.author.physician_type_code || '',
2168 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
2172 "date": fDate(pd.author.time),
2178 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
2179 "extension": pd.author.npi ? pd.author.npi : 'NI'
2184 "last": pd.author.lname,
2185 "first": pd.author.fname
2192 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
2193 "extension": pd.author.facility_npi || "NI"
2197 pd.author.facility_name
2202 , "gender_author": {
2204 "name": all.patient.author.physician_type || '',
2205 "code": all.patient.author.physician_type_code || '',
2206 "code_system": all.patient.author.physician_type_system, "code_system_name": all.patient.author.physician_type_system_name
2210 "date": fDate(all.patient.author.time),
2216 "identifier": all.patient.author.npi ? "2.16.840.1.113883.4.6" : all.patient.author.id,
2217 "extension": all.patient.author.npi ? all.patient.author.npi : 'NI'
2222 "last": all.patient.author.lname,
2223 "first": all.patient.author.fname
2230 "root": all.patient.author.facility_oid || "2.16.840.1.113883.4.6",
2231 "extension": all.patient.author.facility_npi || "NI"
2235 all.patient.author.facility_name
2243 function populateImmunization(pd) {
2247 "date": fDate(pd.administered_on),
2252 "identifier": pd.sha_extension,
2253 "extension": pd.extension || ""
2255 "status": "complete",
2258 "name": pd.code_text,
2259 "code": cleanCode(pd.cvx_code),
2260 "code_system_name": "CVX"
2261 /*"translations": [{
2264 "code_system_name": "CVX"
2272 "name": pd.route_of_administration,
2273 "code": cleanCode(pd.route_code) || "",
2274 "code_system_name": "Medication Route FDA"
2283 "identifier": "2.16.840.1.113883.4.6",
2284 "extension": pd.npi || ""
2291 "street_lines": [pd.address],
2299 "identifier": "2.16.840.1.113883.4.6",
2300 "extension": npiFacility || ""
2302 "name": [pd.facility_name]
2307 "name": "immunization education",
2308 "code": "171044003",
2309 "code_system_name": "SNOMED CT"
2311 "free_text": "Needs Attention for more data."
2315 "name": pd.author.physician_type || '',
2316 "code": pd.author.physician_type_code || '',
2317 "code_system": pd.author.physician_type_system, "code_system_name": pd.author.physician_type_system_name
2321 "date": fDate(pd.author.time),
2327 "identifier": pd.author.npi ? "2.16.840.1.113883.4.6" : pd.author.id,
2328 "extension": pd.author.npi ? pd.author.npi : 'NI'
2333 "last": pd.author.lname,
2334 "first": pd.author.fname
2341 "root": pd.author.facility_oid || "2.16.840.1.113883.4.6",
2342 "extension": pd.author.facility_npi || "NI"
2346 pd.author.facility_name
2354 function populatePayer(pd) {
2357 "identifier": "1fe2cdd0-7aad-11db-9fe1-0800200c9a66"
2361 "identifier": "3e676a50-7aac-11db-9fe1-0800200c9a66"
2365 "code_system_name": "HL7 RoleCode"
2370 "code_system_name": "HL7 RoleCode"
2374 "identifier": "2.16.840.1.113883.19"
2377 "street_lines": ["123 Insurance Road"],
2378 "city": "Blue Bell",
2385 "number": "(781)555-1515",
2386 "type": "work place"
2389 "name": ["Good Health Insurance"],
2391 "street_lines": ["123 Insurance Road"],
2392 "city": "Blue Bell",
2399 "number": "(781)555-1515",
2400 "type": "work place"
2405 "code_system_name": "HL7 RoleCode"
2413 "code_system_name": "HL7 Role"
2416 "identifier": "329fcdf0-7ab3-11db-9fe1-0800200c9a66"
2420 "middle": ["Frankie"],
2425 "street_lines": ["17 Daws Rd."],
2426 "city": "Blue Bell",
2430 "use": "primary home"
2433 "number": "(781)555-1212",
2434 "type": "primary home"
2441 "code_system_name": "HL7 Role"
2445 "identifier": "14d4a520-7aae-11db-9fe1-0800200c9a66",
2446 "extension": "1138345"
2449 "street_lines": ["17 Daws Rd."],
2450 "city": "Blue Bell",
2454 "use": "primary home"
2459 "code_system_name": "HL7 Role"
2472 "identifier": "2.16.840.1.113883.19",
2473 "extension": "1138345"
2476 "street_lines": ["17 Daws Rd."],
2477 "city": "Blue Bell",
2481 "use": "primary home"
2487 "identifier": "f4dce790-8328-11db-9fe1-0800200c9a66"
2491 "name": "Colonoscopy",
2493 "code_system_name": "SNOMED CT"
2500 function populateNote(pd) {
2504 "date": fDate(pd.date),
2509 code_system: "2.16.840.1.113883.6.1",
2510 code_system_name: "LOINC",
2511 code: cleanCode(pd.code),
2512 name: pd.code_text || ""
2514 "author": populateAuthorFromAuthorContainer(pd),
2515 "note": safeTrim(pd.description),
2519 function populateParticipant(participant) {
2522 "prefix": participant.prefix || "",
2523 "suffix": participant.suffix || "",
2524 "middle": [participant.mname] || "",
2525 "last": participant.lname || "",
2526 "first": participant.fname || ""
2528 "typeCode": participant.type || "",
2529 "classCode": "ASSIGNED",
2531 "name": participant.organization_taxonomy_description || "",
2532 "code": cleanCode(participant.organization_taxonomy) || "",
2533 "code_system": "2.16.840.1.113883.6.101",
2534 "code_system_name": "NUCC Health Care Provider Taxonomy"
2537 "identifier": participant.organization_npi ? "2.16.840.1.113883.4.6" : participant.organization_id,
2538 "extension": participant.organization_npi ? participant.organization_npi : ''
2542 "date": participant.date_time,
2548 "number": participant.phonew1 || "",
2557 "city": participant.city,
2558 "state": participant.state,
2559 "zip": participant.postalCode,
2560 "country": participant.country || "US",
2561 "use": participant.address_use || "WP"
2567 function populateHeader(pd) {
2568 // default doc type ToC CCD
2569 let name = "Summarization of Episode Note";
2570 let docCode = "34133-9";
2571 let docOid = "2.16.840.1.113883.10.20.22.1.2";
2572 if (pd.doc_type == 'referral') {
2573 name = "Referral Note";
2574 docCode = "57133-1";
2575 docOid = "2.16.840.1.113883.10.20.22.1.14";
2578 if (pd.doc_type == 'unstructured') {
2579 name = "Patient Documents";
2580 docCode = "34133-9";
2581 docOid = "2.16.840.1.113883.10.20.22.1.10";
2587 "identifier": oidFacility,
2588 "extension": "123456"
2594 "code_system_name": "LOINC"
2598 "extension": "2015-08-01"
2603 "date": fDate(pd.created_time_timezone),
2609 "name": all.author.physician_type || '',
2610 "code": all.author.physician_type_code || '',
2611 "code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
2615 "date": authorDateTime,
2621 "identifier": all.author.npi ? "2.16.840.1.113883.4.6" : all.author.id,
2622 "extension": all.author.npi ? all.author.npi : 'NI'
2627 "last": all.author.lname,
2628 "first": all.author.fname
2634 all.author.streetAddressLine
2636 "city": all.author.city,
2637 "state": all.author.state,
2638 "zip": all.author.postalCode,
2639 "country": all.author.country || "US",
2645 "number": all.author.telecom || "",
2653 "root": oidFacility || "2.16.840.1.113883.4.6",
2654 "extension": npiFacility || ""
2658 all.encounter_provider.facility_name
2663 all.encounter_provider.facility_street
2665 "city": all.encounter_provider.facility_city,
2666 "state": all.encounter_provider.facility_state,
2667 "zip": all.encounter_provider.facility_postal_code,
2668 "country": all.encounter_provider.facility_country_code || "US",
2674 "number": all.encounter_provider.facility_phone,
2675 "type": "work primary"
2684 "root": "2.16.840.1.113883.4.6",
2685 "extension": npiFacility || ""
2689 pd.custodian.organization || pd.custodian.name
2694 pd.custodian.streetAddressLine
2696 "city": pd.custodian.city,
2697 "state": pd.custodian.state,
2698 "zip": pd.custodian.postalCode,
2699 "country": pd.custodian.country || "US"
2704 "number": pd.custodian.telecom,
2705 "type": "work primary"
2709 "information_recipient": {
2711 "prefix": pd.information_recipient.prefix || "",
2712 "suffix": pd.information_recipient.suffix || "",
2713 "middle": [pd.information_recipient.mname] || "",
2714 "last": pd.information_recipient.lname || "",
2715 "first": pd.information_recipient.fname || ""
2718 "name": pd.information_recipient.organization || "org"
2722 let participants = [];
2723 let docParticipants = pd.document_participants || {participant: []};
2726 count = isOne(docParticipants.participant);
2731 participants = [populateParticipant(docParticipants.participant)];
2733 // grab the values of our object
2734 participants = Object.values(docParticipants.participant).filter(pcpt => pcpt.type).map(pcpt => populateParticipant(pcpt));
2736 if (participants.length) {
2737 head.participants = participants;
2740 if (isOne(all.encounter_list.encounter) === 1) {
2741 let primary_care_provider = pd.primary_care_provider || {provider: {}};
2742 head.component_of = {
2745 "identifier": oidFacility || "",
2746 "extension": "PT-" + (pd.patient.id || "")
2750 "name": pd.primary_diagnosis.text || "",
2751 "code": pd.primary_diagnosis.code || "",
2752 "code_system_name": pd.primary_diagnosis.code_type || ""
2756 "date": pd.primary_diagnosis.encounter_date || "",
2760 "date": pd.primary_diagnosis.encounter_end_date || "",
2764 "responsible_party": {
2765 "root": oidFacility,
2767 "last": pd.author.lname,
2768 "first": pd.author.fname
2771 "encounter_participant": {
2772 "root": oidFacility,
2774 "last": primary_care_provider.provider.lname || "",
2775 "first": primary_care_provider.provider.fname || ""
2780 pd.encounter_provider.facility_street
2782 "city": pd.encounter_provider.facility_city,
2783 "state": pd.encounter_provider.facility_state,
2784 "zip": pd.encounter_provider.facility_postal_code,
2785 "country": pd.encounter_provider.facility_country_code || "US",
2791 "number": pd.encounter_provider.facility_phone,
2792 "type": "work primary"
2802 function getMeta(pd) {
2805 "type": pd.doc_type,
2808 "identifier": oidFacility || "NI",
2809 "extension": "TT988"
2812 "confidentiality": "Normal",
2814 "identifier": oidFacility || "NI",
2815 "extension": "sTT988"
2822 / * function generateCcda
2823 /* The main document builder
2824 /* pd array the xml parsed array of data sent from CCM.
2826 function generateCcda(pd) {
2833 let primary_care_provider = all.primary_care_provider || {};
2834 npiProvider = primary_care_provider.provider ? primary_care_provider.provider.npi : "NI";
2835 oidFacility = all.encounter_provider.facility_oid ? all.encounter_provider.facility_oid : "2.16.840.1.113883.19.5.99999.1";
2836 npiFacility = all.encounter_provider.facility_npi;
2837 webRoot = all.serverRoot;
2838 documentLocation = all.document_location;
2840 authorDateTime = pd.created_time_timezone;
2841 if (pd.author.time.length > 7) {
2842 authorDateTime = pd.author.time;
2843 } else if (all.encounter_list && all.encounter_list.encounter) {
2844 if (isOne(all.encounter_list.encounter) === 1) {
2845 authorDateTime = all.encounter_list.encounter.date;
2847 authorDateTime = all.encounter_list.encounter[0].date;
2851 authorDateTime = fDate(authorDateTime);
2853 let demographic = populateDemographic(pd.patient, pd.guardian, pd);
2854 // This populates documentationOf. We are using providerOrganization also.
2855 if (pd.primary_care_provider) {
2856 Object.assign(demographic, populateProviders(pd));
2858 data.demographics = Object.assign(demographic);
2862 encs.encounters = [];
2864 count = isOne(pd.encounter_list.encounter);
2869 for (let i in pd.encounter_list.encounter) {
2870 enc[i] = populateEncounter(pd.encounter_list.encounter[i]);
2871 encs.encounters.push(enc[i]);
2873 } else if (count !== 0) {
2874 enc = populateEncounter(pd.encounter_list.encounter);
2875 encs.encounters.push(enc);
2878 data.encounters = Object.assign(encs.encounters);
2885 count = isOne(pd.history_physical.vitals_list.vitals);
2890 for (let i in pd.history_physical.vitals_list.vitals) {
2891 vitals[i] = populateVital(pd.history_physical.vitals_list.vitals[i]);
2892 vitals.vitals.push(vitals[i]);
2894 } else if (count !== 0) {
2895 vital = populateVital(pd.history_physical.vitals_list.vitals);
2896 vitals.vitals.push(vital);
2899 data.vitals = Object.assign(vitals.vitals);
2904 meds.medications = [];
2906 count = isOne(pd.medications.medication);
2911 for (let i in pd.medications.medication) {
2912 m[i] = populateMedication(pd.medications.medication[i]);
2913 meds.medications.push(m[i]);
2915 } else if (count !== 0) {
2916 m = populateMedication(pd.medications.medication);
2917 meds.medications.push(m);
2920 data.medications = Object.assign(meds.medications);
2925 allergies.allergies = [];
2927 count = isOne(pd.allergies.allergy);
2932 for (let i in pd.allergies.allergy) {
2933 allergy[i] = populateAllergy(pd.allergies.allergy[i]);
2934 allergies.allergies.push(allergy[i]);
2936 } else if (count <= 1) {
2937 allergy = populateAllergy(pd.allergies.allergy);
2938 allergies.allergies.push(allergy);
2942 data.allergies = Object.assign(allergies.allergies);
2947 problems.problems = [];
2949 count = isOne(pd.problem_lists.problem);
2954 for (let i in pd.problem_lists.problem) {
2955 problem[i] = populateProblem(pd.problem_lists.problem[i], pd);
2956 problems.problems.push(problem[i]);
2958 } else if (count !== 0) {
2959 problem = populateProblem(pd.problem_lists.problem);
2960 problems.problems.push(problem);
2963 data.problems = Object.assign(problems.problems);
2968 many.procedures = [];
2970 count = isOne(pd.procedures.procedure);
2975 for (let i in pd.procedures.procedure) {
2976 theone[i] = populateProcedure(pd.procedures.procedure[i]);
2977 many.procedures.push(theone[i]);
2979 } else if (count !== 0) {
2980 theone = populateProcedure(pd.procedures.procedure);
2981 many.procedures.push(theone);
2984 data.procedures = Object.assign(many.procedures);
2989 many.medical_devices = [];
2991 count = isOne(pd.medical_devices.device);
2996 for (let i in pd.medical_devices.device) {
2997 theone[i] = populateMedicalDevice(pd.medical_devices.device[i]);
2998 many.medical_devices.push(theone[i]);
3000 } else if (count !== 0) {
3001 theone = populateMedicalDevice(pd.medical_devices.device);
3002 many.medical_devices.push(theone);
3005 data.medical_devices = Object.assign(many.medical_devices);
3009 data.results = Object.assign(getResultSet(pd.results, pd)['results']);
3012 // Referral TODO sjp I'm not happy with this.
3013 // different referral sources. 1st is dynamic with doc gen from CCM.
3014 // 2nd is the latest referral from transactions.
3015 if (pd.referral_reason[0].text !== "") {
3016 data.referral_reason = Object.assign(getReferralReason(pd.referral_reason[0], pd));
3017 } else if (pd.referral_reason[1].text !== "" && typeof pd.referral_reason[1].text !== 'undefined') {
3018 data.referral_reason = Object.assign(getReferralReason(pd.referral_reason[1], pd));
3020 data.referral_reason = {}; // leave as empty so we can get our null flavor section.
3025 many.health_concerns = [];
3027 count = isOne(pd.health_concerns.concern);
3032 for (let i in pd.health_concerns.concern) {
3033 theone[i] = getHealthConcerns(pd.health_concerns.concern[i]);
3034 many.health_concerns.push(theone[i]);
3037 } else if (count !== 0) {
3038 theone = getHealthConcerns(pd.health_concerns.concern);
3039 many.health_concerns.push(theone);
3042 data.health_concerns = Object.assign(many.health_concerns);
3044 data.health_concerns = {"type": "act"}; // leave it as an empty section that we'll null flavor
3049 many.immunizations = [];
3051 count = isOne(pd.immunizations.immunization);
3056 for (let i in pd.immunizations.immunization) {
3057 theone[i] = populateImmunization(pd.immunizations.immunization[i]);
3058 many.immunizations.push(theone[i]);
3060 } else if (count !== 0) {
3061 theone = populateImmunization(pd.immunizations.immunization);
3062 many.immunizations.push(theone);
3065 data.immunizations = Object.assign(many.immunizations);
3070 many.plan_of_care = [];
3072 count = isOne(pd.planofcare.item);
3077 for (let i in pd.planofcare.item) {
3078 if (cleanCode(pd.planofcare.item[i].date) === '') {
3082 theone[i] = getPlanOfCare(pd.planofcare.item[i]);
3084 many.plan_of_care.push(theone[i]);
3087 } else if (count !== 0) {
3088 theone = getPlanOfCare(pd.planofcare.item);
3090 many.plan_of_care.push(theone);
3094 data.plan_of_care = Object.assign(many.plan_of_care);
3101 count = isOne(pd.goals.item);
3106 for (let i in pd.goals.item) {
3107 theone[i] = getGoals(pd.goals.item[i]);
3108 many.goals.push(theone[i]);
3110 } else if (count !== 0) {
3111 theone = getGoals(pd.goals.item);
3112 many.goals.push(theone);
3115 data.goals = Object.assign(many.goals);
3120 many.clinicalNoteAssessments = [];
3122 count = isOne(pd.clinical_notes.evaluation_note);
3127 for (let i in pd.clinical_notes.evaluation_note) {
3128 theone[i] = getAssessments(pd.clinical_notes.evaluation_note[i]);
3129 many.clinicalNoteAssessments.push(theone[i]);
3130 break; // for now only one assessment. @todo concat notes to one.
3132 } else if (count !== 0) {
3133 theone = getAssessments(pd.clinical_notes.evaluation_note);
3134 many.clinicalNoteAssessments.push(theone);
3137 data.clinicalNoteAssessments = Object.assign(many.clinicalNoteAssessments);
3140 // Functional Status.
3143 many.functional_status = [];
3145 count = isOne(pd.functional_status.item);
3150 for (let i in pd.functional_status.item) {
3151 theone[i] = getFunctionalStatus(pd.functional_status.item[i]);
3152 many.functional_status.push(theone[i]);
3154 } else if (count !== 0) {
3155 theone = getFunctionalStatus(pd.functional_status.item);
3156 many.functional_status.push(theone);
3159 data.functional_status = Object.assign(many.functional_status);
3165 many.mental_status = [];
3167 count = isOne(pd.mental_status.item);
3172 for (let i in pd.mental_status.item) {
3173 theone[i] = getMentalStatus(pd.mental_status.item[i]);
3174 many.mental_status.push(theone[i]);
3176 } else if (count !== 0) {
3177 theone = getMentalStatus(pd.mental_status.item);
3178 many.mental_status.push(theone);
3181 data.mental_status = Object.assign(many.mental_status);
3187 many.social_history = [];
3189 count = isOne(pd.history_physical.social_history.history_element);
3194 for (let i in pd.history_physical.social_history.history_element) {
3196 theone[i] = populateSocialHistory(pd.history_physical.social_history.history_element[i]);
3197 many.social_history.push(theone[i]);
3199 } else if (count !== 0) {
3200 theone = populateSocialHistory(pd.history_physical.social_history.history_element);
3201 many.social_history.push(theone);
3204 data.social_history = Object.assign(many.social_history);
3207 for (let currentNote in pd.clinical_notes) {
3210 switch (pd.clinical_notes[currentNote].clinical_notes_type) {
3211 case 'evaluation_note':
3213 case 'progress_note':
3215 case 'history_physical':
3216 pd.clinical_notes[currentNote].code_text = "History and Physical";
3220 case 'general_note':
3222 case 'discharge_summary':
3224 case 'procedure_note':
3226 case 'consultation_note':
3228 case 'imaging_narrative':
3230 case 'laboratory_report_narrative':
3232 case 'pathology_report_narrative':
3238 count = isOne(pd.clinical_notes[currentNote]);
3243 for (let i in pd.clinical_notes[currentNote]) {
3244 theone[i] = populateNote(pd.clinical_notes[currentNote]);
3245 many.push(theone[i]);
3247 } else if (count !== 0) {
3248 theone = populateNote(pd.clinical_notes[currentNote]);
3252 data[currentNote] = Object.assign(many);
3255 // Care Team and members
3256 if (pd.care_team.is_active == 'active') {
3257 data.care_team = Object.assign(populateCareTeamMembers(pd));
3260 // ------------------------------------------ End Sections ---------------------------------------- //
3262 // sections data objects
3263 doc.data = Object.assign(data);
3264 // document meta data and header objects
3265 let meta = getMeta(pd);
3266 let header = populateHeader(pd);
3267 meta.ccda_header = Object.assign(header);
3268 doc.meta = Object.assign(meta);
3270 if (pd.timezone_local_offset) {
3271 populateTimezones(doc, pd.timezone_local_offset, 0);
3274 let xml = bbg.generateCCD(doc);
3277 if (enableDebug === true) {
3278 let place = documentLocation + "/documents/temp/";
3279 if (fs.existsSync(place)) {
3280 fs.writeFile(place + "ccda.json", JSON.stringify(all, null, 4), function (err) {
3282 return console.log(err);
3285 fs.writeFile(place + "ccda.xml", xml, function (err) {
3287 return console.log(err);
3296 let unstructuredTemplate = null;
3298 function generateUnstructured(pd) {
3304 // include unstructured document type oid in header
3305 pd.doc_type = 'unstructured';
3307 let primary_care_provider = all.primary_care_provider || {};
3308 npiProvider = primary_care_provider.provider ? primary_care_provider.provider.npi : "NI";
3309 oidFacility = all.encounter_provider.facility_oid ? all.encounter_provider.facility_oid : "2.16.840.1.113883.19.5.99999.1";
3310 npiFacility = all.encounter_provider.facility_npi || "NI";
3311 webRoot = all.serverRoot;
3312 documentLocation = all.document_location;
3313 authorDateTime = pd.created_time_timezone;
3314 if (pd.author.time.length > 7) {
3315 authorDateTime = pd.author.time;
3316 } else if (all.encounter_list && all.encounter_list.encounter) {
3317 if (isOne(all.encounter_list.encounter) === 1) {
3318 authorDateTime = all.encounter_list.encounter.date;
3320 authorDateTime = all.encounter_list.encounter[0].date;
3323 authorDateTime = fDate(authorDateTime);
3324 // Demographics is needed in unstructured
3325 let demographic = populateDemographic(pd.patient, pd.guardian, pd);
3326 data.demographics = Object.assign(demographic);
3328 if (pd.primary_care_provider) {
3329 Object.assign(demographic, populateProviders(pd));
3331 doc.data = Object.assign(data);
3333 // document meta data and header objects
3334 let meta = getMeta(pd);
3335 let header = populateHeader(pd);
3336 meta.ccda_header = Object.assign(header);
3337 doc.meta = Object.assign(meta);
3339 // set TZ offset for moment
3340 if (pd.timezone_local_offset) {
3341 populateTimezones(doc, pd.timezone_local_offset, 0);
3344 let xml = bbg.generateCCD(doc);
3345 unstructuredTemplate = unstructuredTemplate.trim();
3346 xml = xml.replace(/<\/ClinicalDocument>/g, unstructuredTemplate);
3347 xml += "</ClinicalDocument>" + "\n";
3350 if (enableDebug === true) {
3351 let place = documentLocation + "/documents/temp/";
3352 if (fs.existsSync(place)) {
3353 fs.writeFile(place + "unstructured.xml", xml, function (err) {
3355 return console.log(err);
3364 function processConnection(connection) {
3365 conn = connection; // make it global
3366 let remoteAddress = conn.remoteAddress + ':' + conn.remotePort;
3367 conn.setEncoding('utf8');
3368 //console.log('server remote address ', remoteAddress);
3369 let xml_complete = "";
3371 function eventData(xml) {
3372 xml_complete = xml.toString();
3373 // ensure we have an array start and end
3374 if (xml_complete.match(/^<CCDA/g) && xml_complete.match(/<\/CCDA>$/g)) {
3377 /* eslint-disable-next-line no-control-regex */
3378 xml_complete = xml_complete.replace(/(\u000b\u001c)/gm, "").trim();
3379 xml_complete = xml_complete.replace(/\t\s+/g, " ").trim();
3380 // convert xml data set for document to json array
3381 to_json(xml_complete, function (error, data) {
3384 "toJson error: " + error + "Len: " + xml_complete.length
3386 return "ERROR: Failed json build";
3388 let unstructured = "";
3389 let isUnstruturedData = !!data.CCDA.patient_files;
3390 // extract unstructured documents file component templates. One per file.
3391 if (isUnstruturedData) {
3392 unstructuredTemplate = xml_complete.substring(
3393 xml_complete.lastIndexOf("<patient_files>") + 15,
3394 xml_complete.lastIndexOf("</patient_files>")
3397 // create doc_type document i.e. CCD Referral etc.
3398 if (data.CCDA.doc_type !== "unstructured") {
3399 doc = generateCcda(data.CCDA);
3400 if (data.CCDA.xslUrl) {
3401 xslUrl = data.CCDA.xslUrl || "";
3403 doc = headReplace(doc, xslUrl);
3405 unstructured = generateUnstructured(data.CCDA);
3406 if (data.CCDA.xslUrl) {
3407 xslUrl = data.CCDA.xslUrl || "";
3409 doc = headReplace(unstructured, xslUrl);
3410 // combine the two documents to send back all at once.
3411 doc += unstructured;
3413 // auto build an Unstructured document of supplied embedded files.
3415 data.CCDA.doc_type !== "unstructured" &&
3418 unstructured = generateUnstructured(data.CCDA);
3419 unstructured = headReplace(unstructured, xslUrl);
3420 // combine the two documents to send back all at once.
3421 doc += unstructured;
3424 // send results back to eagerly awaiting CCM for disposal.
3427 /* eslint-disable-next-line no-control-regex */
3428 .replace(/(\u000b\u001c|\r)/gm, "")
3431 let numChunks = Math.ceil(doc.length / 1024);
3432 for (let i = 0, o = 0; i < numChunks; ++i, o += 1024) {
3433 chunk = doc.substring(o, o + 1024);
3436 conn.write(String.fromCharCode(28) + "\r\r" + "");
3441 function eventCloseConn() {
3442 //console.log('connection from %s closed', remoteAddress);
3445 function eventErrorConn(err) {
3446 console.log('Connection %s error: %s', remoteAddress, err.message);
3447 console.log(err.stack);
3451 // Connection Events //
3452 // CCM will send one File Separator characters to mark end of array.
3453 let received = new DataStack(String.fromCharCode(28));
3454 conn.on("data", data => {
3455 received.push(data);
3456 while (!received.endOfCcda() && data.length > 0) {
3458 eventData(received.returnData());
3462 conn.once('close', eventCloseConn);
3463 conn.on('error', eventErrorConn);
3466 function setUp(server) {
3467 server.on('connection', processConnection);
3468 server.listen(6661, '127.0.0.1', function () { // never change port!
3469 //console.log('server listening to ', server.address());
3473 // start up listener for requests from CCM or others.
3476 /* ---------------------------------For future use in header. Do not remove!-------------------------------------------- */
3480 "identifier": "2.16.840.1.113883.4.6",
3481 "extension": "999999943252"
3486 "last": pd.data_enterer.lname,
3487 "first": pd.data_enterer.fname
3493 pd.data_enterer.streetAddressLine
3495 "city": pd.data_enterer.city,
3496 "state": pd.data_enterer.state,
3497 "zip": pd.data_enterer.postalCode,
3498 "country": pd.data_enterer.country
3503 "number": pd.data_enterer.telecom,
3504 "type": "work place"
3511 "identifier": "2.16.840.1.113883.19.5",
3512 "extension": "KP00017"
3517 "last": pd.informer.lname || "",
3518 "first": pd.informer.fname || ""
3524 pd.informer.streetAddressLine || ""
3526 "city": pd.informer.city,
3527 "state": pd.informer.state,
3528 "zip": pd.informer.postalCode,
3529 "country": pd.informer.country
3534 "number": pd.informer.telecom || "",
3535 "type": "work place"
3539 /*"service_event": {
3543 "code_system_name": "SNOMED CT"
3547 "date": "2021-03-11",
3551 "date": pd.created_time,
3561 "identifier": "2.16.840.1.113883.4.6",
3562 "extension": npiProvider
3567 "last": pd.information_recipient.lname || "DAH",
3568 "first": pd.information_recipient.fname || "DAH"
3574 pd.information_recipient.streetAddressLine
3576 "city": pd.information_recipient.city,
3577 "state": pd.information_recipient.state,
3578 "zip": pd.information_recipient.postalCode,
3579 "country": pd.information_recipient.country || "US"
3584 "number": pd.information_recipient.telecom,
3585 "type": "work place"
3592 "identifier": "2.16.840.1.113883.19.5.9999.1393"
3596 pd.encounter_provider.facility_name
3601 pd.encounter_provider.facility_street
3603 "city": pd.encounter_provider.facility_city,
3604 "state": pd.encounter_provider.facility_state,
3605 "zip": pd.encounter_provider.facility_postal_code,
3606 "country": pd.encounter_provider.facility_country_code || "US"
3611 "number": pd.encounter_provider.facility_phone,
3612 "type": "primary work"
3621 "code_system_name": "Provider Codes"
3627 "name": "Primary Performer",
3629 "code_system_name": "Provider Role"