2 // Copyright (C) 2010-2011 Aron Racho <aron@mi-squred.com>
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
9 require_once(src_dir() . "/clinical_rules.php");
10 require_once(library_src('RuleCriteriaFilterFactory.php'));
11 require_once(library_src('RuleCriteriaTargetFactory.php'));
14 * Responsible for handling the persistence (CRU operations, deletes are
15 * not currently supported).
16 * This class should be kept synchronized with clinical_rules.php
21 const SQL_RULE_DETAIL
=
22 "SELECT lo.title as title, cr.*
23 FROM clinical_rules cr
25 ON (cr.id = lo.option_id AND lo.list_id = 'clinical_rules' AND lo.activity = 1)";
27 const SQL_RULE_REMINDER_INTERVAL
=
35 const SQL_RULE_FILTER
=
36 "SELECT PASSWORD(CONCAT( id, include_flag, required_flag, method, method_detail, value )) AS guid, rule_filter.*
37 FROM rule_filter WHERE id = ?";
39 const SQL_RULE_TARGET
=
40 "SELECT PASSWORD(CONCAT( id, group_id, include_flag, required_flag, method, value, rule_target.interval )) AS guid, rule_target.*
41 FROM rule_target WHERE id = ?";
43 const SQL_RULE_FILTER_BY_GUID
=
44 "SELECT * FROM rule_filter
45 WHERE PASSWORD(CONCAT( id, include_flag, required_flag, method, method_detail, value )) = ?";
47 const SQL_RULE_TARGET_BY_GUID
=
48 "SELECT * FROM rule_target
49 WHERE PASSWORD(CONCAT( id, group_id, include_flag, required_flag, method, value, rule_target.interval )) = ?";
51 const SQL_RULE_TARGET_BY_ID_GROUP_ID
=
52 "SELECT PASSWORD(CONCAT( id, group_id, include_flag, required_flag, method, value, rule_target.interval )) AS guid, rule_target.*
53 FROM rule_target WHERE id = ? AND group_id = ?";
55 const SQL_RULE_ACTIONS
=
56 "SELECT PASSWORD( CONCAT(id, category, item, group_id) ) AS guid, rule_action.* FROM rule_action
59 const SQL_RULE_ACTION_BY_GUID
=
60 "SELECT rule_action.*,
61 rule_action_item.clin_rem_link,
62 rule_action_item.reminder_message,
63 rule_action_item.custom_flag
64 FROM rule_action JOIN rule_action_item ON (rule_action_item.category = rule_action.category AND rule_action_item.item = rule_action.item )
65 WHERE PASSWORD( CONCAT(rule_action.id, rule_action.category, rule_action.item, rule_action.group_id ) ) = ?";
67 const SQL_UPDATE_FLAGS
=
68 "UPDATE clinical_rules
69 SET active_alert_flag = ?,
70 passive_alert_flag = ?,
73 patient_reminder_flag = ?,
78 WHERE id = ? AND pid = 0";
80 const SQL_UPDATE_TITLE
=
83 WHERE list_id = 'clinical_rules' AND option_id = ?";
85 const SQL_REMOVE_INTERVALS
=
86 "DELETE FROM rule_reminder
89 const SQL_INSERT_INTERVALS
=
90 "INSERT INTO rule_reminder
91 (id, method, method_detail, value)
92 VALUES ( ?, ?, ?, ?)";
94 const SQL_UPDATE_FILTER
=
95 "UPDATE rule_filter SET include_flag = ?, required_flag = ?, method = ?, method_detail = ?, value = ?
96 WHERE PASSWORD(CONCAT( id, include_flag, required_flag, method, method_detail, value )) = ?";
98 const SQL_INSERT_FILTER
=
99 "INSERT INTO rule_filter (id, include_flag, required_flag, method, method_detail, value )
100 VALUES ( ?, ?, ?, ?, ?, ? )";
102 const SQL_UPDATE_TARGET
=
103 "UPDATE rule_target SET include_flag = ?, required_flag = ?, method = ?, value = ?
104 WHERE PASSWORD(CONCAT( id, group_id, include_flag, required_flag, method, value, rule_target.interval )) = ?";
106 const SQL_INSERT_TARGET
=
107 "INSERT INTO rule_target ( id, include_flag, required_flag, method, value, group_id )
108 VALUES ( ?, ?, ?, ?, ?, ? )";
110 var $filterCriteriaFactory;
111 var $targetCriteriaFactory;
113 function __construct()
115 $this->filterCriteriaFactory
= new RuleCriteriaFilterFactory();
116 $this->targetCriteriaFactory
= new RuleCriteriaTargetFactory();
120 * Returns a Rule object if the supplied rule id matches a record in
121 * clinical_rules. An optional patient id parameter allows you to get the
122 * rules specific to the patient.
124 * Returns null if no rule is found matching the id or patient.
129 function getRule($id, $pid = 0)
131 $ruleResult = sqlQuery(
132 self
::SQL_RULE_DETAIL
. " WHERE id = ? AND pid = ?",
140 $rule = new Rule($id, $ruleResult['title']);
142 $rule->setDeveloper($ruleResult['developer']);
143 $rule->setFunding($ruleResult['funding_source']);
144 $rule->setRelease($ruleResult['release_version']);
145 $rule->setWeb_ref($ruleResult['web_reference']);
147 $this->fillRuleTypes($rule, $ruleResult);
148 $this->fillRuleReminderIntervals($rule);
149 $this->fillRuleFilterCriteria($rule);
150 $this->fillRuleTargetActionGroups($rule);
162 * Adds a RuleType to the given rule based on the sql result row
163 * passed to it, evaluating the *_flag columns.
166 private function fillRuleTypes($rule, $ruleResult)
168 if ($ruleResult['active_alert_flag'] == 1) {
169 $rule->addRuleType(RuleType
::from(RuleType
::ActiveAlert
));
172 if ($ruleResult['passive_alert_flag'] == 1) {
173 $rule->addRuleType(RuleType
::from(RuleType
::PassiveAlert
));
177 if ($ruleResult['cqm_flag'] == 1) {
178 $rule->addRuleType(RuleType
::from(RuleType
::CQM
));
181 if ($ruleResult['amc_flag'] == 1) {
182 $rule->addRuleType(RuleType
::from(RuleType
::AMC
));
185 if ($ruleResult['patient_reminder_flag'] == 1) {
186 $rule->addRuleType(RuleType
::from(RuleType
::PatientReminder
));
191 * Fills the given rule with criteria derived from the rule_filter
192 * table. Relies on the RuleCriteriaFilterFactory for the parsing of
193 * rows in this table into concrete subtypes of RuleCriteria.
196 private function fillRuleFilterCriteria($rule)
198 $stmt = sqlStatement(self
::SQL_RULE_FILTER
, array( $rule->id
));
199 $criterion = $this->gatherCriteria($rule, $stmt, $this->filterCriteriaFactory
);
200 $ruleFilters = new RuleFilters();
201 $rule->setRuleFilters($ruleFilters);
202 if (sizeof($criterion) > 0) {
203 foreach ($criterion as $criteria) {
204 $ruleFilters->add($criteria);
209 private function fillRuleTargetActionGroups($rule)
211 $stmt = sqlStatement(self
::SQL_RULE_TARGET
, array( $rule->id
));
212 $criterion = $this->gatherCriteria($rule, $stmt, $this->targetCriteriaFactory
);
214 $ruleTargetGroups = $this->fetchRuleTargetCriteria($rule);
215 $ruleActionGroups = $this->fetchRuleActions($rule);
217 $groupCount = max(end(array_keys($ruleTargetGroups)), end(array_keys($ruleActionGroups)));
218 for ($groupId = 0; $groupId <= $groupCount; $groupId++
) {
219 $group = new RuleTargetActionGroup($groupId);
221 if (isset($ruleTargetGroups[$groupId])) {
222 $group->setRuleTargets($ruleTargetGroups[$groupId]);
226 if (isset($ruleActionGroups[$groupId])) {
227 $group->setRuleActions($ruleActionGroups[$groupId]);
231 if ($addGroup == true) {
232 $groups[$groupId]= $group;
236 $rule->setGroups($groups);
242 private function fetchRuleTargetCriteria($rule)
244 $stmt = sqlStatement(self
::SQL_RULE_TARGET
, array( $rule->id
));
245 $criterion = $this->gatherCriteria(
248 $this->targetCriteriaFactory
250 $ruleTargetGroups = array();
251 if (sizeof($criterion) > 0) {
252 foreach ($criterion as $criteria) {
253 if (!isset($ruleTargetGroups[$criteria->groupId
])) {
254 $ruleTargetGroups[$criteria->groupId
] = new RuleTargets();
257 $ruleTargetGroups[$criteria->groupId
]->add($criteria);
261 ksort($ruleTargetGroups);
262 return $ruleTargetGroups;
268 private function fetchRuleActions($rule)
270 $stmt = sqlStatement(self
::SQL_RULE_ACTIONS
, array( $rule->id
));
271 $ruleActionGroups = array();
272 for ($iter=0; $row=sqlFetchArray($stmt); $iter++
) {
273 $action = new RuleAction();
274 $action->category
= $row['category'];
275 $action->item
= $row['item'];
276 $action->guid
= $row['guid'];
277 $action->groupId
= $row['group_id'];
278 if (!isset($ruleActionGroups[$action->groupId
])) {
279 $ruleActionGroups[$action->groupId
] = new RuleActions();
282 $ruleActionGroups[$action->groupId
]->add($action);
285 ksort($ruleActionGroups);
286 return $ruleActionGroups;
290 * @param string $guid
291 * @return RuleCriteria
293 function getRuleFilterCriteria($rule, $guid)
295 $stmt = sqlStatement(self
::SQL_RULE_FILTER_BY_GUID
, array( $guid ));
296 $criterion = $this->gatherCriteria(
299 $this->filterCriteriaFactory
301 if (sizeof($criterion) > 0) {
302 $criteria = $criterion[0];
303 $criteria->guid
= $guid;
304 return $criterion[0];
311 * @param string $guid
312 * @return array of RuleTargetActionGroup
314 function getRuleTargetActionGroups($rule)
316 $criterion = $this->getRuleTargetCriteria($rule);
317 $actions = $this->getRuleAction($rule);
318 if (sizeof($criterion) > 0) {
319 $criteria = $criterion[0];
320 $criteria->guid
= $guid;
321 return $criterion[0];
328 * @param string $guid
329 * @return RuleCriteria
331 function getRuleTargetCriteria($rule, $guid)
333 $stmt = sqlStatement(self
::SQL_RULE_TARGET_BY_GUID
, array( $guid ));
334 $criterion = $this->gatherCriteria(
337 $this->targetCriteriaFactory
339 if (sizeof($criterion) > 0) {
340 $criteria = $criterion[0];
341 $criteria->guid
= $guid;
349 * @param string $guid
350 * @return RuleCriteria
352 function getRuleTargetCriteriaByGroupId($rule, $groupId)
354 $stmt = sqlStatement(self
::SQL_RULE_TARGET_BY_ID_GROUP_ID
, array( $rule->id
, $groupId ));
355 $criterion = $this->gatherCriteria(
358 $this->targetCriteriaFactory
360 if (sizeof($criterion) > 0) {
361 $criteria = $criterion[0];
362 return $criterion[0];
369 * Given a sql source for gathering rule criteria (target or filter), this
370 * method relies on its supplied subtype of RuleCriteriaFactory to parse out
371 * instances of RuleCriteria from the sql source (typically rule_filter or
374 * Returns an array of RuleCriteria subtypes, if they were parsable from the
375 * supplied sql source.
377 * @param RuleCriteriaFactory $factory
379 private function gatherCriteria($rule, $stmt, $factory)
381 $criterion = array();
382 for ($iter=0; $row=sqlFetchArray($stmt); $iter++
) {
383 $guid = $row['guid'];
384 $method = $row['method'];
385 $methodDetail = $row['method_detail'];
386 $value = $row['value'];
387 $inclusion = $row['include_flag'] == 1;
388 $optional = $row['required_flag'] == 1;
389 $groupId = $row['group_id'];
392 $criteria = $factory->build(
402 if (is_null($criteria)) {
403 // unrecognized critera
407 if (!is_null($groupId)) {
408 $criteria->groupId
= $groupId;
412 array_push($criterion, $criteria);
419 * Creates a ReminderIntervals object from rows in the rule_reminder table,
420 * and sets it in the supplied Rule.
423 private function fillRuleReminderIntervals($rule)
425 $stmt = sqlStatement(self
::SQL_RULE_REMINDER_INTERVAL
, array( $rule->id
));
426 $reminderInterval = new ReminderIntervals();
428 for ($iter=0; $row=sqlFetchArray($stmt); $iter++
) {
429 $amount = $row['value'];
430 $unit = TimeUnit
::from($row['method_detail']);
431 $methodParts = explode('_', $row['method']);
432 $type = ReminderIntervalType
::from($methodParts[0]);
433 $range = ReminderIntervalRange
::from($methodParts[2]);
434 if (!is_null($type) && !is_null($range) && !is_null($unit)) {
435 $detail = new ReminderIntervalDetail($type, $range, $amount, $unit);
436 $reminderInterval->addDetail($detail);
440 $rule->setReminderIntervals($reminderInterval);
447 function getRuleAction($rule, $guid)
449 $result = sqlQuery(self
::SQL_RULE_ACTION_BY_GUID
, array($guid));
455 $action = new RuleAction();
456 $action->guid
= $guid;
457 $action->id
= $result['id'];
458 $action->category
= $result['category'];
459 $action->item
= $result['item'];
460 $action->reminderLink
= $result['clin_rem_link'];
461 $action->reminderMessage
= $result['reminder_message'];
462 $action->customRulesInput
= $result['custom_flag'] == 1;
463 $action->groupId
= $result['group_id'];
465 $target = $this->getRuleTargetCriteriaByGroupId($rule, $action->groupId
);
467 $action->targetCriteria
= $target;
472 function deleteRuleAction($rule, $guid)
474 sqlStatement("DELETE FROM rule_action WHERE PASSWORD( CONCAT(id, category, item, group_id) ) = '". $guid . "'");
477 function deleteRuleTarget($rule, $guid)
479 sqlStatement("DELETE FROM rule_target WHERE PASSWORD(CONCAT( id, group_id, include_flag, required_flag, method, value, rule_target.interval )) = '". $guid . "'");
482 function deleteRuleFilter($rule, $guid)
484 sqlStatement("DELETE FROM rule_filter WHERE PASSWORD(CONCAT( id, include_flag, required_flag, method, method_detail, value )) = '". $guid . "'");
487 function updateSummary($ruleId, $types, $title, $developer, $funding, $release, $web_ref)
489 $rule = $this->getRule($ruleId);
491 if (is_null($rule)) {
493 $result = sqlQuery("select count(*)+1 AS id from clinical_rules");
494 $ruleId = "rule_" . $result['id'];
496 "INSERT INTO clinical_rules (id, pid, active_alert_flag, passive_alert_flag, cqm_flag, amc_flag, patient_reminder_flag, developer, funding_source, release_version, web_reference ) " .
497 "VALUES (?,?,?,?,?,?,?,?,?,?,?) ",
501 in_array(RuleType
::ActiveAlert
, $types) ?
1 : 0,
502 in_array(RuleType
::PassiveAlert
, $types) ?
1 : 0,
503 in_array(RuleType
::CQM
, $types) ?
1 : 0,
504 in_array(RuleType
::AMC
, $types) ?
1 : 0,
505 in_array(RuleType
::PatientReminder
, $types) ?
1 : 0,
514 $this->doRuleLabel(false, "clinical_rules", $ruleId, $title);
519 sqlStatement(self
::SQL_UPDATE_FLAGS
, array(
520 in_array(RuleType
::ActiveAlert
, $types) ?
1 : 0,
521 in_array(RuleType
::PassiveAlert
, $types) ?
1 : 0,
522 in_array(RuleType
::CQM
, $types) ?
1 : 0,
523 in_array(RuleType
::AMC
, $types) ?
1 : 0,
524 in_array(RuleType
::PatientReminder
, $types) ?
1 : 0,
532 sqlStatement(self
::SQL_UPDATE_TITLE
, array( $title,
541 * @param ReminderIntervals $intervals
543 function updateIntervals($rule, $intervals)
545 // remove old intervals
546 sqlStatement(self
::SQL_REMOVE_INTERVALS
, array( $rule->id
));
548 // insert new intervals
549 foreach ($intervals->getTypes() as $type) {
550 $typeDetails = $intervals->getDetailFor($type);
551 foreach ($typeDetails as $detail) {
552 sqlStatement(self
::SQL_INSERT_INTERVALS
, array(
554 $type->code
. "_reminder_" . $detail->intervalRange
->code
, // method
555 $detail->timeUnit
->code
, // method_detail
556 $detail->amount
// value
565 * @param RuleCriteria $criteria
567 function updateFilterCriteria($rule, $criteria)
569 $dbView = $criteria->getDbView();
570 $method = "filt_" . $dbView->method
;
572 $guid = $criteria->guid
;
573 if (is_null($guid)) {
575 sqlStatement(self
::SQL_INSERT_FILTER
, array(
577 $dbView->inclusion ?
1 : 0,
578 $dbView->optional ?
1 : 0,
579 $dbView->method
= $method,
580 $dbView->methodDetail
= $dbView->methodDetail
,
581 $dbView->value
= $dbView->value
));
584 sqlStatement(self
::SQL_UPDATE_FILTER
, array(
585 $dbView->inclusion ?
1 : 0,
586 $dbView->optional ?
1 : 0,
587 $dbView->method
= $method,
588 $dbView->methodDetail
= $dbView->methodDetail
,
589 $dbView->value
= $dbView->value
,
597 * @param RuleCriteria $criteria
599 function updateTargetCriteria($rule, $criteria)
601 $dbView = $criteria->getDbView();
602 $method = "target_" . $dbView->method
;
604 $guid = $criteria->guid
;
605 $group_id = $criteria->groupId
;
607 if (is_null($guid)) {
610 $result = sqlQuery("SELECT max(group_id) AS group_id FROM rule_target WHERE id = ?", array($rule->id
));
613 $group_id = $result['group_id'] ?
$result['group_id'] +
1 : 1;
617 sqlStatement(self
::SQL_INSERT_TARGET
, array(
619 $dbView->inclusion ?
1 : 0,
620 $dbView->optional ?
1 : 0,
621 $dbView->method
= $method,
622 $dbView->value
= $dbView->value
,
626 sqlStatement(self
::SQL_UPDATE_TARGET
, array(
627 $dbView->inclusion ?
1 : 0,
628 $dbView->optional ?
1 : 0,
629 $dbView->method
= $method,
630 $dbView->value
= $dbView->value
,
636 "SELECT COUNT(*) AS interval_count FROM rule_target WHERE rule_target.id = ? AND rule_target.method = ?",
637 array($rule->id
, 'target_interval')
639 if ($result && $result['interval_count'] > 0) {
643 SET rule_target.value = ?, rule_target.interval = ?, rule_target.include_flag = '1', rule_target.required_flag = '1'
644 WHERE rule_target.method = ?
645 AND rule_target.id = ?";
647 sqlStatement($intervalSql, array(
648 $dbView->intervalType
,
654 sqlStatement("INSERT INTO rule_target ( rule_target.value, rule_target.interval, rule_target.method, rule_target.id, rule_target.include_flag, rule_target.required_flag ) "
655 . "VALUES ( ?, ?, ?, ?, '1', '1' ) ", array(
656 $dbView->intervalType
,
663 function getAllowedFilterCriteriaTypes()
666 foreach (RuleCriteriaType
::values() as $type) {
667 $criteria = RuleCriteriaType
::from($type);
668 array_push($allowed, $criteria);
674 function getAllowedTargetCriteriaTypes()
677 array_push($allowed, RuleCriteriaType
::from(RuleCriteriaType
::lifestyle
));
678 array_push($allowed, RuleCriteriaType
::from(RuleCriteriaType
::custom
));
679 array_push($allowed, RuleCriteriaType
::from(RuleCriteriaType
::custom_bucket
));
686 * @param RuleCriteriaType $criteriaType
687 * @return RuleCriteria
689 function createFilterRuleCriteria($rule, $criteriaType)
691 return $this->filterCriteriaFactory
->buildNewInstance($rule->id
, $criteriaType);
697 * @param RuleCriteriaType $criteriaType
698 * @return RuleCriteria
700 function createTargetRuleCriteria($rule, $criteriaType)
702 return $this->targetCriteriaFactory
->buildNewInstance($rule->id
, $criteriaType);
708 * @param RuleAction $action
710 function updateRuleAction($action)
712 $ruleId = $action->id
;
713 $rule = $this->getRule($ruleId);
714 $groupId = $action->groupId
;
715 $guid = $action->guid
;
717 $category = $action->category
;
718 $categoryLbl = $action->categoryLbl
;
719 $item = $action->item
;
720 $itemLbl = $action->itemLbl
;
721 $link = $action->reminderLink
;
722 $message = $action->reminderMessage
;
723 $customOption = $action->customRulesInput
;
725 // do labels -- if new category or item, insert them
726 $exists = $this->labelExists('rule_action_category', $category, $categoryLbl);
728 $category = 'act_cat_' . $categoryLbl;
731 $this->doRuleLabel($exists, 'rule_action_category', $category, $categoryLbl);
733 $exists = $this->labelExists('rule_action', $item, $itemLbl);
735 $item = 'act_' . $itemLbl;
738 $this->doRuleLabel($exists, 'rule_action', $item, $itemLbl);
740 // persist action itself
742 // its a brand new action
744 "INSERT INTO rule_action (id, group_id, category, item ) VALUES (?,?,?,?)",
745 array( $ruleId, $groupId, $category, $item )
748 // its an action edit
749 if (!is_null($groupId)) {
751 "UPDATE rule_action SET group_id = ?, category = ?, item = ? " .
752 "WHERE PASSWORD( CONCAT(rule_action.id, rule_action.category, rule_action.item, rule_action.group_id ) ) = ? ",
753 array( $groupId, $category, $item, $guid )
758 // handle rule action_item
759 $result = sqlQuery("SELECT * FROM rule_action_item WHERE category = ? AND item = ?", array($category, $item));
761 sqlStatement("UPDATE rule_action_item SET clin_rem_link = ?, reminder_message = ?, custom_flag = ? "
762 . "WHERE category = ? AND item = ?", array(
769 sqlStatement("INSERT INTO rule_action_item (clin_rem_link, reminder_message, custom_flag, category, item) "
770 . "VALUES (?,?,?,?,?)", array(
779 private function doRuleLabel($exists, $listId, $optionId, $title)
783 sqlStatement("UPDATE list_options SET title = ? WHERE list_id = ? AND option_id = ?", array(
789 $result = sqlQuery("select max(seq)+10 AS seq from list_options where list_id = ? AND activity = 1", array($listId));
790 $seq = $result['seq'];
791 sqlStatement("INSERT INTO list_options (list_id,option_id,title,seq) VALUES ( ?, ?, ?, ? )", array(
799 private function labelExists($listId, $optionId, $title)
801 $result = sqlQuery("SELECT COUNT(*) AS CT FROM list_options WHERE list_id = ? AND option_id = ? AND title = ? AND activity = 1", array($listId, $optionId, $title));
802 if ($result && $result['CT'] > 0) {