removing more undefined variables to clean up log
[openemr.git] / library / clinical_rules.php
blob04bc9ac9548d9ab7d39173fdd2a0010573e72fb7
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 (isset($action['is_plan']) &&$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 // Display the active screen reminder.
95 // Parameters:
96 // $patient_id - pid of selected patient
97 // $mode - choose either 'reminders-all' or 'reminders-due' (required)
98 // $dateTarget - target date. If blank then will test with current date as target.
99 // $organize_mode - Way to organize the results (default or plans)
100 function active_alert_summary($patient_id,$mode,$dateTarget='',$organize_mode='default') {
102 // Set date to current if not set
103 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
105 // Collect active actions
106 $actions = test_rules_clinic('','active_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
108 if (empty($actions)) {
109 return false;
112 $returnOutput = "";
114 // Display the actions
115 foreach ($actions as $action) {
117 // Deal with plan names first
118 if ($action['is_plan']) {
119 $returnOutput .= "<br><b>";
120 $returnOutput .= htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
121 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
122 $returnOutput .= "</b><br>";
123 continue;
126 // Display Reminder Details
127 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
128 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
130 // Display due status
131 if ($action['due_status']) {
132 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
133 if ($action['due_status'] == "past_due") {
134 $returnOutput .= "&nbsp;&nbsp;(<span style='color:red'>";
136 else if ($action['due_status'] == "due") {
137 $returnOutput .= "&nbsp;&nbsp;(<span style='color:purple'>";
139 else if ($action['due_status'] == "not_due") {
140 $returnOutput .= "&nbsp;&nbsp;(<span style='color:green'>";
142 else {
143 $returnOutput .= "&nbsp;&nbsp;(<span>";
145 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
147 else {
148 $returnOutput .= "<br>";
151 return $returnOutput;
154 // Test the clinic rules of entire clinic and create a report or patient reminders
155 // (can also test on one patient or patients of one provider)
156 // Parameters:
157 // $provider - id of a selected provider. If blank, then will test entire clinic. If 'collate_outer' or
158 // 'collate_inner', then will test each provider in entire clinic; outer will nest plans
159 // inside collated providers, while inner will nest the providers inside the plans (note
160 // inner and outer are only different if organize_mode is set to plans).
161 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules.
162 // $dateTarget - target date. If blank then will test with current date as target.
163 // If an array, then is holding two dates ('dateBegin' and 'dateTarget')
164 // $mode - choose either 'report' or 'reminders-all' or 'reminders-due' (required)
165 // $patient_id - pid of patient. If blank then will check all patients.
166 // $plan - test for specific plan only
167 // $organize_mode - Way to organize the results (default, plans)
168 // 'default':
169 // Returns a two-dimensional array of results organized by rules:
170 // reminders-due mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
171 // reminders-all mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
172 // report mode - returns an array of rows for the Clinical Quality Measures (CQM) report
173 // 'plans':
174 // Returns similar to default, but organizes by the active plans
175 // $options - can hold various option (for now, used to hold the manual number of labs for the AMC report)
177 function test_rules_clinic($provider='',$type='',$dateTarget='',$mode='',$patient_id='',$plan='',$organize_mode='default',$options=array()) {
179 // If dateTarget is an array, then organize them.
180 if (is_array($dateTarget)) {
181 $dateArray = $dateTarget;
182 $dateTarget = $dateTarget['dateTarget'];
185 // Set date to current if not set
186 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
188 // Prepare the results array
189 $results = array();
191 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
192 // then run through this function recursively and return results.
193 if (($provider == "collate_outer") || ($provider == "collate_inner" && $organize_mode != 'plans')) {
194 // First, collect an array of all providers
195 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
196 $ures = sqlStatement($query);
197 // Second, run through each provider recursively
198 while ($urow = sqlFetchArray($ures)) {
199 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode);
200 if (!empty($newResults)) {
201 $provider_item['is_provider'] = TRUE;
202 $provider_item['prov_lname'] = $urow['lname'];
203 $provider_item['prov_fname'] = $urow['fname'];
204 $provider_item['npi'] = $urow['npi'];
205 $provider_item['federaltaxid'] = $urow['federaltaxid'];
206 array_push($results,$provider_item);
207 $results = array_merge($results,$newResults);
210 // done, so now can return results
211 return $results;
214 // If set organize-mode to plans, then collects active plans and run through this
215 // function recursively and return results.
216 if ($organize_mode == "plans") {
217 // First, collect active plans
218 $plans_resolve = resolve_plans_sql($plan,$patient_id);
219 // Second, run through function recursively
220 foreach ($plans_resolve as $plan_item) {
221 // (if collate_inner, then nest a collation of providers within each plan)
222 if ($provider == "collate_inner") {
223 // First, collect an array of all providers
224 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
225 $ures = sqlStatement($query);
226 // Second, run through each provider recursively
227 $provider_results = array();
228 while ($urow = sqlFetchArray($ures)) {
229 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan_item['id']);
230 if (!empty($newResults)) {
231 $provider_item['is_provider'] = TRUE;
232 $provider_item['prov_lname'] = $urow['lname'];
233 $provider_item['prov_fname'] = $urow['fname'];
234 $provider_item['npi'] = $urow['npi'];
235 $provider_item['federaltaxid'] = $urow['federaltaxid'];
236 array_push($provider_results,$provider_item);
237 $provider_results = array_merge($provider_results,$newResults);
240 if (!empty($provider_results)) {
241 $plan_item['is_plan'] = TRUE;
242 array_push($results,$plan_item);
243 $results = array_merge($results,$provider_results);
246 else {
247 // (not collate_inner, so do not nest providers within each plan)
248 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id']);
249 if (!empty($newResults)) {
250 $plan_item['is_plan'] = TRUE;
251 array_push($results,$plan_item);
252 $results = array_merge($results,$newResults);
256 // done, so now can return results
257 return $results;
260 // Collect all patient ids
261 $patientData = array();
262 if (!empty($patient_id)) {
263 // only look at the selected patient
264 $patientData[0]['pid'] = $patient_id;
266 else {
267 if (empty($provider)) {
268 // Look at entire practice
269 $rez = sqlStatement("SELECT `pid` FROM `patient_data`");
270 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
271 $patientData[$iter]=$row;
274 else {
275 // Look at one provider
276 $rez = sqlStatement("SELECT `pid` FROM `patient_data` " .
277 "WHERE providerID=?", array($provider) );
278 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
279 $patientData[$iter]=$row;
283 // Go through each patient(s)
285 // If in report mode, then tabulate for each rule:
286 // Total Patients
287 // Patients that pass the filter
288 // Patients that pass the target
289 // If in reminders mode, then create reminders for each rule:
290 // Reminder that action is due soon
291 // Reminder that action is due
292 // Reminder that action is post-due
294 //Collect applicable rules
295 // Note that due to a limitation in the this function, the patient_id is explicitly
296 // for grouping items when not being done in real-time or for official reporting.
297 // So for cases such as patient reminders on a clinic scale, the calling function
298 // will actually need rather than pass in a explicit patient_id for each patient in
299 // a separate call to this function.
300 if ($mode != "report") {
301 // Use per patient custom rules (if exist)
302 // Note as discussed above, this only works for single patient instances.
303 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
305 else { // $mode = "report"
306 // Only use default rules (do not use patient custom rules)
307 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
310 foreach( $rules as $rowRule ) {
312 // If using cqm or amc type, then use the hard-coded rules set.
313 // Note these rules are only used in report mode.
314 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
316 require_once( dirname(__FILE__)."/classes/rulesets/ReportManager.php");
317 $manager = new ReportManager();
318 if ($rowRule['amc_flag']) {
319 // Send array of dates ('dateBegin' and 'dateTarget')
320 $tempResults = $manager->runReport( $rowRule, $patientData, $dateArray, $options );
322 else {
323 // Send target date
324 $tempResults = $manager->runReport( $rowRule, $patientData, $dateTarget );
326 if (!empty($tempResults)) {
327 foreach ($tempResults as $tempResult) {
328 array_push($results,$tempResult);
332 // Go on to the next rule
333 continue;
336 // If in reminder mode then need to collect the measurement dates
337 // from rule_reminder table
338 $target_dates = array();
339 if ($mode != "report") {
340 // Calculate the dates to check for
341 if ($type == "patient_reminder") {
342 $reminder_interval_type = "patient_reminder";
344 else { // $type == "passive_alert" or $type == "active_alert"
345 $reminder_interval_type = "clinical_reminder";
347 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
349 else { // $mode == "report"
350 // Only use the target date in the report
351 $target_dates[0] = $dateTarget;
354 //Reset the counters
355 $total_patients = 0;
356 $pass_filter = 0;
357 $exclude_filter = 0;
358 $pass_target = 0;
360 // Find the number of target groups
361 $targetGroups = returnTargetGroups($rowRule['id']);
363 if ( (count($targetGroups) == 1) || ($mode == "report") ) {
364 //skip this section if not report and more than one target group
365 foreach( $patientData as $rowPatient ) {
367 // Count the total patients
368 $total_patients++;
370 $dateCounter = 1; // for reminder mode to keep track of which date checking
371 foreach ( $target_dates as $dateFocus ) {
373 //Skip if date is set to SKIP
374 if ($dateFocus == "SKIP") {
375 $dateCounter++;
376 continue;
379 //Set date counter and reminder token (applicable for reminders only)
380 if ($dateCounter == 1) {
381 $reminder_due = "soon_due";
383 else if ($dateCounter == 2) {
384 $reminder_due = "due";
386 else { // $dateCounter == 3
387 $reminder_due = "past_due";
390 // First, deal with deceased patients
391 // (for now will simply not pass the filter, but can add a database item
392 // if ever want to create rules for dead people)
393 // Could also place this function at the total_patients level if wanted.
394 // (But then would lose the option of making rules for dead people)
395 // Note using the dateTarget rather than dateFocus
396 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
397 continue;
400 // Check if pass filter
401 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
402 if ($passFilter === "EXCLUDED") {
403 // increment EXCLUDED and pass_filter counters
404 // and set as FALSE for reminder functionality.
405 $pass_filter++;
406 $exclude_filter++;
407 $passFilter = FALSE;
409 if ($passFilter) {
410 // increment pass filter counter
411 $pass_filter++;
413 else {
414 $dateCounter++;
415 continue;
418 // Check if pass target
419 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
420 if ($passTarget) {
421 // increment pass target counter
422 $pass_target++;
423 // send to reminder results
424 if ($mode == "reminders-all") {
425 // place the completed actions into the reminder return array
426 $actionArray = resolve_action_sql($rowRule['id'],'1');
427 foreach ($actionArray as $action) {
428 $action_plus = $action;
429 $action_plus['due_status'] = "not_due";
430 $action_plus['pid'] = $rowPatient['pid'];
431 $results = reminder_results_integrate($results, $action_plus);
434 break;
436 else {
437 // send to reminder results
438 if ($mode != "report") {
439 // place the uncompleted actions into the reminder return array
440 $actionArray = resolve_action_sql($rowRule['id'],'1');
441 foreach ($actionArray as $action) {
442 $action_plus = $action;
443 $action_plus['due_status'] = $reminder_due;
444 $action_plus['pid'] = $rowPatient['pid'];
445 $results = reminder_results_integrate($results, $action_plus);
449 $dateCounter++;
454 // Calculate and save the data for the rule
455 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
456 if ($mode == "report") {
457 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
458 $newRow=array_merge($newRow,$rowRule);
459 array_push($results, $newRow);
462 // Now run through the target groups if more than one
463 if (count($targetGroups) > 1) {
464 foreach ($targetGroups as $i) {
466 //Reset the target counter
467 $pass_target = 0;
469 foreach( $patientData as $rowPatient ) {
471 $dateCounter = 1; // for reminder mode to keep track of which date checking
472 foreach ( $target_dates as $dateFocus ) {
474 //Skip if date is set to SKIP
475 if ($dateFocus == "SKIP") {
476 $dateCounter++;
477 continue;
480 //Set date counter and reminder token (applicable for reminders only)
481 if ($dateCounter == 1) {
482 $reminder_due = "soon_due";
484 else if ($dateCounter == 2) {
485 $reminder_due = "due";
487 else { // $dateCounter == 3
488 $reminder_due = "past_due";
491 // First, deal with deceased patients
492 // (for now will simply not pass the filter, but can add a database item
493 // if ever want to create rules for dead people)
494 // Could also place this function at the total_patients level if wanted.
495 // (But then would lose the option of making rules for dead people)
496 // Note using the dateTarget rather than dateFocus
497 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
498 continue;
501 // Check if pass filter
502 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
503 if ($passFilter === "EXCLUDED") {
504 $passFilter = FALSE;
506 if (!$passFilter) {
507 // increment pass filter counter
508 $dateCounter++;
509 continue;
512 //Check if pass target
513 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
514 if ($passTarget) {
515 // increment pass target counter
516 $pass_target++;
517 // send to reminder results
518 if ($mode == "reminders-all") {
519 // place the completed actions into the reminder return array
520 $actionArray = resolve_action_sql($rowRule['id'],$i);
521 foreach ($actionArray as $action) {
522 $action_plus = $action;
523 $action_plus['due_status'] = "not_due";
524 $action_plus['pid'] = $rowPatient['pid'];
525 $results = reminder_results_integrate($results, $action_plus);
528 break;
530 else {
531 // send to reminder results
532 if ($mode != "report") {
533 // place the actions into the reminder return array
534 $actionArray = resolve_action_sql($rowRule['id'],$i);
535 foreach ($actionArray as $action) {
536 $action_plus = $action;
537 $action_plus['due_status'] = $reminder_due;
538 $action_plus['pid'] = $rowPatient['pid'];
539 $results = reminder_results_integrate($results, $action_plus);
543 $dateCounter++;
547 // Calculate and save the data for the rule
548 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
550 // Collect action for title (just use the first one, if more than one)
551 $actionArray = resolve_action_sql($rowRule['id'],$i);
552 $action = $actionArray[0];
553 if ($mode == "report") {
554 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
555 array_push($results, $newRow);
561 // Return the data
562 return $results;
565 // Test filter of a selected rule on a selected patient
566 // Parameters:
567 // $patient_id - pid of selected patient.
568 // $rule - id(string) of selected rule
569 // $dateTarget - target date.
570 // Return:
571 // boolean (if pass filter then TRUE, if excluded then 'EXCLUDED', if not pass filter then FALSE)
572 function test_filter($patient_id,$rule,$dateTarget) {
574 // Set date to current if not set
575 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
577 // Collect patient information
578 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
581 // ----------------- INCLUSIONS -----------------
584 // -------- Age Filter (inclusion) ------------
585 // Calculate patient age in years and months
586 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
587 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
589 // Min age (year) Filter (assume that there in not more than one of each)
590 $filter = resolve_filter_sql($rule,'filt_age_min');
591 if (!empty($filter)) {
592 $row = $filter[0];
593 if ($row ['method_detail'] == "year") {
594 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
595 return false;
598 if ($row ['method_detail'] == "month") {
599 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
600 return false;
604 // Max age (year) Filter (assume that there in not more than one of each)
605 $filter = resolve_filter_sql($rule,'filt_age_max');
606 if (!empty($filter)) {
607 $row = $filter[0];
608 if ($row ['method_detail'] == "year") {
609 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
610 return false;
613 if ($row ['method_detail'] == "month") {
614 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
615 return false;
620 // -------- Gender Filter (inclusion) ---------
621 // Gender Filter (assume that there in not more than one of each)
622 $filter = resolve_filter_sql($rule,'filt_sex');
623 if (!empty($filter)) {
624 $row = $filter[0];
625 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
626 return false;
630 // -------- Database Filter (inclusion) ------
631 // Database Filter
632 $filter = resolve_filter_sql($rule,'filt_database');
633 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
635 // -------- Lists Filter (inclusion) ----
636 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
637 // surgeries and allergies.
638 $filter = resolve_filter_sql($rule,'filt_lists');
639 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
641 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
642 // Procedure Target (includes) (may need to include an interval in the future)
643 $filter = resolve_filter_sql($rule,'filt_proc');
644 if ((!empty($filter)) && !procedure_check($patient_id,$filter,'',$dateTarget)) return false;
647 // ----------------- EXCLUSIONS -----------------
650 // -------- Lists Filter (EXCLUSION) ----
651 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
652 // surgeries and allergies.
653 $filter = resolve_filter_sql($rule,'filt_lists',0);
654 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
656 // Passed all filters, so return true.
657 return true;
660 // Return an array containing existing group ids for a rule
661 // Parameters:
662 // $rule - id(string) of rule
663 // Return:
664 // array, listing of group ids
665 function returnTargetGroups($rule) {
667 $sql = sqlStatement("SELECT DISTINCT `group_id` FROM `rule_target` " .
668 "WHERE `id`=?", array($rule) );
670 $groups = array();
671 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
672 array_push($groups,$row['group_id']);
674 return $groups;
677 // Test targets of a selected rule on a selected patient
678 // Parameters:
679 // $patient_id - pid of selected patient.
680 // $rule - id(string) of selected rule (if blank, then will ignore grouping)
681 // $group_id - group id of target group
682 // $dateTarget - target date.
683 // Return:
684 // boolean (if target passes then true, otherwise false)
685 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
687 // -------- Interval Target ----
688 $interval = resolve_target_sql($rule,$group_id,'target_interval');
690 // -------- Database Target ----
691 // Database Target (includes)
692 $target = resolve_target_sql($rule,$group_id,'target_database');
693 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
695 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
696 // Procedure Target (includes)
697 $target = resolve_target_sql($rule,$group_id,'target_proc');
698 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
700 // -------- Appointment Target ----
701 // Appointment Target (includes) (Specialized functionality for appointment reminders)
702 $target = resolve_target_sql($rule,$group_id,'target_appt');
703 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
705 // Passed all target tests, so return true.
706 return true;
709 // Function to return active plans
710 // Parameters:
711 // $type - plan type filter (normal or cqm or blank)
712 // $patient_id - pid of selected patient. (if custom plan does not exist then
713 // will use the default plan)
714 // $configurableOnly - true if only want the configurable (per patient) plans
715 // (ie. ignore cqm plans)
716 // Return: array containing plans
717 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
719 if ($configurableOnly) {
720 // Collect all default, configurable (per patient) plans into an array
721 // (ie. ignore the cqm rules)
722 $sql = sqlStatement("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
724 else {
725 // Collect all default plans into an array
726 $sql = sqlStatement("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
728 $returnArray= array();
729 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
730 array_push($returnArray,$row);
733 // Now collect the pertinent plans
734 $newReturnArray = array();
736 // Need to select rules (use custom if exist)
737 foreach ($returnArray as $plan) {
738 $customPlan = sqlQuery("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
740 // Decide if use default vs custom plan (preference given to custom plan)
741 if (!empty($customPlan)) {
742 if ($type == "cqm" ) {
743 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
744 $goPlan = $plan;
746 else {
747 // merge the custom plan with the default plan
748 $mergedPlan = array();
749 foreach ($customPlan as $key => $value) {
750 if ($value == NULL && preg_match("/_flag$/",$key)) {
751 // use default setting
752 $mergedPlan[$key] = $plan[$key];
754 else {
755 // use custom setting
756 $mergedPlan[$key] = $value;
759 $goPlan = $mergedPlan;
762 else {
763 $goPlan = $plan;
766 // Use the chosen plan if set
767 if (!empty($type)) {
768 if ($goPlan["${type}_flag"] == 1) {
769 // active, so use the plan
770 array_push($newReturnArray,$goPlan);
773 else {
774 if ($goPlan['normal_flag'] == 1 ||
775 $goPlan['cqm_flag'] == 1) {
776 // active, so use the plan
777 array_push($newReturnArray,$goPlan);
781 $returnArray = $newReturnArray;
783 return $returnArray;
786 // Function to return a specific plan
787 // Parameters:
788 // $plan - id(string) of plan
789 // $patient_id - pid of selected patient. (if set to 0, then will return
790 // the default rule).
791 // Return: array containing a rule
792 function collect_plan($plan,$patient_id='0') {
794 return sqlQuery("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id) );
798 // Function to set a specific plan activity for a specific patient
799 // Parameters:
800 // $plan - id(string) of plan
801 // $type - plan filter (normal,cqm)
802 // $setting - activity of plan (yes,no,default)
803 // $patient_id - pid of selected patient.
804 // Return: nothing
805 function set_plan_activity_patient($plan,$type,$setting,$patient_id) {
807 // Don't allow messing with the default plans here
808 if ($patient_id == "0") {
809 return;
812 // Convert setting
813 if ($setting == "on") {
814 $setting = 1;
816 else if ($setting == "off") {
817 $setting = 0;
819 else { // $setting == "default"
820 $setting = NULL;
823 // Collect patient specific plan, if already exists.
824 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
825 $patient_plan = sqlQuery($query, array($plan,$patient_id) );
827 if (empty($patient_plan)) {
828 // Create a new patient specific plan with flags all set to default
829 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
830 sqlStatement($query, array($plan, $patient_id) );
833 // Update patient specific row
834 $query = "UPDATE `clinical_plans` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
835 sqlStatement($query, array($setting,$plan,$patient_id) );
839 // Function to return active rules
840 // Parameters:
841 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
842 // $patient_id - pid of selected patient. (if custom rule does not exist then
843 // will use the default rule)
844 // $configurableOnly - true if only want the configurable (per patient) rules
845 // (ie. ignore cqm and amc rules)
846 // $plan - collect rules for specific plan
847 // Return: array containing rules
848 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='') {
850 if ($configurableOnly) {
851 // Collect all default, configurable (per patient) rules into an array
852 // (ie. ignore the cqm and amc rules)
853 $sql = sqlStatement("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
855 else {
856 // Collect all default rules into an array
857 $sql = sqlStatement("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
859 $returnArray= array();
860 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
861 array_push($returnArray,$row);
864 // Now filter rules for plan (if applicable)
865 if (!empty($plan)) {
866 $planReturnArray = array();
867 foreach ($returnArray as $rule) {
868 $standardRule = sqlQuery("SELECT * FROM `clinical_plans_rules` " .
869 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
870 if (!empty($standardRule)) {
871 array_push($planReturnArray,$rule);
874 $returnArray = $planReturnArray;
877 // Now collect the pertinent rules
878 $newReturnArray = array();
880 // Need to select rules (use custom if exist)
881 foreach ($returnArray as $rule) {
882 $customRule = sqlQuery("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
884 // Decide if use default vs custom rule (preference given to custom rule)
885 if (!empty($customRule)) {
886 if ($type == "cqm" || $type == "amc" ) {
887 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
888 $goRule = $rule;
890 else {
891 // merge the custom rule with the default rule
892 $mergedRule = array();
893 foreach ($customRule as $key => $value) {
894 if ($value == NULL && preg_match("/_flag$/",$key)) {
895 // use default setting
896 $mergedRule[$key] = $rule[$key];
898 else {
899 // use custom setting
900 $mergedRule[$key] = $value;
903 $goRule = $mergedRule;
906 else {
907 $goRule = $rule;
910 // Use the chosen rule if set
911 if (!empty($type)) {
912 if ($goRule["${type}_flag"] == 1) {
913 // active, so use the rule
914 array_push($newReturnArray,$goRule);
917 else {
918 // no filter, so return the rule
919 array_push($newReturnArray,$goRule);
922 $returnArray = $newReturnArray;
924 return $returnArray;
927 // Function to return a specific rule
928 // Parameters:
929 // $rule - id(string) of rule
930 // $patient_id - pid of selected patient. (if set to 0, then will return
931 // the default rule).
932 // Return: array containing a rule
933 function collect_rule($rule,$patient_id='0') {
935 return sqlQuery("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
939 // Function to set a specific rule activity for a specific patient
940 // Parameters:
941 // $rule - id(string) of rule
942 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
943 // $setting - activity of rule (yes,no,default)
944 // $patient_id - pid of selected patient.
945 // Return: nothing
946 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
948 // Don't allow messing with the default rules here
949 if ($patient_id == "0") {
950 return;
953 // Convert setting
954 if ($setting == "on") {
955 $setting = 1;
957 else if ($setting == "off") {
958 $setting = 0;
960 else { // $setting == "default"
961 $setting = NULL;
964 // Collect patient specific rule, if already exists.
965 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
966 $patient_rule = sqlQuery($query, array($rule,$patient_id) );
968 if (empty($patient_rule)) {
969 // Create a new patient specific rule with flags all set to default
970 $query = "INSERT into `clinical_rules` (`id`, `pid`) VALUES (?,?)";
971 sqlStatement($query, array($rule, $patient_id) );
974 // Update patient specific row
975 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
976 sqlStatement($query, array($setting,$rule,$patient_id) );
980 // Function to return applicable reminder dates (relative)
981 // Parameters:
982 // $rule - id(string) of selected rule
983 // $reminder_method - string label of filter type
984 // Return: array containing reminder features
985 function resolve_reminder_sql($rule,$reminder_method) {
986 $sql = sqlStatement("SELECT `method_detail`, `value` FROM `rule_reminder` " .
987 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method) );
989 $returnArray= array();
990 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
991 array_push($returnArray,$row);
993 return $returnArray;
996 // Function to return applicable filters
997 // Parameters:
998 // $rule - id(string) of selected rule
999 // $filter_method - string label of filter type
1000 // $include_flag - to allow selection for included or excluded filters
1001 // Return: array containing filters
1002 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
1003 $sql = sqlStatement("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1004 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
1006 $returnArray= array();
1007 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1008 array_push($returnArray,$row);
1010 return $returnArray;
1013 // Function to return applicable targets
1014 // Parameters:
1015 // $rule - id(string) of selected rule
1016 // $group_id - group id of target group (if blank, then will ignore grouping)
1017 // $target_method - string label of target type
1018 // $include_flag - to allow selection for included or excluded targets
1019 // Return: array containing targets
1020 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
1022 if ($group_id) {
1023 $sql = sqlStatement("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1024 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
1026 else {
1027 $sql = sqlStatement("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1028 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
1031 $returnArray= array();
1032 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1033 array_push($returnArray,$row);
1035 return $returnArray;
1038 // Function to return applicable actions
1039 // Parameters:
1040 // $rule - id(string) of selected rule
1041 // $group_id - group id of target group (if blank, then will ignore grouping)
1042 // Return: array containing actions
1043 function resolve_action_sql($rule,$group_id='') {
1045 if ($group_id) {
1046 $sql = sqlStatement("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1047 "FROM `rule_action` as a " .
1048 "JOIN `rule_action_item` as b " .
1049 "ON a.category = b.category AND a.item = b.item " .
1050 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
1052 else {
1053 $sql = sqlStatement("SELECT b.category, b.item, b.value, b.custom_flag " .
1054 "FROM `rule_action` as a " .
1055 "JOIN `rule_action_item` as b " .
1056 "ON a.category = b.category AND a.item = b.item " .
1057 "WHERE a.id=?", array($rule) );
1060 $returnArray= array();
1061 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1062 array_push($returnArray,$row);
1064 return $returnArray;
1067 // Function to check database filters and targets
1068 // Parameters:
1069 // $patient_id - pid of selected patient.
1070 // $filter - array containing filter/target elements
1071 // $interval - used for the interval elements
1072 // $dateTarget - target date. blank is current date.
1073 // Return: boolean if check passed, otherwise false
1074 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
1075 $isMatch = false; //matching flag
1077 // Set date to current if not set
1078 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1080 // Unpackage interval information
1081 // (Assume only one for now and only pertinent for targets)
1082 $intervalType = '';
1083 $intervalValue = '';
1084 if (!empty($interval)) {
1085 $intervalType = $interval[0]['value'];
1086 $intervalValue = $interval[0]['interval'];
1089 foreach( $filter as $row ) {
1090 // Row description
1091 // [0]=>special modes
1092 $temp_df = explode("::",$row['value']);
1094 if ($temp_df[0] == "CUSTOM") {
1095 // Row description
1096 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1097 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1098 // Record the match
1099 $isMatch = true;
1101 else {
1102 // If this is a required entry then return false
1103 if ($row['required_flag']) return false;
1106 else if ($temp_df[0] == "LIFESTYLE") {
1107 // Row description
1108 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1109 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1110 // Record the match
1111 $isMatch = true;
1113 else {
1114 // If this is a required entry then return false
1115 if ($row['required_flag']) return false;
1118 else {
1119 // Default mode
1120 // Row description
1121 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1122 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)) {
1123 // Record the match
1124 $isMatch = true;
1126 else {
1127 // If this is a required entry then return false
1128 if ($row['required_flag']) return false;
1133 // return results of check
1134 return $isMatch;
1137 // Function to check procedure filters and targets
1138 // Parameters:
1139 // $patient_id - pid of selected patient.
1140 // $filter - array containing filter/target elements
1141 // $interval - used for the interval elements
1142 // $dateTarget - target date. blank is current date.
1143 // Return: boolean if check passed, otherwise false
1144 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
1145 $isMatch = false; //matching flag
1147 // Set date to current if not set
1148 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1150 // Unpackage interval information
1151 // (Assume only one for now and only pertinent for targets)
1152 $intervalType = '';
1153 $intervalValue = '';
1154 if (!empty($interval)) {
1155 $intervalType = $interval[0]['value'];
1156 $intervalValue = $interval[0]['interval'];
1159 foreach( $filter as $row ) {
1160 // Row description
1161 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1162 // code description
1163 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1164 $temp_df = explode("::",$row['value']);
1165 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)) {
1166 // Record the match
1167 $isMatch = true;
1169 else {
1170 // If this is a required entry then return false
1171 if ($row['required_flag']) return false;
1175 // return results of check
1176 return $isMatch;
1179 // Function to check for appointment
1180 // Parameters:
1181 // $patient_id - pid of selected patient.
1182 // $dateTarget - target date.
1183 // Return: boolean if appt exist, otherwise false
1184 function appointment_check($patient_id,$dateTarget='') {
1185 $isMatch = false; //matching flag
1187 // Set date to current if not set (although should always be set)
1188 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1189 $dateTargetRound = date('Y-m-d',$dateTarget);
1191 // Set current date
1192 $currentDate = date('Y-m-d H:i:s');
1193 $currentDateRound = date('Y-m-d',$dateCurrent);
1195 // Basically, if the appointment is within the current date to the target date,
1196 // then return true. (will not send reminders on same day as appointment)
1197 $sql = sqlStatement("SELECT openemr_postcalendar_events.pc_eid, " .
1198 "openemr_postcalendar_events.pc_title, " .
1199 "openemr_postcalendar_events.pc_eventDate, " .
1200 "openemr_postcalendar_events.pc_startTime, " .
1201 "openemr_postcalendar_events.pc_endTime " .
1202 "FROM openemr_postcalendar_events " .
1203 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1204 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1205 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1207 // return results of check
1209 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1210 // Plan to send back array of appt info (eid, time, date, etc.)
1211 // to do this.
1212 if (sqlNumRows($sql) > 0) {
1213 $isMatch = true;
1216 return $isMatch;
1219 // Function to check lists filters and targets
1220 // Customizable and currently includes diagnoses, medications,
1221 // allergies and surgeries.
1222 // Parameters:
1223 // $patient_id - pid of selected patient.
1224 // $filter - array containing lists filter/target elements
1225 // $dateTarget - target date. blank is current date.
1226 // Return: boolean if check passed, otherwise false
1227 function lists_check($patient_id,$filter,$dateTarget) {
1228 $isMatch = false; //matching flag
1230 // Set date to current if not set
1231 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1233 foreach ( $filter as $row ) {
1234 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1235 // Record the match
1236 $isMatch = true;
1238 else {
1239 // If this is a required entry then return false
1240 if ($row['required_flag']) return false;
1244 // return results of check
1245 return $isMatch;
1248 // Function to check for existance of data in database for a patient
1249 // Parameters:
1250 // $patient_id - pid of selected patient.
1251 // $table - selected mysql table
1252 // $column - selected mysql column
1253 // $data_comp - data comparison (eq,ne,gt,ge,lt,le)
1254 // $data - selected data in the mysql database
1255 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1256 // $num_items_thres - number of items threshold
1257 // $intervalType - type of interval (ie. year)
1258 // $intervalValue - searched for within this many times of the interval type
1259 // $dateTarget - target date.
1260 // Return: boolean if check passed, otherwise false
1261 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1263 // Set date to current if not set
1264 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1266 // Collect the correct column label for patient id in the table
1267 $patient_id_label = collect_database_label('pid',$table);
1269 // Get the interval sql query string
1270 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1272 // If just checking for existence (ie. data is empty),
1273 // then simply set the comparison operator to ne.
1274 if (empty($data)) {
1275 $data_comp = "ne";
1278 // get the appropriate sql comparison operator
1279 $compSql = convertCompSql($data_comp);
1281 // check for items
1282 if (empty($column)) {
1283 // simple search for any table entries
1284 $sql = sqlStatement("SELECT * " .
1285 "FROM `" . add_escape_custom($table) . "` " .
1286 "WHERE `" . add_escape_custom($patient_id_label) . "`=?", array($patient_id) );
1288 else {
1289 // search for number of specific items
1290 $sql = sqlStatement("SELECT `" . add_escape_custom($column) . "` " .
1291 "FROM `" . add_escape_custom($table) . "` " .
1292 "WHERE `" . add_escape_custom($column) ."`" . $compSql . "? " .
1293 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1294 $dateSql, array($data,$patient_id) );
1297 // See if number of returned items passes the comparison
1298 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1301 // Function to check for existence of procedure(s) for a patient
1302 // Parameters:
1303 // $patient_id - pid of selected patient.
1304 // $proc_title - procedure title
1305 // $proc_code - procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1306 // $result_comp - results comparison (eq,ne,gt,ge,lt,le)
1307 // $result_data - results data
1308 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1309 // $num_items_thres - number of items threshold
1310 // $intervalType - type of interval (ie. year)
1311 // $intervalValue - searched for within this many times of the interval type
1312 // $dateTarget - target date.
1313 // Return: boolean if check passed, otherwise false
1314 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1316 // Set date to current if not set
1317 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1319 // Set the table exception (for looking up pertinent date and pid sql columns)
1320 $table = "PROCEDURE-EXCEPTION";
1322 // Collect the correct column label for patient id in the table
1323 $patient_id_label = collect_database_label('pid',$table);
1325 // Get the interval sql query string
1326 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1328 // If just checking for existence (ie result_data is empty),
1329 // then simply set the comparison operator to ne.
1330 if (empty($result_data)) {
1331 $result_comp = "ne";
1334 // get the appropriate sql comparison operator
1335 $compSql = convertCompSql($result_comp);
1337 // explode the code array
1338 $codes= array();
1339 if (!empty($proc_code)) {
1340 $codes = explode("||",$proc_code);
1342 else {
1343 $codes[0] = '';
1346 // ensure proc_title is at least blank
1347 if (empty($proc_title)) {
1348 $proc_title = '';
1351 // collect specific items (use both title and/or codes) that fulfill request
1352 $sqlBindArray=array();
1353 $sql_query = "SELECT procedure_result.result " .
1354 "FROM `procedure_type`, " .
1355 "`procedure_order`, " .
1356 "`procedure_report`, " .
1357 "`procedure_result` " .
1358 "WHERE procedure_type.procedure_type_id = procedure_order.procedure_type_id " .
1359 "AND procedure_order.procedure_order_id = procedure_report.procedure_order_id " .
1360 "AND procedure_report.procedure_report_id = procedure_result.procedure_report_id " .
1361 "AND ";
1362 foreach ($codes as $tem) {
1363 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
1364 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
1365 array_push($sqlBindArray,$tem,$tem);
1367 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
1368 "AND procedure_result.result " . $compSql . " ? " .
1369 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
1370 array_push($sqlBindArray,$proc_title,$result_data,$patient_id);
1371 $sql = sqlStatement($sql_query,$sqlBindArray);
1373 // See if number of returned items passes the comparison
1374 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1377 // Function to check for existance of data for a patient in the rule_patient_data table
1378 // Parameters:
1379 // $patient_id - pid of selected patient.
1380 // $category - label in category column
1381 // $item - label in item column
1382 // $complete - label in complete column (YES,NO, or blank)
1383 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1384 // $num_items_thres - number of items threshold
1385 // $intervalType - type of interval (ie. year)
1386 // $intervalValue - searched for within this many times of the interval type
1387 // $dateTarget - target date.
1388 // Return: boolean if check passed, otherwise false
1389 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
1391 // Set the table
1392 $table = 'rule_patient_data';
1394 // Collect the correct column label for patient id in the table
1395 $patient_id_label = collect_database_label('pid',$table);
1397 // Get the interval sql query string
1398 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1400 // search for number of specific items
1401 $sql = sqlStatement("SELECT `result` " .
1402 "FROM `" . add_escape_custom($table) . "` " .
1403 "WHERE `category`=? " .
1404 "AND `item`=? " .
1405 "AND `complete`=? " .
1406 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1407 $dateSql, array($category,$item,$complete,$patient_id) );
1409 // See if number of returned items passes the comparison
1410 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1413 // Function to check for existance of data for a patient in lifestyle section
1414 // Parameters:
1415 // $patient_id - pid of selected patient.
1416 // $lifestyle - selected label of mysql column of patient history
1417 // $status - specific status of selected lifestyle element
1418 // $dateTarget - target date. blank is current date.
1419 // Return: boolean if check passed, otherwise false
1420 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
1422 // Set date to current if not set
1423 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1425 // Collect pertinent history data
1426 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
1428 // See if match
1429 $stringFlag = strstr($history[$lifestyle], "|".$status);
1430 if (empty($status)) {
1431 // Only ensuring any data has been entered into the field
1432 $stringFlag = true;
1434 if ( $history[$lifestyle] &&
1435 $history[$lifestyle] != '|0|' &&
1436 $stringFlag ) {
1437 return true;
1439 else {
1440 return false;
1444 // Function to check for lists item of a patient
1445 // Fully customizable and includes diagnoses, medications,
1446 // allergies, and surgeries.
1447 // Parameters:
1448 // $patient_id - pid of selected patient.
1449 // $type - type (medical_problem, allergy, medication, etc)
1450 // $value - value searching for
1451 // $dateTarget - target date. blank is current date.
1452 // Return: boolean if check passed, otherwise false
1453 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
1455 // Set date to current if not set
1456 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1458 // Attempt to explode the value into a code type and code (if applicable)
1459 $value_array = explode("::",$value);
1460 if (count($value_array) == 2) {
1462 // Collect the code type and code
1463 $code_type = $value_array[0];
1464 $code = $value_array[1];
1466 if ($code_type=='CUSTOM') {
1467 // Deal with custom code type first (title column in lists table)
1468 $response = sqlQuery("SELECT * FROM `lists` " .
1469 "WHERE `type`=? " .
1470 "AND `pid`=? " .
1471 "AND `title`=? " .
1472 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1473 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
1474 if (!empty($response)) return true;
1476 else {
1477 // Deal with the set code types (diagnosis column in lists table)
1478 $response = sqlQuery("SELECT * FROM `lists` " .
1479 "WHERE `type`=? " .
1480 "AND `pid`=? " .
1481 "AND `diagnosis` LIKE ? " .
1482 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1483 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
1484 if (!empty($response)) return true;
1487 else { // count($value_array) == 1
1488 // Search the title column in lists table
1489 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
1490 $response = sqlQuery("SELECT * FROM `lists` " .
1491 "WHERE `type`=? " .
1492 "AND `pid`=? " .
1493 "AND `title`=? ".
1494 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1495 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
1496 if (!empty($response)) return true;
1499 return false;
1502 // Function to return part of sql query to deal with interval
1503 // Parameters:
1504 // $table - selected mysql table (or EXCEPTION(s))
1505 // $intervalType - type of interval (ie. year)
1506 // $intervalValue - searched for within this many times of the interval type
1507 // $dateTarget - target date.
1508 // Return: string containing pertinent date interval filter for mysql query
1509 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
1511 $dateSql="";
1513 // Collect the correct column label for date in the table
1514 $date_label = collect_database_label('date',$table);
1516 // Deal with interval
1517 if (!empty($intervalType)) {
1518 switch($intervalType) {
1519 case "year":
1520 $dateSql = "AND (" . add_escape_custom($date_label) .
1521 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1522 "', INTERVAL " . add_escape_custom($intervalValue) .
1523 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
1524 break;
1525 case "month":
1526 $dateSql = "AND (" . add_escape_custom($date_label) .
1527 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1528 "', INTERVAL " . add_escape_custom($intervalValue) .
1529 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
1530 break;
1531 case "week":
1532 $dateSql = "AND (" . add_escape_custom($date_label) .
1533 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1534 "', INTERVAL " . add_escape_custom($intervalValue) .
1535 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
1536 break;
1537 case "day":
1538 $dateSql = "AND (" . add_escape_custom($date_label) .
1539 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1540 "', INTERVAL " . add_escape_custom($intervalValue) .
1541 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
1542 break;
1543 case "hour":
1544 $dateSql = "AND (" . add_escape_custom($date_label) .
1545 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1546 "', INTERVAL " . add_escape_custom($intervalValue) .
1547 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
1548 break;
1549 case "minute":
1550 $dateSql = "AND (" . add_escape_custom($date_label) .
1551 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1552 "', INTERVAL " . add_escape_custom($intervalValue) .
1553 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
1554 break;
1555 case "second":
1556 $dateSql = "AND (" . add_escape_custom($date_label) .
1557 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1558 "', INTERVAL " . add_escape_custom($intervalValue) .
1559 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
1560 break;
1561 case "flu_season":
1562 // Flu season to be hard-coded as September thru February
1563 // (Should make this modifiable in the future)
1564 // ($intervalValue is not used)
1565 $dateArray = explode("-",$dateTarget);
1566 $Year = $dateArray[0];
1567 $dateThisYear = $Year . "-09-01";
1568 $dateLastYear = ($Year-1) . "-09-01";
1569 $dateSql =" " .
1570 "AND ((" .
1571 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
1572 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
1573 "OR (" .
1574 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
1575 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
1576 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
1577 break;
1580 else {
1581 $dateSql = "AND " . add_escape_custom($date_label) .
1582 " <= '" . add_escape_custom($dateTarget) . "' ";
1585 // return the sql interval string
1586 return $dateSql;
1589 // Function to collect generic column labels from tables.
1590 // It currently works for date and pid.
1591 // Will need to expand this as algorithm grows.
1592 // Parameters:
1593 // $label - element (pid or date)
1594 // $table - selected mysql table (or EXCEPTION(s))
1595 // Return: string containing official label of selected element
1596 function collect_database_label($label,$table) {
1598 if ($table == 'PROCEDURE-EXCEPTION') {
1599 // return cell to get procedure collection
1600 // special case since reuqires joing of multiple
1601 // tables to get this value
1602 if ($label == "pid") {
1603 $returnedLabel = "procedure_order.patient_id";
1605 else if ($label == "date") {
1606 $returnedLabel = "procedure_report.date_collected";
1608 else {
1609 // unknown label, so return the original label
1610 $returnedLabel = $label;
1613 else if ($table == 'immunizations') {
1614 // return requested label for immunization table
1615 if ($label == "pid") {
1616 $returnedLabel = "patient_id";
1618 else if ($label == "date") {
1619 $returnedLabel = "`administered_date`";
1621 else {
1622 // unknown label, so return the original label
1623 $returnedLabel = $label;
1626 else {
1627 // return requested label for default tables
1628 if ($label == "pid") {
1629 $returnedLabel = "pid";
1631 else if ($label == "date") {
1632 $returnedLabel = "`date`";
1634 else {
1635 // unknown label, so return the original label
1636 $returnedLabel = $label;
1640 return $returnedLabel;
1643 // Simple function to avoid processing of duplicate actions
1644 // Parameters:
1645 // $actions - 2-dimensional array with all current active targets
1646 // $action - array of selected target to test for duplicate
1647 // Return: boolean, true if duplicate, false if not duplicate
1648 function is_duplicate_action($actions,$action) {
1649 foreach ($actions as $row) {
1650 if ($row['category'] == $action['category'] &&
1651 $row['item'] == $action['item'] &&
1652 $row['value'] == $action['value']) {
1653 // Is a duplicate
1654 return true;
1658 // Not a duplicate
1659 return false;
1662 // Calculate the reminder dates.
1663 // Parameters:
1664 // $rule - id(string) of selected rule
1665 // $dateTarget - target date. If blank then will test with current date as target.
1666 // $type - either 'patient_reminder' or 'clinical_reminder'
1667 // For now, will always return an array of 3 dates:
1668 // first date is before the target date (past_due) (default of 1 month)
1669 // second date is the target date (due)
1670 // third date is after the target date (soon_due) (default of 2 weeks)
1671 function calculate_reminder_dates($rule, $dateTarget='',$type) {
1673 // Set date to current if not set
1674 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1676 // Collect the current date settings (to ensure not skip)
1677 $res = resolve_reminder_sql($rule, $type.'_current');
1678 if (!empty($res)) {
1679 $row = $res[0];
1680 if ($row ['method_detail'] == "SKIP") {
1681 $dateTarget = "SKIP";
1685 // Collect the past_due date
1686 $past_due_date = "";
1687 $res = resolve_reminder_sql($rule, $type.'_post');
1688 if (!empty($res)) {
1689 $row = $res[0];
1690 if ($row ['method_detail'] == "week") {
1691 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
1693 if ($row ['method_detail'] == "month") {
1694 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
1696 if ($row ['method_detail'] == "hour") {
1697 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1699 if ($row ['method_detail'] == "SKIP") {
1700 $past_due_date = "SKIP";
1703 else {
1704 // empty settings, so use default of one month
1705 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
1708 // Collect the soon_due date
1709 $soon_due_date = "";
1710 $res = resolve_reminder_sql($rule, $type.'_pre');
1711 if (!empty($res)) {
1712 $row = $res[0];
1713 if ($row ['method_detail'] == "week") {
1714 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
1716 if ($row ['method_detail'] == "month") {
1717 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
1719 if ($row ['method_detail'] == "hour") {
1720 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1722 if ($row ['method_detail'] == "SKIP") {
1723 $soon_due_date = "SKIP";
1726 else {
1727 // empty settings, so use default of one month
1728 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
1731 // Return the array of three dates
1732 return array($soon_due_date,$dateTarget,$past_due_date);
1735 // Adds an action into the reminder array
1736 // Parameters:
1737 // $reminderOldArray - Contains the current array of reminders
1738 // $reminderNew - Array of a new reminder
1739 // Return:
1740 // An array of reminders
1741 function reminder_results_integrate($reminderOldArray, $reminderNew) {
1743 $results = array();
1745 // If reminderArray is empty, then insert new reminder
1746 if (empty($reminderOldArray)) {
1747 array_push($results, $reminderNew);
1748 return $results;
1751 // If duplicate reminder, then replace the old one
1752 $duplicate = false;
1753 foreach ($reminderOldArray as $reminderOld) {
1754 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
1755 $reminderOld['category'] == $reminderNew['category'] &&
1756 $reminderOld['item'] == $reminderNew['item']) {
1757 array_push($results, $reminderNew);
1758 $duplicate = true;
1760 else {
1761 array_push($results, $reminderOld);
1765 // If a new reminder, then insert the new reminder
1766 if (!$duplicate) {
1767 array_push($results, $reminderNew);
1770 return $results;
1773 // Compares number of items with requested comparison operator
1774 // Parameters:
1775 // $comp - Comparison operator(eq,ne,gt,ge,lt,le)
1776 // $thres - Threshold used in comparison
1777 // $num_items - Number of items
1778 // Return:
1779 // Boolean of comparison results
1780 function itemsNumberCompare($comp, $thres, $num_items) {
1782 if ( ($comp == "eq") && ($num_items == $thres) ) {
1783 return true;
1785 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
1786 return true;
1788 else if ( ($comp == "gt") && ($num_items > $thres) ) {
1789 return true;
1791 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
1792 return true;
1794 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
1795 return true;
1797 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
1798 return true;
1800 else {
1801 return false;
1805 // Converts a text comparison operator to sql equivalent
1806 // Parameters:
1807 // $comp - Comparison operator(eq,ne,gt,ge,lt,le)
1808 // Return:
1809 // String containing sql compatible comparison operator
1810 function convertCompSql($comp) {
1812 if ($comp == "eq") {
1813 return "=";
1815 else if ($comp == "ne") {
1816 return "!=";
1818 else if ($comp == "gt") {
1819 return ">";
1821 else if ($comp == "ge") {
1822 return ">=";
1824 else if ($comp == "lt") {
1825 return "<";
1827 else { // ($comp == "le")
1828 return "<=";
1832 // Function to find age in years (with decimal) on the target date
1833 // Parameters:
1834 // $dob - date of birth
1835 // $target - date to calculate age on
1836 // Return: decimal, years(decimal) from dob to target(date)
1837 function convertDobtoAgeYearDecimal($dob,$target) {
1839 // Prepare dob (Y M D)
1840 $dateDOB = explode(" ",$dob);
1842 // Prepare target (Y-M-D H:M:S)
1843 $dateTargetTemp = explode(" ",$target);
1844 $dateTarget = explode("-",$dateTargetTemp[0]);
1846 // Collect differences
1847 $iDiffYear = $dateTarget[0] - $dateDOB[0];
1848 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
1849 $iDiffDay = $dateTarget[2] - $dateDOB[2];
1851 // If birthday has not happen yet for this year, subtract 1.
1852 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
1854 $iDiffYear--;
1857 // Ensure diffYear is not less than 0
1858 if ($iDiffYear < 0) $iDiffYear = 0;
1860 return $iDiffYear;
1863 // Function to find age in months (with decimal) on the target date
1864 // Parameters:
1865 // $dob - date of birth
1866 // $target - date to calculate age on
1867 // Return: decimal, months(decimal) from dob to target(date)
1868 function convertDobtoAgeMonthDecimal($dob,$target) {
1870 // Prepare dob (Y M D)
1871 $dateDOB = explode(" ",$dob);
1873 // Prepare target (Y-M-D H:M:S)
1874 $dateTargetTemp = explode(" ",$target);
1875 $dateTarget = explode("-",$dateTargetTemp[0]);
1877 // Collect differences
1878 $iDiffYear = $dateTarget[0] - $dateDOB[0];
1879 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
1880 $iDiffDay = $dateTarget[2] - $dateDOB[2];
1882 // If birthday has not happen yet for this year, subtract 1.
1883 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
1885 $iDiffYear--;
1888 // Ensure diffYear is not less than 0
1889 if ($iDiffYear < 0) $iDiffYear = 0;
1891 return (12 * $iDiffYear) + $iDiffMonth;
1894 // Function to calculate the percentage
1895 // Parameters:
1896 // $pass_filter - number of patients that pass filter
1897 // $exclude_filter - number of patients that are excluded
1898 // $pass_target - number of patients that pass target
1899 // Return: String of a number formatted into a percentage
1900 function calculate_percentage($pass_filt,$exclude_filt,$pass_targ) {
1901 if ($pass_filt > 0) {
1902 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100) . xl('%');
1904 else {
1905 $perc = "0". xl('%');
1907 return $perc;