Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RAH Random Forest Regressor

An rah handler that scores a REDCap alert through a scikit-learn random forest and writes the predictions back into the record.

An alert fires, its email body carries the model's inputs as TOML, this handler runs the regressor, and the predicted values go back to REDCap through the API. The model itself is a RandomForestRegressor you've trained and saved with joblib; scripts/simulate_panas.py builds an example one from simulated PANAS data if you want something to test against.

Configuration

This handler runs under an rah route. Here's a route that handles alerts whose subject is rf_predict:

[routes.rf_predict]
handler = "rah-random-forest:predict"

# A TOML file holding the REDCap API url and token (see below).
redcap_info_file = "/path/to/secrets/redcap.toml"

# The REDCap field that holds the record id. It's read from the alert body and
# written to the imported record.
redcap_id_field = "record_id"

# A joblib dump of {"model": rf, "columns": [...], "targets": [...]}, the shape
# scripts/simulate_panas.py writes. If it won't load, every message on the
# route fails permanently.
model_file = "/path/to/models/my_rf.joblib"

# How the model's input columns are filled from the alert body:
#   model_column = "alert_field"
# Every column the model expects needs an entry here. An alert can name its
# fields whatever it likes; this is where the two get reconciled.
[routes.rf_predict.input_fields]
panas20_q01 = "q1"
panas20_q02 = "q2"
# ...one line per model column

# Where each prediction lands in REDCap:
#   model_target = "redcap_field"
# You can write every target or just the ones you care about.
[routes.rf_predict.target_fields]
pa = "pa_zscore"
na = "na_zscore"
ar = "ar_zscore"

Both maps are keyed by the model's own names -- its columns on the input side, its targets on the output side. That's deliberate. The model is the fixed thing here: its columns and targets are baked into the joblib, and the alert and the REDCap project are free to name their fields however they want. Keying on the model means the handler can check the config against the model up front and tell you exactly which column has no mapping, instead of finding out halfway through the first live message. A route whose input_fields misses a column, or whose target_fields names a target the model doesn't produce, can't work for any message, so the handler rejects it before it looks at the body.

You don't have to wait for a live message to find out whether the maps line up. rah doctor asks this handler to check its own route, so a config that doesn't match the model shows up under the checkups line, naming the column or target that's wrong. rah process runs the same check at startup and reports it, then starts anyway -- a broken route still takes its mail, and each message fails where you can see it.

Set dry_run = true on the route (or in [global]) to run everything except the REDCap write. The handler logs the exact JSON it would have imported and leaves the message in place.

The redcap_info_file

A separate TOML file, kept out of the main config because it holds a secret:

url = "https://redcap.example.edu/api/"
token = "..."  # the project's API token

The alert body

The email body is TOML: the record id under whatever key you set as redcap_id_field, plus one entry for each alert field named in input_fields. For the route above:

record_id = "1023"
q1 = 3
q2 = 5
# ...through q20

Values are cast to numbers before they reach the model, so a stray non-numeric value (or a field the alert forgot to send) fails completely rather than feeding the model garbage.

Development

uv sync
uv run pytest

About

A random forest regressor handler for redcap-alert-handler

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages