Problem States
We are implementing problem state protocols to define how each problem should behave throughout its lifecycle. These protocols determine:
- The set of possible states for a problem
- Which interventions are linked to each state
- How transitions between states occur
π§ Main Componentsβ
π Protocol Definition Schemaβ
Defines the structure of a protocol, including its states and transitions.
β‘οΈ Read more
π§ Protocol Definitionβ
Responsible for evaluating a problem goal's state by iterating through transition rules.
β‘οΈ View code
Key behavior:
- Calls the fetcher to get the current values and applies the operator and value defined in the protocol.
- If any rule evaluates truthfuly, it will return an the new state in the response object.
- The order of the transitions is important, since they will be evaluated from top to bottom, and will return the state of the first one that evalutes to true.
- Returns a
EvaluationResults::EvaluationResult
.
βοΈ Problem State Processorβ
Evaluates and updates the problem's state using the protocol definition.
Also handles creation of interventions and other side effects.
β‘οΈ Read more
π Data Fetcherβ
Fetches data from ARC used in state evaluations to decide if it should transition states.
β‘οΈ Read more
π‘οΈ Guardianβ
Determines if a problem should be evaluated.
β‘οΈ View code
Conditions:
:problem_states
feature flag is enabled- The problem goal already has a protocol associated (if it was already evaluated, it will keep being evaluated even if patient in not enabled pod or problem no longer supported)
- Patient is in an enabled care pod (
APP_CONFIG.ProblemStates.EnabledCarePodIds
) - Problem type is supported (
APP_CONFIG.ProblemStates.SupportedProblems
)
π§ͺ Frontend Feature Flagβ
The UI uses its own feature flag: :problem_states_ui
, enabled per user email.
π Collector (Instrumentation)β
Responsible of observability over the protocol executions.
β‘οΈ View code
Tracked data includes:
- Trigger source
- Manual transitions
- Evaluation results
- Intervention creation
- Transition and condition evaluation
- Callback executions
π§ Userflowβ
We use Userflow to help care team members understand the current state and next steps.
- Tooltips are placed using CSS selectors and element IDs
- All relevant element ids should use the
userflow-
prefix - If a flow needs to target a new element, add the corresponding ID
π Added id example
π οΈ Creating or Updating a Protocolβ
1. Define or update the Protocolβ
Create or update a Protocol Definition.
Ensure all required data fetchers exist, or create any missing ones.
2. Enable the Problem Typeβ
Add the problem type to:
APP_CONFIG.ProblemStates.SupportedProblems
And to:
Problems::ProblemProtocol::ALLOWED_PROBLEMS
3. Sync the Protocol to the Databaseβ
Run the sync job to store the protocol in ARCβs database:
Problems::SyncProblemProtocolsJob.perform_now("depression")
After this, new problem goals of this type will be automatically evaluated and linked to the protocol.
4. Link Existing Problems to the Protocolβ
Existing problems wonβt be linked to the new protocol version automatically (we are linking the problem goal with the specific protocol in problem_goal.state.problem_protocol_id
). To link to the new version of the protocol, run the evaluator job:
a. Dry run (to validate the changes):
protocol = Problems::ProblemProtocol.current_protocol('depression')
Problems::ProblemProtocolEvaluatorJob.perform_now(protocol, dry_run: true)
Check results in S3 and validate expectations.
b. Run the job for real:
protocol = Problems::ProblemProtocol.current_protocol('depression')
Problems::ProblemProtocolEvaluatorJob.perform_now(protocol, dry_run: false)
5. Completionβ
At this point:
- The protocol is stored in the database
- Existing problems of that type are linked to the protocol
- All new problems of the same type will be evaluated and linked on creation
6. Relative/Optionalβ
Some problems have interventions created when the problem is started. In those cases, we want to block those so that automatic intervention creation only happens through the protocol. In order to do that, we can add an early return in the initial_interventions
method in the problem's model class, based on the Guardian. For example, for type_one_diabetes
:
def initial_interventions
return [] if Problems::Guardian.allowed_to_continue?(problem)
Flipper.enabled?(:care_gaps_automations) ? [monitor_vitals, coordinate_pair_team_appointment] : [custom]
end
7. Testingβ
You can now create a problem goal of that type for a patient in an enabled care pod (look at APP_CONFIG.ProblemStates.EnabledCarePodIds
to see the enabled care pods), either manually or by a trigger (e.g. completing a PHQ9 with depression indications will create a depression problem). The new problem should have a state assigned to it on creation. Every problem goal with a state will display the state dropdown on the careplan, while problem goals with no state will display the state chip/dropdown (depending on current status).