My clinical rule editor in 4.1 is buggy

2011-10-13
2013-04-06
1 2 > >> (Page 1 of 2)
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-10-13

    I've tried to put in some clinical rules similar to A1C for diabetes by modeling my entry after what is already in the program.  For some reason, I have trouble getting the rules to enter properly into the relevant tables.  I notied that my table rule_target in stock form had 80 entries, many of which were exact matches in all columns.  I figured it shouldn't be that way.

    Furthermore, I had a hard time displaying the interval value with command line statements in MySQL and then discovered that one of the columns in the table is named interval, and that this is a reserved word in MySQL.  Could this be a problem?

    Ronald Leemhuis

     
  • Brady Miller

    Brady Miller - 2011-10-13

    Hi,

    Regarding use of interval(mysql reserved word). Good practice to surround elements in your sql statements with backticks (ie. `interval`), which then avoids any issues. Note the CDR engine does this in library/clinical_rules.php. Alternatively you can also use the full name (ie. rule_targets.interval) to avoid issues, which is done in the CDR admin GUI (code at main/super/rules) .

    There should be 50 rule_target entries by default (note many may look similar, but they are all different). If have over that many with replicated rows, then may be useful to paste them here to try to isolate the issue.

    -brady
    www.open-emr.org

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-10-13

    Thanks, Brady.  Using the backtick in my query allowed me to count the distinct values in the rule_target table.  As suspected, I had 80 values only 50 of which were distinctly different in all columns.

    I repopulated the table with the 50 distinct values.  Now the rule editor does not appear buggy.  I'll let you know if it does not work properly in actual use.

    Ronald Leemhuis MD

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-10-18

    Turns out, I'm still having some (not as much, though!) trouble with the rule editor.  I figured for sure I messed up the tables with all my playing around, but it turns out the latest development version online has the same problem saving rules properly.  One thing I learned is that if one makes an sql dump file on a clean installation of openemr, one can save that file and cut and past sections into a new sql file which can be "sourced" to recreate whatever tables you want in virgin form.

    To make new rules I figure I can go to the underlying tables and add the right information in strategic places, but I would rather make my efforts contribute to the community effort rather than just fix my own installation.  I am willing to incorporate official Medicare rules for diabetes care in addition to the rudimentary ones already incorporated, but I want to be in tune with the community so that I'll be contributing to a shared effort. 

    Any ideas on how to do that?

    Ronald Leemhuis MD

     
  • Tony McCormick

    Tony McCormick - 2011-10-18

    Actually providing importable SQL rule sets would work great!   Just need to make sure you don't assume primary key, so that can be imported no matter what has already been added.

    Tony
    www.mi-squared.com
    twitter: @tonymi2

     
  • Brady Miller

    Brady Miller - 2011-10-18

    Hi,

    The rules can be built manually without the CDR editor, which is probably the best way to build rules that we include in the codebase (note the CDR editor uses numbers to identify added rules, while the rules included in the codebase use descriptive labels; this was done purposefully so we could continue to include new or improved rules without worrying about conflicts with rules that users create). Check out the format used in mysql to build rules. For example, following the 'Pneumonia Vaccination Status for Older Adults' rule has following elements:

    table `clinical_rules`:
    INSERT INTO `clinical_rules` ( `id`, `pid`, `active_alert_flag`, `passive_alert_flag`, `cqm_flag`, `cqm_nqf_code`, `cqm_pqri_code`, `amc_flag`, `amc_code`, `patient_reminder_flag` ) VALUES ('rule_pneumovacc_ge_65', 0, 0, 1, 0, '', '', 0, '', 0);
    -Note the rule_pneumovacc_ge_65 element here, which is used to identify this rule elsewhere

    table list_options:
    INSERT INTO `list_options` ( `list_id`, `option_id`, `title`, `seq`, `is_default` ) VALUES ('clinical_rules', 'rule_pneumovacc_ge_65', 'Pneumonia Vaccination Status for Older Adults', 570, 0);

    table rule_filter:
    INSERT INTO `rule_filter` ( `id`, `include_flag`, `required_flag`, `method`, `method_detail`, `value` ) VALUES ('rule_pneumovacc_ge_65', 1, 1, 'filt_age_min', 'year', '65');

    table rule_target:
    INSERT INTO `rule_target` ( `id`, `group_id`, `include_flag`, `required_flag`, `method`, `value`, `interval` ) VALUES ('rule_pneumovacc_ge_65', 1, 1, 0, 'target_database', '::immunizations::cvx_code::eq::33::ge::1', 0);
    INSERT INTO `rule_target` ( `id`, `group_id`, `include_flag`, `required_flag`, `method`, `value`, `interval` ) VALUES ('rule_pneumovacc_ge_65', 1, 1, 0, 'target_database', '::immunizations::cvx_code::eq::100::ge::1', 0);
    INSERT INTO `rule_target` ( `id`, `group_id`, `include_flag`, `required_flag`, `method`, `value`, `interval` ) VALUES ('rule_pneumovacc_ge_65', 1, 1, 0, 'target_database', '::immunizations::cvx_code::eq::109::ge::1', 0);
    INSERT INTO `rule_target` ( `id`, `group_id`, `include_flag`, `required_flag`, `method`, `value`, `interval` ) VALUES ('rule_pneumovacc_ge_65', 1, 1, 0, 'target_database', '::immunizations::cvx_code::eq::133::ge::1', 0);

    table rule_reminder:
    INSERT INTO `rule_reminder` ( `id`, `method`, `method_detail`, `value` ) VALUES ('rule_pneumovacc_ge_65', 'clinical_reminder_pre', 'week', '2');
    INSERT INTO `rule_reminder` ( `id`, `method`, `method_detail`, `value` ) VALUES ('rule_pneumovacc_ge_65', 'clinical_reminder_post', 'month', '1');
    INSERT INTO `rule_reminder` ( `id`, `method`, `method_detail`, `value` ) VALUES ('rule_pneumovacc_ge_65', 'patient_reminder_pre', 'week', '2');
    INSERT INTO `rule_reminder` ( `id`, `method`, `method_detail`, `value` ) VALUES ('rule_pneumovacc_ge_65', 'patient_reminder_post', 'month', '1');

    table rule_action:
    INSERT INTO `rule_action` ( `id`, `group_id`, `category`, `item` ) VALUES ('rule_pneumovacc_ge_65', 1, 'act_cat_treat', 'act_pneumovacc');

    table rule_action_item:
    INSERT INTO `rule_action_item` ( `category`, `item`, `clin_rem_link`, `reminder_message`, `custom_flag` ) VALUES ('act_cat_treat', 'act_pneumovacc', '', '', 0);

    Once you follow a couple already existent rules, you will begin to see how to add additional rules. To add a rule, probably most important thing is settling on a id name, so rec. providing your suggested rule, rule_id name along with what you plan to filter for and target for.

    Also, to get more insight into the CDR engine, check out the wiki page we used while developing it here:
    http://open-emr.org/wiki/index.php/Clinical_Decision_Rules#Notes_and_Issues

    hope this helps get you started,
    -brady
    www.open-emr.org

     
  • Brady Miller

    Brady Miller - 2011-10-18

    Hi,

    Also, one more element that I forgot in list_options table pertaining to above rule:
    INSERT INTO `list_options` ( `list_id`, `option_id`, `title`, `seq`, `is_default` ) VALUES ('rule_action' ,'act_pneumovacc', 'Pneumococcal Vaccine', 60, 0);

    -brady
    www.open-emr.org

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-10-18

    Thanks, Brady, for summarizing all the necessary information for a clinical rule.  I was planning to dig into all the tables to see what was necessary, but there it is.  I think the best approach to writing rules is this:

    1.  See what Medicare officially requires for diabetes, CV disease etc - one condition at a time.
    2.  Categorize each rule into types and find an existing rule of similar type.
    3.  Come up with labels and names consistent with previous naming conventions and methods
    4.  Modify the SQL code that enters the data for the existing rule using a text editor so it should work with the new rule.  Test each rule one at a time and revert to the previous state if things get messed up.
    5.  Once we learn how to do this reliably we can start entering information more intensively.

    The key will be to have templates made up.  Maybe I can find something useful to cut and paste from a mysqldump file.

    Ronald Leemhuis MD

     
  • Brady Miller

    Brady Miller - 2011-10-18

    Hi,

    Sounds good. There's a clear summary of what the current rules do here (see '9) Currently supported Standard Rule/Plan sets on installation') :
    http://open-emr.org/wiki/index.php/CDR_User_Manual

    Will probably be a good idea to document above process on a new wiki page and place it here on the wiki:
    http://open-emr.org/wiki/index.php/CDR_Engine

    A good place to start is the discrepancies between the current rules and the Medicare suggestions. If the rule has only a minor difference, then may be able to fix the original rule. If more than minor then could make a new rule with a _mc appended to end of name id etc.and redo the rule. And, of course, if the rule does not even exist yet, then simply create the rule. Also note, something I didn't mention, is there is a cool plan mechanism, which will group rules into plans utilizing the following tables:
    clinical_plans
    clinical_plans_rules

    Knowing how to manually create the rules also allows some added functionality, like looking for procedure filter/targets and placing in plans (these functions are not yet in the CDR editor).

    -brady
    www.open-emr.org

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-11-13

    It appears that the clinical rule editor as currently configured is very strict in matching diagnoses or medications for inclusion.  For example, INR testing reminders are created if one of the medications listed is exactly "coumadin" or "warfarin."   The engine scans for a diagnosis of "diabetes" or any of a long list of very specific ICD9 codes such as "ICD9:250.00"  If the diagnosis is listed as "Type 2 diabetes" it is not recognized.

    I'm thinking that it may be possible without much trouble to make the code look for similarities or use wild cards in the inclusion rules like "ICD9:250*"  or "*diabetes" and to make sure it is not tripped up by upper/lower case differences.  Any thoughts on this?

    Ron Leemhuis

     
  • Brady Miller

    Brady Miller - 2011-11-13

    Hi,

    Is it getting tripped up by upper/lower case differences? (it should not be)

    Also, the ICD:250 codes are in a sql query as so 'LIKE %ICD9:250%', so should just need to add ICD9:250 and you will get the desired ICD9:250* effect.

    if curious, here's the function doing the diagnosis codes in library/clinical_rules.php:

    // Function to check for lists item of a patient
    //  Fully customizable and includes diagnoses, medications,
    //    allergies, and surgeries.
    // Parameters:
    //   $patient_id - pid of selected patient.
    //   $type  - type (medical_problem, allergy, medication, etc)
    //   $value  - value searching for
    //   $dateTarget - target date. blank is current date.
    // Return: boolean if check passed, otherwise false
    function exist_lists_item($patient_id,$type,$value,$dateTarget) {
      // Set date to current if not set
      $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
      // Attempt to explode the value into a code type and code (if applicable)
      $value_array = explode("::",$value);
      if (count($value_array) == 2) {
        // Collect the code type and code
        $code_type = $value_array[0];
        $code = $value_array[1];
        if ($code_type=='CUSTOM') {
          // Deal with custom code type first (title column in lists table)
          $response = sqlQuery("SELECT * FROM `lists` " .
            "WHERE `type`=? " .
            "AND `pid`=? " .
            "AND `title`=? " .
            "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
            "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
          if (!empty($response)) return true;
        }
        else {
          // Deal with the set code types (diagnosis column in lists table)
          $response = sqlQuery("SELECT * FROM `lists` " .
            "WHERE `type`=? " .
            "AND `pid`=? " .
            "AND `diagnosis` LIKE ? " .
            "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
            "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
          if (!empty($response)) return true;
        }
      }
      else { // count($value_array) == 1
        // Search the title column in lists table
        //   Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
        $response = sqlQuery("SELECT * FROM `lists` " .
          "WHERE `type`=? " .
          "AND `pid`=? " .
          "AND `title`=? ".
          "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
          "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
        if (!empty($response)) return true;
      }
      return false;
    }
    

    (Note the 1st and last sql-query are for the diagnosis(or meds or allergies) titles and that the 2nd query if for diagnosis codes.

    -brady
    www.open-emr.org

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-11-13

    Thanks, Brady.

    I confirmed what you said about the ICD9 codes (i.e. "diagnosis") matching using the LIKE command.  If I change a diagnosis code in the lists table to something like ICD9:250.00adfljfljlfjjflfhfk, it matches and works properly because the 250.00 is in the clinical rules.

    However, this behaves differently with the "title," as the code cited above requires an equality rather than a LIKE command.  I think I'll try making it a LIKE statement because then any narrative diagnosis that has the consecutive letters diabetes would qualify.  That would be a big plus.

    Also, I think the stock configuration of clinical rules should have fewer diagnosis codes.  There does not seem to be a reason to compare diagnoses to 250.00, 250.01, 250.40 etc etc etc when you could just use the common digits of the highest branch of the ICD9 tree.

    Shouldn't the official code use a LIKE command for the titles as well?

    Ron Leemhuis

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-11-13

    I tried changing one line to substitute a LIKE for an =.  However, it does not seem to accomplish the desired effect.

    Old line: "AND `title`=? " .

    New line:  "AND `title` LIKE ? " .

    It matches diabetes but not diabetes2 - I can't figure out why not.

    Ultimately I'll want it to match anywhere in the field, not just at the beginning.

    Hmm…

    Ron Leemhuis

     
  • Kevin Yeh

    Kevin Yeh - 2011-11-13

    http://dev.mysql.com/doc/refman/5.1/en/pattern-matching.html
    Dr. Leemhuis,
    Here's some info on mysql syntax with the LIKE operator, but basically to match either diabetes or diabetes2
    you need to do

    AND LIKE "diabetes%"
    

    which will only match if the diabetes is at the beginning (a faster search)
    or

     AND LIKE "%diabetes%"
    

    which will match if diabetes appears anywhere in the string.

    the % is a wildcard indicating zero or more matches in your string.

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-11-13

    Thanks, yehster.  I've played around with that sort of syntax but haven't yet gotten it to work for the "Custom" diagnosis codes, which are just narrative titles.  The ICD9 diagnoses seem to work well with the "LIKE" command.

    One thing I noticed was that the Custom codes are listed as Custom::diabetes, for example in the rule_filter table.  The code cited above searches for "CUSTOM" in capital letters.  The code started to behave better, but still not right, when I changed "CUSTOM" to "Custom" in the code.  I've been trying to figure out whether any of the variables may be carrying along invisible trailing characters such as spaces or CRs or something like that to make the "LIKE" command not work even though the = command does.

    The puzzling thing is that if I have a narrative diagnosis of diabetes (with no ICD9 codes), the = command works but the LIKE command does not - even when I keep the diagnosis unchanged.  Hmm..

    Ron Leemhuis

     
  • Brady Miller

    Brady Miller - 2011-11-13

    Ron and everybody,

    Good point on the 250. vs 250.xx issue. Note there is a check for ICD9:250 in all the diabetes rules, whcih basically means all the other ICD:250.xx checks are simply draining performance from the engine. So, I think we can remove all the ICD9:250x codes (this should be a straightforward myswp DROP that could incorporate in the upgrade (and even the patch), which will stop lots of needless checks. Does this sound reasonable as making it a TODO (note we are rather overwhelmed right now with TODOs :)

    Also, mysql is generally case-insensitive (unless use BINARY collation, which is only done in the translation tables in openemr). So, diabetes and DiAbEtes should both work as titles. Also, follow the code above, to see what happens if you change CUSTOM to Custom; the if ($code_type=='CUSTOM') { will now be false, and you will now be searching for the Custom:diabetes in the coding fields.

    For LIKE to work as you want, rec subsituting it (in both the first and third query above) and changing to %.$code.% in first query and to %.$value.% in the third query. We could attach this behavior to a setting in Administration->CDR, since other may find it useful. Can also make it dependent on the cetegory of the list item (ie. medical_problem, medication, allergy).

    -brady
    www.open-emr.org

     
  • Brady Miller

    Brady Miller - 2011-11-13

    clarifcation above:
    changing to "%".$code."%" in first query and to "%".$value."%" in the third query

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-11-13

    Thanks again, Brady.  I have been trying things like this, but I hesitated to put regular parentheses within regular parentheses so tried single parens only.  Will try this.  The other thing I tried was to reverse the ? and the `title` because I am not sure that A LIKE B is the same thing as B LIKE A, and we want the "%" things added to the `title` not the ?.

    Will give it a try.

    Ron Leemhuis

     
  • Brady Miller

    Brady Miller - 2011-11-13

    Specifically,

    Change(query 1 and 3):

    "AND `title`=? ".
    

    to

    "AND `title` LIKE ? ".
    

    Change (query 1):

    " array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
    

    to

    " array($type,$patient_id,"%".$code."%",$dateTarget,$dateTarget,$dateTarget) );
    

    Change (query 3):

    array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
    

    to

    array($type,$patient_id,"%".$value."%",$dateTarget,$dateTarget,$dateTarget) );
    

    -brady
    www.open-emr.org

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-11-13

    My head is spinning at the moment.  I tried everything but  changing the arrays, which I bet will help a lot.  I tried regular quotes, single quotes, left handed quotes and generally got syntax errors.  I tried reversing the ? and the `title` business.  Reversing the ? and the `title` may be what is necessary because I was able to confirm that A like B is not the same as B like A and what we want is going to be the reverse of what is written.

    Also, it appears that the CUSTOM thing IS case sensitive because it is in a PHP domain rather than MySQL domain - just a thought.  In any case, changing it to Custom made a difference.

    Another thing I tried was to change rules_filter value for diabetes from Custom::diabetes to diabetes alone (and back).

    My hunch, Brady, is that you're close to fixing this.

    Ron Leemhuis

     
  • Brady Miller

    Brady Miller - 2011-11-13

    Hi Ron,

    I feel your pain(I pretty much did everything you described above when developing the code for query 2 in above example where there is a working LIKE binding(the ? thing)). So, the working example of this is above in query 2. Sometime, it's good to restart anew. My suggestion is to revert to original CUSTOM:diabetes and go from there. Then make my mods above, which should then work for any title with a diabetes in it. (Also, note that by changing it to diabetes alone will then put you to query 3 in the above code (query 1 and query 3 are basically the same in order to support CUSTOM:<title> or just <title> entries.

    -brady
    www.open-emr.org

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-11-14

    I figured out a very straightforward way to do the wildcard search for query #1 - the narrative diagnosis like diabetes in rules will pick up anything with diabetes anywhere in the name (even, for example "family history of diabetes").

    Here it is, Brady:

    Change the line with `title` in it to this:

    "AND locate(?,`title`)  " .

    The locate function returns zero if the second argument is not a substring and the numerical position (a positive number) if it is.  Any positive number satisfies the logical test.  Voila.

    I think the same thing will work for search #3.

    Please try it. 

    This is powerful and easy.

    Ron Leemhuis

     
  • Ronald Leemhuis

    Ronald Leemhuis - 2011-11-14

    I removed more than 200 unnecessary records from the rules_filter table.  With the fix to the clinical_rules.php file to pick up diagnoses more efficiently, only top level ICD9 codes and/or single word narrative descriptions are necessary.  Turns out the CUSTOM:: data has the CUSTOM label in caps for the remaining data.  Some of the deleted ones used lower case and may not have been handled properly.  Could it be the rules editor that put them in lower case?

    Also, I put a rule in for the diagnosis "hypertension" instead of "HTN."

    Once we start using a lot of rules, it will be important to avoid bloat.

    Ron Leemhuis

     
  • Brady Miller

    Brady Miller - 2011-11-14

    Always good to see new ways of doing things, but doesn't the LIKE above work? The problem with the locate() is that it won't be compatible with other database such as postgresql. If the locate() were the only way to do this, then would be ok. But since LIKE can also do this, might as well keep it compatible just in case we decide to try to support other database software in the future.

    Note that this change will effect all lists titles (medications, allergies, surgeries, medical_problems, etc.), so perhaps it may make sense to control this with global(s). Some may want strict checks on certain categories and non-strict checks on other categories. For example, some may not want something like 'borderline diabetes' to get swept into the 'diabetes' rules (especially if a clinic wants a separate ruleset for the borderline diabetics). Will be good to hear thoughts of users on this issue.

    Check out the default rules_filter entries in the database.sql file. This is the place to optimize things; for example, I don't really know why I added all those ICD9:250xx redundant entries since the ICD9:250 is there… Should put some thought into the others to ensure we don't make things not stringent enough (for example are all 648's diabetes); but would be nice, if possible to reduce these all to just the code without the decimal, which would also greatly improve the performance of the CDR engine. Also adding more titles (such as hypertension) makes sense. Please feel free to modify this toward what you think would be best. Then when have it ready, can also figure out a way to fix these in the patch/upgrade mechanism.

    Note they are all CUSTOM by default in caps. If the editor is not using caps, then this would be a bug.

    thanks,
    -brady
    www.open-emr.org

     
  • Brady Miller

    Brady Miller - 2011-11-14

    Also,

    Another option for the %diabetes% issue that would require no code mods in the engine would be to simply store it in the term in the database, so CUSTOM::%diabetes% instead of CUSTOM:diabetes should work. This could be then set in the CDR editor as an option when adding or modifying elements. Then get very granular control the option.

    -brady
    www.open-emr.org

     
1 2 > >> (Page 1 of 2)

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:





No, thanks