Hey all, here’s my summary of the platform meeting today.
Firstly, we can break down this feature into two separate sub-features:
- Allowing multiple numerics for a concept, each defined by some set of factors.
- Flagging critical observational data against the numerics that apply to that patient.
I’ll cover a multi-step approach we can take to implement both, and we should be able to complete sub-feature 1 without breaking any existing functionality before moving on to 2.
First off, I refined my original designs based on our discussion on the call. I’ve also put in some example data to help visualize what this could look like:
To summarize the example setup in the above graph (using Systolic blood pressure), lets say we have the following patients:
- Patient A is Female, aged 20
- Patient B is Female, aged 50
- Patient C is Male, aged 20
We have the following factor groups (that each map to their own numeric):
- Group 0: Female, aged 10-30 → (critical range 175-75, normal range 135-95)
- Group 1: Female (no age restrictions) → (critical range 185-70, normal range 150-75)
So, implementing the above data structure (and its relevant APIs) would give us feature 1, but we still need feature 2, aka a way to actually decide which of the numerics we want to have apply when making a patient observation.
Using the above data again, that should look like:
- Patient A will map to factor group 0 and 1
- Patient B will map to factor group 1
- Patient C will map to no factor group and so will use the numeric defined in the old concept_numeric table
Currently in O3, the flagging of critical observational data is done using the concept_numeric table (thanks Grace for the demo!):
If an entered observation is in the critical range for a concept (as defined by the hi_critical and low_critical columns in concept_numeric), it’ll hilight the observation in red (In the above you can see that BP, Heart Rate and R. Rate are all at critical levels). Obviously this will need to change to account for the new concept_numeric_factored table but for the time being we can introduce feature 1 without breaking this existing functionality.
Now, onto actually implementing feature 2.
First, we need a way to define how to find a factor (such as age) in a patient’s data. I was hoping that all this data would be stored dynamically in some generic “PatientAttribute” table but it seems that’s not the case. (That table would have one row for each attribute of the patient (birthdate, sex, ethnicity…). It’d make looking up attributes and adding new ones easy because we’d just key into the same generic table, but alas, one can dream).
In reality, factors can come from multiple different sources. Ex: As Daniel and Ian pointed out, age and sex are stored in the “person” table (under the gender and birthdate fields), and ethnicity is somewhere else. For fields like these that are represented as specific columns in some table I’d recommend the following solution:
Notice the “validator” column I have in the numeric_factor table above. Here we can define the Java class name of a validator for that type of field (Ex: MinAgeFactorValidator). This validator will be coded to know the db table and column that we need to look up to find the patient’s value for that attribute/factor (so for sex, it’d know to go to “patient.gender”). It’d also have a method that returns true if a patient matches a given factor (Ex: True for a person aged 30 and a factor: min_age=10). So when checking if a patient matches a factor, we simply de-reference the validator by class name, then run its validate method.
OMRS actually already does something similar to this with the PatientIdentifierType table. See openmrs-core/api/src/main/java/org/openmrs/PatientIdentifierType.java at 11cbda0a208b5b287c76b265865005108b27d780 · openmrs/openmrs-core · GitHub and openmrs-core/api/src/main/java/org/openmrs/api/impl/PatientServiceImpl.java at 11cbda0a208b5b287c76b265865005108b27d780 · openmrs/openmrs-core · GitHub.
Obviously it’s not a perfect solution because it requires us to code up a validator for each new type of factor that we want to add but I think for now at least it’s the path of least resistance. Later we can look into abstracting this further to the point of not needing these custom validators.
Also important, I think there’s more to be discussed about what happens when a patient matches multiple numerics (in the above example, Patient A meets the conditions of factor group 0 AND 1). Some solutions for validation in that case that we discussed:
- Choose whatever factor group is the most restrictive (ie has the most number of factors) and break ties with some “priority” column
- Take the lowest and highest values of all the numerics the patient matches and use those (ie if one numeric has a critical range 20-50 and another has a range 10-40, you’d use a critical range of 10-50)
- Validate against all the numerics the user matches with and return the most critical condition (ie if a patient has a value 40, and one numeric has a normal range 30-45 and another has a critical range of 38-50, we’d return that the patient is critical)
I’m personally in favour of the 3rd approach since I feel like it’s the most clear. In the UI we could also potentially display all the numerics the patient matches and show which ones they have critical levels for.
As a side note: I’d also take the concept_numeric_factored table a step further and split the ranges into their own table, concept_numeric_range. That way we can avoid hardcoding the individual ranges as columns like we do in the concept_numeric table (hi_critical, low_critical…). This just makes it more flexible if we ever want to add another range in since all we’d need is more rows in the concept_numeric_range, and not new columns in concept_numeric_factored.
This is just a side thought though, not essential to the implementation.
Some other notes:
- As Grace mentioned, currently ranges are always inclusive (meaning a range 30-40 includes values 30 and 40).
- We can retain the existing functionality in the concept_numeric table for defining absolute min and max values for entering oservation data (Ex: throw an error if you try to enter a negative heartrate).
- We’ll likely need to be able to define age factors on both a weekly and yearly scale so that we can track numerics for newborns so the solution might need to change slightly to account for this. We could introduce “min/max_age_weeks” factors with their own validators that will count the age in weeks instead of years. It’ll need some thought.
- For future iterations it would be good to add support for "OR"ing the factors of a factor group together to allow more complex groups.
Thanks all! And apologies, it seems I’m incapable of being brief on this topic haha