Skip to content

Commit

Permalink
Interim development of a clinical decision making engine.
Browse files Browse the repository at this point in the history
Engine depends on rules, filters, targets, and actions. Basically,
if a patient fits in the filter, and the target(s) is false, then the
action(s) happens. The current scheme allows one filter
and multiple targets and actions per rule, is very flexible, and supports
internationalization. Engine is used for clinical decision support,
clinical quality measures (CQM) and patient reminders.

FUNCTIONALITY:
1) Clinical Decision Support widget can be found in the top right
   of the patient summary screen.
2) Clinical Reminder widget can be found in bottom left (above vitals
   widget) of the patient summary screen.
3) Clinical Reminder page can be found at Administration->Patient
   Reminders. The batch to send the messages can be run from this script
   by clicking the Send REminder Batch button.
4) CQM report can be found at Reports->Clinic->Quality Measures.

TODO:
1. Finish rules for the 10 CQM rules (will require algorithm adjustments
   to deal with the encounter types and frequencies that are used in the
   CQM rules).
2. Ensure NIST is accomplished for CQM and clinical decision support.
3. Get the email and voice patient reminder mechanisms to work (may ask
   Visolve to help with testing the modified mavix//voice email scripts).
4. Ensure NIST is accomplished for the patient reminders.
5. Add a admin gui to allow per patient customization (note the
   mechanism to do this already exist, so just need a simple gui for it)
6. Add mechanism to allow multiple filters per rule.
7. Add a admin gui to allow rule modification and creation of new rules.
8. Add a mechanism for plans.
  • Loading branch information
bradymiller committed Nov 26, 2010
1 parent 1dc940d commit 2d9fba1
Show file tree
Hide file tree
Showing 18 changed files with 3,919 additions and 13 deletions.
250 changes: 250 additions & 0 deletions interface/batchcom/batch_phone_notification.php
@@ -0,0 +1,250 @@
<?php
// Copyright (C) 2010 Maviq <info@maviq.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
////////////////////////////////////////////////////////////////////
// Package: cron_phone_notification
// Purpose: to be run by cron every hour, look for appointments
// in the pre-notification period and send an phone reminder
// Based on cron_email_notification by Larry Lart
// Created by:
// Updated by: Maviq on 01/12/2010
////////////////////////////////////////////////////////////////////

$backpic = "";
//phone notification
$ignoreAuth=1;

//Set the working directory to the path of the file
$current_dir = dirname($_SERVER['SCRIPT_FILENAME']);
chdir($current_dir);

//SANITIZE ALL ESCAPES
$sanitize_all_escapes=true;

//STOP FAKE REGISTER GLOBALS
$fake_register_globals=false;

require_once("../../interface/globals.php");
require_once("$srcdir/maviq_phone_api.php");
require_once("$srcdir/formdata.inc.php");

$type = "Phone";
$before_trigger_hours = 72; // 3 days is default
//Get the values from Global
$before_trigger_hours = $GLOBALS['phone_notification_hour'];
//set up the phone notification settings for external phone service
$phone_url = $GLOBALS['phone_gateway_url'] ;
$phone_id = $GLOBALS['phone_gateway_username'];
$phone_token = $GLOBALS['phone_gateway_password'];
$phone_time_range = $GLOBALS['phone_time_range'];

//get the facility_id-message map
$facilities = cron_getFacilitiesMap();
//print_r($facilities);
$fac_phone_map = $facilities['phone_map'];
$fac_msg_map = $facilities['msg_map'];

// get patient data for send alert
$db_patient = cron_getPhoneAlertpatientData($type, $before_trigger_hours);
echo "<br>" . htmlspecialchars( xl("Total Records Found") . ": " . count($db_patient), ENT_QUOTES);

//Create a new instance of the phone service client
$client = new MaviqClient($phone_id, $phone_token, $phone_url);

for($p=0;$p<count($db_patient);$p++)
{
$prow =$db_patient[$p];

//Get the apptDate and apptTime
$p_date = $prow['pc_eventDate'];
//Need to format date to m/d/Y for Maviq API
$pieces = explode("-",$p_date);
$appt_date = date("m/d/Y", mktime( 0,0,0,$pieces[1],$pieces[2],$pieces[0]));
$appt_time = $prow['pc_startTime'];
//get the greeting
$greeting = $fac_msg_map[$prow['pc_facility']];
if ($greeting == null) {
//Use the default when the message is not found
$greeting = $GLOBALS['phone_appt_message']['Default'];
}
//Set up the parameters for the call
$data = array(
"firstName" => $prow['fname'],
"lastName" => $prow['lname'],
"phone" => $prow['phone_home'],
"apptDate" => $appt_date,
"apptTime" => $appt_time,
"doctor" => $prow['pc_aid'],
"greeting" => $greeting,
"timeRange" => $phone_time_range,
"type" => "appointment",
"timeZone" => date('P'),
"callerId" => $fac_phone_map[$prow['pc_facility']]
);

//Make the call
$response = $client->sendRequest("appointment", "POST", $data);

// check response for success or error
if($response->IsError) {
$strMsg = "Error starting phone call for {$prow['fname']} | {$prow['lname']} | {$prow['phone_home']} | {$appt_date} | {$appt_time} | {$response->ErrorMessage}\n";
}
else {
$strMsg = "\n========================".$type." || ".date("Y-m-d H:i:s")."=========================";
$strMsg .= "\nPhone reminder sent successfully: {$prow['fname']} | {$prow['lname']} | | {$prow['phone_home']} | {$appt_date} | {$appt_time} ";
// insert entry in notification_log table
cron_InsertNotificationLogEntry($prow,$greeting,$phone_url);

//update entry >> pc_sendalertsms='Yes'
cron_updateentry($type,$prow['pid'],$prow['pc_eid']);

}

//echo $strMsg;
WriteLog( $strMsg );

}

sqlClose();

////////////////////////////////////////////////////////////////////
// Function: cron_updateentry
// Purpose: update status yes if alert send to patient
////////////////////////////////////////////////////////////////////
function cron_updateentry($type,$pid,$pc_eid)
{

$query = "update openemr_postcalendar_events set ";

// larry :: and here again same story - this time for sms pc_sendalertsms - no such field in the table
if($type=='SMS')
$query.=" pc_sendalertsms='YES' ";
elseif ($type=='Email')
$query.=" pc_sendalertemail='YES' ";
//Added by Yijin for phone reminder.. Uses the same field as SMS.
elseif($type=='Phone')
$query.=" pc_sendalertsms='YES' ";

$query .=" where pc_pid=? and pc_eid=? ";
//echo "<br>".$query;
$db_sql = (sqlStatement($query, array($pid, $pc_eid)));
}

////////////////////////////////////////////////////////////////////
// Function: cron_getPhoneAlertpatientData
// Purpose: get patient data for send to alert
////////////////////////////////////////////////////////////////////
function cron_getPhoneAlertpatientData( $type, $trigger_hours )
{

//Added by Yijin 1/12/10 to handle phone reminders. Patient needs to have hipaa Voice flag set to yes and a home phone
if($type=='Phone'){
$ssql = " and pd.hipaa_voice='YES' and pd.phone_home<>'' and ope.pc_sendalertsms='NO' and ope.pc_apptstatus != '*' ";

$check_date = date("Y-m-d", mktime(date("H")+$trigger_hours, 0, 0, date("m"), date("d"), date("Y")));

}

$patient_field = "pd.pid,pd.title,pd.fname,pd.lname,pd.mname,pd.phone_cell,pd.email,pd.hipaa_allowsms,pd.hipaa_allowemail,pd.phone_home,pd.hipaa_voice,";
$ssql .= " and (ope.pc_eventDate=?)";

$query = "select $patient_field pd.pid,ope.pc_eid,ope.pc_pid,ope.pc_title,
ope.pc_hometext,ope.pc_eventDate,ope.pc_endDate,
ope.pc_duration,ope.pc_alldayevent,ope.pc_startTime,ope.pc_endTime,ope.pc_facility
from
openemr_postcalendar_events as ope ,patient_data as pd
where
ope.pc_pid=pd.pid $ssql
order by
ope.pc_eventDate,ope.pc_endDate,pd.pid";

$db_patient = (sqlStatement($query) , array($check_date) );
$patient_array = array();
$cnt=0;
while ($prow = sqlFetchArray($db_patient))
{
$patient_array[$cnt] = $prow;
$cnt++;
}
return $patient_array;
}

////////////////////////////////////////////////////////////////////
// Function: cron_InsertNotificationLogEntry
// Purpose: insert log entry in table
////////////////////////////////////////////////////////////////////
function cron_InsertNotificationLogEntry($prow,$phone_msg,$phone_gateway)
{
$patient_info = $prow['title']." ".$prow['fname']." ".$prow['mname']." ".$prow['lname']."|||".$prow['phone_home'];

$message = $phone_msg;

$sql_loginsert = "INSERT INTO `notification_log` ( `iLogId` , `pid` , `pc_eid` , `message`, `type` , `patient_info` , `smsgateway_info` , `pc_eventDate` , `pc_endDate` , `pc_startTime` , `pc_endTime` , `dSentDateTime` ) VALUES ";
$sql_loginsert .= "(NULL , ?, ?, ?, 'Phone', ?, ?, ?, ?, ?, ?, ?)";
$db_loginsert = ( sqlStatement( $sql_loginsert ), array($prow[pid], $prow[pc_eid], $message, $patient_info, $phone_gateway, $prow[pc_eventDate], $prow[pc_endDate], $prow[pc_startTime], $prow[pc_endTime], date("Y-m-d H:i:s")) );
}

////////////////////////////////////////////////////////////////////
// Function: WriteLog
// Purpose: written log into file
////////////////////////////////////////////////////////////////////
function WriteLog( $data )
{
$log_file = $GLOBALS['phone_reminder_log_dir'];

if ($log_file != null) {

$filename = $log_file . "/"."phone_reminder_cronlog_".date("Ymd").".html";

if (!$fp = fopen($filename, 'a'))
{
print "Cannot open file ($filename)";

}else {

$sdata = "\n====================================================================\n";

if (!fwrite($fp, $data.$sdata))
{
print "Cannot write to file ($filename)";
}

fclose($fp);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: cron_getFacilities
// Purpose: get facilities data once and store in map
////////////////////////////////////////////////////////////////////
function cron_getFacilitiesMap()
{
//get the facility_name-message map from Globals
$message_map = $GLOBALS['phone_appt_message'];
//create a new array to store facility_id to message map
$facility_msg_map = array();
$facility_phone_map = array();
//get facilities from the database
$query = "select fac.id, fac.name, fac.phone from facility as fac";
$db_res = (sqlStatement($query));
while ($prow = sqlFetchArray($db_res))
{
$facility_msg_map[$prow['id']] = $message_map[$prow['name']];
$facility_phone_map[$prow['id']] = $prow['phone'];
}

$facility_map = array(
'msg_map' => $facility_msg_map,
'phone_map' => $facility_phone_map
);

return $facility_map;

}
?>

73 changes: 73 additions & 0 deletions interface/batchcom/batch_reminders.php
@@ -0,0 +1,73 @@
<?php
// Copyright (C) 2010 OpenEMR Support LLC
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

//SANITIZE ALL ESCAPES
$sanitize_all_escapes=true;

//STOP FAKE REGISTER GLOBALS
$fake_register_globals=false;

require_once(dirname(__FILE__)."/../../interface/globals.php");
require_once ($GLOBALS['srcdir'] . "/classes/postmaster.php");
require_once ($GLOBALS['srcdir'] . "/maviq_phone_api.php");
require_once($GLOBALS['srcdir'] . "/reminders.php");
?>

<html>
<head>
<?php html_header_show();?>
<link rel="stylesheet" href="<?php echo $css_header;?>" type="text/css">
<link rel="stylesheet" href="batchcom.css" type="text/css">
</head>
<body class="body_top">
<span class="title"><?php echo htmlspecialchars(xl('Patient Reminder Batch Job'), ENT_NOQUOTES)?></span>

<?php
// Collect the sender information
// TODO
// $sender_name
// $email_address
//
?>

<table>
<tr>
<td class='text' align='left' colspan="3"><br>

<?php $update_rem_log = update_reminders(); ?>

<span class="text"><?php echo htmlspecialchars(xl('The patient reminders have been updated'), ENT_NOQUOTES) . ":"?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total active actions'), ENT_NOQUOTES) . ": " . $update_rem_log['total_active_actions'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total active reminders before update'), ENT_NOQUOTES) . ": " . $update_rem_log['total_pre_active_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total unsent reminders before update'), ENT_NOQUOTES) . ": " . $update_rem_log['total_pre_unsent_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total active reminders after update'), ENT_NOQUOTES) . ": " . $update_rem_log['total_post_active_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total unsent reminders after update'), ENT_NOQUOTES) . ": " . $update_rem_log['total_post_unsent_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total new reminders'), ENT_NOQUOTES) . ": " . $update_rem_log['number_new_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total updated reminders'), ENT_NOQUOTES) . ": " . $update_rem_log['number_updated_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total inactivated reminders'), ENT_NOQUOTES) . ": " . $update_rem_log['number_inactivated_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total unchanged reminders'), ENT_NOQUOTES) . ": " . $update_rem_log['number_unchanged_reminders'];?></span><br>

<?php $send_rem_log = send_reminders(); ?>

<br><span class="text"><?php echo htmlspecialchars(xl('The patient reminders have been sent'), ENT_NOQUOTES) . ":"?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total unsent reminders before sending process'), ENT_NOQUOTES) . ": " . $send_rem_log['total_pre_unsent_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total unsent reminders after sending process'), ENT_NOQUOTES) . ": " . $send_rem_log['total_post_unsent_reminders'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total successful reminders sent via email'), ENT_NOQUOTES) . ": " . $send_rem_log['number_success_emails'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total failed reminders sent via email'), ENT_NOQUOTES) . ": " . $send_rem_log['number_failed_emails'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total successful reminders sent via phone'), ENT_NOQUOTES) . ": " . $send_rem_log['number_success_calls'];?></span><br>
<span class="text"><?php echo htmlspecialchars(xl('Total failed reminders sent via phone'), ENT_NOQUOTES) . ": " . $send_rem_log['number_unchanged_reminders'];?></span><br>

<br><span class="text"><?php echo htmlspecialchars(xl('(Email delivery is immediate, while automated VOIP is sent to the service provider for further processing.)'), ENT_NOQUOTES)?></span><br>

<br><input type="button" value="<?php echo htmlspecialchars(xl('Close'), ENT_QUOTES); ?>" onClick="window.close()"><br><br><br>
</td>
</tr>
</table>
<br><br>
</body>
</html>

6 changes: 6 additions & 0 deletions interface/main/left_nav.php
Expand Up @@ -990,6 +990,7 @@ function selpopup(selobj) {
<?php if (acl_check('admin', 'acl' )) genMiscLink('RTop','adm','0',xl('ACL'),'usergroup/adminacl.php'); ?>
<?php if (acl_check('admin', 'super' )) genMiscLink('RTop','adm','0',xl('Files'),'super/manage_site_files.php'); ?>
<?php if (acl_check('admin', 'super' )) genMiscLink('RTop','adm','0',xl('Backup'),'main/backup.php'); ?>
<?php if (acl_check('admin', 'super' )) genMiscLink('RTop','adm','0',xl('Patient Reminders'),'patient_file/reminder/patient_reminders.php?mode=admin&patient_id='); ?>
<?php if ( ($GLOBALS['include_de_identification']) && (acl_check('admin', 'super' )) ) genMiscLink('RTop','adm','0',xl('De Identification'),'de_identification_forms/de_identification_screen1.php'); ?>
<?php if ( ($GLOBALS['include_de_identification']) && (acl_check('admin', 'super' )) ) genMiscLink('RTop','adm','0',xl('Re Identification'),'de_identification_forms/re_identification_input_screen.php'); ?>
<?php if (acl_check('admin', 'super') && !empty($GLOBALS['code_types']['IPPF'])) genMiscLink('RTop','adm','0',xl('Export'),'main/ippf_export.php'); ?>
Expand All @@ -1015,6 +1016,11 @@ function selpopup(selobj) {
<?php genMiscLink('RTop','rep','0',xl('Referrals'),'reports/referrals_report.php'); ?>
</ul>
</li>
<li><span><?php xl('Clinic','e') ?></span>
<ul>
<?php genMiscLink('RTop','rep','0',xl('Quality Measures'),'reports/cqm.php'); ?>
</ul>
</li>
<li class="open"><span><?php xl('Visits','e') ?></span>
<ul>
<?php if (!$GLOBALS['disable_calendar']) genMiscLink('RTop','rep','0',xl('Appointments'),'reports/appointments_report.php'); ?>
Expand Down

0 comments on commit 2d9fba1

Please sign in to comment.