Skip to main content

🧠 Problem State Processor

The ProblemStateProcessor class manages and processes state transitions for problem goals. It determines the next state, updates it, creates interventions, and triggers any associated side effects.

All it's decisions are based in the protocol definition. All state evaluations are delegated to the ProtocolDefinition.


⚙️ Overview

Purpose

The processor is called whenever an event might trigger a transition in a problem goal state. It:

  • Handles both manual and automatic state transitions (evaluates whether we need to transition to a new state or not)
  • Updates the problem goal state (which also creates a new problem state transition)
  • Creates interventions based on the new state
  • Executes side-effect callbacks for manual transitions
  • Logs the process and errors

Key Features

  • Manual and Automatic Transitions: Supports both user-initiated (manual) and system-initiated (automatic) state transitions.
  • Intervention Creation: Automatically creates interventions based on the new state.
  • Dynamic Field Resolution: Resolves dynamic fields for due dates and custom fields.
  • Error Handling: Reports errors to the logging system and gracefully handles failures.
  • Dry Run Support: Allows testing transitions without making actual changes.

🔁 process Method

Description

This is the entry point. Called to evaluate if we need to transition the problem state, and if so, execute the transition effects (e.g. create interventions)

Parameters

  • problem_goal (required): the problem goal to process.
  • new_state (optional): manually selected state. Used from the front end state dropdown.
  • transition_reason (optional): reason for manual transition. Filled by the user that is changing the state manually.
  • dry_run (optional): if true, doesn't persist changes. This is useful to make sure the outcome is what we expect before affecting actual data. Used in backfill scripts.
  • fetcher (optional): data fetcher instance. Used by the transition's conditions to decide if we should move to a certain state.
  • problem_protocol (optional): protocol to evaluate against.
  • skip_interventions (optional): skips creating interventions. Useful for backfill scripts, in which we might want to set the state, but not create interventions associated with the state.
  • trigger_source (optional): the source of the trigger for the transition. Used for logging purposes.
  • force_intervention_creation (optional): creates interventions even if the state hasn't changed.

Returns

  • true if processing succeeds.
  • false if an error occurs.

⚡ When Is It Triggered?

The process method runs in several scenarios:

  • ✅ When a PHQ-9 form is completed by the patient. Calls the process asyncronically for every active problem goal of that patient with an enabled problem type
  • ✅ When an Intake form is completed by the patient. Calls the process asyncronically for every active problem goal of that patient with an enabled problem type
  • ✅ On intervention completion. Calls the process synchronically for all problem goals linked to that intervention
  • ✅ When a problem is created (initial state, no interventions)
  • ✅ When a problem is started (creates interventions)
  • ✅ When running ProblemProtocolEvaluatorJob
  • ✅ When a reading is created. Calls the process synchronically for all problem goals with a problem type configured for the reading type (A1C Reading types and BP Reading types)

🧩 Intervention Creation

Interventions are automatically created when:

  • The state changes and the problem is in_progress
  • A problem is started, even if the state hasn't changed yet
  • There are interventions to create. This logic is based on:
    • the skip_interventions processor parameter
    • the force_intervention_creation processor parameters
    • the new state being diferent than the old state
    • the state always_create_interventions_for configuration
    • the state's interventions always_create_for configuration

Deduplication is handled using:

  • deduplication_key
  • deduplication_params
  • deduplication_resolver

🔁 Callbacks

Manual transitions can trigger callbacks.
Example: In the housing protocol, changing the state manually updates the patient's housing_status.


🎯 Operations

The processor currently supports different operations for each intervention inside a state. The supported operations are:

  • Create: This is the default operation. When the processor processes a problem goal and there are interventions to create, it attempts to create every intervention with this operator if the deduplication checks don't find an existing match. If there is a match, no action is taken.
  • Update: When the processor processes a problem goal and there are interventions to create, it checks with the deduplication logic whether a matching intervention exists. If one is found, it updates the intervention with the fields defined in the intervention. If no match is found, no action is taken.
  • Upsert: This is a combination of the previous two operations. The processor first checks for matches based on deduplication logic. If an intervention is found, it updates it. If no match is found, it creates a new one.

⏳ Dynamic Values

Some values can't be hardcoded in the protocol definition and must be resolved at runtime.

  • Dates: Expressed in unit.unit_type. Examples: 1.week, 3.months
  • Dynamic values: Use the dynamic# prefix and are resolved by the DynamicFieldResolver. In that file you can find the currently supported dynamic resolvers
  • Deduplication resolvers: Use the deduplication_resolver key. Supported resolvers and their implementation can be found here

🚨 Error Handling

If processing fails:

  • Error is reported to Rails.error.report
  • The Collector logs and finalizes the trace
  • The method returns false