Read about OpenEMR's Response to the COVID-19 Pandemic at

Flummoxed by cds rule logic

harleytuck wrote on Friday, January 29, 2016:

Hi Folks-

Can somebody please explain the logic behind the filters and criteria in the Rule Detail panel? I’ve been studying the existing rules but they don’t seem to apply very directly.

I’m trying to make a rule that pops an active alert if a pt’s allergies issue still has the original “[checkbox] None” showing. I figure that looking in lists_touch.type to see if ‘allergies’ does NOT appear captures what I’m looking for.

One of the most promising setups I’ve tried is:

Demographics filter criteria:
Criteria Characteristics Requirements
lists_touch.type Required Exclusion Value: allergy | Frequency: = 0
Target/ Action Groups:
No Clinical Targets
Actions: Assessment: (I added item “ALLERGIES NOT ASSESSED” to Clinical Rule Action Item list)

However, that particular arrangement doesn’t trigger on anybody’s record. Other permutations I’ve tried have all failed in various ways.

My understanding is that the Demographics filter determines, “if this person has this condition”
The Clinical Target is what to look for to know that the alert has been satisfied
Actions is what sort of activity to tell the user to do.
What really throws me is the “optional/ inclusion” “yes/ no” thing


Thanks- Harley

bradymiller wrote on Friday, January 29, 2016:

Hi Harley,

Don’t set a filter. Instead just set a target(see attached). This will work but you will have an issue with the interval in future; this a bug and the gui should support adding a target without a interval. After you create the rule, you can remove the interval setting manually from the rule_target sql table. The row that need to be removed is highlighted in screenshot below.

It appears the GUI rule editor has flip flopped the Option/Required setting. This setting correlates with the required_flag in both the rule_target and rule_filter tables(note the comment of the sql row also appears to be flip flopped; 1 should means required and 0 should mean optional). This setting solves the problem when there are multiple filter items or target items. When it is set to 1, then that filter or target is required to pass (for example, if there are two elements in a target and both need to be there, then would set this to 1; a good example if a blood pressure where need the systolic BP and diastolic BP columns to be populated). When it is set to 0, then that filter or target is not required itself to pass, but only one of them is required to pass(a good example of this is when filtering through 20 ICD10 codes; in this case only 1 is needed to pass(ie. considered optional))(another good example if when looking for immunization targets where there are several possible targets(ie. CVX codes), but only 1 needs to be positive). Recommend doing a quick glance through the rule_filters and rule_target sql tables and it will make things much more clear.

It appears three easy to fix bugs are apparent from looking at this:

  1. The Rules GUI for the targets should give option of not setting a Interval.
  2. The Rules GUI for the filters and targets should flip/flop the Required/Optional settings.
  3. The sql comments for the required_flag sql row in the rule_target and rule_filters sql tables should be flip flopped(I am guessing this is what originally threw off Aaron when he wrote the GUI rule editor).

Any developers want to take this on? It would be a moderately easy(and small) project.


aethelwulffe wrote on Saturday, January 30, 2016:

I’ll gladly hit it. It seems like a pretty trivial bit of code that would reduce a lot of confusion…and tis the season for this stuff.
I need to actually do some stuff with the code base on this for extra familiarization. The pieces (cdr, cqm, and all the different and sometimes oddly or legacy named stuff like PQRI) are pretty scattered and it is hard to follow when just diving in.
I have a long way to go before I am able to use OpenEMR to do 9 measure groups of PQRS (And later MIPS) for any of the billion individual measures…and in a comprehensive way.

harleytuck wrote on Monday, February 01, 2016:

Hi Brady-

Thnks for the detailed response but it didn’t work for me.
I went in and deleted target_interval for my rule. With no demogs filter criteria, and the target set per your pic, I get an alert every time I pull up any pt, no matter if they have any allergy entry (including OpenEMR’s “None” or NewCrop’s variations on “NKDA”) in their issues or the original “[checkbox] None”.

I set the allergy frequency to " = 0" and I get no popups on any pt.

Taking a different approach, I see that the pt summary page right column’s “Allergies” widget says “Nothing Recorded” until an entry is made. Where does that widget get its information? I could just have my rule look there.

  • HT

tmccormi wrote on Tuesday, February 02, 2016:

Nothing Recorded means that there are no records for allergies OR that the NONE check box has not been checked. Clicking None writes a record to the list_touch table

aethelwulffe wrote on Tuesday, February 09, 2016:

Havn’t abandoned this bit, just doing a LOT of stuff with the rules engine at the moment.

aethelwulffe wrote on Wednesday, February 10, 2016:

Big huge disasterous issues with Rules:
First…just a funny one…It is supposed to be NQF, not NFQ.

Second, wanna make the rules run twice as fast as it does right now, and enjoy an environment where (currently) you can’t easily destroy the whole system by renaming a calendar category from “Office Visit” to something else?
Well, instead of the elaborate joins to look up calendar categories and search up strings that might be changed, you can simply use the facility location code (POS=“11” for office visit) from the billing line item.
Viola: Utter dependability, and a huge speed improvement!

-I have found about a dozen different places to massively improve the queries for rules.

tmccormi wrote on Thursday, February 11, 2016:

Place of Service code … a USA standard since 1972 I think :slight_smile:

aethelwulffe wrote on Friday, February 12, 2016:

Should be good for backwards compatibility then.

I’ve been trying to figure out the rule processing behavior for a long time, and it has not made sense to me. Finally did a lot of debugging and looking at the code in library\clinical_rules.php and found the source of the confusion.

The code has a bug (and probably has for a very long time)

I’ve only examined the case when a target of type “custom” is being used.

I’m looking at version 5.0.2 patch 4

In test_rules_clinic(), the logic tests a rule target against 3 target dates, in this order:
– today + the warning interval
– today
– today - the past due interval

The logic examines the interval: target date less target interval -> target date, checking the rule_patient_data table to see if a row in the table exists for the patient that matches this interval, category/item pair, and frequency (as defined by the rule target)

So, for example, if the target specifies
– reminder, prostate cancer screening, completed=yes, frequency > 0, interval 12 months
then a db query is done looking for at least one row with the interval described above and the specified category/item combination and completed status

If the target criteria is found in the db, the rule status is set to “not due”, and processing stops.
If the target criteria is not satisfied, the status is set to “soon_due”, “due”, or “past_due” depending whether the first, second, or third target date is being examined, and then the next target date is tried (if there is another target date to try)

To me this seems wrong!

To me, the logic should be:
– if, for the first target date, the target criteria is satisfied, then status = “not due”
– else, if, for the second target date, the target criteria is satisfied, then status = “soon_due”
– else, if, for the third target date, the target criteria is satisfied, then status = “due”
– else, status = “past_due”

Example: suppose the warning interval is 1 month, the past due interval is 3 months, and the target interval is 12 months. This should mean:
– the target action should be performed every 12 months
– if today, the last time the action was performed was within the past 11 months, the rule is “not due”
– if it was last done between 11 and 12 months ago, the rule is “due soon”
– if it was last done between 12 and 15 months ago, the rule is “due”
– if it was last done more than 15 months ago, or never done, the rule is “past due”

I modified clinical_rules.php to match my desired logic, replacing

$action_plus[‘due_status’] = “not_due”;


if ($dateCounter == 1)
$action_plus[‘due_status’] = “not_due”;
elseif ($dateCounter == 2)
$action_plus[‘due_status’] = “soon_due”;
else // $dateCounter == 3
$action_plus[‘due_status’] = “due”;

This change is made below this code, which exists in two places:
// 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) {

with this change, the reminder status is calculated according to my expectation described above.

Without this change, by hard coding the status to “not due” whenever rule target criteria is satisfied, the rule always shows as either “not due” (if the target is satisfied any time between target_date3 less target interval -> target_date1) or “past due” (if satisfied prior to target_date3). It never gets the “due” or “soon_due” status.
Not sure how to get this reviewed or part of an official release (I’m new to this community)

Hope this helps someone

1 Like

hi @hanksterr7 ,

Thanks for the detailed explanation. Recommend submitting a PR (Pull Request) on github with the code changes:
Pull requests · openemr/openemr · GitHub

If need guidance on how to do this, I’m happy to help.