3 * Copyright (C) 2016-2018 Jerry Padgett <sjpadgett@gmail.com>
5 * LICENSE: This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * @author Jerry Padgett <sjpadgett@gmail.com>
20 * @link http://www.open-emr.org
24 var net = require('net');
25 var to_json = require('xmljson').to_json;
26 var bbg = require('oe-blue-button-generate');
27 //var bbm = require('blue-button-model'); //for use set global-not needed here
28 //var fs = require('fs');
29 //var bb = require('blue-button');
30 var fs = require('fs');
32 var server = net.createServer();
33 var conn = ''; // make our connection scope global to script
39 // some useful routines for populating template sections
40 function validate(toValidate, ref, retObj) {
42 if (typeof ref[p].dataType === "undefined") {
44 if (!toValidate[p]) toValidate[p] = {};
45 validate(toValidate[p], ref[p], retObj[p]);
47 if (typeof toValidate === "undefined") toValidate = {};
48 var trimmed = trim(toValidate[p]);
49 retObj[p] = typeEnforcer(ref[p].dataType, trimmed);
55 function typeEnforcer(type, val) {
59 if (typeof val === "string") {
60 validVal = val.toLowerCase() === "true";
66 if ((val === null) || (val === "undefined") || (typeof val === "undefined")) {
68 } else if (typeof val == "object") {
71 validVal = trim(String(val));
75 if (typeof val === 'undefined' || val === null) {
77 } else if (Array.isArray(val)) {
79 val.forEach(function (v) {
80 validVal.push(trim(v));
83 validVal = [trim(val)];
87 var asInt = parseInt(val, 10);
88 if (isNaN(asInt)) asInt = 0;
92 var asNum = parseFloat(val);
93 if (isNaN(asNum)) asNum = 0;
101 if (typeof s === 'string') return s.trim();
106 return trim(s).toLowerCase().replace(/[^a-zA-Z0-9]+/g, '-').replace(/\-+$/, '');
109 function fDate(str) {
110 str = String(str); // at least ensure string so cast it...
112 if (Number(str) === 0) {
113 return (new Date()).toISOString().slice(0,10).replace(/-/g,"");
115 if (str.length === 8 || (str.length === 14 && (1 * str.substring(12, 14)) === 0)) {
116 return [str.slice(0, 4), str.slice(4, 6), str.slice(6, 8)].join('-')
117 } else if (str.length === 10 && (1 * str.substring(0, 2)) <= 12) {
118 // case mm/dd/yyyy or mm-dd-yyyy
119 return [str.slice(6, 10), str.slice(0, 2), str.slice(3, 5)].join('-')
120 } else if (str.length === 14 && (1 * str.substring(12, 14)) > 0) {
121 // maybe a real time so parse
126 function getPrecision(str) {
130 if (Number(str) === 0) {
133 if (str.length > 8) {
136 if (str.length > 12) {
143 function templateDate(date, precision) {
144 return [{'date': fDate(date), 'precision': precision}]
147 function cleanCode(code) {
148 return code.replace(/[.#]/, "");
151 function isOne(who) {
153 if (who !== null && typeof who === 'object') {
154 return who.hasOwnProperty('extension') || who.hasOwnProperty('id') ? 1 : Object.keys(who).length;
163 function headReplace(content) {
164 var r = '<?xml version="1.0" encoding="UTF-8"?>\n' +
165 '<ClinicalDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:hl7-org:v3 http://xreg2.nist.gov:8080/hitspValidation/schema/cdar2c32/infrastructure/cda/C32_CDA.xsd"' +
166 ' xmlns="urn:hl7-org:v3" xmlns:mif="urn:hl7-org:v3/mif">\n';
167 r += content.substr(content.search(/<realmCode/i));
171 // Data model for Blue Button
172 function populateDemographic(pd, g) {
174 "relation": g.relation,
176 "street_lines": [g.address],
180 "country": g.country,
181 "use": "primary home"
184 "last": g.display_name, //@todo parse first/last
185 "first": g.display_name
189 "type": "primary home"
195 "middle": [pd.mname],
201 "date": fDate(pd.dob),
205 "gender": pd.gender.toUpperCase(),
207 "identifier": oidFacility,
208 "extension": "PT-" + pd.id
210 "marital_status": pd.status.toUpperCase() || "U",
212 "street_lines": [pd.street],
215 "zip": pd.postalCode,
216 "country": pd.country,
217 "use": "primary home"
220 "number": pd.phone_home,
221 "type": "primary home"
224 "ethnicity": pd.ethnicity || "U",
226 "language": pd.language,
228 "mode": "Expressed spoken",
229 "proficiency": "Good"
231 "religion": pd.religion.toUpperCase() || "",
238 "attributed_provider": {
241 "root": "2.16.840.1.113883.4.6",
242 "extension": npiFacility || "UNK"
246 "number": all.encounter_provider.facility_phone || "",
250 "full": all.encounter_provider.facility_name || ""
254 "guardians": g.display_name ? guardian : ''
259 function populateProviders() {
265 "root": "2.16.840.1.113883.4.6",
266 "extension": all.primary_care_provider.provider[0].npi || "UNK"
271 "name": all.primary_care_provider.provider[0].physician_type || "Not Avail",
272 "code": all.primary_care_provider.provider[0].physician_type_code || "",
273 "code_system_name": "Provider Codes"
278 "last": all.primary_care_provider.provider[0].lname || "",
279 "first": all.primary_care_provider.provider[0].fname || ""
285 all.encounter_provider.facility_street
287 "city": all.encounter_provider.facility_city,
288 "state": all.encounter_provider.facility_state,
289 "zip": all.encounter_provider.facility_postal_code,
290 "country": all.encounter_provider.facility_country_code
296 "number": all.encounter_provider.facility_phone || "",
304 "identifier": "2.16.840.1.113883.19.5.9999.1393" //@todo need facility oid
308 all.encounter_provider.facility_name
313 all.encounter_provider.facility_street
315 "city": all.encounter_provider.facility_city,
316 "state": all.encounter_provider.facility_state,
317 "zip": all.encounter_provider.facility_postal_code,
318 "country": all.encounter_provider.facility_country_code
323 "number": all.encounter_provider.facility_phone,
324 "type": "primary work"
337 function populateMedication(pd) {
338 pd.status = 'Completed'; //@todo invoke prescribed
342 "date": fDate(pd.start_date),
346 "date": pd.end_date ? fDate(pd.end_date) : "",
351 "identifier": pd.sha_extension,
352 "extension": pd.extension || "UNK"
358 "identifier": "2a620155-9d11-439e-92b3-5d9815ff4ee8",
361 "unencoded_name": pd.drug,
368 "code_system_name": "RXNORM"
370 "code_system_name": "RXNORM"
377 "date": fDate(pd.start_date),
386 "date": fDate(pd.start_date),
387 "precision": getPrecision(fDate(pd.start_date))
391 "identifier": "2.16.840.1.113883.4.6",
392 "extension": pd.npi || "UNK"
402 "name": "instruction",
404 "code_system_name": "SNOMED CT"
406 "free_text": pd.instructions || "None Available"
411 "name": pd.route || "",
412 "code": pd.route_code || "",
413 "code_system_name": "Medication Route FDA"
417 "code": pd.form_code,
418 "code_system_name": "Medication Route FDA"
421 "value": parseFloat(pd.size),
425 "value": parseFloat(pd.dosage),
430 "value": parseFloat(pd.dosage),
438 "identifier": "2.16.840.1.113883.4.6",
439 "extension": pd.npi || "UNK"
443 "identifier": pd.sha_extension,
444 "extension": pd.extension || "UNK"
446 "name": [pd.performer_name]
451 "code": pd.form_code,
452 "code_system_name": "RXNORM"
457 "code_system_name": "ActCode"
462 "code_system_name": "SNOMED CT"
467 "identifier": "db734647-fc99-424c-a864-7e3cda82e703",
473 "code_system_name": "SNOMED CT"
477 "date": fDate(pd.start_date),
482 "name": pd.indications,
483 "code": pd.indications_code,
484 "code_system_name": "SNOMED CT"
489 "identifier": "1.2.3.4.56789.1",
490 "extension": "cb734647-fc99-424c-a864-7e3cda82e704"
494 "identifier": "2.16.840.1.113883.19.5.9999.456",
495 "extension": "2981823"
498 "street_lines": [pd.address],
506 "identifier": "2.16.840.1.113883.19.5.9999.1393"
508 "name": [pd.performer_name]
513 "identifier": "2a620155-9d11-439e-92b3-5d9815ff4ee8"
515 "unencoded_name": pd.drug,
522 "code_system_name": "RXNORM"
524 "code_system_name": "RXNORM"
532 function populateEncounter(pd) {
535 "name": pd.visit_category ? pd.visit_category : '',
536 "code": pd.encounter_procedures ? pd.encounter_procedures.procedures.code : '',
537 "code_system_name": "CPT4",
539 "name": "Ambulatory",
541 "code_system_name": "ActCode"
545 "identifier": pd.sha_extension,
546 "extension": pd.extension
550 "date": fDate(pd.date),
551 "precision": "second" //getPrecision(fDate(pd.date_formatted))
556 "identifier": "2.16.840.1.113883.4.6",
557 "extension": pd.npi || "UNK"
560 "name": pd.physician_type,
561 "code": pd.physician_type_code,
562 "code_system_name": "SNOMED CT"
566 "last": pd.lname || "",
567 "first": pd.fname || ""
572 "number": pd.work_phone,
580 "name": pd.location_details,
582 "code_system_name": "HealthcareServiceLocation"
585 "street_lines": [pd.facility_address],
586 "city": pd.facility_city,
587 "state": pd.facility_state,
588 "zip": pd.facility_zip,
589 "country": pd.facility_country
594 "identifier": pd.sha_extension,
595 "extension": pd.extension
598 "name": pd.encounter_reason,
600 "code_system_name": "SNOMED CT"
604 "date": fDate(pd.date),
612 function populateAllergy(pd) {
615 "identifier": "36e3e930-7b14-11db-9fe1-0800200c9a66",
616 "extension": pd.id || "UNK"
620 "date": fDate(pd.startdate),
626 "identifier": "4adc1020-7b14-11db-9fe1-0800200c9a66",
627 "extension": pd.extension || "UNK"
631 "code": pd.rxnorm_code === 0 ? "" : pd.rxnorm_code,
632 "code_system_name": "RXNORM"
635 "name": "Propensity to adverse reactions to drug",
636 "code": pd.snomed_code || "420134006",
637 "code_system_name": "SNOMED CT"
641 "date": fDate(pd.startdate),
646 "name": pd.allergy_status,
647 "code": pd.status_code === 0 ? "" : pd.status_code,
648 "code_system_name": "SNOMED CT"
652 "identifier": "4adc1020-7b14-11db-9fe1-0800200c9a64"
655 "low": templateDate(pd.startdate, "day"),
656 "high": templateDate(pd.enddate, "day")
659 "name": pd.reaction_text,
660 "code": pd.reaction_code === 0 ? "" : pd.reaction_code,
661 "code_system_name": "SNOMED CT"
666 "code": pd.outcome_code === 0 ? "" : pd.outcome_code,
667 "code_system_name": "SNOMED CT"
669 /*"interpretation": {
672 "code_system_name": "Observation Interpretation"
679 "code": pd.outcome_code === 0 ? "" : pd.outcome_code,
680 "code_system_name": "SNOMED CT"
682 /*"interpretation": {
685 "code_system_name": "Observation Interpretation"
692 function populateProblem(pd) {
696 "date": fDate(pd.start_date_table),
700 "date": fDate(pd.end_date),
705 "identifier": pd.sha_extension,
706 "extension": pd.extension || "UNK"
710 "name": trim(pd.title),
711 "code": cleanCode(pd.code),
712 "code_system_name": "ICD10"
716 "date": fDate(pd.start_date),
720 "date": fDate(pd.end_date),
721 "precision": getPrecision()
729 "identifier": "2.16.840.1.113883.4.6",
730 "extension": all.primary_care_provider.provider[0].npi || "UNK"
735 "last": all.primary_care_provider.provider[0].lname || "",
736 "first": all.primary_care_provider.provider[0].fname || ""
741 "onset_age_unit": "Year",
746 "date": fDate(pd.start_date),
750 "date": fDate(pd.end_date),
751 "precision": getPrecision()
755 "patient_status": pd.observation,
756 "source_list_identifiers": [{
757 "identifier": pd.sha_extension,
758 "extension": pd.extension || "UNK"
764 function populateProcedure(pd) {
767 "name": pd.description,
769 "code_system_name": "CPT4"
772 "identifier": "d68b7e32-7810-4f5b-9cc2-acd54b0fd85d",
773 "extension": pd.extension
775 "status": "completed",
778 "date": fDate(pd.date),
785 "code_system_name": ""
789 "identifier": "c2ee9ee9-ae31-4628-a919-fec1cbb58683"
794 "code_system_name": "SNOMED CT"
799 "identifier": "2.16.840.1.113883.4.6",
800 "extension": pd.npi || "UNK"
803 "street_lines": [pd.address],
810 "number": pd.work_phone,
815 "identifier": pd.facility_sha_extension,
816 "extension": pd.facility_extension
818 "name": [pd.facility_name],
820 "street_lines": [pd.facility_address],
821 "city": pd.facility_city,
822 "state": pd.facility_state,
823 "zip": pd.facility_zip,
824 "country": pd.facility_country
827 "number": pd.facility_phone,
832 "procedure_type": "procedure"
836 function populateResult(pd) {
837 let icode = pd.subtest.abnormal_flag;
838 switch (pd.subtest.abnormal_flag.toUpperCase()) {
851 "identifier": pd.subtest.root,
852 "extension": pd.subtest.extension
856 "code": pd.subtest.result_code || "",
857 "code_system_name": "LOINC"
861 "date": fDate(pd.date_ordered),
865 "status": pd.order_status,
867 "range": pd.subtest.range //OpenEMR doesn't have high/low so show range as text.
869 /*"reference_range": {
870 "low": pd.subtest.range,
871 "high": pd.subtest.range,
872 "unit": pd.subtest.unit
874 "interpretations": [icode],
875 "value": parseFloat(pd.subtest.result_value) || pd.subtest.result_value || "",
876 "unit": pd.subtest.unit
880 function getResultSet(results) {
882 if (!results) return '';
884 let tResult = results.result[0] || results.result;
887 "identifier": tResult.root,
888 "extension": tResult.extension
894 "date": fDate(tResult.date_ordered),
895 "precision": getPrecision(fDate(tResult.date_ordered))
900 "identifier": "2.16.840.1.113883.4.6",
901 "extension": all.primary_care_provider.provider.npi || "UNK"
906 "last": all.primary_care_provider.provider.lname || "",
907 "first": all.primary_care_provider.provider.fname || ""
912 "name": tResult.test_name,
913 "code": tResult.test_code,
914 "code_system_name": "LOINC"
923 count = isOne(results.result);
928 for (let i in results.result) {
929 theone[i] = populateResult(results.result[i]);
930 many.results.push(theone[i]);
932 } else if (count !== 0) {
933 theone = populateResult(results.result);
934 many.results.push(theone);
936 rs.results = Object.assign(resultSet);
937 rs.results.results = Object.assign(many.results);
941 function getPlanOfCare(pd) {
944 "name": pd.code_text,
946 "code_system_name": "SNOMED CT"
949 "identifier": "9a6d1bac-17d3-4195-89a4-1121bc809b4a"
953 "date": fDate(pd.date),
957 "type": "observation",
961 "subType": pd.description
965 function populateVital(pd) {
968 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.1",
969 "extension": pd.extension_bps
972 "name": "Blood Pressure Systolic",
974 "code_system_name": "LOINC"
976 "status": "completed",
979 "date": fDate(pd.effectivetime),
980 "precision": getPrecision(fDate(pd.effectivetime))
983 "value": parseFloat(pd.bps),
987 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.1",
988 "extension": pd.extension_bpd
991 "name": "Blood Pressure Diastolic",
993 "code_system_name": "LOINC"
995 "status": "completed",
998 "date": fDate(pd.effectivetime),
999 "precision": getPrecision(fDate(pd.effectivetime))
1002 "interpretations": ["Normal"],
1003 "value": parseFloat(pd.bpd),
1007 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.1",
1008 "extension": pd.extension_pulse
1011 "name": "Heart Rate",
1013 "code_system_name": "LOINC"
1015 "status": "completed",
1018 "date": fDate(pd.effectivetime),
1019 "precision": getPrecision(fDate(pd.effectivetime))
1022 "interpretations": ["Normal"],
1023 "value": parseFloat(pd.pulse),
1027 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.1",
1028 "extension": pd.extension_breath
1031 "name": "Respiratory Rate",
1033 "code_system_name": "LOINC"
1035 "status": "completed",
1038 "date": fDate(pd.effectivetime),
1039 "precision": getPrecision(fDate(pd.effectivetime))
1042 "interpretations": ["Normal"],
1043 "value": parseFloat(pd.breath),
1047 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.1",
1048 "extension": pd.extension_temperature
1051 "name": "Body Temperature",
1053 "code_system_name": "LOINC"
1055 "status": "completed",
1058 "date": fDate(pd.effectivetime),
1059 "precision": getPrecision(fDate(pd.effectivetime))
1062 "interpretations": ["Normal"],
1063 "value": parseFloat(pd.temperature),
1067 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.1",
1068 "extension": pd.extension_height
1073 "code_system_name": "LOINC"
1075 "status": "completed",
1078 "date": fDate(pd.effectivetime),
1079 "precision": getPrecision(fDate(pd.effectivetime))
1082 "interpretations": ["Normal"],
1083 "value": parseFloat(pd.height),
1084 "unit": pd.unit_height
1087 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.1",
1088 "extension": pd.extension_weight
1091 "name": "Weight Measured",
1093 "code_system_name": "LOINC"
1095 "status": "completed",
1098 "date": fDate(pd.effectivetime),
1099 "precision": getPrecision(fDate(pd.effectivetime))
1102 "interpretations": ["Normal"],
1103 "value": parseFloat(pd.weight),
1104 "unit": pd.unit_weight
1107 "identifier": "2.16.840.1.113883.3.140.1.0.6.10.14.1",
1108 "extension": pd.extension_BMI
1111 "name": "BMI (Body Mass Index)",
1113 "code_system_name": "LOINC"
1115 "status": "completed",
1118 "date": fDate(pd.effectivetime),
1119 "precision": getPrecision(fDate(pd.effectivetime))
1122 "interpretations": ["Normal"],
1123 "value": parseFloat(pd.BMI),
1128 function populateSocialHistory(pd) {
1131 "low": templateDate(pd.date, "day")
1132 //"high": templateDate(pd.date, "day")
1135 "identifier": pd.sha_extension,
1136 "extension": pd.extension
1141 "value": pd.description
1145 function populateImmunization(pd) {
1149 "date": fDate(pd.administered_on),
1150 "precision": "month"
1154 "identifier": "e6f1ba43-c0ed-4b9b-9f12-f435d8ad8f92",
1155 "extension": pd.extension || "UNK"
1157 "status": "complete",
1160 "name": pd.code_text,
1161 "code": pd.cvx_code,
1162 "code_system_name": "CVX"
1163 /*"translations": [{
1166 "code_system_name": "CVX"
1170 "manufacturer": "UNK"
1174 "name": pd.route_of_administration,
1175 "code": pd.route_code,
1176 "code_system_name": "Medication Route FDA"
1185 "identifier": "2.16.840.1.113883.4.6",
1186 "extension": npiProvider
1193 "street_lines": [pd.address],
1201 "identifier": "2.16.840.1.113883.4.6",
1202 "extension": npiFacility
1204 "name": [pd.facility_name]
1209 "name": "immunization education",
1210 "code": "171044003",
1211 "code_system_name": "SNOMED CT"
1213 "free_text": "Needs Attention for more data."
1218 function populatePayer(pd) {
1221 "identifier": "1fe2cdd0-7aad-11db-9fe1-0800200c9a66"
1225 "identifier": "3e676a50-7aac-11db-9fe1-0800200c9a66"
1229 "code_system_name": "HL7 RoleCode"
1234 "code_system_name": "HL7 RoleCode"
1238 "identifier": "2.16.840.1.113883.19"
1241 "street_lines": ["123 Insurance Road"],
1242 "city": "Blue Bell",
1249 "number": "(781)555-1515",
1250 "type": "work place"
1253 "name": ["Good Health Insurance"],
1255 "street_lines": ["123 Insurance Road"],
1256 "city": "Blue Bell",
1263 "number": "(781)555-1515",
1264 "type": "work place"
1269 "code_system_name": "HL7 RoleCode"
1277 "code_system_name": "HL7 Role"
1280 "identifier": "329fcdf0-7ab3-11db-9fe1-0800200c9a66"
1284 "middle": ["Frankie"],
1289 "street_lines": ["17 Daws Rd."],
1290 "city": "Blue Bell",
1294 "use": "primary home"
1297 "number": "(781)555-1212",
1298 "type": "primary home"
1305 "code_system_name": "HL7 Role"
1309 "identifier": "14d4a520-7aae-11db-9fe1-0800200c9a66",
1310 "extension": "1138345"
1313 "street_lines": ["17 Daws Rd."],
1314 "city": "Blue Bell",
1318 "use": "primary home"
1323 "code_system_name": "HL7 Role"
1336 "identifier": "2.16.840.1.113883.19",
1337 "extension": "1138345"
1340 "street_lines": ["17 Daws Rd."],
1341 "city": "Blue Bell",
1345 "use": "primary home"
1351 "identifier": "f4dce790-8328-11db-9fe1-0800200c9a66"
1355 "name": "Colonoscopy",
1357 "code_system_name": "SNOMED CT"
1364 function populateHeader(pd) {
1368 "identifier": oidFacility,
1369 "extension": "TT988"
1373 "name": "Continuity of Care Document",
1375 "code_system_name": "LOINC"
1378 "2.16.840.1.113883.10.20.22.1.1",
1379 "2.16.840.1.113883.10.20.22.1.2"
1381 "title": "Clinical Health Summary",
1384 "date": fDate(pd.created_time) || "",
1385 "precision": getPrecision(fDate(pd.created_time))
1393 "identifier": "2.16.840.1.113883.4.6",
1394 "extension": pd.author.npi || "UNK"
1399 "last": pd.author.lname,
1400 "first": pd.author.fname
1406 pd.author.streetAddressLine
1408 "city": pd.author.city,
1409 "state": pd.author.state,
1410 "zip": pd.author.postalCode,
1411 "country": pd.author.country
1416 "number": pd.author.telecom,
1430 "root": "2.16.840.1.113883.4.6",
1431 "extension": npiFacility || "UNK"
1435 pd.encounter_provider.facility_name
1440 pd.encounter_provider.facility_street
1442 "city": pd.encounter_provider.facility_city,
1443 "state": pd.encounter_provider.facility_state,
1444 "zip": pd.encounter_provider.facility_postal_code,
1445 "country": pd.encounter_provider.facility_country_code
1450 "number": pd.encounter_provider.facility_phone,
1451 "type": "work primary"
1462 "root": "2.16.840.1.113883.4.6",
1463 "extension": npiFacility || "UNK"
1467 pd.encounter_provider.facility_name
1472 pd.encounter_provider.facility_street
1474 "city": pd.encounter_provider.facility_city,
1475 "state": pd.encounter_provider.facility_state,
1476 "zip": pd.encounter_provider.facility_postal_code,
1477 "country": pd.encounter_provider.facility_country_code
1482 "number": pd.encounter_provider.facility_phone,
1483 "type": "work primary"
1490 "identifier": "2.16.840.1.113883.4.6",
1491 "extension": "999999943252"
1496 "last": pd.data_enterer.lname,
1497 "first": pd.data_enterer.fname
1503 pd.data_enterer.streetAddressLine
1505 "city": pd.data_enterer.city,
1506 "state": pd.data_enterer.state,
1507 "zip": pd.data_enterer.postalCode,
1508 "country": pd.data_enterer.country
1513 "number": pd.data_enterer.telecom,
1514 "type": "work place"
1521 "identifier": "2.16.840.1.113883.19.5",
1522 "extension": "KP00017"
1527 "last": pd.informer.lname || "",
1528 "first": pd.informer.fname || ""
1534 pd.informer.streetAddressLine || ""
1536 "city": pd.informer.city,
1537 "state": pd.informer.state,
1538 "zip": pd.informer.postalCode,
1539 "country": pd.informer.country
1544 "number": pd.informer.telecom || "",
1545 "type": "work place"
1549 "service_event": { // @todo maybe move this to attributed or write z-schema template
1553 "code_system_name": "SNOMED CT"
1558 "precision": getPrecision()
1561 "date": pd.created_time,
1562 "precision": getPrecision()
1571 "identifier": "2.16.840.1.113883.4.6",
1572 "extension": "PseudoMD-1"
1577 "last": pd.information_recipient.lname,
1578 "first": pd.information_recipient.fname
1584 pd.information_recipient.streetAddressLine
1586 "city": pd.information_recipient.city,
1587 "state": pd.information_recipient.state,
1588 "zip": pd.information_recipient.postalCode,
1589 "country": pd.information_recipient.country
1594 "number": pd.information_recipient.telecom,
1595 "type": "work place"
1602 "identifier": "2.16.840.1.113883.19.5.9999.1393"
1606 pd.encounter_provider.facility_name
1611 pd.encounter_provider.facility_street
1613 "city": pd.encounter_provider.facility_city,
1614 "state": pd.encounter_provider.facility_state,
1615 "zip": pd.encounter_provider.facility_postal_code,
1616 "country": pd.encounter_provider.facility_country_code
1621 "number": pd.encounter_provider.facility_phone,
1622 "type": "primary work"
1631 "code_system_name": "Provider Codes"
1637 "name": "Primary Performer",
1639 "code_system_name": "Provider Role"
1648 function getMeta(pd) {
1654 "identifier": "2.16.840.1.113883.19.5.99999.1",
1655 "extension": "TT988"
1658 "confidentiality": "Normal",
1660 "identifier": "2.16.840.1.113883.19.5.99999.19",
1661 "extension": "sTT988"
1667 function genCcda(pd) {
1675 npiProvider = all.primary_care_provider.provider[0].npi;
1676 oidFacility = all.encounter_provider.facility_oid;
1677 npiFacility = all.encounter_provider.facility_npi;
1680 let demographic = populateDemographic(pd.patient, pd.guardian, pd);
1681 Object.assign(demographic, populateProviders());
1682 data.demographics = Object.assign(demographic);
1687 count = isOne(pd.history_physical.vitals_list.vitals);
1692 for (let i in pd.history_physical.vitals_list.vitals) {
1693 theone = populateVital(pd.history_physical.vitals_list.vitals[i]);
1694 many.vitals.push.apply(many.vitals, theone);
1696 } else if (count === 1) {
1697 theone = populateVital(pd.history_physical.vitals_list.vitals);
1698 many.vitals.push(theone);
1700 data.vitals = Object.assign(many.vitals);
1704 meds.medications = [];
1706 count = isOne(pd.medications.medication);
1711 for (let i in pd.medications.medication) {
1712 m[i] = populateMedication(pd.medications.medication[i]);
1713 meds.medications.push(m[i]);
1715 } else if (count !== 0) {
1716 m = populateMedication(pd.medications.medication);
1717 meds.medications.push(m);
1719 data.medications = Object.assign(meds.medications);
1723 encs.encounters = [];
1725 count = isOne(pd.encounter_list.encounter);
1730 for (let i in pd.encounter_list.encounter) {
1731 enc[i] = populateEncounter(pd.encounter_list.encounter[i]);
1732 encs.encounters.push(enc[i]);
1734 } else if (count !== 0) {
1735 enc = populateEncounter(pd.encounter_list.encounter);
1736 encs.encounters.push(enc);
1738 data.encounters = Object.assign(encs.encounters);
1743 allergies.allergies = [];
1745 count = isOne(pd.allergies.allergy);
1750 for (let i in pd.allergies.allergy) {
1751 allergy[i] = populateAllergy(pd.allergies.allergy[i]);
1752 allergies.allergies.push(allergy[i]);
1754 } else if (count !== 0) {
1755 allergy = populateAllergy(pd.allergies.allergy);
1756 allergies.allergies.push(allergy);
1758 data.allergies = Object.assign(allergies.allergies);
1763 problems.problems = [];
1765 count = isOne(pd.problem_lists.problem);
1770 for (let i in pd.problem_lists.problem) {
1771 problem[i] = populateProblem(pd.problem_lists.problem[i], pd);
1772 problems.problems.push(problem[i]);
1774 } else if (count !== 0) {
1775 problem = populateProblem(pd.problem_lists.problem);
1776 problems.problems.push(problem);
1778 data.problems = Object.assign(problems.problems);
1782 many.procedures = [];
1784 count = isOne(pd.procedures.procedure);
1789 for (let i in pd.procedures.procedure) {
1790 theone[i] = populateProcedure(pd.procedures.procedure[i]);
1791 many.procedures.push(theone[i]);
1793 } else if (count !== 0) {
1794 theone = populateProcedure(pd.procedures.procedure);
1795 many.procedures.push(theone);
1798 data.procedures = Object.assign(many.procedures);
1801 data.results = Object.assign(getResultSet(pd.results, pd)['results']);
1806 many.immunizations = [];
1808 count = isOne(pd.immunizations.immunization);
1813 for (let i in pd.immunizations.immunization) {
1814 theone[i] = populateImmunization(pd.immunizations.immunization[i]);
1815 many.immunizations.push(theone[i]);
1817 } else if (count !== 0) {
1818 theone = populateImmunization(pd.immunizations.immunization);
1819 many.immunizations.push(theone);
1821 data.immunizations = Object.assign(many.immunizations);
1825 many.plan_of_care = [];
1827 count = isOne(pd.planofcare); // ccm doesn't send array of items
1832 for (let i in pd.planofcare.item) {
1833 theone[i] = getPlanOfCare(pd.planofcare.item[i]);
1834 many.plan_of_care.push(theone[i]);
1836 } else if (count !== 0) {
1837 theone = getPlanOfCare(pd.planofcare.item);
1838 many.plan_of_care.push(theone);
1841 data.plan_of_care = Object.assign(many.plan_of_care);
1845 many.social_history = [];
1847 count = isOne(pd.history_physical.social_history.history_element);
1852 for (let i in pd.history_physical.social_history.history_element) {
1854 theone[i] = populateSocialHistory(pd.history_physical.social_history.history_element[i]);
1855 many.social_history.push(theone[i]);
1857 } else if (count !== 0) {
1858 theone = populateSocialHistory(pd.history_physical.social_history.history_element);
1859 many.social_history.push(theone);
1862 data.social_history = Object.assign(many.social_history);
1864 // ------------------------------------------ End Sections ----------------------------------------//
1866 doc.data = Object.assign(data);
1867 let meta = getMeta(pd);
1868 let header = populateHeader(pd);
1870 meta.ccda_header = Object.assign(header);
1871 doc.meta = Object.assign(meta);
1872 let xml = bbg.generateCCD(doc);
1875 /*fs.writeFile("bbtest.json", JSON.stringify(doc, null, 4), function (err) {
1877 return console.log(err);
1879 console.log("Json saved!");
1881 fs.writeFile("bbtest.xml", xml, function (err) {
1883 return console.log(err);
1885 console.log("Xml saved!");
1891 function processConnection(connection) {
1892 conn = connection; // make it global
1893 var remoteAddress = conn.remoteAddress + ':' + conn.remotePort;
1894 //console.log(remoteAddress);
1895 conn.setEncoding('utf8');
1897 function eventData(xml) {
1898 xml = xml.replace(/(\u000b|\u001c)/gm, "").trim();
1899 // Sanity check from service manager
1900 if (xml === 'status' || xml.length < 80) {
1901 conn.write("statusok" + String.fromCharCode(28) + "\r\r");
1906 // ---------------------start--------------------------------
1908 xml = xml.toString().replace(/\t\s+/g, ' ').trim();
1910 to_json(xml, function (error, data) {
1911 // console.log(JSON.stringify(data, null, 4));
1912 if (error) { // need try catch
1913 console.log('toJson error: ' + error + 'Len: ' + xml.length);
1916 doc = genCcda(data.CCDA);
1919 doc = headReplace(doc);
1920 doc = doc.toString().replace(/(\u000b|\u001c|\r)/gm, "").trim();
1923 let numChunks = Math.ceil(doc.length / 1024);
1924 for (let i = 0, o = 0; i < numChunks; ++i, o += 1024) {
1925 chunk = doc.substr(o, 1024);
1929 conn.write(String.fromCharCode(28) + "\r\r" + '');
1934 function eventCloseConn() {
1935 //console.log('connection from %s closed', remoteAddress);
1938 function eventErrorConn(err) {
1939 //console.log('Connection %s error: %s', remoteAddress, err.message);
1942 // Connection Events //
1943 conn.on('data', eventData);
1944 conn.once('close', eventCloseConn);
1945 conn.on('error', eventErrorConn);
1948 function setUp(server) {
1949 server.on('connection', processConnection);
1950 server.listen(6661, 'localhost', function () {
1951 //console.log('server listening to %j', server.address());