CDR engine report enhancement for patient provider relationships.
[openemr.git] / library / clinical_rules.php
blob991a38ca7794b2142d28218165afb73124ca67a5
1 <?php
2 /**
3 * Clinical Decision Rules(CDR) engine functions.
5 * Copyright (C) 2010-2012 Brady Miller <brady@sparmy.com>
6 * Copyright (C) 2011 Medical Information Integration, LLC
7 * Copyright (C) 2011 Ensofttek, LLC
9 * LICENSE: This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
20 * @package OpenEMR
21 * @author Brady Miller <brady@sparmy.com>
22 * @author Medical Information Integration, LLC
23 * @author Ensofttek, LLC
24 * @link http://www.open-emr.org
27 require_once(dirname(__FILE__) . "/patient.inc");
28 require_once(dirname(__FILE__) . "/forms.inc");
29 require_once(dirname(__FILE__) . "/formdata.inc.php");
30 require_once(dirname(__FILE__) . "/options.inc.php");
32 /**
33 * Display the clinical summary widget.
35 * @param integer $patient_id pid of selected patient
36 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
37 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
38 * @param string $organize_mode Way to organize the results (default or plans)
40 function clinical_summary_widget($patient_id,$mode,$dateTarget='',$organize_mode='default') {
42 // Set date to current if not set
43 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
45 // Collect active actions
46 $actions = test_rules_clinic('','passive_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
48 // Display the actions
49 foreach ($actions as $action) {
51 // Deal with plan names first
52 if (isset($action['is_plan']) &&$action['is_plan']) {
53 echo "<br><b>";
54 echo htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
55 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
56 echo "</b><br>";
57 continue;
60 if ($action['custom_flag']) {
61 // Start link for reminders that use the custom rules input screen
62 echo "<a href='../rules/patient_data.php?category=" .
63 htmlspecialchars( $action['category'], ENT_QUOTES) . "&item=" .
64 htmlspecialchars( $action['item'], ENT_QUOTES) .
65 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
67 else if ($action['clin_rem_link']) {
68 // Start link for reminders that use the custom rules input screen
69 echo "<a href='../../../" . $action['reminder_message'] .
70 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
72 else {
73 // continue, since no link will be created
76 // Display Reminder Details
77 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
78 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
80 if ($action['custom_flag'] || $action['clin_rem_link']) {
81 // End link for reminders that use an html link
82 echo "</a>";
85 // Display due status
86 if ($action['due_status']) {
87 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
88 if ($action['due_status'] == "past_due") {
89 echo "&nbsp;&nbsp;(<span style='color:red'>";
91 else if ($action['due_status'] == "due") {
92 echo "&nbsp;&nbsp;(<span style='color:purple'>";
94 else if ($action['due_status'] == "not_due") {
95 echo "&nbsp;&nbsp;(<span style='color:green'>";
97 else {
98 echo "&nbsp;&nbsp;(<span>";
100 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
102 else {
103 echo "<br>";
110 * Display the active screen reminder.
112 * @param integer $patient_id pid of selected patient
113 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
114 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
115 * @param string $organize_mode Way to organize the results (default or plans)
116 * @return string html display output.
118 function active_alert_summary($patient_id,$mode,$dateTarget='',$organize_mode='default') {
120 // Set date to current if not set
121 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
123 // Collect active actions
124 $actions = test_rules_clinic('','active_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
126 if (empty($actions)) {
127 return false;
130 $returnOutput = "";
132 // Display the actions
133 foreach ($actions as $action) {
135 // Deal with plan names first
136 if ($action['is_plan']) {
137 $returnOutput .= "<br><b>";
138 $returnOutput .= htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
139 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
140 $returnOutput .= "</b><br>";
141 continue;
144 // Display Reminder Details
145 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
146 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
148 // Display due status
149 if ($action['due_status']) {
150 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
151 if ($action['due_status'] == "past_due") {
152 $returnOutput .= "&nbsp;&nbsp;(<span style='color:red'>";
154 else if ($action['due_status'] == "due") {
155 $returnOutput .= "&nbsp;&nbsp;(<span style='color:purple'>";
157 else if ($action['due_status'] == "not_due") {
158 $returnOutput .= "&nbsp;&nbsp;(<span style='color:green'>";
160 else {
161 $returnOutput .= "&nbsp;&nbsp;(<span>";
163 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
165 else {
166 $returnOutput .= "<br>";
169 return $returnOutput;
173 * Process clinic rules.
175 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
176 * on one patient or patients of one provider). The structure of the returned results is dependent on the
177 * $organize_mode and $mode parameters.
178 * <pre>The results are dependent on the $organize_mode parameter settings
179 * 'default' organize_mode:
180 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
181 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
182 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
183 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
184 * 'plans' organize_mode:
185 * Returns similar to default, but organizes by the active plans
186 * </pre>
188 * @param integer $provider id of a selected provider. If blank, then will test entire clinic. If 'collate_outer' or 'collate_inner', then will test each provider in entire clinic; outer will nest plans inside collated providers, while inner will nest the providers inside the plans (note inner and outer are only different if organize_mode is set to plans).
189 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules.
190 * @param string/array $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target. If an array, then is holding two dates ('dateBegin' and 'dateTarget').
191 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
192 * @param integer $patient_id pid of patient. If blank then will check all patients.
193 * @param string $plan test for specific plan only
194 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
195 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
196 * @param string $pat_prov_rel How to choose patients that are related to a chosen provider. 'primary' selects patients that the provider is set as primary provider. 'encounter' selectes patients that the provider has seen. This parameter is only applicable if the $provider parameter is set to a provider or collation setting.
197 * @return array See above for organization structure of the results.
199 function test_rules_clinic($provider='',$type='',$dateTarget='',$mode='',$patient_id='',$plan='',$organize_mode='default',$options=array(),$pat_prov_rel='primary') {
201 // If dateTarget is an array, then organize them.
202 if (is_array($dateTarget)) {
203 $dateArray = $dateTarget;
204 $dateTarget = $dateTarget['dateTarget'];
207 // Set date to current if not set
208 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
210 // Prepare the results array
211 $results = array();
213 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
214 // then run through this function recursively and return results.
215 if (($provider == "collate_outer") || ($provider == "collate_inner" && $organize_mode != 'plans')) {
216 // First, collect an array of all providers
217 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
218 $ures = sqlStatement($query);
219 // Second, run through each provider recursively
220 while ($urow = sqlFetchArray($ures)) {
221 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode,$options,$pat_prov_rel);
222 if (!empty($newResults)) {
223 $provider_item['is_provider'] = TRUE;
224 $provider_item['prov_lname'] = $urow['lname'];
225 $provider_item['prov_fname'] = $urow['fname'];
226 $provider_item['npi'] = $urow['npi'];
227 $provider_item['federaltaxid'] = $urow['federaltaxid'];
228 array_push($results,$provider_item);
229 $results = array_merge($results,$newResults);
232 // done, so now can return results
233 return $results;
236 // If set organize-mode to plans, then collects active plans and run through this
237 // function recursively and return results.
238 if ($organize_mode == "plans") {
239 // First, collect active plans
240 $plans_resolve = resolve_plans_sql($plan,$patient_id);
241 // Second, run through function recursively
242 foreach ($plans_resolve as $plan_item) {
243 // (if collate_inner, then nest a collation of providers within each plan)
244 if ($provider == "collate_inner") {
245 // First, collect an array of all providers
246 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
247 $ures = sqlStatement($query);
248 // Second, run through each provider recursively
249 $provider_results = array();
250 while ($urow = sqlFetchArray($ures)) {
251 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel);
252 if (!empty($newResults)) {
253 $provider_item['is_provider'] = TRUE;
254 $provider_item['prov_lname'] = $urow['lname'];
255 $provider_item['prov_fname'] = $urow['fname'];
256 $provider_item['npi'] = $urow['npi'];
257 $provider_item['federaltaxid'] = $urow['federaltaxid'];
258 array_push($provider_results,$provider_item);
259 $provider_results = array_merge($provider_results,$newResults);
262 if (!empty($provider_results)) {
263 $plan_item['is_plan'] = TRUE;
264 array_push($results,$plan_item);
265 $results = array_merge($results,$provider_results);
268 else {
269 // (not collate_inner, so do not nest providers within each plan)
270 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel);
271 if (!empty($newResults)) {
272 $plan_item['is_plan'] = TRUE;
273 array_push($results,$plan_item);
274 $results = array_merge($results,$newResults);
278 // done, so now can return results
279 return $results;
282 // Collect all patient ids
283 $patientData = array();
284 if (!empty($patient_id)) {
285 // only look at the selected patient
286 $patientData[0]['pid'] = $patient_id;
288 else {
289 if (empty($provider)) {
290 // Look at entire practice
291 $rez = sqlStatement("SELECT `pid` FROM `patient_data`");
293 else {
294 // Look at an individual physician
295 if( $pat_prov_rel == 'encounter' ){
296 // Choose patients that are related to specific physician be any encounter
297 $rez = sqlStatement("SELECT DISTINCT pid FROM `form_encounter` ".
298 " WHERE provider_id=?", array($provider));
300 else { //$pat_prov_rel == 'primary'
301 // Choose patients that are assigned to the specific physician (primary physician in patient demographics)
302 $rez = sqlStatement("SELECT `pid` FROM `patient_data` " .
303 "WHERE providerID=?", array($provider) );
306 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
307 $patientData[$iter]=$row;
310 // Go through each patient(s)
312 // If in report mode, then tabulate for each rule:
313 // Total Patients
314 // Patients that pass the filter
315 // Patients that pass the target
316 // If in reminders mode, then create reminders for each rule:
317 // Reminder that action is due soon
318 // Reminder that action is due
319 // Reminder that action is post-due
321 //Collect applicable rules
322 // Note that due to a limitation in the this function, the patient_id is explicitly
323 // for grouping items when not being done in real-time or for official reporting.
324 // So for cases such as patient reminders on a clinic scale, the calling function
325 // will actually need rather than pass in a explicit patient_id for each patient in
326 // a separate call to this function.
327 if ($mode != "report") {
328 // Use per patient custom rules (if exist)
329 // Note as discussed above, this only works for single patient instances.
330 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
332 else { // $mode = "report"
333 // Only use default rules (do not use patient custom rules)
334 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
337 foreach( $rules as $rowRule ) {
339 // If using cqm or amc type, then use the hard-coded rules set.
340 // Note these rules are only used in report mode.
341 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
343 require_once( dirname(__FILE__)."/classes/rulesets/ReportManager.php");
344 $manager = new ReportManager();
345 if ($rowRule['amc_flag']) {
346 // Send array of dates ('dateBegin' and 'dateTarget')
347 $tempResults = $manager->runReport( $rowRule, $patientData, $dateArray, $options );
349 else {
350 // Send target date
351 $tempResults = $manager->runReport( $rowRule, $patientData, $dateTarget );
353 if (!empty($tempResults)) {
354 foreach ($tempResults as $tempResult) {
355 array_push($results,$tempResult);
359 // Go on to the next rule
360 continue;
363 // If in reminder mode then need to collect the measurement dates
364 // from rule_reminder table
365 $target_dates = array();
366 if ($mode != "report") {
367 // Calculate the dates to check for
368 if ($type == "patient_reminder") {
369 $reminder_interval_type = "patient_reminder";
371 else { // $type == "passive_alert" or $type == "active_alert"
372 $reminder_interval_type = "clinical_reminder";
374 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
376 else { // $mode == "report"
377 // Only use the target date in the report
378 $target_dates[0] = $dateTarget;
381 //Reset the counters
382 $total_patients = 0;
383 $pass_filter = 0;
384 $exclude_filter = 0;
385 $pass_target = 0;
387 // Find the number of target groups
388 $targetGroups = returnTargetGroups($rowRule['id']);
390 if ( (count($targetGroups) == 1) || ($mode == "report") ) {
391 //skip this section if not report and more than one target group
392 foreach( $patientData as $rowPatient ) {
394 // Count the total patients
395 $total_patients++;
397 $dateCounter = 1; // for reminder mode to keep track of which date checking
398 foreach ( $target_dates as $dateFocus ) {
400 //Skip if date is set to SKIP
401 if ($dateFocus == "SKIP") {
402 $dateCounter++;
403 continue;
406 //Set date counter and reminder token (applicable for reminders only)
407 if ($dateCounter == 1) {
408 $reminder_due = "soon_due";
410 else if ($dateCounter == 2) {
411 $reminder_due = "due";
413 else { // $dateCounter == 3
414 $reminder_due = "past_due";
417 // First, deal with deceased patients
418 // (for now will simply not pass the filter, but can add a database item
419 // if ever want to create rules for dead people)
420 // Could also place this function at the total_patients level if wanted.
421 // (But then would lose the option of making rules for dead people)
422 // Note using the dateTarget rather than dateFocus
423 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
424 continue;
427 // Check if pass filter
428 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
429 if ($passFilter === "EXCLUDED") {
430 // increment EXCLUDED and pass_filter counters
431 // and set as FALSE for reminder functionality.
432 $pass_filter++;
433 $exclude_filter++;
434 $passFilter = FALSE;
436 if ($passFilter) {
437 // increment pass filter counter
438 $pass_filter++;
440 else {
441 $dateCounter++;
442 continue;
445 // Check if pass target
446 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
447 if ($passTarget) {
448 // increment pass target counter
449 $pass_target++;
450 // send to reminder results
451 if ($mode == "reminders-all") {
452 // place the completed actions into the reminder return array
453 $actionArray = resolve_action_sql($rowRule['id'],'1');
454 foreach ($actionArray as $action) {
455 $action_plus = $action;
456 $action_plus['due_status'] = "not_due";
457 $action_plus['pid'] = $rowPatient['pid'];
458 $results = reminder_results_integrate($results, $action_plus);
461 break;
463 else {
464 // send to reminder results
465 if ($mode != "report") {
466 // place the uncompleted actions into the reminder return array
467 $actionArray = resolve_action_sql($rowRule['id'],'1');
468 foreach ($actionArray as $action) {
469 $action_plus = $action;
470 $action_plus['due_status'] = $reminder_due;
471 $action_plus['pid'] = $rowPatient['pid'];
472 $results = reminder_results_integrate($results, $action_plus);
476 $dateCounter++;
481 // Calculate and save the data for the rule
482 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
483 if ($mode == "report") {
484 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
485 $newRow=array_merge($newRow,$rowRule);
486 array_push($results, $newRow);
489 // Now run through the target groups if more than one
490 if (count($targetGroups) > 1) {
491 foreach ($targetGroups as $i) {
493 //Reset the target counter
494 $pass_target = 0;
496 foreach( $patientData as $rowPatient ) {
498 $dateCounter = 1; // for reminder mode to keep track of which date checking
499 foreach ( $target_dates as $dateFocus ) {
501 //Skip if date is set to SKIP
502 if ($dateFocus == "SKIP") {
503 $dateCounter++;
504 continue;
507 //Set date counter and reminder token (applicable for reminders only)
508 if ($dateCounter == 1) {
509 $reminder_due = "soon_due";
511 else if ($dateCounter == 2) {
512 $reminder_due = "due";
514 else { // $dateCounter == 3
515 $reminder_due = "past_due";
518 // First, deal with deceased patients
519 // (for now will simply not pass the filter, but can add a database item
520 // if ever want to create rules for dead people)
521 // Could also place this function at the total_patients level if wanted.
522 // (But then would lose the option of making rules for dead people)
523 // Note using the dateTarget rather than dateFocus
524 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
525 continue;
528 // Check if pass filter
529 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
530 if ($passFilter === "EXCLUDED") {
531 $passFilter = FALSE;
533 if (!$passFilter) {
534 // increment pass filter counter
535 $dateCounter++;
536 continue;
539 //Check if pass target
540 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
541 if ($passTarget) {
542 // increment pass target counter
543 $pass_target++;
544 // send to reminder results
545 if ($mode == "reminders-all") {
546 // place the completed actions into the reminder return array
547 $actionArray = resolve_action_sql($rowRule['id'],$i);
548 foreach ($actionArray as $action) {
549 $action_plus = $action;
550 $action_plus['due_status'] = "not_due";
551 $action_plus['pid'] = $rowPatient['pid'];
552 $results = reminder_results_integrate($results, $action_plus);
555 break;
557 else {
558 // send to reminder results
559 if ($mode != "report") {
560 // place the actions into the reminder return array
561 $actionArray = resolve_action_sql($rowRule['id'],$i);
562 foreach ($actionArray as $action) {
563 $action_plus = $action;
564 $action_plus['due_status'] = $reminder_due;
565 $action_plus['pid'] = $rowPatient['pid'];
566 $results = reminder_results_integrate($results, $action_plus);
570 $dateCounter++;
574 // Calculate and save the data for the rule
575 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
577 // Collect action for title (just use the first one, if more than one)
578 $actionArray = resolve_action_sql($rowRule['id'],$i);
579 $action = $actionArray[0];
580 if ($mode == "report") {
581 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
582 array_push($results, $newRow);
588 // Return the data
589 return $results;
593 * Test filter of a selected rule on a selected patient
595 * @param integer $patient_id pid of selected patient.
596 * @param string $rule id(string) of selected rule
597 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
598 * @return boolean/string if pass filter then TRUE; if excluded then 'EXCLUDED'; if not pass filter then FALSE
600 function test_filter($patient_id,$rule,$dateTarget) {
602 // Set date to current if not set
603 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
605 // Collect patient information
606 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
609 // ----------------- INCLUSIONS -----------------
612 // -------- Age Filter (inclusion) ------------
613 // Calculate patient age in years and months
614 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
615 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
617 // Min age (year) Filter (assume that there in not more than one of each)
618 $filter = resolve_filter_sql($rule,'filt_age_min');
619 if (!empty($filter)) {
620 $row = $filter[0];
621 if ($row ['method_detail'] == "year") {
622 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
623 return false;
626 if ($row ['method_detail'] == "month") {
627 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
628 return false;
632 // Max age (year) Filter (assume that there in not more than one of each)
633 $filter = resolve_filter_sql($rule,'filt_age_max');
634 if (!empty($filter)) {
635 $row = $filter[0];
636 if ($row ['method_detail'] == "year") {
637 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
638 return false;
641 if ($row ['method_detail'] == "month") {
642 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
643 return false;
648 // -------- Gender Filter (inclusion) ---------
649 // Gender Filter (assume that there in not more than one of each)
650 $filter = resolve_filter_sql($rule,'filt_sex');
651 if (!empty($filter)) {
652 $row = $filter[0];
653 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
654 return false;
658 // -------- Database Filter (inclusion) ------
659 // Database Filter
660 $filter = resolve_filter_sql($rule,'filt_database');
661 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
663 // -------- Lists Filter (inclusion) ----
664 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
665 // surgeries and allergies.
666 $filter = resolve_filter_sql($rule,'filt_lists');
667 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
669 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
670 // Procedure Target (includes) (may need to include an interval in the future)
671 $filter = resolve_filter_sql($rule,'filt_proc');
672 if ((!empty($filter)) && !procedure_check($patient_id,$filter,'',$dateTarget)) return false;
675 // ----------------- EXCLUSIONS -----------------
678 // -------- Lists Filter (EXCLUSION) ----
679 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
680 // surgeries and allergies.
681 $filter = resolve_filter_sql($rule,'filt_lists',0);
682 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
684 // Passed all filters, so return true.
685 return true;
689 * Return an array containing existing group ids for a rule
691 * @param string $rule id(string) of rule
692 * @return array listing of group ids
694 function returnTargetGroups($rule) {
696 $sql = sqlStatement("SELECT DISTINCT `group_id` FROM `rule_target` " .
697 "WHERE `id`=?", array($rule) );
699 $groups = array();
700 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
701 array_push($groups,$row['group_id']);
703 return $groups;
707 * Test targets of a selected rule on a selected patient
709 * @param integer $patient_id pid of selected patient.
710 * @param string $rule id(string) of selected rule (if blank, then will ignore grouping)
711 * @param integer $group_id group id of target group
712 * @param string $dateTarget target date (format Y-m-d H:i:s).
713 * @return boolean if target passes then true, otherwise false
715 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
717 // -------- Interval Target ----
718 $interval = resolve_target_sql($rule,$group_id,'target_interval');
720 // -------- Database Target ----
721 // Database Target (includes)
722 $target = resolve_target_sql($rule,$group_id,'target_database');
723 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
725 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
726 // Procedure Target (includes)
727 $target = resolve_target_sql($rule,$group_id,'target_proc');
728 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
730 // -------- Appointment Target ----
731 // Appointment Target (includes) (Specialized functionality for appointment reminders)
732 $target = resolve_target_sql($rule,$group_id,'target_appt');
733 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
735 // Passed all target tests, so return true.
736 return true;
740 * Function to return active plans
742 * @param string $type plan type filter (normal or cqm or blank)
743 * @param integer $patient_id pid of selected patient. (if custom plan does not exist then will use the default plan)
744 * @param boolean $configurableOnly true if only want the configurable (per patient) plans (ie. ignore cqm plans)
745 * @return array active plans
747 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
749 if ($configurableOnly) {
750 // Collect all default, configurable (per patient) plans into an array
751 // (ie. ignore the cqm rules)
752 $sql = sqlStatement("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
754 else {
755 // Collect all default plans into an array
756 $sql = sqlStatement("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
758 $returnArray= array();
759 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
760 array_push($returnArray,$row);
763 // Now collect the pertinent plans
764 $newReturnArray = array();
766 // Need to select rules (use custom if exist)
767 foreach ($returnArray as $plan) {
768 $customPlan = sqlQuery("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
770 // Decide if use default vs custom plan (preference given to custom plan)
771 if (!empty($customPlan)) {
772 if ($type == "cqm" ) {
773 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
774 $goPlan = $plan;
776 else {
777 // merge the custom plan with the default plan
778 $mergedPlan = array();
779 foreach ($customPlan as $key => $value) {
780 if ($value == NULL && preg_match("/_flag$/",$key)) {
781 // use default setting
782 $mergedPlan[$key] = $plan[$key];
784 else {
785 // use custom setting
786 $mergedPlan[$key] = $value;
789 $goPlan = $mergedPlan;
792 else {
793 $goPlan = $plan;
796 // Use the chosen plan if set
797 if (!empty($type)) {
798 if ($goPlan["${type}_flag"] == 1) {
799 // active, so use the plan
800 array_push($newReturnArray,$goPlan);
803 else {
804 if ($goPlan['normal_flag'] == 1 ||
805 $goPlan['cqm_flag'] == 1) {
806 // active, so use the plan
807 array_push($newReturnArray,$goPlan);
811 $returnArray = $newReturnArray;
813 return $returnArray;
818 * Function to return a specific plan
820 * @param string $plan id(string) of plan
821 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
822 * @return array a plan
824 function collect_plan($plan,$patient_id='0') {
826 return sqlQuery("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id) );
831 * Function to set a specific plan activity for a specific patient
833 * @param string $plan id(string) of plan
834 * @param string $type plan filter (normal,cqm)
835 * @param string $setting activity of plan (yes,no,default)
836 * @param integer $patient_id pid of selected patient.
838 function set_plan_activity_patient($plan,$type,$setting,$patient_id) {
840 // Don't allow messing with the default plans here
841 if ($patient_id == "0") {
842 return;
845 // Convert setting
846 if ($setting == "on") {
847 $setting = 1;
849 else if ($setting == "off") {
850 $setting = 0;
852 else { // $setting == "default"
853 $setting = NULL;
856 // Collect patient specific plan, if already exists.
857 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
858 $patient_plan = sqlQuery($query, array($plan,$patient_id) );
860 if (empty($patient_plan)) {
861 // Create a new patient specific plan with flags all set to default
862 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
863 sqlStatement($query, array($plan, $patient_id) );
866 // Update patient specific row
867 $query = "UPDATE `clinical_plans` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
868 sqlStatement($query, array($setting,$plan,$patient_id) );
873 * Function to return active rules
875 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
876 * @param integer $patient_id pid of selected patient. (if custom rule does not exist then will use the default rule)
877 * @param boolean $configurableOnly true if only want the configurable (per patient) rules (ie. ignore cqm and amc rules)
878 * @param string $plan collect rules for specific plan
879 * @return array rules
881 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='') {
883 if ($configurableOnly) {
884 // Collect all default, configurable (per patient) rules into an array
885 // (ie. ignore the cqm and amc rules)
886 $sql = sqlStatement("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
888 else {
889 // Collect all default rules into an array
890 $sql = sqlStatement("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
892 $returnArray= array();
893 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
894 array_push($returnArray,$row);
897 // Now filter rules for plan (if applicable)
898 if (!empty($plan)) {
899 $planReturnArray = array();
900 foreach ($returnArray as $rule) {
901 $standardRule = sqlQuery("SELECT * FROM `clinical_plans_rules` " .
902 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
903 if (!empty($standardRule)) {
904 array_push($planReturnArray,$rule);
907 $returnArray = $planReturnArray;
910 // Now collect the pertinent rules
911 $newReturnArray = array();
913 // Need to select rules (use custom if exist)
914 foreach ($returnArray as $rule) {
915 $customRule = sqlQuery("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
917 // Decide if use default vs custom rule (preference given to custom rule)
918 if (!empty($customRule)) {
919 if ($type == "cqm" || $type == "amc" ) {
920 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
921 $goRule = $rule;
923 else {
924 // merge the custom rule with the default rule
925 $mergedRule = array();
926 foreach ($customRule as $key => $value) {
927 if ($value == NULL && preg_match("/_flag$/",$key)) {
928 // use default setting
929 $mergedRule[$key] = $rule[$key];
931 else {
932 // use custom setting
933 $mergedRule[$key] = $value;
936 $goRule = $mergedRule;
939 else {
940 $goRule = $rule;
943 // Use the chosen rule if set
944 if (!empty($type)) {
945 if ($goRule["${type}_flag"] == 1) {
946 // active, so use the rule
947 array_push($newReturnArray,$goRule);
950 else {
951 // no filter, so return the rule
952 array_push($newReturnArray,$goRule);
955 $returnArray = $newReturnArray;
957 return $returnArray;
961 * Function to return a specific rule
963 * @param string $rule id(string) of rule
964 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
965 * @return array rule
967 function collect_rule($rule,$patient_id='0') {
969 return sqlQuery("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
974 * Function to set a specific rule activity for a specific patient
976 * @param string $rule id(string) of rule
977 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
978 * @param string $setting activity of rule (yes,no,default)
979 * @param integer $patient_id pid of selected patient.
981 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
983 // Don't allow messing with the default rules here
984 if ($patient_id == "0") {
985 return;
988 // Convert setting
989 if ($setting == "on") {
990 $setting = 1;
992 else if ($setting == "off") {
993 $setting = 0;
995 else { // $setting == "default"
996 $setting = NULL;
999 // Collect patient specific rule, if already exists.
1000 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
1001 $patient_rule = sqlQuery($query, array($rule,$patient_id) );
1003 if (empty($patient_rule)) {
1004 // Create a new patient specific rule with flags all set to default
1005 $query = "INSERT into `clinical_rules` (`id`, `pid`) VALUES (?,?)";
1006 sqlStatement($query, array($rule, $patient_id) );
1009 // Update patient specific row
1010 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
1011 sqlStatement($query, array($setting,$rule,$patient_id) );
1016 * Function to return applicable reminder dates (relative)
1018 * @param string $rule id(string) of selected rule
1019 * @param string $reminder_method string label of filter type
1020 * @return array reminder features
1022 function resolve_reminder_sql($rule,$reminder_method) {
1023 $sql = sqlStatement("SELECT `method_detail`, `value` FROM `rule_reminder` " .
1024 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method) );
1026 $returnArray= array();
1027 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1028 array_push($returnArray,$row);
1030 return $returnArray;
1034 * Function to return applicable filters
1036 * @param string $rule id(string) of selected rule
1037 * @param string $filter_method string label of filter type
1038 * @param string $include_flag to allow selection for included or excluded filters
1039 * @return array filters
1041 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
1042 $sql = sqlStatement("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1043 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
1045 $returnArray= array();
1046 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1047 array_push($returnArray,$row);
1049 return $returnArray;
1053 * Function to return applicable targets
1055 * @param string $rule id(string) of selected rule
1056 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1057 * @param string $target_method string label of target type
1058 * @param string $include_flag to allow selection for included or excluded targets
1059 * @return array targets
1061 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
1063 if ($group_id) {
1064 $sql = sqlStatement("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1065 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
1067 else {
1068 $sql = sqlStatement("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1069 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
1072 $returnArray= array();
1073 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1074 array_push($returnArray,$row);
1076 return $returnArray;
1080 * Function to return applicable actions
1082 * @param string $rule id(string) of selected rule
1083 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1084 * @return array actions
1086 function resolve_action_sql($rule,$group_id='') {
1088 if ($group_id) {
1089 $sql = sqlStatement("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1090 "FROM `rule_action` as a " .
1091 "JOIN `rule_action_item` as b " .
1092 "ON a.category = b.category AND a.item = b.item " .
1093 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
1095 else {
1096 $sql = sqlStatement("SELECT b.category, b.item, b.value, b.custom_flag " .
1097 "FROM `rule_action` as a " .
1098 "JOIN `rule_action_item` as b " .
1099 "ON a.category = b.category AND a.item = b.item " .
1100 "WHERE a.id=?", array($rule) );
1103 $returnArray= array();
1104 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1105 array_push($returnArray,$row);
1107 return $returnArray;
1111 * Function to check database filters and targets
1113 * @param string $patient_id pid of selected patient.
1114 * @param array $filter array containing filter/target elements
1115 * @param array $interval array containing interval elements
1116 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1117 * @return boolean true if check passed, otherwise false
1119 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
1120 $isMatch = false; //matching flag
1122 // Set date to current if not set
1123 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1125 // Unpackage interval information
1126 // (Assume only one for now and only pertinent for targets)
1127 $intervalType = '';
1128 $intervalValue = '';
1129 if (!empty($interval)) {
1130 $intervalType = $interval[0]['value'];
1131 $intervalValue = $interval[0]['interval'];
1134 foreach( $filter as $row ) {
1135 // Row description
1136 // [0]=>special modes
1137 $temp_df = explode("::",$row['value']);
1139 if ($temp_df[0] == "CUSTOM") {
1140 // Row description
1141 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1142 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1143 // Record the match
1144 $isMatch = true;
1146 else {
1147 // If this is a required entry then return false
1148 if ($row['required_flag']) return false;
1151 else if ($temp_df[0] == "LIFESTYLE") {
1152 // Row description
1153 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1154 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1155 // Record the match
1156 $isMatch = true;
1158 else {
1159 // If this is a required entry then return false
1160 if ($row['required_flag']) return false;
1163 else {
1164 // Default mode
1165 // Row description
1166 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1167 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)) {
1168 // Record the match
1169 $isMatch = true;
1171 else {
1172 // If this is a required entry then return false
1173 if ($row['required_flag']) return false;
1178 // return results of check
1179 return $isMatch;
1183 * Function to check procedure filters and targets
1185 * @param string $patient_id pid of selected patient.
1186 * @param array $filter array containing filter/target elements
1187 * @param array $interval array containing interval elements
1188 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1189 * @return boolean true if check passed, otherwise false
1191 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
1192 $isMatch = false; //matching flag
1194 // Set date to current if not set
1195 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1197 // Unpackage interval information
1198 // (Assume only one for now and only pertinent for targets)
1199 $intervalType = '';
1200 $intervalValue = '';
1201 if (!empty($interval)) {
1202 $intervalType = $interval[0]['value'];
1203 $intervalValue = $interval[0]['interval'];
1206 foreach( $filter as $row ) {
1207 // Row description
1208 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1209 // code description
1210 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1211 $temp_df = explode("::",$row['value']);
1212 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)) {
1213 // Record the match
1214 $isMatch = true;
1216 else {
1217 // If this is a required entry then return false
1218 if ($row['required_flag']) return false;
1222 // return results of check
1223 return $isMatch;
1227 * Function to check for appointment
1229 * @todo Complete this to allow appointment reminders.
1230 * @param string $patient_id pid of selected patient.
1231 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1232 * @return boolean true if appt exist, otherwise false
1234 function appointment_check($patient_id,$dateTarget='') {
1235 $isMatch = false; //matching flag
1237 // Set date to current if not set (although should always be set)
1238 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1239 $dateTargetRound = date('Y-m-d',$dateTarget);
1241 // Set current date
1242 $currentDate = date('Y-m-d H:i:s');
1243 $currentDateRound = date('Y-m-d',$dateCurrent);
1245 // Basically, if the appointment is within the current date to the target date,
1246 // then return true. (will not send reminders on same day as appointment)
1247 $sql = sqlStatement("SELECT openemr_postcalendar_events.pc_eid, " .
1248 "openemr_postcalendar_events.pc_title, " .
1249 "openemr_postcalendar_events.pc_eventDate, " .
1250 "openemr_postcalendar_events.pc_startTime, " .
1251 "openemr_postcalendar_events.pc_endTime " .
1252 "FROM openemr_postcalendar_events " .
1253 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1254 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1255 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1257 // return results of check
1259 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1260 // Plan to send back array of appt info (eid, time, date, etc.)
1261 // to do this.
1262 if (sqlNumRows($sql) > 0) {
1263 $isMatch = true;
1266 return $isMatch;
1270 * Function to check lists filters and targets. Customizable and currently includes diagnoses, medications, allergies and surgeries.
1272 * @param string $patient_id pid of selected patient.
1273 * @param array $filter array containing lists filter/target elements
1274 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1275 * @return boolean true if check passed, otherwise false
1277 function lists_check($patient_id,$filter,$dateTarget) {
1278 $isMatch = false; //matching flag
1280 // Set date to current if not set
1281 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1283 foreach ( $filter as $row ) {
1284 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1285 // Record the match
1286 $isMatch = true;
1288 else {
1289 // If this is a required entry then return false
1290 if ($row['required_flag']) return false;
1294 // return results of check
1295 return $isMatch;
1299 * Function to check for existance of data in database for a patient
1301 * @param string $patient_id pid of selected patient.
1302 * @param string $table selected mysql table
1303 * @param string $column selected mysql column
1304 * @param string $data_comp data comparison (eq,ne,gt,ge,lt,le)
1305 * @param string $data selected data in the mysql database
1306 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1307 * @param integer $num_items_thres number of items threshold
1308 * @param string $intervalType type of interval (ie. year)
1309 * @param integer $intervalValue searched for within this many times of the interval type
1310 * @param string $dateTarget target date(format Y-m-d H:i:s).
1311 * @return boolean true if check passed, otherwise false
1313 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1315 // Set date to current if not set
1316 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1318 // Collect the correct column label for patient id in the table
1319 $patient_id_label = collect_database_label('pid',$table);
1321 // Get the interval sql query string
1322 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1324 // If just checking for existence (ie. data is empty),
1325 // then simply set the comparison operator to ne.
1326 if (empty($data)) {
1327 $data_comp = "ne";
1330 // get the appropriate sql comparison operator
1331 $compSql = convertCompSql($data_comp);
1333 // check for items
1334 if (empty($column)) {
1335 // simple search for any table entries
1336 $sql = sqlStatement("SELECT * " .
1337 "FROM `" . add_escape_custom($table) . "` " .
1338 "WHERE `" . add_escape_custom($patient_id_label) . "`=?", array($patient_id) );
1340 else {
1341 // search for number of specific items
1342 $sql = sqlStatement("SELECT `" . add_escape_custom($column) . "` " .
1343 "FROM `" . add_escape_custom($table) . "` " .
1344 "WHERE `" . add_escape_custom($column) ."`" . $compSql . "? " .
1345 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1346 $dateSql, array($data,$patient_id) );
1349 // See if number of returned items passes the comparison
1350 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1354 * Function to check for existence of procedure(s) for a patient
1356 * @param string $patient_id pid of selected patient.
1357 * @param string $proc_title procedure title
1358 * @param string $proc_code procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1359 * @param string $results_comp results comparison (eq,ne,gt,ge,lt,le)
1360 * @param string $result_data results data
1361 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1362 * @param integer $num_items_thres number of items threshold
1363 * @param string $intervalType type of interval (ie. year)
1364 * @param integer $intervalValue searched for within this many times of the interval type
1365 * @param string $dateTarget target date(format Y-m-d H:i:s).
1366 * @return boolean true if check passed, otherwise false
1368 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1370 // Set date to current if not set
1371 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1373 // Set the table exception (for looking up pertinent date and pid sql columns)
1374 $table = "PROCEDURE-EXCEPTION";
1376 // Collect the correct column label for patient id in the table
1377 $patient_id_label = collect_database_label('pid',$table);
1379 // Get the interval sql query string
1380 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1382 // If just checking for existence (ie result_data is empty),
1383 // then simply set the comparison operator to ne.
1384 if (empty($result_data)) {
1385 $result_comp = "ne";
1388 // get the appropriate sql comparison operator
1389 $compSql = convertCompSql($result_comp);
1391 // explode the code array
1392 $codes= array();
1393 if (!empty($proc_code)) {
1394 $codes = explode("||",$proc_code);
1396 else {
1397 $codes[0] = '';
1400 // ensure proc_title is at least blank
1401 if (empty($proc_title)) {
1402 $proc_title = '';
1405 // collect specific items (use both title and/or codes) that fulfill request
1406 $sqlBindArray=array();
1407 $sql_query = "SELECT procedure_result.result " .
1408 "FROM `procedure_type`, " .
1409 "`procedure_order`, " .
1410 "`procedure_report`, " .
1411 "`procedure_result` " .
1412 "WHERE procedure_type.procedure_type_id = procedure_order.procedure_type_id " .
1413 "AND procedure_order.procedure_order_id = procedure_report.procedure_order_id " .
1414 "AND procedure_report.procedure_report_id = procedure_result.procedure_report_id " .
1415 "AND ";
1416 foreach ($codes as $tem) {
1417 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
1418 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
1419 array_push($sqlBindArray,$tem,$tem);
1421 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
1422 "AND procedure_result.result " . $compSql . " ? " .
1423 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
1424 array_push($sqlBindArray,$proc_title,$result_data,$patient_id);
1425 $sql = sqlStatement($sql_query,$sqlBindArray);
1427 // See if number of returned items passes the comparison
1428 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1432 * Function to check for existance of data for a patient in the rule_patient_data table
1434 * @param string $patient_id pid of selected patient.
1435 * @param string $category label in category column
1436 * @param string $item label in item column
1437 * @param string $complete label in complete column (YES,NO, or blank)
1438 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1439 * @param integer $num_items_thres number of items threshold
1440 * @param string $intervalType type of interval (ie. year)
1441 * @param integer $intervalValue searched for within this many times of the interval type
1442 * @param string $dateTarget target date(format Y-m-d H:i:s).
1443 * @return boolean true if check passed, otherwise false
1445 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
1447 // Set the table
1448 $table = 'rule_patient_data';
1450 // Collect the correct column label for patient id in the table
1451 $patient_id_label = collect_database_label('pid',$table);
1453 // Get the interval sql query string
1454 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1456 // search for number of specific items
1457 $sql = sqlStatement("SELECT `result` " .
1458 "FROM `" . add_escape_custom($table) . "` " .
1459 "WHERE `category`=? " .
1460 "AND `item`=? " .
1461 "AND `complete`=? " .
1462 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1463 $dateSql, array($category,$item,$complete,$patient_id) );
1465 // See if number of returned items passes the comparison
1466 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1470 * Function to check for existance of data for a patient in lifestyle section
1472 * @param string $patient_id pid of selected patient.
1473 * @param string $lifestyle selected label of mysql column of patient history
1474 * @param string $status specific status of selected lifestyle element
1475 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1476 * @return boolean true if check passed, otherwise false
1478 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
1480 // Set date to current if not set
1481 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1483 // Collect pertinent history data
1484 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
1486 // See if match
1487 $stringFlag = strstr($history[$lifestyle], "|".$status);
1488 if (empty($status)) {
1489 // Only ensuring any data has been entered into the field
1490 $stringFlag = true;
1492 if ( $history[$lifestyle] &&
1493 $history[$lifestyle] != '|0|' &&
1494 $stringFlag ) {
1495 return true;
1497 else {
1498 return false;
1503 * Function to check for lists item of a patient. Fully customizable and includes diagnoses, medications,
1504 * allergies, and surgeries.
1506 * @param string $patient_id pid of selected patient.
1507 * @param string $type type (medical_problem, allergy, medication, etc)
1508 * @param string $value value searching for
1509 * @param string $dateTarget target date(format Y-m-d H:i:s).
1510 * @return boolean true if check passed, otherwise false
1512 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
1514 // Set date to current if not set
1515 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1517 // Attempt to explode the value into a code type and code (if applicable)
1518 $value_array = explode("::",$value);
1519 if (count($value_array) == 2) {
1521 // Collect the code type and code
1522 $code_type = $value_array[0];
1523 $code = $value_array[1];
1525 if ($code_type=='CUSTOM') {
1526 // Deal with custom code type first (title column in lists table)
1527 $response = sqlQuery("SELECT * FROM `lists` " .
1528 "WHERE `type`=? " .
1529 "AND `pid`=? " .
1530 "AND `title`=? " .
1531 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1532 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
1533 if (!empty($response)) return true;
1535 else {
1536 // Deal with the set code types (diagnosis column in lists table)
1537 $response = sqlQuery("SELECT * FROM `lists` " .
1538 "WHERE `type`=? " .
1539 "AND `pid`=? " .
1540 "AND `diagnosis` LIKE ? " .
1541 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1542 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
1543 if (!empty($response)) return true;
1546 else { // count($value_array) == 1
1547 // Search the title column in lists table
1548 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
1549 $response = sqlQuery("SELECT * FROM `lists` " .
1550 "WHERE `type`=? " .
1551 "AND `pid`=? " .
1552 "AND `title`=? ".
1553 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1554 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
1555 if (!empty($response)) return true;
1558 return false;
1562 * Function to return part of sql query to deal with interval
1564 * @param string $table selected mysql table (or EXCEPTION(s))
1565 * @param string $intervalType type of interval (ie. year)
1566 * @param string $intervalValue searched for within this many times of the interval type
1567 * @param string $dateTarget target date(format Y-m-d H:i:s).
1568 * @return string contains pertinent date interval filter for mysql query
1570 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
1572 $dateSql="";
1574 // Collect the correct column label for date in the table
1575 $date_label = collect_database_label('date',$table);
1577 // Deal with interval
1578 if (!empty($intervalType)) {
1579 switch($intervalType) {
1580 case "year":
1581 $dateSql = "AND (" . add_escape_custom($date_label) .
1582 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1583 "', INTERVAL " . add_escape_custom($intervalValue) .
1584 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
1585 break;
1586 case "month":
1587 $dateSql = "AND (" . add_escape_custom($date_label) .
1588 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1589 "', INTERVAL " . add_escape_custom($intervalValue) .
1590 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
1591 break;
1592 case "week":
1593 $dateSql = "AND (" . add_escape_custom($date_label) .
1594 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1595 "', INTERVAL " . add_escape_custom($intervalValue) .
1596 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
1597 break;
1598 case "day":
1599 $dateSql = "AND (" . add_escape_custom($date_label) .
1600 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1601 "', INTERVAL " . add_escape_custom($intervalValue) .
1602 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
1603 break;
1604 case "hour":
1605 $dateSql = "AND (" . add_escape_custom($date_label) .
1606 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1607 "', INTERVAL " . add_escape_custom($intervalValue) .
1608 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
1609 break;
1610 case "minute":
1611 $dateSql = "AND (" . add_escape_custom($date_label) .
1612 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1613 "', INTERVAL " . add_escape_custom($intervalValue) .
1614 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
1615 break;
1616 case "second":
1617 $dateSql = "AND (" . add_escape_custom($date_label) .
1618 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1619 "', INTERVAL " . add_escape_custom($intervalValue) .
1620 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
1621 break;
1622 case "flu_season":
1623 // Flu season to be hard-coded as September thru February
1624 // (Should make this modifiable in the future)
1625 // ($intervalValue is not used)
1626 $dateArray = explode("-",$dateTarget);
1627 $Year = $dateArray[0];
1628 $dateThisYear = $Year . "-09-01";
1629 $dateLastYear = ($Year-1) . "-09-01";
1630 $dateSql =" " .
1631 "AND ((" .
1632 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
1633 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
1634 "OR (" .
1635 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
1636 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
1637 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
1638 break;
1641 else {
1642 $dateSql = "AND " . add_escape_custom($date_label) .
1643 " <= '" . add_escape_custom($dateTarget) . "' ";
1646 // return the sql interval string
1647 return $dateSql;
1651 * Function to collect generic column labels from tables. It currently works for date
1652 * and pid. Will need to expand this as algorithm grows.
1654 * @param string $label element (pid or date)
1655 * @param string $table selected mysql table (or EXCEPTION(s))
1656 * @return string contains official label of selected element
1658 function collect_database_label($label,$table) {
1660 if ($table == 'PROCEDURE-EXCEPTION') {
1661 // return cell to get procedure collection
1662 // special case since reuqires joing of multiple
1663 // tables to get this value
1664 if ($label == "pid") {
1665 $returnedLabel = "procedure_order.patient_id";
1667 else if ($label == "date") {
1668 $returnedLabel = "procedure_report.date_collected";
1670 else {
1671 // unknown label, so return the original label
1672 $returnedLabel = $label;
1675 else if ($table == 'immunizations') {
1676 // return requested label for immunization table
1677 if ($label == "pid") {
1678 $returnedLabel = "patient_id";
1680 else if ($label == "date") {
1681 $returnedLabel = "`administered_date`";
1683 else {
1684 // unknown label, so return the original label
1685 $returnedLabel = $label;
1688 else {
1689 // return requested label for default tables
1690 if ($label == "pid") {
1691 $returnedLabel = "pid";
1693 else if ($label == "date") {
1694 $returnedLabel = "`date`";
1696 else {
1697 // unknown label, so return the original label
1698 $returnedLabel = $label;
1702 return $returnedLabel;
1706 * Simple function to avoid processing of duplicate actions
1708 * @param string $actions 2-dimensional array with all current active targets
1709 * @param string $action array of selected target to test for duplicate
1710 * @return boolean true if duplicate, false if not duplicate
1712 function is_duplicate_action($actions,$action) {
1713 foreach ($actions as $row) {
1714 if ($row['category'] == $action['category'] &&
1715 $row['item'] == $action['item'] &&
1716 $row['value'] == $action['value']) {
1717 // Is a duplicate
1718 return true;
1722 // Not a duplicate
1723 return false;
1727 * Calculate the reminder dates.
1729 * This function returns an array that contains three elements (each element is a date).
1730 * <pre>The three dates are:
1731 * first date is before the target date (past_due) (default of 1 month)
1732 * second date is the target date (due)
1733 * third date is after the target date (soon_due) (default of 2 weeks)
1734 * </pre>
1736 * @param string $rule id(string) of selected rule
1737 * @param string $dateTarget target date(format Y-m-d H:i:s).
1738 * @param string $type either 'patient_reminder' or 'clinical_reminder'
1739 * @return array see above for description of returned array
1741 function calculate_reminder_dates($rule, $dateTarget='',$type) {
1743 // Set date to current if not set
1744 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1746 // Collect the current date settings (to ensure not skip)
1747 $res = resolve_reminder_sql($rule, $type.'_current');
1748 if (!empty($res)) {
1749 $row = $res[0];
1750 if ($row ['method_detail'] == "SKIP") {
1751 $dateTarget = "SKIP";
1755 // Collect the past_due date
1756 $past_due_date = "";
1757 $res = resolve_reminder_sql($rule, $type.'_post');
1758 if (!empty($res)) {
1759 $row = $res[0];
1760 if ($row ['method_detail'] == "week") {
1761 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
1763 if ($row ['method_detail'] == "month") {
1764 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
1766 if ($row ['method_detail'] == "hour") {
1767 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1769 if ($row ['method_detail'] == "SKIP") {
1770 $past_due_date = "SKIP";
1773 else {
1774 // empty settings, so use default of one month
1775 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
1778 // Collect the soon_due date
1779 $soon_due_date = "";
1780 $res = resolve_reminder_sql($rule, $type.'_pre');
1781 if (!empty($res)) {
1782 $row = $res[0];
1783 if ($row ['method_detail'] == "week") {
1784 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
1786 if ($row ['method_detail'] == "month") {
1787 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
1789 if ($row ['method_detail'] == "hour") {
1790 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1792 if ($row ['method_detail'] == "SKIP") {
1793 $soon_due_date = "SKIP";
1796 else {
1797 // empty settings, so use default of one month
1798 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
1801 // Return the array of three dates
1802 return array($soon_due_date,$dateTarget,$past_due_date);
1806 * Adds an action into the reminder array
1808 * @param array $reminderOldArray Contains the current array of reminders
1809 * @param array $reminderNew Array of a new reminder
1810 * @return array Reminders
1812 function reminder_results_integrate($reminderOldArray, $reminderNew) {
1814 $results = array();
1816 // If reminderArray is empty, then insert new reminder
1817 if (empty($reminderOldArray)) {
1818 array_push($results, $reminderNew);
1819 return $results;
1822 // If duplicate reminder, then replace the old one
1823 $duplicate = false;
1824 foreach ($reminderOldArray as $reminderOld) {
1825 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
1826 $reminderOld['category'] == $reminderNew['category'] &&
1827 $reminderOld['item'] == $reminderNew['item']) {
1828 array_push($results, $reminderNew);
1829 $duplicate = true;
1831 else {
1832 array_push($results, $reminderOld);
1836 // If a new reminder, then insert the new reminder
1837 if (!$duplicate) {
1838 array_push($results, $reminderNew);
1841 return $results;
1845 * Compares number of items with requested comparison operator
1847 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
1848 * @param string $thres Threshold used in comparison
1849 * @param integer $num_items Number of items
1850 * @return boolean Comparison results
1852 function itemsNumberCompare($comp, $thres, $num_items) {
1854 if ( ($comp == "eq") && ($num_items == $thres) ) {
1855 return true;
1857 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
1858 return true;
1860 else if ( ($comp == "gt") && ($num_items > $thres) ) {
1861 return true;
1863 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
1864 return true;
1866 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
1867 return true;
1869 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
1870 return true;
1872 else {
1873 return false;
1878 * Converts a text comparison operator to sql equivalent
1880 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
1881 * @return string contains sql compatible comparison operator
1883 function convertCompSql($comp) {
1885 if ($comp == "eq") {
1886 return "=";
1888 else if ($comp == "ne") {
1889 return "!=";
1891 else if ($comp == "gt") {
1892 return ">";
1894 else if ($comp == "ge") {
1895 return ">=";
1897 else if ($comp == "lt") {
1898 return "<";
1900 else { // ($comp == "le")
1901 return "<=";
1906 * Function to find age in years (with decimal) on the target date
1908 * @param string $dob date of birth
1909 * @param string $target date to calculate age on
1910 * @return float years(decimal) from dob to target(date)
1912 function convertDobtoAgeYearDecimal($dob,$target) {
1914 // Prepare dob (Y M D)
1915 $dateDOB = explode(" ",$dob);
1917 // Prepare target (Y-M-D H:M:S)
1918 $dateTargetTemp = explode(" ",$target);
1919 $dateTarget = explode("-",$dateTargetTemp[0]);
1921 // Collect differences
1922 $iDiffYear = $dateTarget[0] - $dateDOB[0];
1923 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
1924 $iDiffDay = $dateTarget[2] - $dateDOB[2];
1926 // If birthday has not happen yet for this year, subtract 1.
1927 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
1929 $iDiffYear--;
1932 // Ensure diffYear is not less than 0
1933 if ($iDiffYear < 0) $iDiffYear = 0;
1935 return $iDiffYear;
1939 * Function to find age in months (with decimal) on the target date
1941 * @param string $dob date of birth
1942 * @param string $target date to calculate age on
1943 * @return float months(decimal) from dob to target(date)
1945 function convertDobtoAgeMonthDecimal($dob,$target) {
1947 // Prepare dob (Y M D)
1948 $dateDOB = explode(" ",$dob);
1950 // Prepare target (Y-M-D H:M:S)
1951 $dateTargetTemp = explode(" ",$target);
1952 $dateTarget = explode("-",$dateTargetTemp[0]);
1954 // Collect differences
1955 $iDiffYear = $dateTarget[0] - $dateDOB[0];
1956 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
1957 $iDiffDay = $dateTarget[2] - $dateDOB[2];
1959 // If birthday has not happen yet for this year, subtract 1.
1960 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
1962 $iDiffYear--;
1965 // Ensure diffYear is not less than 0
1966 if ($iDiffYear < 0) $iDiffYear = 0;
1968 return (12 * $iDiffYear) + $iDiffMonth;
1972 * Function to calculate the percentage for reports.
1974 * @param integer $pass_filter number of patients that pass filter
1975 * @param integer $exclude_filter number of patients that are excluded
1976 * @param integer $pass_target number of patients that pass target
1977 * @return string Number formatted into a percentage
1979 function calculate_percentage($pass_filt,$exclude_filt,$pass_targ) {
1980 if ($pass_filt > 0) {
1981 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100) . xl('%');
1983 else {
1984 $perc = "0". xl('%');
1986 return $perc;