/** * Process clinic rules. * * Test the clinic rules of entire clinic and create a report or patient reminders (can also test * on one patient or patients of one provider). The structure of the returned results is dependent on the * $organize_mode and $mode parameters. * <pre>The results are dependent on the $organize_mode parameter settings * 'default' organize_mode: * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings): * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status') * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status') * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report * 'plans' organize_mode: * Returns similar to default, but organizes by the active plans * </pre> * * @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). * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules. * @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'). * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required) * @param integer $patient_id pid of patient. If blank then will check all patients. * @param string $plan test for specific plan only * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results. * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report) * @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. * @param integer $start applicable patient to start at (when batching process) * @param integer $batchSize number of patients to batch (when batching process) * @return array See above for organization structure of the results. */ function test_rules_clinic($provider = '', $type = '', $dateTarget = '', $mode = '', $patient_id = '', $plan = '', $organize_mode = 'default', $options = array(), $pat_prov_rel = 'primary', $start = NULL, $batchSize = NULL) { // If dateTarget is an array, then organize them. if (is_array($dateTarget)) { $dateArray = $dateTarget; $dateTarget = $dateTarget['dateTarget']; } // Set date to current if not set $dateTarget = $dateTarget ? $dateTarget : date('Y-m-d H:i:s'); // Prepare the results array $results = array(); // If set the $provider to collate_outer (or collate_inner without plans organize mode), // then run through this function recursively and return results. if ($provider == "collate_outer" || $provider == "collate_inner" && $organize_mode != 'plans') { // First, collect an array of all providers $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname"; $ures = sqlStatementCdrEngine($query); // Second, run through each provider recursively while ($urow = sqlFetchArray($ures)) { $newResults = test_rules_clinic($urow['id'], $type, $dateTarget, $mode, $patient_id, $plan, $organize_mode, $options, $pat_prov_rel, $start, $batchSize); if (!empty($newResults)) { $provider_item['is_provider'] = TRUE; $provider_item['prov_lname'] = $urow['lname']; $provider_item['prov_fname'] = $urow['fname']; $provider_item['npi'] = $urow['npi']; $provider_item['federaltaxid'] = $urow['federaltaxid']; array_push($results, $provider_item); $results = array_merge($results, $newResults); } } // done, so now can return results return $results; } // If set organize-mode to plans, then collects active plans and run through this // function recursively and return results. if ($organize_mode == "plans") { // First, collect active plans $plans_resolve = resolve_plans_sql($plan, $patient_id); // Second, run through function recursively foreach ($plans_resolve as $plan_item) { // (if collate_inner, then nest a collation of providers within each plan) if ($provider == "collate_inner") { // First, collect an array of all providers $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname"; $ures = sqlStatementCdrEngine($query); // Second, run through each provider recursively $provider_results = array(); while ($urow = sqlFetchArray($ures)) { $newResults = test_rules_clinic($urow['id'], $type, $dateTarget, $mode, $patient_id, $plan_item['id'], 'default', $options, $pat_prov_rel, $start, $batchSize); if (!empty($newResults)) { $provider_item['is_provider'] = TRUE; $provider_item['prov_lname'] = $urow['lname']; $provider_item['prov_fname'] = $urow['fname']; $provider_item['npi'] = $urow['npi']; $provider_item['federaltaxid'] = $urow['federaltaxid']; array_push($provider_results, $provider_item); $provider_results = array_merge($provider_results, $newResults); } } if (!empty($provider_results)) { $plan_item['is_plan'] = TRUE; array_push($results, $plan_item); $results = array_merge($results, $provider_results); } } else { // (not collate_inner, so do not nest providers within each plan) $newResults = test_rules_clinic($provider, $type, $dateTarget, $mode, $patient_id, $plan_item['id'], 'default', $options, $pat_prov_rel, $start, $batchSize); if (!empty($newResults)) { $plan_item['is_plan'] = TRUE; array_push($results, $plan_item); $results = array_merge($results, $newResults); } } } // done, so now can return results return $results; } // Collect applicable patient pids $patientData = array(); $patientData = buildPatientArray($patient_id, $provider, $pat_prov_rel, $start, $batchSize); // Go through each patient(s) // // If in report mode, then tabulate for each rule: // Total Patients // Patients that pass the filter // Patients that pass the target // If in reminders mode, then create reminders for each rule: // Reminder that action is due soon // Reminder that action is due // Reminder that action is post-due //Collect applicable rules // Note that due to a limitation in the this function, the patient_id is explicitly // for grouping items when not being done in real-time or for official reporting. // So for cases such as patient reminders on a clinic scale, the calling function // will actually need rather than pass in a explicit patient_id for each patient in // a separate call to this function. if ($mode != "report") { // Use per patient custom rules (if exist) // Note as discussed above, this only works for single patient instances. $rules = resolve_rules_sql($type, $patient_id, FALSE, $plan); } else { // $mode = "report" // Only use default rules (do not use patient custom rules) $rules = resolve_rules_sql($type, $patient_id, FALSE, $plan); } foreach ($rules as $rowRule) { // If using cqm or amc type, then use the hard-coded rules set. // Note these rules are only used in report mode. if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) { require_once dirname(__FILE__) . "/classes/rulesets/ReportManager.php"; $manager = new ReportManager(); if ($rowRule['amc_flag']) { // Send array of dates ('dateBegin' and 'dateTarget') $tempResults = $manager->runReport($rowRule, $patientData, $dateArray, $options); } else { // Send target date $tempResults = $manager->runReport($rowRule, $patientData, $dateTarget); } if (!empty($tempResults)) { foreach ($tempResults as $tempResult) { array_push($results, $tempResult); } } // Go on to the next rule continue; } // If in reminder mode then need to collect the measurement dates // from rule_reminder table $target_dates = array(); if ($mode != "report") { // Calculate the dates to check for if ($type == "patient_reminder") { $reminder_interval_type = "patient_reminder"; } else { // $type == "passive_alert" or $type == "active_alert" $reminder_interval_type = "clinical_reminder"; } $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type); } else { // $mode == "report" // Only use the target date in the report $target_dates[0] = $dateTarget; } //Reset the counters $total_patients = 0; $pass_filter = 0; $exclude_filter = 0; $pass_target = 0; // Find the number of target groups $targetGroups = returnTargetGroups($rowRule['id']); if (count($targetGroups) == 1 || $mode == "report") { //skip this section if not report and more than one target group foreach ($patientData as $rowPatient) { // Count the total patients $total_patients++; $dateCounter = 1; // for reminder mode to keep track of which date checking foreach ($target_dates as $dateFocus) { //Skip if date is set to SKIP if ($dateFocus == "SKIP") { $dateCounter++; continue; } //Set date counter and reminder token (applicable for reminders only) if ($dateCounter == 1) { $reminder_due = "soon_due"; } else { if ($dateCounter == 2) { $reminder_due = "due"; } else { // $dateCounter == 3 $reminder_due = "past_due"; } } // First, deal with deceased patients // (for now will simply not pass the filter, but can add a database item // if ever want to create rules for dead people) // Could also place this function at the total_patients level if wanted. // (But then would lose the option of making rules for dead people) // Note using the dateTarget rather than dateFocus if (is_patient_deceased($rowPatient['pid'], $dateTarget)) { continue; } // Check if pass filter $passFilter = test_filter($rowPatient['pid'], $rowRule['id'], $dateFocus); if ($passFilter === "EXCLUDED") { // increment EXCLUDED and pass_filter counters // and set as FALSE for reminder functionality. $pass_filter++; $exclude_filter++; $passFilter = FALSE; } if ($passFilter) { // increment pass filter counter $pass_filter++; } else { $dateCounter++; continue; } // Check if pass target $passTarget = test_targets($rowPatient['pid'], $rowRule['id'], '', $dateFocus); if ($passTarget) { // increment pass target counter $pass_target++; // send to reminder results if ($mode == "reminders-all") { // place the completed actions into the reminder return array $actionArray = resolve_action_sql($rowRule['id'], '1'); foreach ($actionArray as $action) { $action_plus = $action; $action_plus['due_status'] = "not_due"; $action_plus['pid'] = $rowPatient['pid']; $results = reminder_results_integrate($results, $action_plus); } } break; } else { // send to reminder results if ($mode != "report") { // place the uncompleted actions into the reminder return array $actionArray = resolve_action_sql($rowRule['id'], '1'); foreach ($actionArray as $action) { $action_plus = $action; $action_plus['due_status'] = $reminder_due; $action_plus['pid'] = $rowPatient['pid']; $results = reminder_results_integrate($results, $action_plus); } } } $dateCounter++; } } } // Calculate and save the data for the rule $percentage = calculate_percentage($pass_filter, $exclude_filter, $pass_target); if ($mode == "report") { $newRow = array('is_main' => TRUE, 'total_patients' => $total_patients, 'excluded' => $exclude_filter, 'pass_filter' => $pass_filter, 'pass_target' => $pass_target, 'percentage' => $percentage); $newRow = array_merge($newRow, $rowRule); array_push($results, $newRow); } // Now run through the target groups if more than one if (count($targetGroups) > 1) { foreach ($targetGroups as $i) { //Reset the target counter $pass_target = 0; foreach ($patientData as $rowPatient) { $dateCounter = 1; // for reminder mode to keep track of which date checking foreach ($target_dates as $dateFocus) { //Skip if date is set to SKIP if ($dateFocus == "SKIP") { $dateCounter++; continue; } //Set date counter and reminder token (applicable for reminders only) if ($dateCounter == 1) { $reminder_due = "soon_due"; } else { if ($dateCounter == 2) { $reminder_due = "due"; } else { // $dateCounter == 3 $reminder_due = "past_due"; } } // First, deal with deceased patients // (for now will simply not pass the filter, but can add a database item // if ever want to create rules for dead people) // Could also place this function at the total_patients level if wanted. // (But then would lose the option of making rules for dead people) // Note using the dateTarget rather than dateFocus if (is_patient_deceased($rowPatient['pid'], $dateTarget)) { continue; } // Check if pass filter $passFilter = test_filter($rowPatient['pid'], $rowRule['id'], $dateFocus); if ($passFilter === "EXCLUDED") { $passFilter = FALSE; } if (!$passFilter) { // increment pass filter counter $dateCounter++; continue; } //Check if pass target $passTarget = test_targets($rowPatient['pid'], $rowRule['id'], $i, $dateFocus); if ($passTarget) { // increment pass target counter $pass_target++; // send to reminder results if ($mode == "reminders-all") { // place the completed actions into the reminder return array $actionArray = resolve_action_sql($rowRule['id'], $i); foreach ($actionArray as $action) { $action_plus = $action; $action_plus['due_status'] = "not_due"; $action_plus['pid'] = $rowPatient['pid']; $results = reminder_results_integrate($results, $action_plus); } } break; } else { // send to reminder results if ($mode != "report") { // place the actions into the reminder return array $actionArray = resolve_action_sql($rowRule['id'], $i); foreach ($actionArray as $action) { $action_plus = $action; $action_plus['due_status'] = $reminder_due; $action_plus['pid'] = $rowPatient['pid']; $results = reminder_results_integrate($results, $action_plus); } } } $dateCounter++; } } // Calculate and save the data for the rule $percentage = calculate_percentage($pass_filter, $exclude_filter, $pass_target); // Collect action for title (just use the first one, if more than one) $actionArray = resolve_action_sql($rowRule['id'], $i); $action = $actionArray[0]; if ($mode == "report") { $newRow = array('is_sub' => TRUE, 'action_category' => $action['category'], 'action_item' => $action['item'], 'total_patients' => '', 'excluded' => '', 'pass_filter' => '', 'pass_target' => $pass_target, 'percentage' => $percentage); array_push($results, $newRow); } } } } // Return the data return $results; }
/** * Function to update reminders. * * Function that updates reminders and returns an array with a specific data structure. * <pre>The data structure of the return array includes the following elements * 'total_active_actions' - Number of active actions. * 'total_pre_active_reminders' - Number of active reminders before processing. * 'total_pre_unsent_reminders' - Number of unsent reminders before processing. * 'total_post_active_reminders' - Number of active reminders after processing. * 'total_post_unsent_reminders' - Number of unsent reminders after processing. * 'number_new_reminders' - Number of new reminders * 'number_updated_reminders' - Number of updated reminders (due_status change) * 'number_inactivated_reminders' - Number of inactivated reminders. * 'number_unchanged_reminders' - Number of unchanged reminders. * </pre> * * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target. * @param integer $patient_id pid of patient. If blank then will check all patients. * @param integer $start applicable patient to start at (when batching process) * @param integer $batchSize number of patients to batch (when batching process) * @return array see above for data structure of returned array */ function update_reminders($dateTarget = '', $patient_id = '', $start = NULL, $batchSize = NULL) { $logging = array(); // Set date to current if not set $dateTarget = $dateTarget ? $dateTarget : date('Y-m-d H:i:s'); // Collect reminders (note that this function removes redundant and keeps the most distant // reminder (ie. prefers 'past_due' over 'due' over 'soon_due') // Note that due to a limitation in the test_rules_clinic function, the patient_id is explicitly // needed to work correctly. So rather than pass in a '' patient_id to do the entire clinic, // we instead need to pass in each patient_id separately. $collectedReminders = array(); $patient_id_complete = ""; if (!empty($patient_id)) { // only one patient id, so run the function $collectedReminders = test_rules_clinic('', 'patient_reminder', $dateTarget, 'reminders-due', $patient_id); $patient_id_complete = $patient_id; } else { // as described above, need to pass in each patient_id // Collect all patient ids $patientData = buildPatientArray('', '', '', $start, $batchSize); for ($iter = 0; $row = sqlFetchArray($rez); $iter++) { $patientData[$iter] = $row; } $first_flag = TRUE; foreach ($patientData as $patient) { // collect reminders $tempCollectReminders = test_rules_clinic('', 'patient_reminder', $dateTarget, 'reminders-due', $patient['pid']); $collectedReminders = array_merge($collectedReminders, $tempCollectReminders); // build the $patient_id_complete variable if ($first_flag) { $patient_id_complete .= $patient['pid']; $first_flag = FALSE; } else { $patient_id_complete .= "," . $patient['pid']; } } } $logging['total_active_actions'] = count($collectedReminders); // For logging purposes only: // Collect number active of active and unsent reminders $logging['total_pre_active_reminders'] = count(fetch_reminders($patient_id_complete)); $logging['total_pre_unsent_reminders'] = count(fetch_reminders($patient_id_complete, 'unsent')); // Migrate reminders into the patient_reminders table $logging['number_new_reminders'] = 0; $logging['number_updated_reminders'] = 0; $logging['number_unchanged_reminders'] = 0; foreach ($collectedReminders as $reminder) { // See if a reminder already exist $sql = "SELECT `id`, `pid`, `due_status`, `category`, `item` FROM `patient_reminders` WHERE " . "`active`='1' AND `pid`=? AND `category`=? AND `item`=?"; $result = sqlQueryCdrEngine($sql, array($reminder['pid'], $reminder['category'], $reminder['item'])); if (empty($result)) { // It does not yet exist, so add a new reminder $sql = "INSERT INTO `patient_reminders` (`pid`, `due_status`, `category`, `item`, `date_created`) " . "VALUES (?, ?, ?, ?, NOW())"; sqlStatementCdrEngine($sql, array($reminder['pid'], $reminder['due_status'], $reminder['category'], $reminder['item'])); $logging['number_new_reminders']++; } else { // It already exist (see if if needs to be updated via adding a new reminder) if ($reminder['due_status'] == $result['due_status']) { // No change in due status, so no need to update $logging['number_unchanged_reminders']++; continue; } else { // Change in due status, so inactivate current reminder and create a new one // First, inactivate the previous reminder $sql = "UPDATE `patient_reminders` SET `active` = '0', `reason_inactivated` = 'due_status_update', " . "`date_inactivated` = NOW() WHERE `id`=?"; sqlStatementCdrEngine($sql, array($result['id'])); // Then, add the new reminder $sql = "INSERT INTO `patient_reminders` (`pid`, `due_status`, `category`, `item`, `date_created`) " . "VALUES (?, ?, ?, ?, NOW())"; sqlStatementCdrEngine($sql, array($reminder['pid'], $reminder['due_status'], $reminder['category'], $reminder['item'])); } } } // Inactivate reminders that no longer exist // Go through each active reminder and ensure it is in the current list $sqlReminders = fetch_reminders($patient_id_complete); $logging['number_inactivated_reminders'] = 0; foreach ($sqlReminders as $row) { $inactivateFlag = true; foreach ($collectedReminders as $reminder) { if ($row['pid'] == $reminder['pid'] && $row['category'] == $reminder['category'] && $row['item'] == $reminder['item'] && $row['due_status'] == $reminder['due_status']) { // The sql reminder has been confirmed, so do not inactivate it $inactivateFlag = false; break; } } if ($inactivateFlag) { // The sql reminder was not confirmed, so inactivate it $sql = "UPDATE `patient_reminders` SET `active` = '0', `reason_inactivated` = 'auto', " . "`date_inactivated` = NOW() WHERE `id`=?"; sqlStatementCdrEngine($sql, array($row['id'])); $logging['number_inactivated_reminders']++; } } // For logging purposes only: // Collect number of active and unsent reminders $logging['total_post_active_reminders'] = count(fetch_reminders($patient_id_complete)); $logging['total_post_unsent_reminders'] = count(fetch_reminders($patient_id_complete, 'unsent')); return $logging; }