CDR module work on the CQM report requirements:
[openemr.git] / library / clinical_rules.php
blob09af895ebc3589d1b8198d41a5996d8bf060b50e
1 <?php
2 // Copyright (C) 2011 by following authors:
3 // -Brady Miller <brady@sparmy.com>
4 // -Ensofttek, LLC
5 // -Medical Information Integration, LLC
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // Functions are kept here that will support the clinical rules.
14 require_once(dirname(__FILE__) . "/patient.inc");
15 require_once(dirname(__FILE__) . "/forms.inc");
16 require_once(dirname(__FILE__) . "/formdata.inc.php");
17 require_once(dirname(__FILE__) . "/options.inc.php");
19 // Display the clinical summary widget.
20 // Parameters:
21 // $patient_id - pid of selected patient
22 // $mode - choose either 'reminders-all' or 'reminders-due' (required)
23 // $dateTarget - target date. If blank then will test with current date as target.
24 // $organize_mode - Way to organize the results (default or plans)
25 function clinical_summary_widget($patient_id,$mode,$dateTarget='',$organize_mode='default') {
27 // Set date to current if not set
28 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
30 // Collect active actions
31 $actions = test_rules_clinic('','passive_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
33 // Display the actions
34 foreach ($actions as $action) {
36 // Deal with plan names first
37 if ($action['is_plan']) {
38 echo "<br><b>";
39 echo htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
40 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
41 echo "</b><br>";
42 continue;
45 if ($action['custom_flag']) {
46 // Start link for reminders that use the custom rules input screen
47 echo "<a href='../rules/patient_data.php?category=" .
48 htmlspecialchars( $action['category'], ENT_QUOTES) . "&item=" .
49 htmlspecialchars( $action['item'], ENT_QUOTES) .
50 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
52 else if ($action['clin_rem_link']) {
53 // Start link for reminders that use the custom rules input screen
54 echo "<a href='../../../" . $action['reminder_message'] .
55 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
57 else {
58 // continue, since no link will be created
61 // Display Reminder Details
62 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
63 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
65 if ($action['custom_flag'] || $action['clin_rem_link']) {
66 // End link for reminders that use an html link
67 echo "</a>";
70 // Display due status
71 if ($action['due_status']) {
72 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
73 if ($action['due_status'] == "past_due") {
74 echo "&nbsp;&nbsp;(<span style='color:red'>";
76 else if ($action['due_status'] == "due") {
77 echo "&nbsp;&nbsp;(<span style='color:purple'>";
79 else if ($action['due_status'] == "not_due") {
80 echo "&nbsp;&nbsp;(<span style='color:green'>";
82 else {
83 echo "&nbsp;&nbsp;(<span>";
85 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
87 else {
88 echo "<br>";
94 // Test the clinic rules of entire clinic and create a report or patient reminders
95 // (can also test on one patient or patients of one provider)
96 // Parameters:
97 // $provider - id of a selected provider. If blank, then will test entire clinic. If 'collate', then will test each
98 // provider separately in entire clinic.
99 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules.
100 // $dateTarget - target date. If blank then will test with current date as target.
101 // $mode - choose either 'report' or 'reminders-all' or 'reminders-due' (required)
102 // $patient_id - pid of patient. If blank then will check all patients.
103 // $plan - test for specific plan only
104 // $organize_mode - Way to organize the results (default, plans)
105 // 'default':
106 // Returns a two-dimensional array of results organized by rules:
107 // reminders-due mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
108 // reminders-all mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
109 // report mode - returns an array of rows for the Clinical Quality Measures (CQM) report
110 // 'plans':
111 // Returns similar to default, but organizes by the active plans
113 function test_rules_clinic($provider='',$type='',$dateTarget='',$mode='',$patient_id='',$plan='',$organize_mode='default') {
115 // Set date to current if not set
116 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
118 // Prepare the results array
119 $results = array();
121 // If set the $provider to collate, then run through this function recursively.
122 if ($provider == "collate") {
123 // First, collect an array of all providers
124 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
125 $ures = sqlStatement($query);
126 // Second, run through each provider recursively
127 while ($urow = sqlFetchArray($ures)) {
128 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode);
129 if (!empty($newResults)) {
130 $provider_item['is_provider'] = TRUE;
131 $provider_item['prov_lname'] = $urow['lname'];
132 $provider_item['prov_fname'] = $urow['fname'];
133 $provider_item['npi'] = $urow['npi'];
134 $provider_item['federaltaxid'] = $urow['federaltaxid'];
135 array_push($results,$provider_item);
136 $results = array_merge($results,$newResults);
139 // done, so now can return results
140 return $results;
143 // If set organize-mode to plans, then collects active plans and run through this function recursively
144 if ($organize_mode == "plans") {
145 // First, collect active plans
146 $plans_resolve = resolve_plans_sql($plan,$patient_id);
147 // Second, run through function recursively
148 foreach ($plans_resolve as $plan_item) {
149 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id']);
150 if (!empty($newResults)) {
151 $plan_item['is_plan'] = TRUE;
152 array_push($results,$plan_item);
153 $results = array_merge($results,$newResults);
156 // done, so now can return results
157 return $results;
160 // Collect all patient ids
161 $patientData = array();
162 if (!empty($patient_id)) {
163 // only look at the selected patient
164 array_push($patientData,$patient_id);
166 else {
167 if (empty($provider)) {
168 // Look at entire practice
169 $rez = sqlStatement("SELECT `pid` FROM `patient_data`");
170 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
171 $patientData[$iter]=$row;
174 else {
175 // Look at one provider
176 $rez = sqlStatement("SELECT `pid` FROM `patient_data` " .
177 "WHERE providerID=?", array($provider) );
178 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
179 $patientData[$iter]=$row;
183 // Go through each patient(s)
185 // If in report mode, then tabulate for each rule:
186 // Total Patients
187 // Patients that pass the filter
188 // Patients that pass the target
189 // If in reminders mode, then create reminders for each rule:
190 // Reminder that action is due soon
191 // Reminder that action is due
192 // Reminder that action is post-due
194 //Collect applicable rules
195 if ($mode != "report") {
196 // Use per patient custom rules (if exist)
197 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
199 else { // $mode = "report"
200 // Only use default rules (do not use patient custom rules)
201 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
204 foreach( $rules as $rowRule ) {
206 // If in reminder mode then need to collect the measurement dates
207 // from rule_reminder table
209 $target_dates = array();
210 if ($mode != "report") {
211 // Calculate the dates to check for
212 if ($type == "patient_reminder") {
213 $reminder_interval_type = "patient_reminder";
215 else { // $type == "passive_alert" or $type == "active_alert"
216 $reminder_interval_type = "clinical_reminder";
218 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
220 else { // $mode == "report"
221 // Only use the target date in the report
222 $target_dates[0] = $dateTarget;
225 //Reset the counters
226 $total_patients = 0;
227 $pass_filter = 0;
228 $exclude_filter = 0;
229 $pass_target = 0;
231 foreach( $patientData as $rowPatient ) {
233 // Count the total patients
234 $total_patients++;
236 $dateCounter = 1; // for reminder mode to keep track of which date checking
237 foreach ( $target_dates as $dateFocus ) {
239 //Skip if date is set to SKIP
240 if ($dateFocus == "SKIP") {
241 $dateCounter++;
242 continue;
245 //Set date counter and reminder token (applicable for reminders only)
246 if ($dateCounter == 1) {
247 $reminder_due = "soon_due";
249 else if ($dateCounter == 2) {
250 $reminder_due = "due";
252 else { // $dateCounter == 3
253 $reminder_due = "past_due";
256 // Check if pass filter
257 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
258 if ($passFilter === "EXCLUDED") {
259 // increment EXCLUDED and pass_filter counters
260 // and set as FALSE for reminder functionality.
261 $pass_filter++;
262 $exclude_filter++;
263 $passFilter = FALSE;
264 error_log("DEBUG: ".$rowPatient['pid'].$rowRule['id'].$dateFocus,0);
266 if ($passFilter) {
267 // increment pass filter counter
268 $pass_filter++;
270 else {
271 $dateCounter++;
272 continue;
275 // Check if pass target
276 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
277 if ($passTarget) {
278 // increment pass target counter
279 $pass_target++;
280 // send to reminder results
281 if ($mode == "reminders-all") {
282 // place the completed actions into the reminder return array
283 $actionArray = resolve_action_sql($rowRule['id'],'1');
284 foreach ($actionArray as $action) {
285 $action_plus = $action;
286 $action_plus['due_status'] = "not_due";
287 $action_plus['pid'] = $rowPatient['pid'];
288 $results = reminder_results_integrate($results, $action_plus);
291 break;
293 else {
294 // send to reminder results
295 if ($mode != "report") {
296 // place the uncompleted actions into the reminder return array
297 $actionArray = resolve_action_sql($rowRule['id'],'1');
298 foreach ($actionArray as $action) {
299 $action_plus = $action;
300 $action_plus['due_status'] = $reminder_due;
301 $action_plus['pid'] = $rowPatient['pid'];
302 $results = reminder_results_integrate($results, $action_plus);
306 $dateCounter++;
310 // Calculate and save the data for the rule
311 if ($pass_filter > 0) {
312 $percentage = number_format(($pass_target/($pass_filter-$exclude_filter))*100) . xl('%');
314 else {
315 $percentage = "0". xl('%');
317 if ($mode == "report") {
318 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
319 $newRow=array_merge($newRow,$rowRule);
320 array_push($results, $newRow);
323 // Find the number of target groups, and go through each one if more than one
324 $targetGroups = numberTargetGroups($rowRule['id']);
325 if ($targetGroups > 1) {
326 if ($mode != "report") {
327 $start_id = 2;
329 else { // $mode == "report"
330 $start_id = 1;
332 for ($i = $start_id; $i <= $targetGroups; $i++){
334 //Reset the target counter
335 $pass_target = 0;
337 foreach( $patientData as $rowPatient ) {
339 $dateCounter = 1; // for reminder mode to keep track of which date checking
340 foreach ( $target_dates as $dateFocus ) {
342 //Skip if date is set to SKIP
343 if ($dateFocus == "SKIP") {
344 $dateCounter++;
345 continue;
348 //Set date counter and reminder token (applicable for reminders only)
349 if ($dateCounter == 1) {
350 $reminder_due = "soon_due";
352 else if ($dateCounter == 2) {
353 $reminder_due = "due";
355 else { // $dateCounter == 3
356 $reminder_due = "past_due";
359 //Check if pass target
360 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
361 if ($passTarget) {
362 // increment pass target counter
363 $pass_target++;
364 // send to reminder results
365 if ($mode == "reminders-all") {
366 // place the completed actions into the reminder return array
367 $actionArray = resolve_action_sql($rowRule['id'],'1');
368 foreach ($actionArray as $action) {
369 $action_plus = $action;
370 $action_plus['due_status'] = "not_due";
371 $action_plus['pid'] = $rowPatient['pid'];
372 $results = reminder_results_integrate($results, $action_plus);
375 break;
377 else {
378 // send to reminder results
379 if ($mode != "report") {
380 // place the actions into the reminder return array
381 $actionArray = resolve_action_sql($rowRule['id'],$i);
382 foreach ($actionArray as $action) {
383 $action_plus = $action;
384 $action_plus['due_status'] = $reminder_due;
385 $action_plus['pid'] = $rowPatient['pid'];
386 $results = reminder_results_integrate($results, $action_plus);
390 $dateCounter++;
394 // Calculate and save the data for the rule
395 if ($pass_filter > 0) {
396 $percentage = number_format(($pass_target/($pass_filter-$exclude_filter))*100) . xl('%');
398 else {
399 $percentage = "0". xl('%');
402 // Collect action for title (just use the first one, if more than one)
403 $actionArray = resolve_action_sql($rowRule['id'],$i);
404 $action = $actionArray[0];
405 if ($mode == "report") {
406 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
407 array_push($results, $newRow);
413 // Return the data
414 return $results;
417 // Test filter of a selected rule on a selected patient
418 // Parameters:
419 // $patient_id - pid of selected patient.
420 // $rule - id(string) of selected rule
421 // $dateTarget - target date.
422 // Return:
423 // boolean (if pass filter then TRUE, if excluded then 'EXCLUDED', if not pass filter then FALSE)
424 function test_filter($patient_id,$rule,$dateTarget) {
426 // Set date to current if not set
427 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
429 // Collect patient information
430 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
432 // -------- Special Filters --------
433 // Check for special flag required by many of the CQM rules, that uses a standard
434 // measurement year (Jan1-Dec31). This adjusted date would then be used for
435 // date to calculate patient age and as the start range if filtering for clinic
436 // appointments. The value (usually will be 1) of this
437 // contains how many years to include.
438 $adjustedDate1 = '';
439 $adjustedDate2 = '';
440 $filter = resolve_filter_sql($rule,'filt_measure_period');
441 if (!empty($filter)) {
442 $row = $filter[0];
443 if ($row['method_detail'] == "year") {
444 $tempDateArray = explode("-",$dateTarget);
445 $tempYear = $tempDateArray[0];
446 // Set too one second before the measurement period
447 $adjustedDate1 = ($tempYear - $row['value']) . "-12-31 23:59:59";
448 // Set too the first second of the measurement period
449 $adjustedDate2 = ($tempYear - ($row['value']-1)) . "-01-01 00:00:00";
450 // Set target date to the last second of the measurement period
451 $dateTarget = ($tempYear - ($row['value']-1)) . "-12-31 23:59:59";
456 // ----------------- INCLUSIONS -----------------
459 // -------- Age Filter (inclusion) ------------
460 // Calculate patient age in years and months
461 if (!empty($adjustedDate1)) {
462 // See above Special Filters section in for details.
463 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$adjustedDate1);
464 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$adjustedDate1);
466 else {
467 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
468 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
470 // Min age (year) Filter (assume that there in not more than one of each)
471 $filter = resolve_filter_sql($rule,'filt_age_min');
472 if (!empty($filter)) {
473 $row = $filter[0];
474 if ($row ['method_detail'] == "year") {
475 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
476 return false;
479 if ($row ['method_detail'] == "month") {
480 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
481 return false;
485 // Max age (year) Filter (assume that there in not more than one of each)
486 $filter = resolve_filter_sql($rule,'filt_age_max');
487 if (!empty($filter)) {
488 $row = $filter[0];
489 if ($row ['method_detail'] == "year") {
490 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
491 return false;
494 if ($row ['method_detail'] == "month") {
495 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
496 return false;
501 // -------- Gender Filter (inclusion) ---------
502 // Gender Filter (assume that there in not more than one of each)
503 $filter = resolve_filter_sql($rule,'filt_sex');
504 if (!empty($filter)) {
505 $row = $filter[0];
506 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
507 return false;
511 // -------- Database Filter (inclusion) ------
512 // Database Filter
513 $filter = resolve_filter_sql($rule,'filt_database');
514 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
516 // -------- Lists Filter (inclusion) ----
517 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
518 // surgeries and allergies.
519 $filter = resolve_filter_sql($rule,'filt_lists');
520 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
522 // -------- Clinic Visit(s) Filter --------
523 $filter = resolve_filter_sql($rule,'filt_encounter_min');
524 if (!empty($filter)) {
525 $row = $filter[0];
526 // For total number of appointments, simply get number of encounters
527 if (!empty($adjustedDate2)) {
528 // See above Special Filters section in for details.
529 $encounters = getEncounters($patient_id,$adjustedDate2,$dateTarget);
531 else {
532 $encounters = getEncounters($patient_id,'',$dateTarget);
534 (empty($encounters)) ? $totalNumberAppt = 0 : $totalNumberAppt = count($encounters);
535 if ($row['value'] && $totalNumberAppt < $row['value']) return false;
539 // ----------------- EXCLUSIONS -----------------
542 // -------- Lists Filter (EXCLUSION) ----
543 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
544 // surgeries and allergies.
545 $filter = resolve_filter_sql($rule,'filt_lists',0);
546 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
548 // Passed all filters, so return true.
549 return true;
552 // Return the number of target groups of a selected rule
553 // Parameters:
554 // $rule - id(string) of rule
555 // Return:
556 // integer, number of target groups associated with rule
557 function numberTargetGroups($rule) {
558 $numberGroups = 1;
560 $sql = sqlQuery("SELECT max(`group_id`) as numberGroups FROM `rule_target` " .
561 "WHERE `id`=?", array($rule) );
563 if ($sql['numberGroups']) $numberGroups = $sql['numberGroups'];
565 return $numberGroups;
568 // Test targets of a selected rule on a selected patient
569 // Parameters:
570 // $patient_id - pid of selected patient.
571 // $rule - id(string) of selected rule (if blank, then will ignore grouping)
572 // $group_id - group id of target group
573 // $dateTarget - target date.
574 // Return:
575 // boolean (if target passes then true, otherwise false)
576 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
578 // -------- Interval Target ----
579 $interval = resolve_target_sql($rule,$group_id,'target_interval');
581 // -------- Database Target ----
582 // Database Target (includes)
583 $target = resolve_target_sql($rule,$group_id,'target_database');
584 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
586 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
587 // Procedure Target (includes)
588 $target = resolve_target_sql($rule,$group_id,'target_proc');
589 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
591 // -------- Appointment Target ----
592 // Appointment Target (includes) (Specialized functionality for appointment reminders)
593 $target = resolve_target_sql($rule,$group_id,'target_appt');
594 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
596 // Passed all target tests, so return true.
597 return true;
600 // Function to return active plans
601 // Parameters:
602 // $type - plan type filter (normal or cqm or blank)
603 // $patient_id - pid of selected patient. (if custom plan does not exist then
604 // will use the default plan)
605 // $configurableOnly - true if only want the configurable (per patient) plans
606 // (ie. ignore cqm plans)
607 // Return: array containing plans
608 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
610 if ($configurableOnly) {
611 // Collect all default, configurable (per patient) plans into an array
612 // (ie. ignore the cqm rules)
613 $sql = sqlStatement("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm` !=1 ORDER BY `id`");
615 else {
616 // Collect all default plans into an array
617 $sql = sqlStatement("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
619 $returnArray= array();
620 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
621 array_push($returnArray,$row);
624 // Now collect the pertinent plans
625 $newReturnArray = array();
627 // Need to select rules (use custom if exist)
628 foreach ($returnArray as $plan) {
629 $customPlan = sqlQuery("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
631 // Decide if use default vs custom plan (preference given to custom plan)
632 if (!empty($customPlan)) {
633 if ($type == "cqm" ) {
634 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
635 $goPlan = $plan;
637 else {
638 // merge the custom plan with the default plan
639 $mergedPlan = array();
640 foreach ($customPlan as $key => $value) {
641 if ($value == NULL && preg_match("/_flag$/",$key)) {
642 // use default setting
643 $mergedPlan[$key] = $plan[$key];
645 else {
646 // use custom setting
647 $mergedPlan[$key] = $value;
650 $goPlan = $mergedPlan;
653 else {
654 $goPlan = $plan;
657 // Use the chosen plan if set
658 if (!empty($type)) {
659 if ($goPlan["${type}_flag"] == 1) {
660 // active, so use the plan
661 array_push($newReturnArray,$goPlan);
664 else {
665 if ($goPlan['normal_flag'] == 1 ||
666 $goPlan['cqm_flag'] == 1) {
667 // active, so use the plan
668 array_push($newReturnArray,$goPlan);
672 $returnArray = $newReturnArray;
674 return $returnArray;
677 // Function to return active rules
678 // Parameters:
679 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
680 // $patient_id - pid of selected patient. (if custom rule does not exist then
681 // will use the default rule)
682 // $configurableOnly - true if only want the configurable (per patient) rules
683 // (ie. ignore cqm and amc rules)
684 // $plan - collect rules for specific plan
685 // Return: array containing rules
686 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='') {
688 if ($configurableOnly) {
689 // Collect all default, configurable (per patient) rules into an array
690 // (ie. ignore the cqm and amc rules)
691 $sql = sqlStatement("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
693 else {
694 // Collect all default rules into an array
695 $sql = sqlStatement("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
697 $returnArray= array();
698 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
699 array_push($returnArray,$row);
702 // Now filter rules for plan (if applicable)
703 if (!empty($plan)) {
704 $planReturnArray = array();
705 foreach ($returnArray as $rule) {
706 $standardRule = sqlQuery("SELECT * FROM `clinical_plans_rules` " .
707 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
708 if (!empty($standardRule)) {
709 array_push($planReturnArray,$rule);
712 $returnArray = $planReturnArray;
715 // Now collect the pertinent rules
716 $newReturnArray = array();
718 // Need to select rules (use custom if exist)
719 foreach ($returnArray as $rule) {
720 $customRule = sqlQuery("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
722 // Decide if use default vs custom rule (preference given to custom rule)
723 if (!empty($customRule)) {
724 if ($type == "cqm" || $type == "amc" ) {
725 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
726 $goRule = $rule;
728 else {
729 // merge the custom rule with the default rule
730 $mergedRule = array();
731 foreach ($customRule as $key => $value) {
732 if ($value == NULL && preg_match("/_flag$/",$key)) {
733 // use default setting
734 $mergedRule[$key] = $rule[$key];
736 else {
737 // use custom setting
738 $mergedRule[$key] = $value;
741 $goRule = $mergedRule;
744 else {
745 $goRule = $rule;
748 // Use the chosen rule if set
749 if (!empty($type)) {
750 if ($goRule["${type}_flag"] == 1) {
751 // active, so use the rule
752 array_push($newReturnArray,$goRule);
755 else {
756 if ($goRule['active_alert_flag'] == 1 ||
757 $goRule['passive_alert_flag'] == 1 ||
758 $goRule['cqm_flag'] == 1 ||
759 $goRule['amc_flag'] == 1 ||
760 $goRule['patient_reminder_flag'] == 1) {
761 // active, so use the rule
762 array_push($newReturnArray,$goRule);
766 $returnArray = $newReturnArray;
768 return $returnArray;
771 // Function to return a specific rule
772 // Parameters:
773 // $rule - id(string) of rule
774 // $patient_id - pid of selected patient. (if set to 0, then will return
775 // the default rule).
776 // Return: array containing a rule
777 function collect_rule($rule,$patient_id='0') {
779 return sqlQuery("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
783 // Function to set a specific rule activity for a specific patient
784 // Parameters:
785 // $rule - id(string) of rule
786 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
787 // $setting - activity of rule (yes,no,default)
788 // $patient_id - pid of selected patient.
789 // Return: nothing
790 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
792 // Don't allow messing with the default rules here
793 if ($patient_id == "0") {
794 return;
797 // Convert setting
798 if ($setting == "on") {
799 $setting = 1;
801 else if ($setting == "off") {
802 $setting = 0;
804 else { // $setting == "default"
805 $setting = NULL;
808 // Collect patient specific rule, if already exists.
809 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
810 $patient_rule = sqlQuery($query, array($rule,$patient_id) );
812 error_log("DEBUG :".$rule.$type.$setting.$patient_id,0);
814 if (empty($patient_rule)) {
815 // Create a new patient specific rule with flags all set to default
816 $query = "INSERT into `clinical_rules` (`id`, `pid`) VALUES (?,?)";
817 sqlStatement($query, array($rule, $patient_id) );
820 // Update patient specific row
821 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
822 sqlStatement($query, array($setting,$rule,$patient_id) );
826 // Function to return applicable reminder dates (relative)
827 // Parameters:
828 // $rule - id(string) of selected rule
829 // $reminder_method - string label of filter type
830 // Return: array containing reminder features
831 function resolve_reminder_sql($rule,$reminder_method) {
832 $sql = sqlStatement("SELECT `method_detail`, `value` FROM `rule_reminder` " .
833 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method) );
835 $returnArray= array();
836 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
837 array_push($returnArray,$row);
839 return $returnArray;
842 // Function to return applicable filters
843 // Parameters:
844 // $rule - id(string) of selected rule
845 // $filter_method - string label of filter type
846 // $include_flag - to allow selection for included or excluded filters
847 // Return: array containing filters
848 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
849 $sql = sqlStatement("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
850 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
852 $returnArray= array();
853 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
854 array_push($returnArray,$row);
856 return $returnArray;
859 // Function to return applicable targets
860 // Parameters:
861 // $rule - id(string) of selected rule
862 // $group_id - group id of target group (if blank, then will ignore grouping)
863 // $target_method - string label of target type
864 // $include_flag - to allow selection for included or excluded targets
865 // Return: array containing targets
866 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
868 if ($group_id) {
869 $sql = sqlStatement("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
870 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
872 else {
873 $sql = sqlStatement("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
874 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
877 $returnArray= array();
878 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
879 array_push($returnArray,$row);
881 return $returnArray;
884 // Function to return applicable actions
885 // Parameters:
886 // $rule - id(string) of selected rule
887 // $group_id - group id of target group (if blank, then will ignore grouping)
888 // Return: array containing actions
889 function resolve_action_sql($rule,$group_id='') {
891 if ($group_id) {
892 $sql = sqlStatement("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
893 "FROM `rule_action` as a " .
894 "JOIN `rule_action_item` as b " .
895 "ON a.category = b.category AND a.item = b.item " .
896 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
898 else {
899 $sql = sqlStatement("SELECT b.category, b.item, b.value, b.custom_flag " .
900 "FROM `rule_action` as a " .
901 "JOIN `rule_action_item` as b " .
902 "ON a.category = b.category AND a.item = b.item " .
903 "WHERE a.id=?", array($rule) );
906 $returnArray= array();
907 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
908 array_push($returnArray,$row);
910 return $returnArray;
913 // Function to check database filters and targets
914 // Parameters:
915 // $patient_id - pid of selected patient.
916 // $filter - array containing filter/target elements
917 // $interval - used for the interval elements
918 // $dateTarget - target date. blank is current date.
919 // Return: boolean if check passed, otherwise false
920 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
921 $isMatch = false; //matching flag
923 // Set date to current if not set
924 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
926 // Unpackage interval information
927 // (Assume only one for now and only pertinent for targets)
928 $intervalType = '';
929 $intervalValue = '';
930 if (!empty($interval)) {
931 $intervalType = $interval[0]['value'];
932 $intervalValue = $interval[0]['interval'];
935 foreach( $filter as $row ) {
936 // Row description
937 // [0]=>special modes
938 $temp_df = explode("::",$row['value']);
940 if ($temp_df[0] == "CUSTOM") {
941 // Row description
942 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
943 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
944 // Record the match
945 $isMatch = true;
947 else {
948 // If this is a required entry then return false
949 if ($row['required_flag']) return false;
952 else if ($temp_df[0] == "LIFESTYLE") {
953 // Row description
954 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
955 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
956 // Record the match
957 $isMatch = true;
959 else {
960 // If this is a required entry then return false
961 if ($row['required_flag']) return false;
964 else {
965 // Default mode
966 // Row description
967 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
968 if (exist_database_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $temp_df[6], $intervalType, $intervalValue, $dateTarget)) {
969 // Record the match
970 $isMatch = true;
972 else {
973 // If this is a required entry then return false
974 if ($row['required_flag']) return false;
979 // return results of check
980 return $isMatch;
983 // Function to check procedure filters and targets
984 // Parameters:
985 // $patient_id - pid of selected patient.
986 // $filter - array containing filter/target elements
987 // $interval - used for the interval elements
988 // $dateTarget - target date. blank is current date.
989 // Return: boolean if check passed, otherwise false
990 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
991 $isMatch = false; //matching flag
993 // Set date to current if not set
994 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
996 // Unpackage interval information
997 // (Assume only one for now and only pertinent for targets)
998 $intervalType = '';
999 $intervalValue = '';
1000 if (!empty($interval)) {
1001 $intervalType = $interval[0]['value'];
1002 $intervalValue = $interval[0]['interval'];
1005 foreach( $filter as $row ) {
1006 // Row description
1007 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1008 // code description
1009 // <type(ICD9,CPT)>:<identifier>; etc.
1010 $temp_df = explode("::",$row['value']);
1011 if (exist_procedure_item($patient_id, $temp_df[0], $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1012 // Record the match
1013 $isMatch = true;
1015 else {
1016 // If this is a required entry then return false
1017 if ($row['required_flag']) return false;
1021 // return results of check
1022 return $isMatch;
1025 // Function to check for appointment
1026 // Parameters:
1027 // $patient_id - pid of selected patient.
1028 // $dateTarget - target date.
1029 // Return: boolean if appt exist, otherwise false
1030 function appointment_check($patient_id,$dateTarget='') {
1031 $isMatch = false; //matching flag
1033 // Set date to current if not set (although should always be set)
1034 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1035 $dateTargetRound = date('Y-m-d',$dateTarget);
1037 // Set current date
1038 $currentDate = date('Y-m-d H:i:s');
1039 $currentDateRound = date('Y-m-d',$dateCurrent);
1041 // Basically, if the appointment is within the current date to the target date,
1042 // then return true. (will not send reminders on same day as appointment)
1043 $sql = sqlStatement("SELECT openemr_postcalendar_events.pc_eid, " .
1044 "openemr_postcalendar_events.pc_title, " .
1045 "openemr_postcalendar_events.pc_eventDate, " .
1046 "openemr_postcalendar_events.pc_startTime, " .
1047 "openemr_postcalendar_events.pc_endTime " .
1048 "FROM openemr_postcalendar_events " .
1049 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1050 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1051 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1053 // return results of check
1055 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1056 // Plan to send back array of appt info (eid, time, date, etc.)
1057 // to do this.
1058 if (sqlNumRows($sql) > 0) {
1059 $isMatch = true;
1062 return $isMatch;
1065 // Function to check lists filters and targets
1066 // Customizable and currently includes diagnoses, medications,
1067 // allergies and surgeries.
1068 // Parameters:
1069 // $patient_id - pid of selected patient.
1070 // $filter - array containing lists filter/target elements
1071 // $dateTarget - target date. blank is current date.
1072 // Return: boolean if check passed, otherwise false
1073 function lists_check($patient_id,$filter,$dateTarget) {
1074 $isMatch = false; //matching flag
1076 // Set date to current if not set
1077 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1079 foreach ( $filter as $row ) {
1080 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1081 // Record the match
1082 $isMatch = true;
1084 else {
1085 // If this is a required entry then return false
1086 if ($row['required_flag']) return false;
1090 // return results of check
1091 return $isMatch;
1094 // Function to check for existance of data in database for a patient
1095 // Parameters:
1096 // $patient_id - pid of selected patient.
1097 // $table - selected mysql table
1098 // $column - selected mysql column
1099 // $data_comp - data comparison (eq,ne,gt,ge,lt,le)
1100 // $data - selected data in the mysql database
1101 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1102 // $num_items_thres - number of items threshold
1103 // $intervalType - type of interval (ie. year)
1104 // $intervalValue - searched for within this many times of the interval type
1105 // $dateTarget - target date.
1106 // Return: boolean if check passed, otherwise false
1107 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1109 // Set date to current if not set
1110 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1112 // Collect the correct column label for patient id in the table
1113 $patient_id_label = collect_database_label('pid',$table);
1115 // Get the interval sql query string
1116 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1118 // If just checking for existence (ie. data is empty),
1119 // then simply set the comparison operator to ne.
1120 if (empty($data)) {
1121 $data_comp = "ne";
1124 // get the appropriate sql comparison operator
1125 $compSql = convertCompSql($data_comp);
1127 // check for items
1128 if (empty($column)) {
1129 // simple search for any table entries
1130 $sql = sqlStatement("SELECT * " .
1131 "FROM `" . add_escape_custom($table) . "` " .
1132 "WHERE `" . add_escape_custom($patient_id_label) . "`=?", array($patient_id) );
1134 else {
1135 // search for number of specific items
1136 $sql = sqlStatement("SELECT `" . add_escape_custom($column) . "` " .
1137 "FROM `" . add_escape_custom($table) . "` " .
1138 "WHERE `" . add_escape_custom($column) ."`" . $compSql . "? " .
1139 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1140 $dateSql, array($data,$patient_id) );
1143 // See if number of returned items passes the comparison
1144 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1147 // Function to check for existence of procedure(s) for a patient
1148 // Parameters:
1149 // $patient_id - pid of selected patient.
1150 // $proc_title - procedure title
1151 // $proc_code - procedure identifier code (array)
1152 // $result_comp - results comparison (eq,ne,gt,ge,lt,le)
1153 // $result_data - results data
1154 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1155 // $num_items_thres - number of items threshold
1156 // $intervalType - type of interval (ie. year)
1157 // $intervalValue - searched for within this many times of the interval type
1158 // $dateTarget - target date.
1159 // Return: boolean if check passed, otherwise false
1160 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1162 // Set date to current if not set
1163 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1165 // Set the table exception (for looking up pertinent date and pid sql columns)
1166 $table = "PROCEDURE-EXCEPTION";
1168 // Collect the correct column label for patient id in the table
1169 $patient_id_label = collect_database_label('pid',$table);
1171 // Get the interval sql query string
1172 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1175 // TODO
1176 // Figure out a way to use the identifiers codes
1177 // TODO
1180 // If just checking for existence (ie result_data is empty),
1181 // then simply set the comparison operator to ne.
1182 if (empty($result_data)) {
1183 $result_comp = "ne";
1186 // get the appropriate sql comparison operator
1187 $compSql = convertCompSql($result_comp);
1189 // collect specific items that fulfill request
1190 $sql = sqlStatement("SELECT procedure_result.result " .
1191 "FROM `procedure_type`, " .
1192 "`procedure_order`, " .
1193 "`procedure_report`, " .
1194 "`procedure_result` " .
1195 "WHERE procedure_type.procedure_type_id = procedure_order.procedure_type_id " .
1196 "AND procedure_order.procedure_order_id = procedure_report.procedure_order_id " .
1197 "AND procedure_report.procedure_report_id = procedure_result.procedure_report_id " .
1198 "AND procedure_type.name = ? " .
1199 "AND procedure_result.result " . $compSql . " ? " .
1200 "AND " . add_escape_custom($patient_id_label) . " = ? " .
1201 $dateSql, array($proc_title,$result_data,$patient_id) );
1203 // See if number of returned items passes the comparison
1204 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1207 // Function to check for existance of data for a patient in the rule_patient_data table
1208 // Parameters:
1209 // $patient_id - pid of selected patient.
1210 // $category - label in category column
1211 // $item - label in item column
1212 // $complete - label in complete column (YES,NO, or blank)
1213 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1214 // $num_items_thres - number of items threshold
1215 // $intervalType - type of interval (ie. year)
1216 // $intervalValue - searched for within this many times of the interval type
1217 // $dateTarget - target date.
1218 // Return: boolean if check passed, otherwise false
1219 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
1221 // Set the table
1222 $table = 'rule_patient_data';
1224 // Collect the correct column label for patient id in the table
1225 $patient_id_label = collect_database_label('pid',$table);
1227 // Get the interval sql query string
1228 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1230 // search for number of specific items
1231 $sql = sqlStatement("SELECT `result` " .
1232 "FROM `" . add_escape_custom($table) . "` " .
1233 "WHERE `category`=? " .
1234 "AND `item`=? " .
1235 "AND `complete`=? " .
1236 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1237 $dateSql, array($category,$item,$complete,$patient_id) );
1239 // See if number of returned items passes the comparison
1240 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1243 // Function to check for existance of data for a patient in lifestyle section
1244 // Parameters:
1245 // $patient_id - pid of selected patient.
1246 // $lifestyle - selected label of mysql column of patient history
1247 // $status - specific status of selected lifestyle element
1248 // $dateTarget - target date. blank is current date.
1249 // Return: boolean if check passed, otherwise false
1250 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
1252 // Set date to current if not set
1253 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1255 // Collect pertinent history data
1256 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
1258 // See if match
1259 $stringFlag = strstr($history[$lifestyle], "|".$status);
1260 if (empty($status)) {
1261 // Only ensuring any data has been entered into the field
1262 $stringFlag = true;
1264 if ( $history[$lifestyle] &&
1265 $history[$lifestyle] != '|0|' &&
1266 $stringFlag ) {
1267 return true;
1269 else {
1270 return false;
1274 // Function to check for lists item of a patient
1275 // Fully customizable and includes diagnoses, medications,
1276 // allergies, and surgeries.
1277 // Parameters:
1278 // $patient_id - pid of selected patient.
1279 // $type - type (medical_problem, allergy, medication, etc)
1280 // $value - value searching for
1281 // $dateTarget - target date. blank is current date.
1282 // Return: boolean if check passed, otherwise false
1283 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
1285 // Set date to current if not set
1286 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1288 if ($type == "medical_problem") {
1289 // Specific search for diagnoses
1290 // Explode the value into diagnosis code type and code
1291 $temp_diag_array = explode("::",$value);
1292 $code_type = $temp_diag_array[0];
1293 $diagnosis = $temp_diag_array[1];
1294 if ($code_type=='CUSTOM') {
1295 // Deal with custom code first (title column in lists table)
1296 $response = sqlQuery("SELECT * FROM `lists` " .
1297 "WHERE `type`=? " .
1298 "AND `pid`=? " .
1299 "AND `title`=? " .
1300 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1301 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$diagnosis,$dateTarget,$dateTarget,$dateTarget) );
1302 if (!empty($response)) return true;
1304 else {
1305 // Deal with the set code types (diagnosis column in lists table)
1306 $response = sqlQuery("SELECT * FROM `lists` " .
1307 "WHERE `type`=? " .
1308 "AND `pid`=? " .
1309 "AND `diagnosis` LIKE ? " .
1310 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1311 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$diagnosis."%",$dateTarget,$dateTarget,$dateTarget) );
1312 if (!empty($response)) return true;
1315 else { // generic lists item that requires no customization
1316 $response = sqlQuery("SELECT * FROM `lists` " .
1317 "WHERE `type`=? " .
1318 "AND `pid`=? " .
1319 "AND `title`=? ".
1320 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1321 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
1322 if (!empty($response)) return true;
1325 return false;
1328 // Function to return part of sql query to deal with interval
1329 // Parameters:
1330 // $table - selected mysql table (or EXCEPTION(s))
1331 // $intervalType - type of interval (ie. year)
1332 // $intervalValue - searched for within this many times of the interval type
1333 // $dateTarget - target date.
1334 // Return: string containing pertinent date interval filter for mysql query
1335 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
1337 $dateSql="";
1339 // Collect the correct column label for date in the table
1340 $date_label = collect_database_label('date',$table);
1342 // Deal with interval
1343 if (!empty($intervalType)) {
1344 switch($intervalType) {
1345 case "year":
1346 $dateSql = "AND (" . add_escape_custom($date_label) .
1347 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1348 "', INTERVAL " . add_escape_custom($intervalValue) .
1349 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
1350 break;
1351 case "month":
1352 $dateSql = "AND (" . add_escape_custom($date_label) .
1353 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1354 "', INTERVAL " . add_escape_custom($intervalValue) .
1355 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
1356 break;
1357 case "week":
1358 $dateSql = "AND (" . add_escape_custom($date_label) .
1359 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1360 "', INTERVAL " . add_escape_custom($intervalValue) .
1361 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
1362 break;
1363 case "day":
1364 $dateSql = "AND (" . add_escape_custom($date_label) .
1365 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1366 "', INTERVAL " . add_escape_custom($intervalValue) .
1367 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
1368 break;
1369 case "hour":
1370 $dateSql = "AND (" . add_escape_custom($date_label) .
1371 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1372 "', INTERVAL " . add_escape_custom($intervalValue) .
1373 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
1374 break;
1375 case "minute":
1376 $dateSql = "AND (" . add_escape_custom($date_label) .
1377 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1378 "', INTERVAL " . add_escape_custom($intervalValue) .
1379 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
1380 break;
1381 case "second":
1382 $dateSql = "AND (" . add_escape_custom($date_label) .
1383 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1384 "', INTERVAL " . add_escape_custom($intervalValue) .
1385 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
1386 break;
1387 case "flu_season":
1388 // Flu season to be hard-coded as September thru February
1389 // (Should make this modifiable in the future)
1390 // ($intervalValue is not used)
1391 $dateArray = explode("-",$dateTarget);
1392 $Year = $dateArray[0];
1393 $dateThisYear = $Year . "-09-01";
1394 $dateLastYear = ($Year-1) . "-09-01";
1395 $dateSql =" " .
1396 "AND ((" .
1397 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
1398 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
1399 "OR (" .
1400 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
1401 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
1402 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
1403 break;
1406 else {
1407 $dateSql = "AND " . add_escape_custom($date_label) .
1408 " <= '" . add_escape_custom($dateTarget) . "' ";
1411 // return the sql interval string
1412 return $dateSql;
1415 // Function to collect generic column labels from tables.
1416 // It currently works for date and pid.
1417 // Will need to expand this as algorithm grows.
1418 // Parameters:
1419 // $label - element (pid or date)
1420 // $table - selected mysql table (or EXCEPTION(s))
1421 // Return: string containing official label of selected element
1422 function collect_database_label($label,$table) {
1424 if ($table == 'PROCEDURE-EXCEPTION') {
1425 // return cell to get procedure collection
1426 // special case since reuqires joing of multiple
1427 // tables to get this value
1428 if ($label == "pid") {
1429 $returnedLabel = "procedure_order.patient_id";
1431 else if ($label == "date") {
1432 $returnedLabel = "procedure_result.date";
1434 else {
1435 // unknown label, so return the original label
1436 $returnedLabel = $label;
1439 else if ($table == 'immunizations') {
1440 // return requested label for immunization table
1441 if ($label == "pid") {
1442 $returnedLabel = "patient_id";
1444 else if ($label == "date") {
1445 $returnedLabel = "`administered_date`";
1447 else {
1448 // unknown label, so return the original label
1449 $returnedLabel = $label;
1452 else {
1453 // return requested label for default tables
1454 if ($label == "pid") {
1455 $returnedLabel = "pid";
1457 else if ($label == "date") {
1458 $returnedLabel = "`date`";
1460 else {
1461 // unknown label, so return the original label
1462 $returnedLabel = $label;
1466 return $returnedLabel;
1469 // Simple function to avoid processing of duplicate actions
1470 // Parameters:
1471 // $actions - 2-dimensional array with all current active targets
1472 // $action - array of selected target to test for duplicate
1473 // Return: boolean, true if duplicate, false if not duplicate
1474 function is_duplicate_action($actions,$action) {
1475 foreach ($actions as $row) {
1476 if ($row['category'] == $action['category'] &&
1477 $row['item'] == $action['item'] &&
1478 $row['value'] == $action['value']) {
1479 // Is a duplicate
1480 return true;
1484 // Not a duplicate
1485 return false;
1488 // Calculate the reminder dates.
1489 // Parameters:
1490 // $rule - id(string) of selected rule
1491 // $dateTarget - target date. If blank then will test with current date as target.
1492 // $type - either 'patient_reminder' or 'clinical_reminder'
1493 // For now, will always return an array of 3 dates:
1494 // first date is before the target date (past_due) (default of 1 month)
1495 // second date is the target date (due)
1496 // third date is after the target date (soon_due) (default of 2 weeks)
1497 function calculate_reminder_dates($rule, $dateTarget='',$type) {
1499 // Set date to current if not set
1500 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1502 // Collect the current date settings (to ensure not skip)
1503 $res = resolve_reminder_sql($rule, $type.'_current');
1504 if (!empty($res)) {
1505 $row = $res[0];
1506 if ($row ['method_detail'] == "SKIP") {
1507 $dateTarget = "SKIP";
1511 // Collect the past_due date
1512 $past_due_date == "";
1513 $res = resolve_reminder_sql($rule, $type.'_post');
1514 if (!empty($res)) {
1515 $row = $res[0];
1516 if ($row ['method_detail'] == "week") {
1517 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
1519 if ($row ['method_detail'] == "month") {
1520 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
1522 if ($row ['method_detail'] == "hour") {
1523 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1525 if ($row ['method_detail'] == "SKIP") {
1526 $past_due_date = "SKIP";
1529 else {
1530 // empty settings, so use default of one month
1531 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
1534 // Collect the soon_due date
1535 $soon_due_date == "";
1536 $res = resolve_reminder_sql($rule, $type.'_pre');
1537 if (!empty($res)) {
1538 $row = $res[0];
1539 if ($row ['method_detail'] == "week") {
1540 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
1542 if ($row ['method_detail'] == "month") {
1543 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
1545 if ($row ['method_detail'] == "hour") {
1546 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1548 if ($row ['method_detail'] == "SKIP") {
1549 $soon_due_date = "SKIP";
1552 else {
1553 // empty settings, so use default of one month
1554 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
1557 // Return the array of three dates
1558 return array($soon_due_date,$dateTarget,$past_due_date);
1561 // Adds an action into the reminder array
1562 // Parameters:
1563 // $reminderOldArray - Contains the current array of reminders
1564 // $reminderNew - Array of a new reminder
1565 // Return:
1566 // An array of reminders
1567 function reminder_results_integrate($reminderOldArray, $reminderNew) {
1569 $results = array();
1571 // If reminderArray is empty, then insert new reminder
1572 if (empty($reminderOldArray)) {
1573 array_push($results, $reminderNew);
1574 return $results;
1577 // If duplicate reminder, then replace the old one
1578 $duplicate = false;
1579 foreach ($reminderOldArray as $reminderOld) {
1580 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
1581 $reminderOld['category'] == $reminderNew['category'] &&
1582 $reminderOld['item'] == $reminderNew['item']) {
1583 array_push($results, $reminderNew);
1584 $duplicate = true;
1586 else {
1587 array_push($results, $reminderOld);
1591 // If a new reminder, then insert the new reminder
1592 if (!$duplicate) {
1593 array_push($results, $reminderNew);
1596 return $results;
1599 // Compares number of items with requested comparison operator
1600 // Parameters:
1601 // $comp - Comparison operator(eq,ne,gt,ge,lt,le)
1602 // $thres - Threshold used in comparison
1603 // $num_items - Number of items
1604 // Return:
1605 // Boolean of comparison results
1606 function itemsNumberCompare($comp, $thres, $num_items) {
1608 if ( ($comp == "eq") && ($num_items == $thres) ) {
1609 return true;
1611 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
1612 return true;
1614 else if ( ($comp == "gt") && ($num_items > $thres) ) {
1615 return true;
1617 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
1618 return true;
1620 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
1621 return true;
1623 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
1624 return true;
1626 else {
1627 return false;
1631 // Converts a text comparison operator to sql equivalent
1632 // Parameters:
1633 // $comp - Comparison operator(eq,ne,gt,ge,lt,le)
1634 // Return:
1635 // String containing sql compatible comparison operator
1636 function convertCompSql($comp) {
1638 if ($comp == "eq") {
1639 return "=";
1641 else if ($comp == "ne") {
1642 return "!=";
1644 else if ($comp == "gt") {
1645 return ">";
1647 else if ($comp == "ge") {
1648 return ">=";
1650 else if ($comp == "lt") {
1651 return "<";
1653 else { // ($comp == "le")
1654 return "<=";
1658 // Function to find age in years (with decimal) on the target date
1659 // Parameters:
1660 // $dob - date of birth
1661 // $target - date to calculate age on
1662 // Return: decimal, years(decimal) from dob to target(date)
1663 function convertDobtoAgeYearDecimal($dob,$target) {
1665 // Grab year, month, and day from dob and dateTarget
1666 $dateDOB = explode(" ",$dob);
1667 $dateTarget = explode(" ",$target);
1669 // Collect differences
1670 $iDiffYear = $dateTarget[0] - $dateDOB[0];
1671 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
1672 $iDiffDay = $dateTarget[2] - $dateDOB[2];
1674 // If birthday has not happen yet for this year, subtract 1.
1675 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
1677 $iDiffYear--;
1680 return $iDiffYear;
1683 // Function to find age in months (with decimal) on the target date
1684 // Parameters:
1685 // $dob - date of birth
1686 // $target - date to calculate age on
1687 // Return: decimal, months(decimal) from dob to target(date)
1688 function convertDobtoAgeMonthDecimal($dob,$target) {
1690 // Grab year, month, and day from dob and dateTarget
1691 $dateDOB = explode(" ",$dob);
1692 $dateTarget = explode(" ",$target);
1694 // Collect differences
1695 $iDiffYear = $dateTarget[0] - $dateDOB[0];
1696 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
1697 $iDiffDay = $dateTarget[2] - $dateDOB[2];
1699 // If birthday has not happen yet for this year, subtract 1.
1700 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
1702 $iDiffYear--;
1705 return (12 * $iDiffYear) + $iDiffMonth;