Bug fix for NFQ_0024 cqm rule:
[openemr.git] / library / clinical_rules.php
blobd791af7250232d081b6f914004d37a66132ba6f0
1 <?php
2 // Copyright (C) 2011 by following authors:
3 // -Brady Miller <brady@sparmy.com>
4 // -Ensofttek, LLC
5 // -Medical Information Integration, LLC
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // Functions are kept here that will support the clinical rules.
14 require_once(dirname(__FILE__) . "/patient.inc");
15 require_once(dirname(__FILE__) . "/forms.inc");
16 require_once(dirname(__FILE__) . "/formdata.inc.php");
17 require_once(dirname(__FILE__) . "/options.inc.php");
19 // Display the clinical summary widget.
20 // Parameters:
21 // $patient_id - pid of selected patient
22 // $mode - choose either 'reminders-all' or 'reminders-due' (required)
23 // $dateTarget - target date. If blank then will test with current date as target.
24 // $organize_mode - Way to organize the results (default or plans)
25 function clinical_summary_widget($patient_id,$mode,$dateTarget='',$organize_mode='default') {
27 // Set date to current if not set
28 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
30 // Collect active actions
31 $actions = test_rules_clinic('','passive_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
33 // Display the actions
34 foreach ($actions as $action) {
36 // Deal with plan names first
37 if ($action['is_plan']) {
38 echo "<br><b>";
39 echo htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
40 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
41 echo "</b><br>";
42 continue;
45 if ($action['custom_flag']) {
46 // Start link for reminders that use the custom rules input screen
47 echo "<a href='../rules/patient_data.php?category=" .
48 htmlspecialchars( $action['category'], ENT_QUOTES) . "&item=" .
49 htmlspecialchars( $action['item'], ENT_QUOTES) .
50 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
52 else if ($action['clin_rem_link']) {
53 // Start link for reminders that use the custom rules input screen
54 echo "<a href='../../../" . $action['reminder_message'] .
55 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
57 else {
58 // continue, since no link will be created
61 // Display Reminder Details
62 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
63 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
65 if ($action['custom_flag'] || $action['clin_rem_link']) {
66 // End link for reminders that use an html link
67 echo "</a>";
70 // Display due status
71 if ($action['due_status']) {
72 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
73 if ($action['due_status'] == "past_due") {
74 echo "&nbsp;&nbsp;(<span style='color:red'>";
76 else if ($action['due_status'] == "due") {
77 echo "&nbsp;&nbsp;(<span style='color:purple'>";
79 else if ($action['due_status'] == "not_due") {
80 echo "&nbsp;&nbsp;(<span style='color:green'>";
82 else {
83 echo "&nbsp;&nbsp;(<span>";
85 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
87 else {
88 echo "<br>";
94 // Test the clinic rules of entire clinic and create a report or patient reminders
95 // (can also test on one patient or patients of one provider)
96 // Parameters:
97 // $provider - id of a selected provider. If blank, then will test entire clinic. If 'collate_outer' or
98 // 'collate_inner', then will test each provider in entire clinic; outer will nest plans
99 // inside collated providers, while inner will nest the providers inside the plans (note
100 // inner and outer are only different if organize_mode is set to plans).
101 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules.
102 // $dateTarget - target date. If blank then will test with current date as target.
103 // If an array, then is holding two dates ('dateBegin' and 'dateTarget')
104 // $mode - choose either 'report' or 'reminders-all' or 'reminders-due' (required)
105 // $patient_id - pid of patient. If blank then will check all patients.
106 // $plan - test for specific plan only
107 // $organize_mode - Way to organize the results (default, plans)
108 // 'default':
109 // Returns a two-dimensional array of results organized by rules:
110 // reminders-due mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
111 // reminders-all mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
112 // report mode - returns an array of rows for the Clinical Quality Measures (CQM) report
113 // 'plans':
114 // Returns similar to default, but organizes by the active plans
115 // $options - can hold various option (for now, used to hold the manual number of labs for the AMC report)
117 function test_rules_clinic($provider='',$type='',$dateTarget='',$mode='',$patient_id='',$plan='',$organize_mode='default',$options=array()) {
119 // If dateTarget is an array, then organize them.
120 if (is_array($dateTarget)) {
121 $dateArray = $dateTarget;
122 $dateTarget = $dateTarget['dateTarget'];
125 // Set date to current if not set
126 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
128 // Prepare the results array
129 $results = array();
131 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
132 // then run through this function recursively and return results.
133 if (($provider == "collate_outer") || ($provider == "collate_inner" && $organize_mode != 'plans')) {
134 // First, collect an array of all providers
135 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
136 $ures = sqlStatement($query);
137 // Second, run through each provider recursively
138 while ($urow = sqlFetchArray($ures)) {
139 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode);
140 if (!empty($newResults)) {
141 $provider_item['is_provider'] = TRUE;
142 $provider_item['prov_lname'] = $urow['lname'];
143 $provider_item['prov_fname'] = $urow['fname'];
144 $provider_item['npi'] = $urow['npi'];
145 $provider_item['federaltaxid'] = $urow['federaltaxid'];
146 array_push($results,$provider_item);
147 $results = array_merge($results,$newResults);
150 // done, so now can return results
151 return $results;
154 // If set organize-mode to plans, then collects active plans and run through this
155 // function recursively and return results.
156 if ($organize_mode == "plans") {
157 // First, collect active plans
158 $plans_resolve = resolve_plans_sql($plan,$patient_id);
159 // Second, run through function recursively
160 foreach ($plans_resolve as $plan_item) {
161 // (if collate_inner, then nest a collation of providers within each plan)
162 if ($provider == "collate_inner") {
163 // First, collect an array of all providers
164 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
165 $ures = sqlStatement($query);
166 // Second, run through each provider recursively
167 $provider_results = array();
168 while ($urow = sqlFetchArray($ures)) {
169 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan_item['id']);
170 if (!empty($newResults)) {
171 $provider_item['is_provider'] = TRUE;
172 $provider_item['prov_lname'] = $urow['lname'];
173 $provider_item['prov_fname'] = $urow['fname'];
174 $provider_item['npi'] = $urow['npi'];
175 $provider_item['federaltaxid'] = $urow['federaltaxid'];
176 array_push($provider_results,$provider_item);
177 $provider_results = array_merge($provider_results,$newResults);
180 if (!empty($provider_results)) {
181 $plan_item['is_plan'] = TRUE;
182 array_push($results,$plan_item);
183 $results = array_merge($results,$provider_results);
186 else {
187 // (not collate_inner, so do not nest providers within each plan)
188 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id']);
189 if (!empty($newResults)) {
190 $plan_item['is_plan'] = TRUE;
191 array_push($results,$plan_item);
192 $results = array_merge($results,$newResults);
196 // done, so now can return results
197 return $results;
200 // Collect all patient ids
201 $patientData = array();
202 if (!empty($patient_id)) {
203 // only look at the selected patient
204 array_push($patientData,$patient_id);
206 else {
207 if (empty($provider)) {
208 // Look at entire practice
209 $rez = sqlStatement("SELECT `pid` FROM `patient_data`");
210 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
211 $patientData[$iter]=$row;
214 else {
215 // Look at one provider
216 $rez = sqlStatement("SELECT `pid` FROM `patient_data` " .
217 "WHERE providerID=?", array($provider) );
218 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
219 $patientData[$iter]=$row;
223 // Go through each patient(s)
225 // If in report mode, then tabulate for each rule:
226 // Total Patients
227 // Patients that pass the filter
228 // Patients that pass the target
229 // If in reminders mode, then create reminders for each rule:
230 // Reminder that action is due soon
231 // Reminder that action is due
232 // Reminder that action is post-due
234 //Collect applicable rules
235 if ($mode != "report") {
236 // Use per patient custom rules (if exist)
237 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
239 else { // $mode = "report"
240 // Only use default rules (do not use patient custom rules)
241 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
244 foreach( $rules as $rowRule ) {
246 // If using cqm or amc type, then use the hard-coded rules set.
247 // Note these rules are only used in report mode.
248 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
250 require_once( dirname(__FILE__)."/classes/rulesets/ReportManager.php");
251 $manager = new ReportManager();
252 if ($rowRule['amc_flag']) {
253 // Send array of dates ('dateBegin' and 'dateTarget')
254 $tempResults = $manager->runReport( $rowRule, $patientData, $dateArray, $options );
256 else {
257 // Send target date
258 $tempResults = $manager->runReport( $rowRule, $patientData, $dateTarget );
260 if (!empty($tempResults)) {
261 foreach ($tempResults as $tempResult) {
262 array_push($results,$tempResult);
266 // Go on to the next rule
267 continue;
270 // If in reminder mode then need to collect the measurement dates
271 // from rule_reminder table
272 $target_dates = array();
273 if ($mode != "report") {
274 // Calculate the dates to check for
275 if ($type == "patient_reminder") {
276 $reminder_interval_type = "patient_reminder";
278 else { // $type == "passive_alert" or $type == "active_alert"
279 $reminder_interval_type = "clinical_reminder";
281 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
283 else { // $mode == "report"
284 // Only use the target date in the report
285 $target_dates[0] = $dateTarget;
288 //Reset the counters
289 $total_patients = 0;
290 $pass_filter = 0;
291 $exclude_filter = 0;
292 $pass_target = 0;
294 foreach( $patientData as $rowPatient ) {
296 // Count the total patients
297 $total_patients++;
299 $dateCounter = 1; // for reminder mode to keep track of which date checking
300 foreach ( $target_dates as $dateFocus ) {
302 //Skip if date is set to SKIP
303 if ($dateFocus == "SKIP") {
304 $dateCounter++;
305 continue;
308 //Set date counter and reminder token (applicable for reminders only)
309 if ($dateCounter == 1) {
310 $reminder_due = "soon_due";
312 else if ($dateCounter == 2) {
313 $reminder_due = "due";
315 else { // $dateCounter == 3
316 $reminder_due = "past_due";
319 // First, deal with deceased patients
320 // (for now will simply not pass the filter, but can add a database item
321 // if ever want to create rules for dead people)
322 // Could also place this function at the total_patients level if wanted.
323 // (But then would lose the option of making rules for dead people)
324 // Note using the dateTarget rather than dateFocus
325 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
326 continue;
329 // Check if pass filter
330 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
331 if ($passFilter === "EXCLUDED") {
332 // increment EXCLUDED and pass_filter counters
333 // and set as FALSE for reminder functionality.
334 $pass_filter++;
335 $exclude_filter++;
336 $passFilter = FALSE;
338 if ($passFilter) {
339 // increment pass filter counter
340 $pass_filter++;
342 else {
343 $dateCounter++;
344 continue;
347 // Check if pass target
348 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
349 if ($passTarget) {
350 // increment pass target counter
351 $pass_target++;
352 // send to reminder results
353 if ($mode == "reminders-all") {
354 // place the completed actions into the reminder return array
355 $actionArray = resolve_action_sql($rowRule['id'],'1');
356 foreach ($actionArray as $action) {
357 $action_plus = $action;
358 $action_plus['due_status'] = "not_due";
359 $action_plus['pid'] = $rowPatient['pid'];
360 $results = reminder_results_integrate($results, $action_plus);
363 break;
365 else {
366 // send to reminder results
367 if ($mode != "report") {
368 // place the uncompleted actions into the reminder return array
369 $actionArray = resolve_action_sql($rowRule['id'],'1');
370 foreach ($actionArray as $action) {
371 $action_plus = $action;
372 $action_plus['due_status'] = $reminder_due;
373 $action_plus['pid'] = $rowPatient['pid'];
374 $results = reminder_results_integrate($results, $action_plus);
378 $dateCounter++;
382 // Calculate and save the data for the rule
383 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
384 if ($mode == "report") {
385 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
386 $newRow=array_merge($newRow,$rowRule);
387 array_push($results, $newRow);
390 // Find the number of target groups, and go through each one if more than one
391 $targetGroups = returnTargetGroups($rowRule['id']);
392 if (count($targetGroups) > 1) {
393 $firstGroup = true;
394 foreach ($targetGroups as $i) {
396 // skip first group if not in report mode
397 // (this is because first group was already queried above)
398 if ($mode != "report" && $firstGroup) {
399 $firstGroup = false;
400 continue;
403 //Reset the target counter
404 $pass_target = 0;
406 foreach( $patientData as $rowPatient ) {
408 $dateCounter = 1; // for reminder mode to keep track of which date checking
409 foreach ( $target_dates as $dateFocus ) {
411 //Skip if date is set to SKIP
412 if ($dateFocus == "SKIP") {
413 $dateCounter++;
414 continue;
417 //Set date counter and reminder token (applicable for reminders only)
418 if ($dateCounter == 1) {
419 $reminder_due = "soon_due";
421 else if ($dateCounter == 2) {
422 $reminder_due = "due";
424 else { // $dateCounter == 3
425 $reminder_due = "past_due";
428 // First, deal with deceased patients
429 // (for now will simply not pass the filter, but can add a database item
430 // if ever want to create rules for dead people)
431 // Could also place this function at the total_patients level if wanted.
432 // (But then would lose the option of making rules for dead people)
433 // Note using the dateTarget rather than dateFocus
434 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
435 continue;
438 // Check if pass filter
439 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
440 if ($passFilter === "EXCLUDED") {
441 $passFilter = FALSE;
443 if (!$passFilter) {
444 // increment pass filter counter
445 $dateCounter++;
446 continue;
449 //Check if pass target
450 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
451 if ($passTarget) {
452 // increment pass target counter
453 $pass_target++;
454 // send to reminder results
455 if ($mode == "reminders-all") {
456 // place the completed actions into the reminder return array
457 $actionArray = resolve_action_sql($rowRule['id'],$i);
458 foreach ($actionArray as $action) {
459 $action_plus = $action;
460 $action_plus['due_status'] = "not_due";
461 $action_plus['pid'] = $rowPatient['pid'];
462 $results = reminder_results_integrate($results, $action_plus);
465 break;
467 else {
468 // send to reminder results
469 if ($mode != "report") {
470 // place the actions into the reminder return array
471 $actionArray = resolve_action_sql($rowRule['id'],$i);
472 foreach ($actionArray as $action) {
473 $action_plus = $action;
474 $action_plus['due_status'] = $reminder_due;
475 $action_plus['pid'] = $rowPatient['pid'];
476 $results = reminder_results_integrate($results, $action_plus);
480 $dateCounter++;
484 // Calculate and save the data for the rule
485 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
487 // Collect action for title (just use the first one, if more than one)
488 $actionArray = resolve_action_sql($rowRule['id'],$i);
489 $action = $actionArray[0];
490 if ($mode == "report") {
491 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
492 array_push($results, $newRow);
498 // Return the data
499 return $results;
502 // Test filter of a selected rule on a selected patient
503 // Parameters:
504 // $patient_id - pid of selected patient.
505 // $rule - id(string) of selected rule
506 // $dateTarget - target date.
507 // Return:
508 // boolean (if pass filter then TRUE, if excluded then 'EXCLUDED', if not pass filter then FALSE)
509 function test_filter($patient_id,$rule,$dateTarget) {
511 // Set date to current if not set
512 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
514 // Collect patient information
515 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
518 // ----------------- INCLUSIONS -----------------
521 // -------- Age Filter (inclusion) ------------
522 // Calculate patient age in years and months
523 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
524 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
526 // Min age (year) Filter (assume that there in not more than one of each)
527 $filter = resolve_filter_sql($rule,'filt_age_min');
528 if (!empty($filter)) {
529 $row = $filter[0];
530 if ($row ['method_detail'] == "year") {
531 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
532 return false;
535 if ($row ['method_detail'] == "month") {
536 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
537 return false;
541 // Max age (year) Filter (assume that there in not more than one of each)
542 $filter = resolve_filter_sql($rule,'filt_age_max');
543 if (!empty($filter)) {
544 $row = $filter[0];
545 if ($row ['method_detail'] == "year") {
546 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
547 return false;
550 if ($row ['method_detail'] == "month") {
551 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
552 return false;
557 // -------- Gender Filter (inclusion) ---------
558 // Gender Filter (assume that there in not more than one of each)
559 $filter = resolve_filter_sql($rule,'filt_sex');
560 if (!empty($filter)) {
561 $row = $filter[0];
562 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
563 return false;
567 // -------- Database Filter (inclusion) ------
568 // Database Filter
569 $filter = resolve_filter_sql($rule,'filt_database');
570 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
572 // -------- Lists Filter (inclusion) ----
573 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
574 // surgeries and allergies.
575 $filter = resolve_filter_sql($rule,'filt_lists');
576 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
579 // ----------------- EXCLUSIONS -----------------
582 // -------- Lists Filter (EXCLUSION) ----
583 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
584 // surgeries and allergies.
585 $filter = resolve_filter_sql($rule,'filt_lists',0);
586 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
588 // Passed all filters, so return true.
589 return true;
592 // Return an array containing existing group ids for a rule
593 // Parameters:
594 // $rule - id(string) of rule
595 // Return:
596 // array, listing of group ids
597 function returnTargetGroups($rule) {
599 $sql = sqlStatement("SELECT DISTINCT `group_id` FROM `rule_target` " .
600 "WHERE `id`=?", array($rule) );
602 $groups = array();
603 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
604 array_push($groups,$row['group_id']);
606 return $groups;
609 // Test targets of a selected rule on a selected patient
610 // Parameters:
611 // $patient_id - pid of selected patient.
612 // $rule - id(string) of selected rule (if blank, then will ignore grouping)
613 // $group_id - group id of target group
614 // $dateTarget - target date.
615 // Return:
616 // boolean (if target passes then true, otherwise false)
617 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
619 // -------- Interval Target ----
620 $interval = resolve_target_sql($rule,$group_id,'target_interval');
622 // -------- Database Target ----
623 // Database Target (includes)
624 $target = resolve_target_sql($rule,$group_id,'target_database');
625 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
627 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
628 // Procedure Target (includes)
629 $target = resolve_target_sql($rule,$group_id,'target_proc');
630 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
632 // -------- Appointment Target ----
633 // Appointment Target (includes) (Specialized functionality for appointment reminders)
634 $target = resolve_target_sql($rule,$group_id,'target_appt');
635 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
637 // Passed all target tests, so return true.
638 return true;
641 // Function to return active plans
642 // Parameters:
643 // $type - plan type filter (normal or cqm or blank)
644 // $patient_id - pid of selected patient. (if custom plan does not exist then
645 // will use the default plan)
646 // $configurableOnly - true if only want the configurable (per patient) plans
647 // (ie. ignore cqm plans)
648 // Return: array containing plans
649 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
651 if ($configurableOnly) {
652 // Collect all default, configurable (per patient) plans into an array
653 // (ie. ignore the cqm rules)
654 $sql = sqlStatement("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
656 else {
657 // Collect all default plans into an array
658 $sql = sqlStatement("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
660 $returnArray= array();
661 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
662 array_push($returnArray,$row);
665 // Now collect the pertinent plans
666 $newReturnArray = array();
668 // Need to select rules (use custom if exist)
669 foreach ($returnArray as $plan) {
670 $customPlan = sqlQuery("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
672 // Decide if use default vs custom plan (preference given to custom plan)
673 if (!empty($customPlan)) {
674 if ($type == "cqm" ) {
675 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
676 $goPlan = $plan;
678 else {
679 // merge the custom plan with the default plan
680 $mergedPlan = array();
681 foreach ($customPlan as $key => $value) {
682 if ($value == NULL && preg_match("/_flag$/",$key)) {
683 // use default setting
684 $mergedPlan[$key] = $plan[$key];
686 else {
687 // use custom setting
688 $mergedPlan[$key] = $value;
691 $goPlan = $mergedPlan;
694 else {
695 $goPlan = $plan;
698 // Use the chosen plan if set
699 if (!empty($type)) {
700 if ($goPlan["${type}_flag"] == 1) {
701 // active, so use the plan
702 array_push($newReturnArray,$goPlan);
705 else {
706 if ($goPlan['normal_flag'] == 1 ||
707 $goPlan['cqm_flag'] == 1) {
708 // active, so use the plan
709 array_push($newReturnArray,$goPlan);
713 $returnArray = $newReturnArray;
715 return $returnArray;
718 // Function to return a specific plan
719 // Parameters:
720 // $plan - id(string) of plan
721 // $patient_id - pid of selected patient. (if set to 0, then will return
722 // the default rule).
723 // Return: array containing a rule
724 function collect_plan($plan,$patient_id='0') {
726 return sqlQuery("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id) );
730 // Function to set a specific plan activity for a specific patient
731 // Parameters:
732 // $plan - id(string) of plan
733 // $type - plan filter (normal,cqm)
734 // $setting - activity of plan (yes,no,default)
735 // $patient_id - pid of selected patient.
736 // Return: nothing
737 function set_plan_activity_patient($plan,$type,$setting,$patient_id) {
739 // Don't allow messing with the default plans here
740 if ($patient_id == "0") {
741 return;
744 // Convert setting
745 if ($setting == "on") {
746 $setting = 1;
748 else if ($setting == "off") {
749 $setting = 0;
751 else { // $setting == "default"
752 $setting = NULL;
755 // Collect patient specific plan, if already exists.
756 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
757 $patient_plan = sqlQuery($query, array($plan,$patient_id) );
759 if (empty($patient_plan)) {
760 // Create a new patient specific plan with flags all set to default
761 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
762 sqlStatement($query, array($plan, $patient_id) );
765 // Update patient specific row
766 $query = "UPDATE `clinical_plans` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
767 sqlStatement($query, array($setting,$plan,$patient_id) );
771 // Function to return active rules
772 // Parameters:
773 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
774 // $patient_id - pid of selected patient. (if custom rule does not exist then
775 // will use the default rule)
776 // $configurableOnly - true if only want the configurable (per patient) rules
777 // (ie. ignore cqm and amc rules)
778 // $plan - collect rules for specific plan
779 // Return: array containing rules
780 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='') {
782 if ($configurableOnly) {
783 // Collect all default, configurable (per patient) rules into an array
784 // (ie. ignore the cqm and amc rules)
785 $sql = sqlStatement("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
787 else {
788 // Collect all default rules into an array
789 $sql = sqlStatement("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
791 $returnArray= array();
792 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
793 array_push($returnArray,$row);
796 // Now filter rules for plan (if applicable)
797 if (!empty($plan)) {
798 $planReturnArray = array();
799 foreach ($returnArray as $rule) {
800 $standardRule = sqlQuery("SELECT * FROM `clinical_plans_rules` " .
801 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
802 if (!empty($standardRule)) {
803 array_push($planReturnArray,$rule);
806 $returnArray = $planReturnArray;
809 // Now collect the pertinent rules
810 $newReturnArray = array();
812 // Need to select rules (use custom if exist)
813 foreach ($returnArray as $rule) {
814 $customRule = sqlQuery("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
816 // Decide if use default vs custom rule (preference given to custom rule)
817 if (!empty($customRule)) {
818 if ($type == "cqm" || $type == "amc" ) {
819 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
820 $goRule = $rule;
822 else {
823 // merge the custom rule with the default rule
824 $mergedRule = array();
825 foreach ($customRule as $key => $value) {
826 if ($value == NULL && preg_match("/_flag$/",$key)) {
827 // use default setting
828 $mergedRule[$key] = $rule[$key];
830 else {
831 // use custom setting
832 $mergedRule[$key] = $value;
835 $goRule = $mergedRule;
838 else {
839 $goRule = $rule;
842 // Use the chosen rule if set
843 if (!empty($type)) {
844 if ($goRule["${type}_flag"] == 1) {
845 // active, so use the rule
846 array_push($newReturnArray,$goRule);
849 else {
850 // no filter, so return the rule
851 array_push($newReturnArray,$goRule);
854 $returnArray = $newReturnArray;
856 return $returnArray;
859 // Function to return a specific rule
860 // Parameters:
861 // $rule - id(string) of rule
862 // $patient_id - pid of selected patient. (if set to 0, then will return
863 // the default rule).
864 // Return: array containing a rule
865 function collect_rule($rule,$patient_id='0') {
867 return sqlQuery("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
871 // Function to set a specific rule activity for a specific patient
872 // Parameters:
873 // $rule - id(string) of rule
874 // $type - rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
875 // $setting - activity of rule (yes,no,default)
876 // $patient_id - pid of selected patient.
877 // Return: nothing
878 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
880 // Don't allow messing with the default rules here
881 if ($patient_id == "0") {
882 return;
885 // Convert setting
886 if ($setting == "on") {
887 $setting = 1;
889 else if ($setting == "off") {
890 $setting = 0;
892 else { // $setting == "default"
893 $setting = NULL;
896 // Collect patient specific rule, if already exists.
897 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
898 $patient_rule = sqlQuery($query, array($rule,$patient_id) );
900 if (empty($patient_rule)) {
901 // Create a new patient specific rule with flags all set to default
902 $query = "INSERT into `clinical_rules` (`id`, `pid`) VALUES (?,?)";
903 sqlStatement($query, array($rule, $patient_id) );
906 // Update patient specific row
907 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
908 sqlStatement($query, array($setting,$rule,$patient_id) );
912 // Function to return applicable reminder dates (relative)
913 // Parameters:
914 // $rule - id(string) of selected rule
915 // $reminder_method - string label of filter type
916 // Return: array containing reminder features
917 function resolve_reminder_sql($rule,$reminder_method) {
918 $sql = sqlStatement("SELECT `method_detail`, `value` FROM `rule_reminder` " .
919 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method) );
921 $returnArray= array();
922 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
923 array_push($returnArray,$row);
925 return $returnArray;
928 // Function to return applicable filters
929 // Parameters:
930 // $rule - id(string) of selected rule
931 // $filter_method - string label of filter type
932 // $include_flag - to allow selection for included or excluded filters
933 // Return: array containing filters
934 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
935 $sql = sqlStatement("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
936 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
938 $returnArray= array();
939 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
940 array_push($returnArray,$row);
942 return $returnArray;
945 // Function to return applicable targets
946 // Parameters:
947 // $rule - id(string) of selected rule
948 // $group_id - group id of target group (if blank, then will ignore grouping)
949 // $target_method - string label of target type
950 // $include_flag - to allow selection for included or excluded targets
951 // Return: array containing targets
952 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
954 if ($group_id) {
955 $sql = sqlStatement("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
956 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
958 else {
959 $sql = sqlStatement("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
960 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
963 $returnArray= array();
964 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
965 array_push($returnArray,$row);
967 return $returnArray;
970 // Function to return applicable actions
971 // Parameters:
972 // $rule - id(string) of selected rule
973 // $group_id - group id of target group (if blank, then will ignore grouping)
974 // Return: array containing actions
975 function resolve_action_sql($rule,$group_id='') {
977 if ($group_id) {
978 $sql = sqlStatement("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
979 "FROM `rule_action` as a " .
980 "JOIN `rule_action_item` as b " .
981 "ON a.category = b.category AND a.item = b.item " .
982 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
984 else {
985 $sql = sqlStatement("SELECT b.category, b.item, b.value, b.custom_flag " .
986 "FROM `rule_action` as a " .
987 "JOIN `rule_action_item` as b " .
988 "ON a.category = b.category AND a.item = b.item " .
989 "WHERE a.id=?", array($rule) );
992 $returnArray= array();
993 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
994 array_push($returnArray,$row);
996 return $returnArray;
999 // Function to check database filters and targets
1000 // Parameters:
1001 // $patient_id - pid of selected patient.
1002 // $filter - array containing filter/target elements
1003 // $interval - used for the interval elements
1004 // $dateTarget - target date. blank is current date.
1005 // Return: boolean if check passed, otherwise false
1006 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
1007 $isMatch = false; //matching flag
1009 // Set date to current if not set
1010 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1012 // Unpackage interval information
1013 // (Assume only one for now and only pertinent for targets)
1014 $intervalType = '';
1015 $intervalValue = '';
1016 if (!empty($interval)) {
1017 $intervalType = $interval[0]['value'];
1018 $intervalValue = $interval[0]['interval'];
1021 foreach( $filter as $row ) {
1022 // Row description
1023 // [0]=>special modes
1024 $temp_df = explode("::",$row['value']);
1026 if ($temp_df[0] == "CUSTOM") {
1027 // Row description
1028 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1029 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1030 // Record the match
1031 $isMatch = true;
1033 else {
1034 // If this is a required entry then return false
1035 if ($row['required_flag']) return false;
1038 else if ($temp_df[0] == "LIFESTYLE") {
1039 // Row description
1040 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1041 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1042 // Record the match
1043 $isMatch = true;
1045 else {
1046 // If this is a required entry then return false
1047 if ($row['required_flag']) return false;
1050 else {
1051 // Default mode
1052 // Row description
1053 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1054 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)) {
1055 // Record the match
1056 $isMatch = true;
1058 else {
1059 // If this is a required entry then return false
1060 if ($row['required_flag']) return false;
1065 // return results of check
1066 return $isMatch;
1069 // Function to check procedure filters and targets
1070 // Parameters:
1071 // $patient_id - pid of selected patient.
1072 // $filter - array containing filter/target elements
1073 // $interval - used for the interval elements
1074 // $dateTarget - target date. blank is current date.
1075 // Return: boolean if check passed, otherwise false
1076 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
1077 $isMatch = false; //matching flag
1079 // Set date to current if not set
1080 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1082 // Unpackage interval information
1083 // (Assume only one for now and only pertinent for targets)
1084 $intervalType = '';
1085 $intervalValue = '';
1086 if (!empty($interval)) {
1087 $intervalType = $interval[0]['value'];
1088 $intervalValue = $interval[0]['interval'];
1091 foreach( $filter as $row ) {
1092 // Row description
1093 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1094 // code description
1095 // <type(ICD9,CPT)>:<identifier>; etc.
1096 $temp_df = explode("::",$row['value']);
1097 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)) {
1098 // Record the match
1099 $isMatch = true;
1101 else {
1102 // If this is a required entry then return false
1103 if ($row['required_flag']) return false;
1107 // return results of check
1108 return $isMatch;
1111 // Function to check for appointment
1112 // Parameters:
1113 // $patient_id - pid of selected patient.
1114 // $dateTarget - target date.
1115 // Return: boolean if appt exist, otherwise false
1116 function appointment_check($patient_id,$dateTarget='') {
1117 $isMatch = false; //matching flag
1119 // Set date to current if not set (although should always be set)
1120 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1121 $dateTargetRound = date('Y-m-d',$dateTarget);
1123 // Set current date
1124 $currentDate = date('Y-m-d H:i:s');
1125 $currentDateRound = date('Y-m-d',$dateCurrent);
1127 // Basically, if the appointment is within the current date to the target date,
1128 // then return true. (will not send reminders on same day as appointment)
1129 $sql = sqlStatement("SELECT openemr_postcalendar_events.pc_eid, " .
1130 "openemr_postcalendar_events.pc_title, " .
1131 "openemr_postcalendar_events.pc_eventDate, " .
1132 "openemr_postcalendar_events.pc_startTime, " .
1133 "openemr_postcalendar_events.pc_endTime " .
1134 "FROM openemr_postcalendar_events " .
1135 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1136 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1137 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1139 // return results of check
1141 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1142 // Plan to send back array of appt info (eid, time, date, etc.)
1143 // to do this.
1144 if (sqlNumRows($sql) > 0) {
1145 $isMatch = true;
1148 return $isMatch;
1151 // Function to check lists filters and targets
1152 // Customizable and currently includes diagnoses, medications,
1153 // allergies and surgeries.
1154 // Parameters:
1155 // $patient_id - pid of selected patient.
1156 // $filter - array containing lists filter/target elements
1157 // $dateTarget - target date. blank is current date.
1158 // Return: boolean if check passed, otherwise false
1159 function lists_check($patient_id,$filter,$dateTarget) {
1160 $isMatch = false; //matching flag
1162 // Set date to current if not set
1163 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1165 foreach ( $filter as $row ) {
1166 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1167 // Record the match
1168 $isMatch = true;
1170 else {
1171 // If this is a required entry then return false
1172 if ($row['required_flag']) return false;
1176 // return results of check
1177 return $isMatch;
1180 // Function to check for existance of data in database for a patient
1181 // Parameters:
1182 // $patient_id - pid of selected patient.
1183 // $table - selected mysql table
1184 // $column - selected mysql column
1185 // $data_comp - data comparison (eq,ne,gt,ge,lt,le)
1186 // $data - selected data in the mysql database
1187 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1188 // $num_items_thres - number of items threshold
1189 // $intervalType - type of interval (ie. year)
1190 // $intervalValue - searched for within this many times of the interval type
1191 // $dateTarget - target date.
1192 // Return: boolean if check passed, otherwise false
1193 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1195 // Set date to current if not set
1196 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1198 // Collect the correct column label for patient id in the table
1199 $patient_id_label = collect_database_label('pid',$table);
1201 // Get the interval sql query string
1202 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1204 // If just checking for existence (ie. data is empty),
1205 // then simply set the comparison operator to ne.
1206 if (empty($data)) {
1207 $data_comp = "ne";
1210 // get the appropriate sql comparison operator
1211 $compSql = convertCompSql($data_comp);
1213 // check for items
1214 if (empty($column)) {
1215 // simple search for any table entries
1216 $sql = sqlStatement("SELECT * " .
1217 "FROM `" . add_escape_custom($table) . "` " .
1218 "WHERE `" . add_escape_custom($patient_id_label) . "`=?", array($patient_id) );
1220 else {
1221 // search for number of specific items
1222 $sql = sqlStatement("SELECT `" . add_escape_custom($column) . "` " .
1223 "FROM `" . add_escape_custom($table) . "` " .
1224 "WHERE `" . add_escape_custom($column) ."`" . $compSql . "? " .
1225 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1226 $dateSql, array($data,$patient_id) );
1229 // See if number of returned items passes the comparison
1230 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1233 // Function to check for existence of procedure(s) for a patient
1234 // Parameters:
1235 // $patient_id - pid of selected patient.
1236 // $proc_title - procedure title
1237 // $proc_code - procedure identifier code (array)
1238 // $result_comp - results comparison (eq,ne,gt,ge,lt,le)
1239 // $result_data - results data
1240 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1241 // $num_items_thres - number of items threshold
1242 // $intervalType - type of interval (ie. year)
1243 // $intervalValue - searched for within this many times of the interval type
1244 // $dateTarget - target date.
1245 // Return: boolean if check passed, otherwise false
1246 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1248 // Set date to current if not set
1249 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1251 // Set the table exception (for looking up pertinent date and pid sql columns)
1252 $table = "PROCEDURE-EXCEPTION";
1254 // Collect the correct column label for patient id in the table
1255 $patient_id_label = collect_database_label('pid',$table);
1257 // Get the interval sql query string
1258 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1261 // TODO
1262 // Figure out a way to use the identifiers codes
1263 // TODO
1266 // If just checking for existence (ie result_data is empty),
1267 // then simply set the comparison operator to ne.
1268 if (empty($result_data)) {
1269 $result_comp = "ne";
1272 // get the appropriate sql comparison operator
1273 $compSql = convertCompSql($result_comp);
1275 // collect specific items that fulfill request
1276 $sql = sqlStatement("SELECT procedure_result.result " .
1277 "FROM `procedure_type`, " .
1278 "`procedure_order`, " .
1279 "`procedure_report`, " .
1280 "`procedure_result` " .
1281 "WHERE procedure_type.procedure_type_id = procedure_order.procedure_type_id " .
1282 "AND procedure_order.procedure_order_id = procedure_report.procedure_order_id " .
1283 "AND procedure_report.procedure_report_id = procedure_result.procedure_report_id " .
1284 "AND procedure_type.name = ? " .
1285 "AND procedure_result.result " . $compSql . " ? " .
1286 "AND " . add_escape_custom($patient_id_label) . " = ? " .
1287 $dateSql, array($proc_title,$result_data,$patient_id) );
1289 // See if number of returned items passes the comparison
1290 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1293 // Function to check for existance of data for a patient in the rule_patient_data table
1294 // Parameters:
1295 // $patient_id - pid of selected patient.
1296 // $category - label in category column
1297 // $item - label in item column
1298 // $complete - label in complete column (YES,NO, or blank)
1299 // $num_items_comp - number items comparison (eq,ne,gt,ge,lt,le)
1300 // $num_items_thres - number of items threshold
1301 // $intervalType - type of interval (ie. year)
1302 // $intervalValue - searched for within this many times of the interval type
1303 // $dateTarget - target date.
1304 // Return: boolean if check passed, otherwise false
1305 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
1307 // Set the table
1308 $table = 'rule_patient_data';
1310 // Collect the correct column label for patient id in the table
1311 $patient_id_label = collect_database_label('pid',$table);
1313 // Get the interval sql query string
1314 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1316 // search for number of specific items
1317 $sql = sqlStatement("SELECT `result` " .
1318 "FROM `" . add_escape_custom($table) . "` " .
1319 "WHERE `category`=? " .
1320 "AND `item`=? " .
1321 "AND `complete`=? " .
1322 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1323 $dateSql, array($category,$item,$complete,$patient_id) );
1325 // See if number of returned items passes the comparison
1326 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1329 // Function to check for existance of data for a patient in lifestyle section
1330 // Parameters:
1331 // $patient_id - pid of selected patient.
1332 // $lifestyle - selected label of mysql column of patient history
1333 // $status - specific status of selected lifestyle element
1334 // $dateTarget - target date. blank is current date.
1335 // Return: boolean if check passed, otherwise false
1336 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
1338 // Set date to current if not set
1339 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1341 // Collect pertinent history data
1342 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
1344 // See if match
1345 $stringFlag = strstr($history[$lifestyle], "|".$status);
1346 if (empty($status)) {
1347 // Only ensuring any data has been entered into the field
1348 $stringFlag = true;
1350 if ( $history[$lifestyle] &&
1351 $history[$lifestyle] != '|0|' &&
1352 $stringFlag ) {
1353 return true;
1355 else {
1356 return false;
1360 // Function to check for lists item of a patient
1361 // Fully customizable and includes diagnoses, medications,
1362 // allergies, and surgeries.
1363 // Parameters:
1364 // $patient_id - pid of selected patient.
1365 // $type - type (medical_problem, allergy, medication, etc)
1366 // $value - value searching for
1367 // $dateTarget - target date. blank is current date.
1368 // Return: boolean if check passed, otherwise false
1369 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
1371 // Set date to current if not set
1372 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1374 // Attempt to explode the value into a code type and code (if applicable)
1375 $value_array = explode("::",$value);
1376 if (count($value_array) == 2) {
1378 // Collect the code type and code
1379 $code_type = $value_array[0];
1380 $code = $value_array[1];
1382 if ($code_type=='CUSTOM') {
1383 // Deal with custom code type first (title column in lists table)
1384 $response = sqlQuery("SELECT * FROM `lists` " .
1385 "WHERE `type`=? " .
1386 "AND `pid`=? " .
1387 "AND `title`=? " .
1388 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1389 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
1390 if (!empty($response)) return true;
1392 else {
1393 // Deal with the set code types (diagnosis column in lists table)
1394 $response = sqlQuery("SELECT * FROM `lists` " .
1395 "WHERE `type`=? " .
1396 "AND `pid`=? " .
1397 "AND `diagnosis` LIKE ? " .
1398 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1399 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
1400 if (!empty($response)) return true;
1403 else { // count($value_array) == 1
1404 // Search the title column in lists table
1405 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
1406 $response = sqlQuery("SELECT * FROM `lists` " .
1407 "WHERE `type`=? " .
1408 "AND `pid`=? " .
1409 "AND `title`=? ".
1410 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1411 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
1412 if (!empty($response)) return true;
1415 return false;
1418 // Function to return part of sql query to deal with interval
1419 // Parameters:
1420 // $table - selected mysql table (or EXCEPTION(s))
1421 // $intervalType - type of interval (ie. year)
1422 // $intervalValue - searched for within this many times of the interval type
1423 // $dateTarget - target date.
1424 // Return: string containing pertinent date interval filter for mysql query
1425 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
1427 $dateSql="";
1429 // Collect the correct column label for date in the table
1430 $date_label = collect_database_label('date',$table);
1432 // Deal with interval
1433 if (!empty($intervalType)) {
1434 switch($intervalType) {
1435 case "year":
1436 $dateSql = "AND (" . add_escape_custom($date_label) .
1437 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1438 "', INTERVAL " . add_escape_custom($intervalValue) .
1439 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
1440 break;
1441 case "month":
1442 $dateSql = "AND (" . add_escape_custom($date_label) .
1443 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1444 "', INTERVAL " . add_escape_custom($intervalValue) .
1445 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
1446 break;
1447 case "week":
1448 $dateSql = "AND (" . add_escape_custom($date_label) .
1449 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1450 "', INTERVAL " . add_escape_custom($intervalValue) .
1451 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
1452 break;
1453 case "day":
1454 $dateSql = "AND (" . add_escape_custom($date_label) .
1455 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1456 "', INTERVAL " . add_escape_custom($intervalValue) .
1457 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
1458 break;
1459 case "hour":
1460 $dateSql = "AND (" . add_escape_custom($date_label) .
1461 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1462 "', INTERVAL " . add_escape_custom($intervalValue) .
1463 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
1464 break;
1465 case "minute":
1466 $dateSql = "AND (" . add_escape_custom($date_label) .
1467 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1468 "', INTERVAL " . add_escape_custom($intervalValue) .
1469 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
1470 break;
1471 case "second":
1472 $dateSql = "AND (" . add_escape_custom($date_label) .
1473 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1474 "', INTERVAL " . add_escape_custom($intervalValue) .
1475 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
1476 break;
1477 case "flu_season":
1478 // Flu season to be hard-coded as September thru February
1479 // (Should make this modifiable in the future)
1480 // ($intervalValue is not used)
1481 $dateArray = explode("-",$dateTarget);
1482 $Year = $dateArray[0];
1483 $dateThisYear = $Year . "-09-01";
1484 $dateLastYear = ($Year-1) . "-09-01";
1485 $dateSql =" " .
1486 "AND ((" .
1487 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
1488 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
1489 "OR (" .
1490 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
1491 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
1492 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
1493 break;
1496 else {
1497 $dateSql = "AND " . add_escape_custom($date_label) .
1498 " <= '" . add_escape_custom($dateTarget) . "' ";
1501 // return the sql interval string
1502 return $dateSql;
1505 // Function to collect generic column labels from tables.
1506 // It currently works for date and pid.
1507 // Will need to expand this as algorithm grows.
1508 // Parameters:
1509 // $label - element (pid or date)
1510 // $table - selected mysql table (or EXCEPTION(s))
1511 // Return: string containing official label of selected element
1512 function collect_database_label($label,$table) {
1514 if ($table == 'PROCEDURE-EXCEPTION') {
1515 // return cell to get procedure collection
1516 // special case since reuqires joing of multiple
1517 // tables to get this value
1518 if ($label == "pid") {
1519 $returnedLabel = "procedure_order.patient_id";
1521 else if ($label == "date") {
1522 $returnedLabel = "procedure_report.date_collected";
1524 else {
1525 // unknown label, so return the original label
1526 $returnedLabel = $label;
1529 else if ($table == 'immunizations') {
1530 // return requested label for immunization table
1531 if ($label == "pid") {
1532 $returnedLabel = "patient_id";
1534 else if ($label == "date") {
1535 $returnedLabel = "`administered_date`";
1537 else {
1538 // unknown label, so return the original label
1539 $returnedLabel = $label;
1542 else {
1543 // return requested label for default tables
1544 if ($label == "pid") {
1545 $returnedLabel = "pid";
1547 else if ($label == "date") {
1548 $returnedLabel = "`date`";
1550 else {
1551 // unknown label, so return the original label
1552 $returnedLabel = $label;
1556 return $returnedLabel;
1559 // Simple function to avoid processing of duplicate actions
1560 // Parameters:
1561 // $actions - 2-dimensional array with all current active targets
1562 // $action - array of selected target to test for duplicate
1563 // Return: boolean, true if duplicate, false if not duplicate
1564 function is_duplicate_action($actions,$action) {
1565 foreach ($actions as $row) {
1566 if ($row['category'] == $action['category'] &&
1567 $row['item'] == $action['item'] &&
1568 $row['value'] == $action['value']) {
1569 // Is a duplicate
1570 return true;
1574 // Not a duplicate
1575 return false;
1578 // Calculate the reminder dates.
1579 // Parameters:
1580 // $rule - id(string) of selected rule
1581 // $dateTarget - target date. If blank then will test with current date as target.
1582 // $type - either 'patient_reminder' or 'clinical_reminder'
1583 // For now, will always return an array of 3 dates:
1584 // first date is before the target date (past_due) (default of 1 month)
1585 // second date is the target date (due)
1586 // third date is after the target date (soon_due) (default of 2 weeks)
1587 function calculate_reminder_dates($rule, $dateTarget='',$type) {
1589 // Set date to current if not set
1590 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1592 // Collect the current date settings (to ensure not skip)
1593 $res = resolve_reminder_sql($rule, $type.'_current');
1594 if (!empty($res)) {
1595 $row = $res[0];
1596 if ($row ['method_detail'] == "SKIP") {
1597 $dateTarget = "SKIP";
1601 // Collect the past_due date
1602 $past_due_date == "";
1603 $res = resolve_reminder_sql($rule, $type.'_post');
1604 if (!empty($res)) {
1605 $row = $res[0];
1606 if ($row ['method_detail'] == "week") {
1607 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
1609 if ($row ['method_detail'] == "month") {
1610 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
1612 if ($row ['method_detail'] == "hour") {
1613 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1615 if ($row ['method_detail'] == "SKIP") {
1616 $past_due_date = "SKIP";
1619 else {
1620 // empty settings, so use default of one month
1621 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
1624 // Collect the soon_due date
1625 $soon_due_date == "";
1626 $res = resolve_reminder_sql($rule, $type.'_pre');
1627 if (!empty($res)) {
1628 $row = $res[0];
1629 if ($row ['method_detail'] == "week") {
1630 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
1632 if ($row ['method_detail'] == "month") {
1633 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
1635 if ($row ['method_detail'] == "hour") {
1636 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1638 if ($row ['method_detail'] == "SKIP") {
1639 $soon_due_date = "SKIP";
1642 else {
1643 // empty settings, so use default of one month
1644 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
1647 // Return the array of three dates
1648 return array($soon_due_date,$dateTarget,$past_due_date);
1651 // Adds an action into the reminder array
1652 // Parameters:
1653 // $reminderOldArray - Contains the current array of reminders
1654 // $reminderNew - Array of a new reminder
1655 // Return:
1656 // An array of reminders
1657 function reminder_results_integrate($reminderOldArray, $reminderNew) {
1659 $results = array();
1661 // If reminderArray is empty, then insert new reminder
1662 if (empty($reminderOldArray)) {
1663 array_push($results, $reminderNew);
1664 return $results;
1667 // If duplicate reminder, then replace the old one
1668 $duplicate = false;
1669 foreach ($reminderOldArray as $reminderOld) {
1670 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
1671 $reminderOld['category'] == $reminderNew['category'] &&
1672 $reminderOld['item'] == $reminderNew['item']) {
1673 array_push($results, $reminderNew);
1674 $duplicate = true;
1676 else {
1677 array_push($results, $reminderOld);
1681 // If a new reminder, then insert the new reminder
1682 if (!$duplicate) {
1683 array_push($results, $reminderNew);
1686 return $results;
1689 // Compares number of items with requested comparison operator
1690 // Parameters:
1691 // $comp - Comparison operator(eq,ne,gt,ge,lt,le)
1692 // $thres - Threshold used in comparison
1693 // $num_items - Number of items
1694 // Return:
1695 // Boolean of comparison results
1696 function itemsNumberCompare($comp, $thres, $num_items) {
1698 if ( ($comp == "eq") && ($num_items == $thres) ) {
1699 return true;
1701 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
1702 return true;
1704 else if ( ($comp == "gt") && ($num_items > $thres) ) {
1705 return true;
1707 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
1708 return true;
1710 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
1711 return true;
1713 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
1714 return true;
1716 else {
1717 return false;
1721 // Converts a text comparison operator to sql equivalent
1722 // Parameters:
1723 // $comp - Comparison operator(eq,ne,gt,ge,lt,le)
1724 // Return:
1725 // String containing sql compatible comparison operator
1726 function convertCompSql($comp) {
1728 if ($comp == "eq") {
1729 return "=";
1731 else if ($comp == "ne") {
1732 return "!=";
1734 else if ($comp == "gt") {
1735 return ">";
1737 else if ($comp == "ge") {
1738 return ">=";
1740 else if ($comp == "lt") {
1741 return "<";
1743 else { // ($comp == "le")
1744 return "<=";
1748 // Function to find age in years (with decimal) on the target date
1749 // Parameters:
1750 // $dob - date of birth
1751 // $target - date to calculate age on
1752 // Return: decimal, years(decimal) from dob to target(date)
1753 function convertDobtoAgeYearDecimal($dob,$target) {
1755 // Prepare dob (Y M D)
1756 $dateDOB = explode(" ",$dob);
1758 // Prepare target (Y-M-D H:M:S)
1759 $dateTargetTemp = explode(" ",$target);
1760 $dateTarget = explode("-",$dateTargetTemp[0]);
1762 // Collect differences
1763 $iDiffYear = $dateTarget[0] - $dateDOB[0];
1764 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
1765 $iDiffDay = $dateTarget[2] - $dateDOB[2];
1767 // If birthday has not happen yet for this year, subtract 1.
1768 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
1770 $iDiffYear--;
1773 // Ensure diffYear is not less than 0
1774 if ($iDiffYear < 0) $iDiffYear = 0;
1776 return $iDiffYear;
1779 // Function to find age in months (with decimal) on the target date
1780 // Parameters:
1781 // $dob - date of birth
1782 // $target - date to calculate age on
1783 // Return: decimal, months(decimal) from dob to target(date)
1784 function convertDobtoAgeMonthDecimal($dob,$target) {
1786 // Prepare dob (Y M D)
1787 $dateDOB = explode(" ",$dob);
1789 // Prepare target (Y-M-D H:M:S)
1790 $dateTargetTemp = explode(" ",$target);
1791 $dateTarget = explode("-",$dateTargetTemp[0]);
1793 // Collect differences
1794 $iDiffYear = $dateTarget[0] - $dateDOB[0];
1795 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
1796 $iDiffDay = $dateTarget[2] - $dateDOB[2];
1798 // If birthday has not happen yet for this year, subtract 1.
1799 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
1801 $iDiffYear--;
1804 // Ensure diffYear is not less than 0
1805 if ($iDiffYear < 0) $iDiffYear = 0;
1807 return (12 * $iDiffYear) + $iDiffMonth;
1810 // Function to calculate the percentage
1811 // Parameters:
1812 // $pass_filter - number of patients that pass filter
1813 // $exclude_filter - number of patients that are excluded
1814 // $pass_target - number of patients that pass target
1815 // Return: String of a number formatted into a percentage
1816 function calculate_percentage($pass_filt,$exclude_filt,$pass_targ) {
1817 if ($pass_filt > 0) {
1818 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100) . xl('%');
1820 else {
1821 $perc = "0". xl('%');
1823 return $perc;