9a1a238a08bf3554d5c1dcb34d5e4b06c4df44af
[openemr.git] / library / clinical_rules.php
blob9a1a238a08bf3554d5c1dcb34d5e4b06c4df44af
1 <?php
2 /**
3 * Clinical Decision Rules(CDR) engine functions.
5 * These functions should not ever attempt to write to
6 * session variables, because the session_write_close() function
7 * is typically called before utilizing these functions.
9 * Copyright (C) 2010-2012 Brady Miller <brady@sparmy.com>
10 * Copyright (C) 2011 Medical Information Integration, LLC
11 * Copyright (C) 2011 Ensofttek, LLC
13 * LICENSE: This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
24 * @package OpenEMR
25 * @author Brady Miller <brady@sparmy.com>
26 * @author Medical Information Integration, LLC
27 * @author Ensofttek, LLC
28 * @link http://www.open-emr.org
31 require_once(dirname(__FILE__) . "/patient.inc");
32 require_once(dirname(__FILE__) . "/forms.inc");
33 require_once(dirname(__FILE__) . "/formdata.inc.php");
34 require_once(dirname(__FILE__) . "/options.inc.php");
35 require_once(dirname(__FILE__) . "/report_database.inc");
37 // This is only pertinent for users of php versions less than 5.2
38 // (ie. this wrapper is only loaded when php version is less than
39 // 5.2; otherwise the native php json functions are used)
40 require_once(dirname(__FILE__) . "/jsonwrapper/jsonwrapper.php");
42 /**
43 * Display the clinical summary widget.
45 * @param integer $patient_id pid of selected patient
46 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
47 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
48 * @param string $organize_mode Way to organize the results (default or plans)
50 function clinical_summary_widget($patient_id,$mode,$dateTarget='',$organize_mode='default') {
52 // Set date to current if not set
53 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
55 // Collect active actions
56 $actions = test_rules_clinic('','passive_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
58 // Display the actions
59 foreach ($actions as $action) {
61 // Deal with plan names first
62 if (isset($action['is_plan']) &&$action['is_plan']) {
63 echo "<br><b>";
64 echo htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
65 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
66 echo "</b><br>";
67 continue;
70 if ($action['custom_flag']) {
71 // Start link for reminders that use the custom rules input screen
72 echo "<a href='../rules/patient_data.php?category=" .
73 htmlspecialchars( $action['category'], ENT_QUOTES) . "&item=" .
74 htmlspecialchars( $action['item'], ENT_QUOTES) .
75 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
77 else if ($action['clin_rem_link']) {
78 // Start link for reminders that use the custom rules input screen
79 echo "<a href='../../../" . $action['reminder_message'] .
80 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
82 else {
83 // continue, since no link will be created
86 // Display Reminder Details
87 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
88 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
90 if ($action['custom_flag'] || $action['clin_rem_link']) {
91 // End link for reminders that use an html link
92 echo "</a>";
95 // Display due status
96 if ($action['due_status']) {
97 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
98 if ($action['due_status'] == "past_due") {
99 echo "&nbsp;&nbsp;(<span style='color:red'>";
101 else if ($action['due_status'] == "due") {
102 echo "&nbsp;&nbsp;(<span style='color:purple'>";
104 else if ($action['due_status'] == "not_due") {
105 echo "&nbsp;&nbsp;(<span style='color:green'>";
107 else {
108 echo "&nbsp;&nbsp;(<span>";
110 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
112 else {
113 echo "<br>";
120 * Display the active screen reminder.
122 * @param integer $patient_id pid of selected patient
123 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
124 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
125 * @param string $organize_mode Way to organize the results (default or plans)
126 * @return string html display output.
128 function active_alert_summary($patient_id,$mode,$dateTarget='',$organize_mode='default') {
130 // Set date to current if not set
131 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
133 // Collect active actions
134 $actions = test_rules_clinic('','active_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
136 if (empty($actions)) {
137 return false;
140 $returnOutput = "";
142 // Display the actions
143 foreach ($actions as $action) {
145 // Deal with plan names first
146 if ($action['is_plan']) {
147 $returnOutput .= "<br><b>";
148 $returnOutput .= htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
149 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
150 $returnOutput .= "</b><br>";
151 continue;
154 // Display Reminder Details
155 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
156 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
158 // Display due status
159 if ($action['due_status']) {
160 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
161 if ($action['due_status'] == "past_due") {
162 $returnOutput .= "&nbsp;&nbsp;(<span style='color:red'>";
164 else if ($action['due_status'] == "due") {
165 $returnOutput .= "&nbsp;&nbsp;(<span style='color:purple'>";
167 else if ($action['due_status'] == "not_due") {
168 $returnOutput .= "&nbsp;&nbsp;(<span style='color:green'>";
170 else {
171 $returnOutput .= "&nbsp;&nbsp;(<span>";
173 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
175 else {
176 $returnOutput .= "<br>";
179 return $returnOutput;
183 * Process clinic rules via a batching method to improve performance and decrease memory overhead.
185 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
186 * on one patient or patients of one provider). The structure of the returned results is dependent on the
187 * $organize_mode and $mode parameters.
188 * <pre>The results are dependent on the $organize_mode parameter settings
189 * 'default' organize_mode:
190 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
191 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
192 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
193 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
194 * 'plans' organize_mode:
195 * Returns similar to default, but organizes by the active plans
196 * </pre>
198 * @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).
199 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules.
200 * @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').
201 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
202 * @param string $plan test for specific plan only
203 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
204 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
205 * @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.
206 * @param integer $batchSize number of patients to batch (default is 100; plan to optimize this default setting in the future)
207 * @param integer $report_id id of report in database (if already bookmarked)
208 * @return array See above for organization structure of the results.
210 function test_rules_clinic_batch_method($provider='',$type='',$dateTarget='',$mode='',$plan='',$organize_mode='default',$options=array(),$pat_prov_rel='primary',$batchSize='',$report_id=NULL) {
212 // Default to a batchsize, if empty
213 if (empty($batchSize)) {
214 $batchSize=100;
217 // Collect total number of pertinent patients (to calculate batching parameters)
218 $totalNumPatients = buildPatientArray('',$provider,$pat_prov_rel,NULL,NULL,TRUE);
220 // Cycle through the batches and collect/combine results
221 if (($totalNumPatients%$batchSize) > 0) {
222 // not perfectly divisible
223 $totalNumberBatches = floor($totalNumPatients/$batchSize) + 1;
225 else {
226 // perfectly divisible
227 $totalNumberBatches = floor($totalNumPatients/$batchSize);
230 // Fix things in the $options array(). This now stores the number of labs to be used in the denominator in the AMC report.
231 // The problem with this variable is that is is added in every batch. So need to fix it by dividing this number by the number
232 // of planned batches(note the fixed array will go into the test_rules_clinic function, however the original will be used
233 // in the report storing/tracking engine.
234 $options_modified=$options;
235 if (!empty($options_modified['labs_manual'])) {
236 $options_modified['labs_manual'] = $options_modified['labs_manual'] / $totalNumberBatches;
239 // Prepare the database to track/store results
240 $fields = array('provider'=>$provider,'mode'=>$mode,'plan'=>$plan,'organize_mode'=>$organize_mode,'pat_prov_rel'=>$pat_prov_rel);
241 if (is_array($dateTarget)) {
242 $fields = array_merge($fields,array(date_target=>$dateTarget['dateTarget']));
243 $fields = array_merge($fields,array(date_begin=>$dateTarget['dateBegin']));
245 else {
246 if (empty($dateTarget)) {
247 $fields = array_merge($fields,array(date_target=>date("Y-m-d H:i:s")));
249 else {
250 $fields = array_merge($fields,array(date_target=>$dateTarget));
253 if (!empty($options)) {
254 foreach ($options as $key => $value) {
255 $fields = array_merge($fields, array($key=>$value));
258 $report_id = beginReportDatabase($type,$fields,$report_id);
259 setTotalItemsReportDatabase($report_id,$totalNumPatients);
261 for ($i=0;$i<$totalNumberBatches;$i++) {
262 $dataSheet_batch = test_rules_clinic($provider,$type,$dateTarget,$mode,'',$plan,$organize_mode,$options_modified,$pat_prov_rel,(($batchSize*$i)+1),$batchSize);
263 if ($i == 0) {
264 // For first cycle, simply copy it to dataSheet
265 $dataSheet = $dataSheet_batch;
267 else {
268 //debug
269 //error_log("CDR: ".print_r($dataSheet,TRUE),0);
270 //error_log("CDR: ".($batchSize*$i)." records",0);
272 // Integrate batch results into main dataSheet
273 foreach ($dataSheet_batch as $key => $row) {
274 if (!$row['is_sub']) {
275 //skip this stuff for the sub entries (and use previous main entry in percentage calculation)
276 $total_patients = $dataSheet[$key]['total_patients'] + $row['total_patients'];
277 $dataSheet[$key]['total_patients'] = $total_patients;
278 $excluded = $dataSheet[$key]['excluded'] + $row['excluded'];
279 $dataSheet[$key]['excluded'] = $excluded;
280 $pass_filter = $dataSheet[$key]['pass_filter'] + $row['pass_filter'];
281 $dataSheet[$key]['pass_filter'] = $pass_filter;
283 $pass_target = $dataSheet[$key]['pass_target'] + $row['pass_target'];
284 $dataSheet[$key]['pass_target'] = $pass_target;
285 $dataSheet[$key]['percentage'] = calculate_percentage($pass_filter,$excluded,$pass_target);
288 //Update database to track results
289 updateReportDatabase($report_id,$total_patients);
292 // Record results in database and send to screen, if applicable.
293 finishReportDatabase($report_id,json_encode($dataSheet));
294 return $dataSheet;
298 * Process clinic rules.
300 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
301 * on one patient or patients of one provider). The structure of the returned results is dependent on the
302 * $organize_mode and $mode parameters.
303 * <pre>The results are dependent on the $organize_mode parameter settings
304 * 'default' organize_mode:
305 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
306 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
307 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
308 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
309 * 'plans' organize_mode:
310 * Returns similar to default, but organizes by the active plans
311 * </pre>
313 * @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).
314 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules.
315 * @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').
316 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
317 * @param integer $patient_id pid of patient. If blank then will check all patients.
318 * @param string $plan test for specific plan only
319 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
320 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
321 * @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.
322 * @param integer $start applicable patient to start at (when batching process)
323 * @param integer $batchSize number of patients to batch (when batching process)
324 * @return array See above for organization structure of the results.
326 function test_rules_clinic($provider='',$type='',$dateTarget='',$mode='',$patient_id='',$plan='',$organize_mode='default',$options=array(),$pat_prov_rel='primary',$start=NULL,$batchSize=NULL) {
328 // If dateTarget is an array, then organize them.
329 if (is_array($dateTarget)) {
330 $dateArray = $dateTarget;
331 $dateTarget = $dateTarget['dateTarget'];
334 // Set date to current if not set
335 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
337 // Prepare the results array
338 $results = array();
340 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
341 // then run through this function recursively and return results.
342 if (($provider == "collate_outer") || ($provider == "collate_inner" && $organize_mode != 'plans')) {
343 // First, collect an array of all providers
344 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
345 $ures = sqlStatementCdrEngine($query);
346 // Second, run through each provider recursively
347 while ($urow = sqlFetchArray($ures)) {
348 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode,$options,$pat_prov_rel,$start,$batchSize);
349 if (!empty($newResults)) {
350 $provider_item['is_provider'] = TRUE;
351 $provider_item['prov_lname'] = $urow['lname'];
352 $provider_item['prov_fname'] = $urow['fname'];
353 $provider_item['npi'] = $urow['npi'];
354 $provider_item['federaltaxid'] = $urow['federaltaxid'];
355 array_push($results,$provider_item);
356 $results = array_merge($results,$newResults);
359 // done, so now can return results
360 return $results;
363 // If set organize-mode to plans, then collects active plans and run through this
364 // function recursively and return results.
365 if ($organize_mode == "plans") {
366 // First, collect active plans
367 $plans_resolve = resolve_plans_sql($plan,$patient_id);
368 // Second, run through function recursively
369 foreach ($plans_resolve as $plan_item) {
370 // (if collate_inner, then nest a collation of providers within each plan)
371 if ($provider == "collate_inner") {
372 // First, collect an array of all providers
373 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
374 $ures = sqlStatementCdrEngine($query);
375 // Second, run through each provider recursively
376 $provider_results = array();
377 while ($urow = sqlFetchArray($ures)) {
378 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize);
379 if (!empty($newResults)) {
380 $provider_item['is_provider'] = TRUE;
381 $provider_item['prov_lname'] = $urow['lname'];
382 $provider_item['prov_fname'] = $urow['fname'];
383 $provider_item['npi'] = $urow['npi'];
384 $provider_item['federaltaxid'] = $urow['federaltaxid'];
385 array_push($provider_results,$provider_item);
386 $provider_results = array_merge($provider_results,$newResults);
389 if (!empty($provider_results)) {
390 $plan_item['is_plan'] = TRUE;
391 array_push($results,$plan_item);
392 $results = array_merge($results,$provider_results);
395 else {
396 // (not collate_inner, so do not nest providers within each plan)
397 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize);
398 if (!empty($newResults)) {
399 $plan_item['is_plan'] = TRUE;
400 array_push($results,$plan_item);
401 $results = array_merge($results,$newResults);
405 // done, so now can return results
406 return $results;
409 // Collect applicable patient pids
410 $patientData = array();
411 $patientData = buildPatientArray($patient_id,$provider,$pat_prov_rel,$start,$batchSize);
413 // Go through each patient(s)
415 // If in report mode, then tabulate for each rule:
416 // Total Patients
417 // Patients that pass the filter
418 // Patients that pass the target
419 // If in reminders mode, then create reminders for each rule:
420 // Reminder that action is due soon
421 // Reminder that action is due
422 // Reminder that action is post-due
424 //Collect applicable rules
425 // Note that due to a limitation in the this function, the patient_id is explicitly
426 // for grouping items when not being done in real-time or for official reporting.
427 // So for cases such as patient reminders on a clinic scale, the calling function
428 // will actually need rather than pass in a explicit patient_id for each patient in
429 // a separate call to this function.
430 if ($mode != "report") {
431 // Use per patient custom rules (if exist)
432 // Note as discussed above, this only works for single patient instances.
433 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
435 else { // $mode = "report"
436 // Only use default rules (do not use patient custom rules)
437 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
440 foreach( $rules as $rowRule ) {
442 // If using cqm or amc type, then use the hard-coded rules set.
443 // Note these rules are only used in report mode.
444 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
446 require_once( dirname(__FILE__)."/classes/rulesets/ReportManager.php");
447 $manager = new ReportManager();
448 if ($rowRule['amc_flag']) {
449 // Send array of dates ('dateBegin' and 'dateTarget')
450 $tempResults = $manager->runReport( $rowRule, $patientData, $dateArray, $options );
452 else {
453 // Send target date
454 $tempResults = $manager->runReport( $rowRule, $patientData, $dateTarget );
456 if (!empty($tempResults)) {
457 foreach ($tempResults as $tempResult) {
458 array_push($results,$tempResult);
462 // Go on to the next rule
463 continue;
466 // If in reminder mode then need to collect the measurement dates
467 // from rule_reminder table
468 $target_dates = array();
469 if ($mode != "report") {
470 // Calculate the dates to check for
471 if ($type == "patient_reminder") {
472 $reminder_interval_type = "patient_reminder";
474 else { // $type == "passive_alert" or $type == "active_alert"
475 $reminder_interval_type = "clinical_reminder";
477 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
479 else { // $mode == "report"
480 // Only use the target date in the report
481 $target_dates[0] = $dateTarget;
484 //Reset the counters
485 $total_patients = 0;
486 $pass_filter = 0;
487 $exclude_filter = 0;
488 $pass_target = 0;
490 // Find the number of target groups
491 $targetGroups = returnTargetGroups($rowRule['id']);
493 if ( (count($targetGroups) == 1) || ($mode == "report") ) {
494 //skip this section if not report and more than one target group
495 foreach( $patientData as $rowPatient ) {
497 // Count the total patients
498 $total_patients++;
500 $dateCounter = 1; // for reminder mode to keep track of which date checking
501 foreach ( $target_dates as $dateFocus ) {
503 //Skip if date is set to SKIP
504 if ($dateFocus == "SKIP") {
505 $dateCounter++;
506 continue;
509 //Set date counter and reminder token (applicable for reminders only)
510 if ($dateCounter == 1) {
511 $reminder_due = "soon_due";
513 else if ($dateCounter == 2) {
514 $reminder_due = "due";
516 else { // $dateCounter == 3
517 $reminder_due = "past_due";
520 // First, deal with deceased patients
521 // (for now will simply not pass the filter, but can add a database item
522 // if ever want to create rules for dead people)
523 // Could also place this function at the total_patients level if wanted.
524 // (But then would lose the option of making rules for dead people)
525 // Note using the dateTarget rather than dateFocus
526 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
527 continue;
530 // Check if pass filter
531 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
532 if ($passFilter === "EXCLUDED") {
533 // increment EXCLUDED and pass_filter counters
534 // and set as FALSE for reminder functionality.
535 $pass_filter++;
536 $exclude_filter++;
537 $passFilter = FALSE;
539 if ($passFilter) {
540 // increment pass filter counter
541 $pass_filter++;
543 else {
544 $dateCounter++;
545 continue;
548 // Check if pass target
549 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
550 if ($passTarget) {
551 // increment pass target counter
552 $pass_target++;
553 // send to reminder results
554 if ($mode == "reminders-all") {
555 // place the completed actions into the reminder return array
556 $actionArray = resolve_action_sql($rowRule['id'],'1');
557 foreach ($actionArray as $action) {
558 $action_plus = $action;
559 $action_plus['due_status'] = "not_due";
560 $action_plus['pid'] = $rowPatient['pid'];
561 $results = reminder_results_integrate($results, $action_plus);
564 break;
566 else {
567 // send to reminder results
568 if ($mode != "report") {
569 // place the uncompleted actions into the reminder return array
570 $actionArray = resolve_action_sql($rowRule['id'],'1');
571 foreach ($actionArray as $action) {
572 $action_plus = $action;
573 $action_plus['due_status'] = $reminder_due;
574 $action_plus['pid'] = $rowPatient['pid'];
575 $results = reminder_results_integrate($results, $action_plus);
579 $dateCounter++;
584 // Calculate and save the data for the rule
585 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
586 if ($mode == "report") {
587 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
588 $newRow=array_merge($newRow,$rowRule);
589 array_push($results, $newRow);
592 // Now run through the target groups if more than one
593 if (count($targetGroups) > 1) {
594 foreach ($targetGroups as $i) {
596 //Reset the target counter
597 $pass_target = 0;
599 foreach( $patientData as $rowPatient ) {
601 $dateCounter = 1; // for reminder mode to keep track of which date checking
602 foreach ( $target_dates as $dateFocus ) {
604 //Skip if date is set to SKIP
605 if ($dateFocus == "SKIP") {
606 $dateCounter++;
607 continue;
610 //Set date counter and reminder token (applicable for reminders only)
611 if ($dateCounter == 1) {
612 $reminder_due = "soon_due";
614 else if ($dateCounter == 2) {
615 $reminder_due = "due";
617 else { // $dateCounter == 3
618 $reminder_due = "past_due";
621 // First, deal with deceased patients
622 // (for now will simply not pass the filter, but can add a database item
623 // if ever want to create rules for dead people)
624 // Could also place this function at the total_patients level if wanted.
625 // (But then would lose the option of making rules for dead people)
626 // Note using the dateTarget rather than dateFocus
627 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
628 continue;
631 // Check if pass filter
632 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
633 if ($passFilter === "EXCLUDED") {
634 $passFilter = FALSE;
636 if (!$passFilter) {
637 // increment pass filter counter
638 $dateCounter++;
639 continue;
642 //Check if pass target
643 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
644 if ($passTarget) {
645 // increment pass target counter
646 $pass_target++;
647 // send to reminder results
648 if ($mode == "reminders-all") {
649 // place the completed actions into the reminder return array
650 $actionArray = resolve_action_sql($rowRule['id'],$i);
651 foreach ($actionArray as $action) {
652 $action_plus = $action;
653 $action_plus['due_status'] = "not_due";
654 $action_plus['pid'] = $rowPatient['pid'];
655 $results = reminder_results_integrate($results, $action_plus);
658 break;
660 else {
661 // send to reminder results
662 if ($mode != "report") {
663 // place the actions into the reminder return array
664 $actionArray = resolve_action_sql($rowRule['id'],$i);
665 foreach ($actionArray as $action) {
666 $action_plus = $action;
667 $action_plus['due_status'] = $reminder_due;
668 $action_plus['pid'] = $rowPatient['pid'];
669 $results = reminder_results_integrate($results, $action_plus);
673 $dateCounter++;
677 // Calculate and save the data for the rule
678 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
680 // Collect action for title (just use the first one, if more than one)
681 $actionArray = resolve_action_sql($rowRule['id'],$i);
682 $action = $actionArray[0];
683 if ($mode == "report") {
684 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
685 array_push($results, $newRow);
691 // Return the data
692 return $results;
696 * Process patient array that is to be tested.
698 * @param integer $provider id of a selected provider. If blank, then will test entire clinic.
699 * @param integer $patient_id pid of patient. If blank then will check all patients.
700 * @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.
701 * @param integer $start applicable patient to start at (when batching process)
702 * @param integer $batchSize number of patients to batch (when batching process)
703 * @param boolean $onlyCount If true, then will just return the total number of applicable records (ignores batching parameters)
704 * @return array/integer Array of patient pid values or number total pertinent patients (if $onlyCount is TRUE)
706 function buildPatientArray($patient_id='',$provider='',$pat_prov_rel='primary',$start=NULL,$batchSize=NULL,$onlyCount=FALSE) {
708 if (!empty($patient_id)) {
709 // only look at the selected patient
710 if ($onlyCount) {
711 $patientNumber = 1;
713 else {
714 $patientData[0]['pid'] = $patient_id;
717 else {
718 if (empty($provider)) {
719 // Look at entire practice
720 if ($start == NULL || $batchSize == NULL || $onlyCount) {
721 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid`");
722 if ($onlyCount) {
723 $patientNumber = sqlNumRows($rez);
726 else {
727 // batching
728 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid` LIMIT ?,?", array(($start-1),$batchSize));
731 else {
732 // Look at an individual physician
733 if( $pat_prov_rel == 'encounter' ){
734 // Choose patients that are related to specific physician by an encounter
735 if ($start == NULL || $batchSize == NULL || $onlyCount) {
736 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
737 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid`", array($provider,$provider));
738 if ($onlyCount) {
739 $patientNumber = sqlNumRows($rez);
742 else {
743 //batching
744 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
745 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid` LIMIT ?,?", array($provider,$provider,($start-1),$batchSize));
748 else { //$pat_prov_rel == 'primary'
749 // Choose patients that are assigned to the specific physician (primary physician in patient demographics)
750 if ($start == NULL || $batchSize == NULL || $onlyCount) {
751 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
752 "WHERE `providerID`=? ORDER BY `pid`", array($provider) );
753 if ($onlyCount) {
754 $patientNumber = sqlNumRows($rez);
757 else {
758 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
759 "WHERE `providerID`=? ORDER BY `pid` LIMIT ?,?", array($provider,($start-1),$batchSize) );
763 // convert the sql query results into an array if returning the array
764 if(!$onlyCount) {
765 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
766 $patientData[$iter]=$row;
771 if ($onlyCount) {
772 // return the number of applicable patients
773 return $patientNumber;
775 else {
776 // return array of patient pids
777 return $patientData;
782 * Test filter of a selected rule on a selected patient
784 * @param integer $patient_id pid of selected patient.
785 * @param string $rule id(string) of selected rule
786 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
787 * @return boolean/string if pass filter then TRUE; if excluded then 'EXCLUDED'; if not pass filter then FALSE
789 function test_filter($patient_id,$rule,$dateTarget) {
791 // Set date to current if not set
792 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
794 // Collect patient information
795 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
798 // ----------------- INCLUSIONS -----------------
801 // -------- Age Filter (inclusion) ------------
802 // Calculate patient age in years and months
803 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
804 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
806 // Min age (year) Filter (assume that there in not more than one of each)
807 $filter = resolve_filter_sql($rule,'filt_age_min');
808 if (!empty($filter)) {
809 $row = $filter[0];
810 if ($row ['method_detail'] == "year") {
811 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
812 return false;
815 if ($row ['method_detail'] == "month") {
816 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
817 return false;
821 // Max age (year) Filter (assume that there in not more than one of each)
822 $filter = resolve_filter_sql($rule,'filt_age_max');
823 if (!empty($filter)) {
824 $row = $filter[0];
825 if ($row ['method_detail'] == "year") {
826 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
827 return false;
830 if ($row ['method_detail'] == "month") {
831 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
832 return false;
837 // -------- Gender Filter (inclusion) ---------
838 // Gender Filter (assume that there in not more than one of each)
839 $filter = resolve_filter_sql($rule,'filt_sex');
840 if (!empty($filter)) {
841 $row = $filter[0];
842 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
843 return false;
847 // -------- Database Filter (inclusion) ------
848 // Database Filter
849 $filter = resolve_filter_sql($rule,'filt_database');
850 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
852 // -------- Lists Filter (inclusion) ----
853 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
854 // surgeries and allergies.
855 $filter = resolve_filter_sql($rule,'filt_lists');
856 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
858 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
859 // Procedure Target (includes) (may need to include an interval in the future)
860 $filter = resolve_filter_sql($rule,'filt_proc');
861 if ((!empty($filter)) && !procedure_check($patient_id,$filter,'',$dateTarget)) return false;
864 // ----------------- EXCLUSIONS -----------------
867 // -------- Lists Filter (EXCLUSION) ----
868 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
869 // surgeries and allergies.
870 $filter = resolve_filter_sql($rule,'filt_lists',0);
871 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
873 // Passed all filters, so return true.
874 return true;
878 * Return an array containing existing group ids for a rule
880 * @param string $rule id(string) of rule
881 * @return array listing of group ids
883 function returnTargetGroups($rule) {
885 $sql = sqlStatementCdrEngine("SELECT DISTINCT `group_id` FROM `rule_target` " .
886 "WHERE `id`=?", array($rule) );
888 $groups = array();
889 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
890 array_push($groups,$row['group_id']);
892 return $groups;
896 * Test targets of a selected rule on a selected patient
898 * @param integer $patient_id pid of selected patient.
899 * @param string $rule id(string) of selected rule (if blank, then will ignore grouping)
900 * @param integer $group_id group id of target group
901 * @param string $dateTarget target date (format Y-m-d H:i:s).
902 * @return boolean if target passes then true, otherwise false
904 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
906 // -------- Interval Target ----
907 $interval = resolve_target_sql($rule,$group_id,'target_interval');
909 // -------- Database Target ----
910 // Database Target (includes)
911 $target = resolve_target_sql($rule,$group_id,'target_database');
912 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
914 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
915 // Procedure Target (includes)
916 $target = resolve_target_sql($rule,$group_id,'target_proc');
917 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
919 // -------- Appointment Target ----
920 // Appointment Target (includes) (Specialized functionality for appointment reminders)
921 $target = resolve_target_sql($rule,$group_id,'target_appt');
922 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
924 // Passed all target tests, so return true.
925 return true;
929 * Function to return active plans
931 * @param string $type plan type filter (normal or cqm or blank)
932 * @param integer $patient_id pid of selected patient. (if custom plan does not exist then will use the default plan)
933 * @param boolean $configurableOnly true if only want the configurable (per patient) plans (ie. ignore cqm plans)
934 * @return array active plans
936 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
938 if ($configurableOnly) {
939 // Collect all default, configurable (per patient) plans into an array
940 // (ie. ignore the cqm rules)
941 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
943 else {
944 // Collect all default plans into an array
945 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
947 $returnArray= array();
948 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
949 array_push($returnArray,$row);
952 // Now collect the pertinent plans
953 $newReturnArray = array();
955 // Need to select rules (use custom if exist)
956 foreach ($returnArray as $plan) {
957 $customPlan = sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
959 // Decide if use default vs custom plan (preference given to custom plan)
960 if (!empty($customPlan)) {
961 if ($type == "cqm" ) {
962 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
963 $goPlan = $plan;
965 else {
966 // merge the custom plan with the default plan
967 $mergedPlan = array();
968 foreach ($customPlan as $key => $value) {
969 if ($value == NULL && preg_match("/_flag$/",$key)) {
970 // use default setting
971 $mergedPlan[$key] = $plan[$key];
973 else {
974 // use custom setting
975 $mergedPlan[$key] = $value;
978 $goPlan = $mergedPlan;
981 else {
982 $goPlan = $plan;
985 // Use the chosen plan if set
986 if (!empty($type)) {
987 if ($goPlan["${type}_flag"] == 1) {
988 // active, so use the plan
989 array_push($newReturnArray,$goPlan);
992 else {
993 if ($goPlan['normal_flag'] == 1 ||
994 $goPlan['cqm_flag'] == 1) {
995 // active, so use the plan
996 array_push($newReturnArray,$goPlan);
1000 $returnArray = $newReturnArray;
1002 return $returnArray;
1007 * Function to return a specific plan
1009 * @param string $plan id(string) of plan
1010 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1011 * @return array a plan
1013 function collect_plan($plan,$patient_id='0') {
1015 return sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id) );
1020 * Function to set a specific plan activity for a specific patient
1022 * @param string $plan id(string) of plan
1023 * @param string $type plan filter (normal,cqm)
1024 * @param string $setting activity of plan (yes,no,default)
1025 * @param integer $patient_id pid of selected patient.
1027 function set_plan_activity_patient($plan,$type,$setting,$patient_id) {
1029 // Don't allow messing with the default plans here
1030 if ($patient_id == "0") {
1031 return;
1034 // Convert setting
1035 if ($setting == "on") {
1036 $setting = 1;
1038 else if ($setting == "off") {
1039 $setting = 0;
1041 else { // $setting == "default"
1042 $setting = NULL;
1045 // Collect patient specific plan, if already exists.
1046 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
1047 $patient_plan = sqlQueryCdrEngine($query, array($plan,$patient_id) );
1049 if (empty($patient_plan)) {
1050 // Create a new patient specific plan with flags all set to default
1051 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
1052 sqlStatementCdrEngine($query, array($plan, $patient_id) );
1055 // Update patient specific row
1056 $query = "UPDATE `clinical_plans` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
1057 sqlStatementCdrEngine($query, array($setting,$plan,$patient_id) );
1062 * Function to return active rules
1064 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1065 * @param integer $patient_id pid of selected patient. (if custom rule does not exist then will use the default rule)
1066 * @param boolean $configurableOnly true if only want the configurable (per patient) rules (ie. ignore cqm and amc rules)
1067 * @param string $plan collect rules for specific plan
1068 * @return array rules
1070 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='') {
1072 if ($configurableOnly) {
1073 // Collect all default, configurable (per patient) rules into an array
1074 // (ie. ignore the cqm and amc rules)
1075 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
1077 else {
1078 // Collect all default rules into an array
1079 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
1081 $returnArray= array();
1082 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1083 array_push($returnArray,$row);
1086 // Now filter rules for plan (if applicable)
1087 if (!empty($plan)) {
1088 $planReturnArray = array();
1089 foreach ($returnArray as $rule) {
1090 $standardRule = sqlQueryCdrEngine("SELECT * FROM `clinical_plans_rules` " .
1091 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
1092 if (!empty($standardRule)) {
1093 array_push($planReturnArray,$rule);
1096 $returnArray = $planReturnArray;
1099 // Now collect the pertinent rules
1100 $newReturnArray = array();
1102 // Need to select rules (use custom if exist)
1103 foreach ($returnArray as $rule) {
1104 $customRule = sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
1106 // Decide if use default vs custom rule (preference given to custom rule)
1107 if (!empty($customRule)) {
1108 if ($type == "cqm" || $type == "amc" ) {
1109 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
1110 $goRule = $rule;
1112 else {
1113 // merge the custom rule with the default rule
1114 $mergedRule = array();
1115 foreach ($customRule as $key => $value) {
1116 if ($value == NULL && preg_match("/_flag$/",$key)) {
1117 // use default setting
1118 $mergedRule[$key] = $rule[$key];
1120 else {
1121 // use custom setting
1122 $mergedRule[$key] = $value;
1125 $goRule = $mergedRule;
1128 else {
1129 $goRule = $rule;
1132 // Use the chosen rule if set
1133 if (!empty($type)) {
1134 if ($goRule["${type}_flag"] == 1) {
1135 // active, so use the rule
1136 array_push($newReturnArray,$goRule);
1139 else {
1140 // no filter, so return the rule
1141 array_push($newReturnArray,$goRule);
1144 $returnArray = $newReturnArray;
1146 return $returnArray;
1150 * Function to return a specific rule
1152 * @param string $rule id(string) of rule
1153 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1154 * @return array rule
1156 function collect_rule($rule,$patient_id='0') {
1158 return sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
1163 * Function to set a specific rule activity for a specific patient
1165 * @param string $rule id(string) of rule
1166 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1167 * @param string $setting activity of rule (yes,no,default)
1168 * @param integer $patient_id pid of selected patient.
1170 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
1172 // Don't allow messing with the default rules here
1173 if ($patient_id == "0") {
1174 return;
1177 // Convert setting
1178 if ($setting == "on") {
1179 $setting = 1;
1181 else if ($setting == "off") {
1182 $setting = 0;
1184 else { // $setting == "default"
1185 $setting = NULL;
1188 // Collect patient specific rule, if already exists.
1189 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
1190 $patient_rule = sqlQueryCdrEngine($query, array($rule,$patient_id) );
1192 if (empty($patient_rule)) {
1193 // Create a new patient specific rule with flags all set to default
1194 $query = "INSERT into `clinical_rules` (`id`, `pid`) VALUES (?,?)";
1195 sqlStatementCdrEngine($query, array($rule, $patient_id) );
1198 // Update patient specific row
1199 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
1200 sqlStatementCdrEngine($query, array($setting,$rule,$patient_id) );
1205 * Function to return applicable reminder dates (relative)
1207 * @param string $rule id(string) of selected rule
1208 * @param string $reminder_method string label of filter type
1209 * @return array reminder features
1211 function resolve_reminder_sql($rule,$reminder_method) {
1212 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value` FROM `rule_reminder` " .
1213 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method) );
1215 $returnArray= array();
1216 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1217 array_push($returnArray,$row);
1219 return $returnArray;
1223 * Function to return applicable filters
1225 * @param string $rule id(string) of selected rule
1226 * @param string $filter_method string label of filter type
1227 * @param string $include_flag to allow selection for included or excluded filters
1228 * @return array filters
1230 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
1231 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1232 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
1234 $returnArray= array();
1235 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1236 array_push($returnArray,$row);
1238 return $returnArray;
1242 * Function to return applicable targets
1244 * @param string $rule id(string) of selected rule
1245 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1246 * @param string $target_method string label of target type
1247 * @param string $include_flag to allow selection for included or excluded targets
1248 * @return array targets
1250 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
1252 if ($group_id) {
1253 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1254 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
1256 else {
1257 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1258 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
1261 $returnArray= array();
1262 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1263 array_push($returnArray,$row);
1265 return $returnArray;
1269 * Function to return applicable actions
1271 * @param string $rule id(string) of selected rule
1272 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1273 * @return array actions
1275 function resolve_action_sql($rule,$group_id='') {
1277 if ($group_id) {
1278 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1279 "FROM `rule_action` as a " .
1280 "JOIN `rule_action_item` as b " .
1281 "ON a.category = b.category AND a.item = b.item " .
1282 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
1284 else {
1285 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.value, b.custom_flag " .
1286 "FROM `rule_action` as a " .
1287 "JOIN `rule_action_item` as b " .
1288 "ON a.category = b.category AND a.item = b.item " .
1289 "WHERE a.id=?", array($rule) );
1292 $returnArray= array();
1293 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1294 array_push($returnArray,$row);
1296 return $returnArray;
1300 * Function to check database filters and targets
1302 * @param string $patient_id pid of selected patient.
1303 * @param array $filter array containing filter/target elements
1304 * @param array $interval array containing interval elements
1305 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1306 * @return boolean true if check passed, otherwise false
1308 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
1309 $isMatch = false; //matching flag
1311 // Set date to current if not set
1312 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1314 // Unpackage interval information
1315 // (Assume only one for now and only pertinent for targets)
1316 $intervalType = '';
1317 $intervalValue = '';
1318 if (!empty($interval)) {
1319 $intervalType = $interval[0]['value'];
1320 $intervalValue = $interval[0]['interval'];
1323 foreach( $filter as $row ) {
1324 // Row description
1325 // [0]=>special modes
1326 $temp_df = explode("::",$row['value']);
1328 if ($temp_df[0] == "CUSTOM") {
1329 // Row description
1330 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1331 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1332 // Record the match
1333 $isMatch = true;
1335 else {
1336 // If this is a required entry then return false
1337 if ($row['required_flag']) return false;
1340 else if ($temp_df[0] == "LIFESTYLE") {
1341 // Row description
1342 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1343 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1344 // Record the match
1345 $isMatch = true;
1347 else {
1348 // If this is a required entry then return false
1349 if ($row['required_flag']) return false;
1352 else {
1353 // Default mode
1354 // Row description
1355 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1356 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)) {
1357 // Record the match
1358 $isMatch = true;
1360 else {
1361 // If this is a required entry then return false
1362 if ($row['required_flag']) return false;
1367 // return results of check
1368 return $isMatch;
1372 * Function to check procedure filters and targets
1374 * @param string $patient_id pid of selected patient.
1375 * @param array $filter array containing filter/target elements
1376 * @param array $interval array containing interval elements
1377 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1378 * @return boolean true if check passed, otherwise false
1380 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
1381 $isMatch = false; //matching flag
1383 // Set date to current if not set
1384 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1386 // Unpackage interval information
1387 // (Assume only one for now and only pertinent for targets)
1388 $intervalType = '';
1389 $intervalValue = '';
1390 if (!empty($interval)) {
1391 $intervalType = $interval[0]['value'];
1392 $intervalValue = $interval[0]['interval'];
1395 foreach( $filter as $row ) {
1396 // Row description
1397 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1398 // code description
1399 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1400 $temp_df = explode("::",$row['value']);
1401 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)) {
1402 // Record the match
1403 $isMatch = true;
1405 else {
1406 // If this is a required entry then return false
1407 if ($row['required_flag']) return false;
1411 // return results of check
1412 return $isMatch;
1416 * Function to check for appointment
1418 * @todo Complete this to allow appointment reminders.
1419 * @param string $patient_id pid of selected patient.
1420 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1421 * @return boolean true if appt exist, otherwise false
1423 function appointment_check($patient_id,$dateTarget='') {
1424 $isMatch = false; //matching flag
1426 // Set date to current if not set (although should always be set)
1427 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1428 $dateTargetRound = date('Y-m-d',$dateTarget);
1430 // Set current date
1431 $currentDate = date('Y-m-d H:i:s');
1432 $currentDateRound = date('Y-m-d',$dateCurrent);
1434 // Basically, if the appointment is within the current date to the target date,
1435 // then return true. (will not send reminders on same day as appointment)
1436 $sql = sqlStatementCdrEngine("SELECT openemr_postcalendar_events.pc_eid, " .
1437 "openemr_postcalendar_events.pc_title, " .
1438 "openemr_postcalendar_events.pc_eventDate, " .
1439 "openemr_postcalendar_events.pc_startTime, " .
1440 "openemr_postcalendar_events.pc_endTime " .
1441 "FROM openemr_postcalendar_events " .
1442 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1443 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1444 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1446 // return results of check
1448 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1449 // Plan to send back array of appt info (eid, time, date, etc.)
1450 // to do this.
1451 if (sqlNumRows($sql) > 0) {
1452 $isMatch = true;
1455 return $isMatch;
1459 * Function to check lists filters and targets. Customizable and currently includes diagnoses, medications, allergies and surgeries.
1461 * @param string $patient_id pid of selected patient.
1462 * @param array $filter array containing lists filter/target elements
1463 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1464 * @return boolean true if check passed, otherwise false
1466 function lists_check($patient_id,$filter,$dateTarget) {
1467 $isMatch = false; //matching flag
1469 // Set date to current if not set
1470 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1472 foreach ( $filter as $row ) {
1473 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1474 // Record the match
1475 $isMatch = true;
1477 else {
1478 // If this is a required entry then return false
1479 if ($row['required_flag']) return false;
1483 // return results of check
1484 return $isMatch;
1488 * Function to check for existance of data in database for a patient
1490 * @param string $patient_id pid of selected patient.
1491 * @param string $table selected mysql table
1492 * @param string $column selected mysql column
1493 * @param string $data_comp data comparison (eq,ne,gt,ge,lt,le)
1494 * @param string $data selected data in the mysql database
1495 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1496 * @param integer $num_items_thres number of items threshold
1497 * @param string $intervalType type of interval (ie. year)
1498 * @param integer $intervalValue searched for within this many times of the interval type
1499 * @param string $dateTarget target date(format Y-m-d H:i:s).
1500 * @return boolean true if check passed, otherwise false
1502 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1504 // Set date to current if not set
1505 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1507 // Collect the correct column label for patient id in the table
1508 $patient_id_label = collect_database_label('pid',$table);
1510 // Get the interval sql query string
1511 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1513 // If just checking for existence (ie. data is empty),
1514 // then simply set the comparison operator to ne.
1515 if (empty($data)) {
1516 $data_comp = "ne";
1519 // get the appropriate sql comparison operator
1520 $compSql = convertCompSql($data_comp);
1522 // check for items
1523 if (empty($column)) {
1524 // simple search for any table entries
1525 $sql = sqlStatementCdrEngine("SELECT * " .
1526 "FROM `" . add_escape_custom($table) . "` " .
1527 "WHERE `" . add_escape_custom($patient_id_label) . "`=?", array($patient_id) );
1529 else {
1530 // search for number of specific items
1531 $sql = sqlStatementCdrEngine("SELECT `" . add_escape_custom($column) . "` " .
1532 "FROM `" . add_escape_custom($table) . "` " .
1533 "WHERE `" . add_escape_custom($column) ."`" . $compSql . "? " .
1534 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1535 $dateSql, array($data,$patient_id) );
1538 // See if number of returned items passes the comparison
1539 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1543 * Function to check for existence of procedure(s) for a patient
1545 * @param string $patient_id pid of selected patient.
1546 * @param string $proc_title procedure title
1547 * @param string $proc_code procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1548 * @param string $results_comp results comparison (eq,ne,gt,ge,lt,le)
1549 * @param string $result_data results data
1550 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1551 * @param integer $num_items_thres number of items threshold
1552 * @param string $intervalType type of interval (ie. year)
1553 * @param integer $intervalValue searched for within this many times of the interval type
1554 * @param string $dateTarget target date(format Y-m-d H:i:s).
1555 * @return boolean true if check passed, otherwise false
1557 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1559 // Set date to current if not set
1560 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1562 // Set the table exception (for looking up pertinent date and pid sql columns)
1563 $table = "PROCEDURE-EXCEPTION";
1565 // Collect the correct column label for patient id in the table
1566 $patient_id_label = collect_database_label('pid',$table);
1568 // Get the interval sql query string
1569 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1571 // If just checking for existence (ie result_data is empty),
1572 // then simply set the comparison operator to ne.
1573 if (empty($result_data)) {
1574 $result_comp = "ne";
1577 // get the appropriate sql comparison operator
1578 $compSql = convertCompSql($result_comp);
1580 // explode the code array
1581 $codes= array();
1582 if (!empty($proc_code)) {
1583 $codes = explode("||",$proc_code);
1585 else {
1586 $codes[0] = '';
1589 // ensure proc_title is at least blank
1590 if (empty($proc_title)) {
1591 $proc_title = '';
1594 // collect specific items (use both title and/or codes) that fulfill request
1595 $sqlBindArray=array();
1596 $sql_query = "SELECT procedure_result.result FROM " .
1597 "procedure_order_code, " .
1598 "procedure_order, " .
1599 "procedure_type, " .
1600 "procedure_report, " .
1601 "procedure_result " .
1602 "WHERE " .
1603 "procedure_order_code.procedure_code = procedure_type.procedure_code AND " .
1604 "procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
1605 "procedure_order.lab_id procedure_type.lab_id AND " .
1606 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
1607 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
1608 "procedure_result.procedure_report_id = procedure_report.procedure_report_id AND " .
1609 "procedure_type.procedure_type = 'ord' AND ";
1610 foreach ($codes as $tem) {
1611 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
1612 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
1613 array_push($sqlBindArray,$tem,$tem);
1615 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
1616 "AND procedure_result.result " . $compSql . " ? " .
1617 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
1618 array_push($sqlBindArray,$proc_title,$result_data,$patient_id);
1620 $sql = sqlStatementCdrEngine($sql_query,$sqlBindArray);
1622 // See if number of returned items passes the comparison
1623 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1627 * Function to check for existance of data for a patient in the rule_patient_data table
1629 * @param string $patient_id pid of selected patient.
1630 * @param string $category label in category column
1631 * @param string $item label in item column
1632 * @param string $complete label in complete column (YES,NO, or blank)
1633 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1634 * @param integer $num_items_thres number of items threshold
1635 * @param string $intervalType type of interval (ie. year)
1636 * @param integer $intervalValue searched for within this many times of the interval type
1637 * @param string $dateTarget target date(format Y-m-d H:i:s).
1638 * @return boolean true if check passed, otherwise false
1640 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
1642 // Set the table
1643 $table = 'rule_patient_data';
1645 // Collect the correct column label for patient id in the table
1646 $patient_id_label = collect_database_label('pid',$table);
1648 // Get the interval sql query string
1649 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1651 // search for number of specific items
1652 $sql = sqlStatementCdrEngine("SELECT `result` " .
1653 "FROM `" . add_escape_custom($table) . "` " .
1654 "WHERE `category`=? " .
1655 "AND `item`=? " .
1656 "AND `complete`=? " .
1657 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1658 $dateSql, array($category,$item,$complete,$patient_id) );
1660 // See if number of returned items passes the comparison
1661 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1665 * Function to check for existance of data for a patient in lifestyle section
1667 * @param string $patient_id pid of selected patient.
1668 * @param string $lifestyle selected label of mysql column of patient history
1669 * @param string $status specific status of selected lifestyle element
1670 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1671 * @return boolean true if check passed, otherwise false
1673 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
1675 // Set date to current if not set
1676 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1678 // Collect pertinent history data
1679 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
1681 // See if match
1682 $stringFlag = strstr($history[$lifestyle], "|".$status);
1683 if (empty($status)) {
1684 // Only ensuring any data has been entered into the field
1685 $stringFlag = true;
1687 if ( $history[$lifestyle] &&
1688 $history[$lifestyle] != '|0|' &&
1689 $stringFlag ) {
1690 return true;
1692 else {
1693 return false;
1698 * Function to check for lists item of a patient. Fully customizable and includes diagnoses, medications,
1699 * allergies, and surgeries.
1701 * @param string $patient_id pid of selected patient.
1702 * @param string $type type (medical_problem, allergy, medication, etc)
1703 * @param string $value value searching for
1704 * @param string $dateTarget target date(format Y-m-d H:i:s).
1705 * @return boolean true if check passed, otherwise false
1707 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
1709 // Set date to current if not set
1710 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1712 // Attempt to explode the value into a code type and code (if applicable)
1713 $value_array = explode("::",$value);
1714 if (count($value_array) == 2) {
1716 // Collect the code type and code
1717 $code_type = $value_array[0];
1718 $code = $value_array[1];
1720 if ($code_type=='CUSTOM') {
1721 // Deal with custom code type first (title column in lists table)
1722 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1723 "WHERE `type`=? " .
1724 "AND `pid`=? " .
1725 "AND `title`=? " .
1726 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1727 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
1728 if (!empty($response)) return true;
1730 else {
1731 // Deal with the set code types (diagnosis column in lists table)
1732 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1733 "WHERE `type`=? " .
1734 "AND `pid`=? " .
1735 "AND `diagnosis` LIKE ? " .
1736 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1737 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
1738 if (!empty($response)) return true;
1741 else { // count($value_array) == 1
1742 // Search the title column in lists table
1743 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
1744 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1745 "WHERE `type`=? " .
1746 "AND `pid`=? " .
1747 "AND `title`=? ".
1748 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1749 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
1750 if (!empty($response)) return true;
1753 return false;
1757 * Function to return part of sql query to deal with interval
1759 * @param string $table selected mysql table (or EXCEPTION(s))
1760 * @param string $intervalType type of interval (ie. year)
1761 * @param string $intervalValue searched for within this many times of the interval type
1762 * @param string $dateTarget target date(format Y-m-d H:i:s).
1763 * @return string contains pertinent date interval filter for mysql query
1765 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
1767 $dateSql="";
1769 // Collect the correct column label for date in the table
1770 $date_label = collect_database_label('date',$table);
1772 // Deal with interval
1773 if (!empty($intervalType)) {
1774 switch($intervalType) {
1775 case "year":
1776 $dateSql = "AND (" . add_escape_custom($date_label) .
1777 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1778 "', INTERVAL " . add_escape_custom($intervalValue) .
1779 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
1780 break;
1781 case "month":
1782 $dateSql = "AND (" . add_escape_custom($date_label) .
1783 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1784 "', INTERVAL " . add_escape_custom($intervalValue) .
1785 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
1786 break;
1787 case "week":
1788 $dateSql = "AND (" . add_escape_custom($date_label) .
1789 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1790 "', INTERVAL " . add_escape_custom($intervalValue) .
1791 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
1792 break;
1793 case "day":
1794 $dateSql = "AND (" . add_escape_custom($date_label) .
1795 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1796 "', INTERVAL " . add_escape_custom($intervalValue) .
1797 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
1798 break;
1799 case "hour":
1800 $dateSql = "AND (" . add_escape_custom($date_label) .
1801 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1802 "', INTERVAL " . add_escape_custom($intervalValue) .
1803 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
1804 break;
1805 case "minute":
1806 $dateSql = "AND (" . add_escape_custom($date_label) .
1807 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1808 "', INTERVAL " . add_escape_custom($intervalValue) .
1809 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
1810 break;
1811 case "second":
1812 $dateSql = "AND (" . add_escape_custom($date_label) .
1813 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1814 "', INTERVAL " . add_escape_custom($intervalValue) .
1815 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
1816 break;
1817 case "flu_season":
1818 // Flu season to be hard-coded as September thru February
1819 // (Should make this modifiable in the future)
1820 // ($intervalValue is not used)
1821 $dateArray = explode("-",$dateTarget);
1822 $Year = $dateArray[0];
1823 $dateThisYear = $Year . "-09-01";
1824 $dateLastYear = ($Year-1) . "-09-01";
1825 $dateSql =" " .
1826 "AND ((" .
1827 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
1828 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
1829 "OR (" .
1830 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
1831 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
1832 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
1833 break;
1836 else {
1837 $dateSql = "AND " . add_escape_custom($date_label) .
1838 " <= '" . add_escape_custom($dateTarget) . "' ";
1841 // return the sql interval string
1842 return $dateSql;
1846 * Function to collect generic column labels from tables. It currently works for date
1847 * and pid. Will need to expand this as algorithm grows.
1849 * @param string $label element (pid or date)
1850 * @param string $table selected mysql table (or EXCEPTION(s))
1851 * @return string contains official label of selected element
1853 function collect_database_label($label,$table) {
1855 if ($table == 'PROCEDURE-EXCEPTION') {
1856 // return cell to get procedure collection
1857 // special case since reuqires joing of multiple
1858 // tables to get this value
1859 if ($label == "pid") {
1860 $returnedLabel = "procedure_order.patient_id";
1862 else if ($label == "date") {
1863 $returnedLabel = "procedure_report.date_collected";
1865 else {
1866 // unknown label, so return the original label
1867 $returnedLabel = $label;
1870 else if ($table == 'immunizations') {
1871 // return requested label for immunization table
1872 if ($label == "pid") {
1873 $returnedLabel = "patient_id";
1875 else if ($label == "date") {
1876 $returnedLabel = "`administered_date`";
1878 else {
1879 // unknown label, so return the original label
1880 $returnedLabel = $label;
1883 else {
1884 // return requested label for default tables
1885 if ($label == "pid") {
1886 $returnedLabel = "pid";
1888 else if ($label == "date") {
1889 $returnedLabel = "`date`";
1891 else {
1892 // unknown label, so return the original label
1893 $returnedLabel = $label;
1897 return $returnedLabel;
1901 * Simple function to avoid processing of duplicate actions
1903 * @param string $actions 2-dimensional array with all current active targets
1904 * @param string $action array of selected target to test for duplicate
1905 * @return boolean true if duplicate, false if not duplicate
1907 function is_duplicate_action($actions,$action) {
1908 foreach ($actions as $row) {
1909 if ($row['category'] == $action['category'] &&
1910 $row['item'] == $action['item'] &&
1911 $row['value'] == $action['value']) {
1912 // Is a duplicate
1913 return true;
1917 // Not a duplicate
1918 return false;
1922 * Calculate the reminder dates.
1924 * This function returns an array that contains three elements (each element is a date).
1925 * <pre>The three dates are:
1926 * first date is before the target date (past_due) (default of 1 month)
1927 * second date is the target date (due)
1928 * third date is after the target date (soon_due) (default of 2 weeks)
1929 * </pre>
1931 * @param string $rule id(string) of selected rule
1932 * @param string $dateTarget target date(format Y-m-d H:i:s).
1933 * @param string $type either 'patient_reminder' or 'clinical_reminder'
1934 * @return array see above for description of returned array
1936 function calculate_reminder_dates($rule, $dateTarget='',$type) {
1938 // Set date to current if not set
1939 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1941 // Collect the current date settings (to ensure not skip)
1942 $res = resolve_reminder_sql($rule, $type.'_current');
1943 if (!empty($res)) {
1944 $row = $res[0];
1945 if ($row ['method_detail'] == "SKIP") {
1946 $dateTarget = "SKIP";
1950 // Collect the past_due date
1951 $past_due_date = "";
1952 $res = resolve_reminder_sql($rule, $type.'_post');
1953 if (!empty($res)) {
1954 $row = $res[0];
1955 if ($row ['method_detail'] == "week") {
1956 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
1958 if ($row ['method_detail'] == "month") {
1959 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
1961 if ($row ['method_detail'] == "hour") {
1962 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1964 if ($row ['method_detail'] == "SKIP") {
1965 $past_due_date = "SKIP";
1968 else {
1969 // empty settings, so use default of one month
1970 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
1973 // Collect the soon_due date
1974 $soon_due_date = "";
1975 $res = resolve_reminder_sql($rule, $type.'_pre');
1976 if (!empty($res)) {
1977 $row = $res[0];
1978 if ($row ['method_detail'] == "week") {
1979 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
1981 if ($row ['method_detail'] == "month") {
1982 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
1984 if ($row ['method_detail'] == "hour") {
1985 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1987 if ($row ['method_detail'] == "SKIP") {
1988 $soon_due_date = "SKIP";
1991 else {
1992 // empty settings, so use default of one month
1993 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
1996 // Return the array of three dates
1997 return array($soon_due_date,$dateTarget,$past_due_date);
2001 * Adds an action into the reminder array
2003 * @param array $reminderOldArray Contains the current array of reminders
2004 * @param array $reminderNew Array of a new reminder
2005 * @return array Reminders
2007 function reminder_results_integrate($reminderOldArray, $reminderNew) {
2009 $results = array();
2011 // If reminderArray is empty, then insert new reminder
2012 if (empty($reminderOldArray)) {
2013 array_push($results, $reminderNew);
2014 return $results;
2017 // If duplicate reminder, then replace the old one
2018 $duplicate = false;
2019 foreach ($reminderOldArray as $reminderOld) {
2020 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
2021 $reminderOld['category'] == $reminderNew['category'] &&
2022 $reminderOld['item'] == $reminderNew['item']) {
2023 array_push($results, $reminderNew);
2024 $duplicate = true;
2026 else {
2027 array_push($results, $reminderOld);
2031 // If a new reminder, then insert the new reminder
2032 if (!$duplicate) {
2033 array_push($results, $reminderNew);
2036 return $results;
2040 * Compares number of items with requested comparison operator
2042 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2043 * @param string $thres Threshold used in comparison
2044 * @param integer $num_items Number of items
2045 * @return boolean Comparison results
2047 function itemsNumberCompare($comp, $thres, $num_items) {
2049 if ( ($comp == "eq") && ($num_items == $thres) ) {
2050 return true;
2052 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
2053 return true;
2055 else if ( ($comp == "gt") && ($num_items > $thres) ) {
2056 return true;
2058 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
2059 return true;
2061 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
2062 return true;
2064 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
2065 return true;
2067 else {
2068 return false;
2073 * Converts a text comparison operator to sql equivalent
2075 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2076 * @return string contains sql compatible comparison operator
2078 function convertCompSql($comp) {
2080 if ($comp == "eq") {
2081 return "=";
2083 else if ($comp == "ne") {
2084 return "!=";
2086 else if ($comp == "gt") {
2087 return ">";
2089 else if ($comp == "ge") {
2090 return ">=";
2092 else if ($comp == "lt") {
2093 return "<";
2095 else { // ($comp == "le")
2096 return "<=";
2102 * Function to find age in years (with decimal) on the target date
2104 * @param string $dob date of birth
2105 * @param string $target date to calculate age on
2106 * @return float years(decimal) from dob to target(date)
2108 function convertDobtoAgeYearDecimal($dob,$target) {
2109 return parseAgeInfo($dob,$target)['age'];
2113 * Function to find age in months (with decimal) on the target date
2115 * @param string $dob date of birth
2116 * @param string $target date to calculate age on
2117 * @return float months(decimal) from dob to target(date)
2119 function convertDobtoAgeMonthDecimal($dob,$target) {
2120 return parseAgeInfo($dob,$target)['age_in_months'];
2124 * Function to calculate the percentage for reports.
2126 * @param integer $pass_filter number of patients that pass filter
2127 * @param integer $exclude_filter number of patients that are excluded
2128 * @param integer $pass_target number of patients that pass target
2129 * @return string Number formatted into a percentage
2131 function calculate_percentage($pass_filt,$exclude_filt,$pass_targ) {
2132 if ($pass_filt > 0) {
2133 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100) . xl('%');
2135 else {
2136 $perc = "0". xl('%');
2138 return $perc;