Skip to content

Commit

Permalink
Manage CCDA data transmission to service (#5658)
Browse files Browse the repository at this point in the history
* Manage CCDA data transmission to service
- CCM chunk send data to service
- add data stacking buffer to service
- honor EOT(file separator character) in service
- repace deprecated js substr with substring

* - add reading from stack by a delimiter method for future use. Helps make clear how fetchBuffer is supposed to work.
  • Loading branch information
sjpadgett committed Aug 3, 2022
1 parent dbe03ee commit 6e4effd
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 45 deletions.
97 changes: 72 additions & 25 deletions ccdaservice/serveccda.js
Expand Up @@ -26,6 +26,48 @@ var webRoot = "";
var authorDateTime = '';
var documentLocation = '';

class DataStack {
constructor(delimiter) {
this.delimiter = delimiter;
this.buffer = "";
}

endOfCcda() {
return this.buffer.length === 0 || this.buffer.indexOf(this.delimiter) === -1;
}

pushToStack(data) {
this.buffer += data;
}

fetchBuffer() {
const delimiterIndex = this.buffer.indexOf(this.delimiter);
if (delimiterIndex !== -1) {
const bufferMsg = this.buffer.slice(0, delimiterIndex);
this.buffer = this.buffer.replace(bufferMsg + this.delimiter, "");
return bufferMsg;
}
return null
}

returnData() {
return this.fetchBuffer();
}

clearStack() {
this.buffer = "";
}

readStackByDelimiter(delimiter) {
let backup = this.delimiter;
let part = '';
this.delimiter = delimiter;
part = this.fetchBuffer();
this.delimiter = backup;
return part;
}
}

function trim(s) {
if (typeof s === 'string') return s.trim();
return s;
Expand Down Expand Up @@ -982,7 +1024,7 @@ function populateEncounter(pd) {
}

function populateAllergy(pd) {

if (!pd) {
return {
"no_know_allergies": "No Known Allergies",
Expand Down Expand Up @@ -1654,13 +1696,13 @@ function getFunctionalStatus(pd) {
let functionalStatusAuthor = {
"code": {
"name": all.author.physician_type || '',
"code": all.author.physician_type_code || '',
"code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
"code": all.author.physician_type_code || '',
"code_system": all.author.physician_type_system, "code_system_name": all.author.physician_type_system_name
},
"date_time": {
"point": {
"date": authorDateTime,
"precision": "tz"
"precision": "tz"
}
},
"identifiers": [
Expand All @@ -1669,13 +1711,13 @@ function getFunctionalStatus(pd) {
"extension": all.author.npi ? all.author.npi : ''
}
],
"name": [
"name": [
{
"last": all.author.lname,
"first": all.author.fname
}
],
"organization": [
"organization": [
{
"identity": [
{
Expand All @@ -1696,7 +1738,7 @@ function getFunctionalStatus(pd) {
"identifiers": [{
"identifier": "9a6d1bac-17d3-4195-89a4-1121bc809000"
}],

"observation": {
"value": {
"name": pd.code_text !== "NULL" ? cleanText(pd.code_text) : "",
Expand Down Expand Up @@ -2178,7 +2220,7 @@ function populateSocialHistory(pd) {
}
]
}
,"gender_author": {
, "gender_author": {
"code": {
"name": all.patient.author.physician_type || '',
"code": all.patient.author.physician_type_code || '',
Expand Down Expand Up @@ -3244,14 +3286,14 @@ function genCcda(pd) {
if (err) {
return console.log(err);
}
console.log("Json saved!");
//console.log("Json saved!");
});

fs.writeFile(place + "ccda.xml", xml, function (err) {
if (err) {
return console.log(err);
}
console.log("Xml saved!");
//console.log("Xml saved!");
});
}
}
Expand All @@ -3267,22 +3309,19 @@ function processConnection(connection) {
let xml_complete = "";

function eventData(xml) {
xml = xml.replace(/(\u000b|\u001c)/gm, "").trim();
// Sanity check from service manager
if (xml === 'status' || xml.length < 80) {
conn.write("statusok" + String.fromCharCode(28) + "\r\r");
conn.end('');
return;
}
xml_complete += xml.toString();
if (xml.toString().match(/<\/CCDA>$/g)) {
// ---------------------start--------------------------------
xml_complete = xml.toString();
//console.log("length: " + xml.length + " " + xml_complete);
// ensure we have an array start and end
if (xml_complete.match(/^<CCDA/g) && xml_complete.match(/<\/CCDA>$/g)) {
let doc = "";
let xslUrl = "";
xml_complete = xml_complete.replace(/(\u000b|\u001c)/gm, "").trim();
// let's not allow windows CR/LF
xml_complete = xml_complete.replace(/[\r\n]/gm, '').trim();
xml_complete = xml_complete.replace(/\t\s+/g, ' ').trim();
// convert xml data set for document to json array
to_json(xml_complete, function (error, data) {
// console.log(JSON.stringify(data, null, 4));
//console.log(JSON.stringify(data, null, 4));
if (error) { // need try catch
console.log('toJson error: ' + error + 'Len: ' + xml_complete.length);
return;
Expand All @@ -3296,11 +3335,10 @@ function processConnection(connection) {

doc = headReplace(doc, xslUrl);
doc = doc.toString().replace(/(\u000b|\u001c|\r)/gm, "").trim();
//console.log(doc);
let chunk = "";
let numChunks = Math.ceil(doc.length / 1024);
for (let i = 0, o = 0; i < numChunks; ++i, o += 1024) {
chunk = doc.substr(o, 1024);
chunk = doc.substring(o, o + 1024);
conn.write(chunk);
}
conn.write(String.fromCharCode(28) + "\r\r" + '');
Expand All @@ -3317,7 +3355,16 @@ function processConnection(connection) {
}

// Connection Events //
conn.on('data', eventData);
// CCM will send two File Separator characters to mark end of array.
let received = new DataStack(String.fromCharCode(28));
conn.on("data", data => {
received.pushToStack(data);
while (!received.endOfCcda() && data.length > 0) {
data = "";
eventData(received.returnData())
}
});

conn.once('close', eventCloseConn);
conn.on('error', eventErrorConn);
}
Expand All @@ -3332,7 +3379,7 @@ function setUp(server) {
// start up listener for requests from CCM or others.
setUp(server);

/* For future use in header. Do not remove! */
/* ---------------------------------For future use in header. Do not remove!-------------------------------------------- */
/*"data_enterer": {
"identifiers": [
{
Expand Down
Expand Up @@ -4,7 +4,7 @@
* CcdaServiceDocumentRequestor handles the communication with the node ccda service in sending and receiving data
* over the socket.
*
* @package openemr
* @package openemr
* @link http://www.open-emr.org
* @author Stephen Nielson <snielson@discoverandchange.com>
* @copyright Copyright (c) 2022 Discover and Change <snielson@discoverandchange.com>
Expand All @@ -28,7 +28,6 @@ public function socket_get($data)
if ($socket === false) {
throw new CcdaServiceConnectionException("Socket Creation Failed");
}

// Let's check if server is already running but suppress warning with @ operator
$server_active = @socket_connect($socket, "localhost", "6661");

Expand Down Expand Up @@ -74,31 +73,38 @@ public function socket_get($data)
throw new CcdaServiceConnectionException("Please Enable C-CDA Alternate Service in Global Settings");
}
}

$data = chr(11) . $data . chr(28) . "\r";
if (strlen($data) > 1024 * 128) {
throw new CcdaServiceConnectionException("Export document exceeds the maximum size of 128KB");
}
// Write to socket!
if (strlen($data) > 1024 * 64) {
$data1 = substr($data, 0, floor(strlen($data) / 2));
$data2 = substr($data, floor(strlen($data) / 2));
$out = socket_write($socket, $data1, strlen($data1));
// give distance a chance to clear buffer
// we could handshake with a little effort
sleep(1);
$out = socket_write($socket, $data2, strlen($data2));
} else {
$out = socket_write($socket, $data, strlen($data));
// add file separator character for server end of message
$data = $data . chr(28) . chr(28);
$len = strlen($data);
// Set default buffer size to target data array size.
$good_buf = socket_set_option($socket, SOL_SOCKET, SO_SNDBUF, $len);
if ($good_buf === false) { // Can't set buffer
error_log("Failed to set socket buffer to " . $len);
}
// make writeSize chunk either the size set above or the default buffer size (64Kb).
$writeSize = socket_get_option($socket, SOL_SOCKET, SO_SNDBUF);
$pos = 0;
$currentCounter = 0;
$maxLineAttempts = ($len / $writeSize) + 1;
do {
$line = substr($data, $pos, min($writeSize, $len - $pos));
$out = socket_write($socket, $line, strlen($line));
if ($out !== false) {
$pos += $out; // bytes written lets advance our position
} else {
break;
}
// pause for the receiving side
usleep(200000);
} while ($out !== false && $pos < $len && $currentCounter++ <= $maxLineAttempts);

socket_set_nonblock($socket);
//Read from socket!
//Read back rendered document from node service!
do {
$line = "";
$line = trim(socket_read($socket, 1024, PHP_NORMAL_READ));
$output .= $line;
} while (!empty($line) && $line !== false);
} while (!empty($line));

$output = substr(trim($output), 0, strlen($output) - 1);
// Close and return.
Expand Down

0 comments on commit 6e4effd

Please sign in to comment.