Documentation/Reference apps/TrialCore runs a clinical trial management app on six state fields
TrialCore runs a clinical trial management app on six state fields
TrialCore is an open-source clinical trial management app with a sponsor console, coordinator view and public study registry. Code on GitHub, demo live.
- apps
- life-sciences
- clinical-trials
- state-machines
- workflows
Overview
An open-source clinical trial management app with a sponsor console, a coordinator's work list and a public study registry, all reading one shared set of records.
Count the lifecycle fields in TrialCore's schemas.py and you get six. There are six schemas, the record types the app stores. I ran the same count over the other 18 apps in the supero-apps repo, and none of them puts a
_state or status field on every schema it defines. The app's files are in the trialcore folder.A trial is mostly that, operationally. A protocol is recruiting or paused. A site is pending or active. A document is a draft until somebody approves it.
28 states, written as data
Here is the participant's lifecycle, as it appears in the schema:
python
{"name": "participant_state", "type": "string", "mandatory": True,
"values": ["screening", "enrolled", "active", "completed", "withdrawn", "screen_failed"]},The other five follow the same form:
trial_state and participant_state have six values each, and the site, visit, adverse event and document enums have four, 28 in all. Every one is mandatory. None is called status: the comment at the top of the file sets the convention that every lifecycle field is named <x>_state, and the policy further down in setup.py lets the site coordinator move a visit or a participant along without holding the admin role.The vocabulary is the domain's own. A visit can be
out_of_window, and the seed data has one, a Week 4 visit for subject CAR-0051 with the note "Visit completed 6 days outside protocol window; deviation logged." A participant can be screen_failed. The dashboard in ui/app.js has a Funnel component that draws one bar per participant state by counting rows.Everyone on the study reads everything
The coordinator role is short enough to quote whole, from setup.py:
python
PolicyDef(role="tenant_user", default_access="none", rules=[
PolicyRule(entity="trial", can_read=True),
PolicyRule(entity="site", can_read=True),
PolicyRule(entity="trial_document", can_read=True),
PolicyRule(entity="participant", can_read=True, can_create=True, can_update=True),
PolicyRule(entity="visit", can_read=True, can_create=True, can_update=True),
PolicyRule(entity="adverse_event", can_read=True, can_create=True, can_update=True),
]),There is no
filter_field anywhere in it.Seventeen of the 19 apps put a
filter_field on at least one entity, the argument that limits a role to rows matching a field such as the owner's login, and TrialCore is one of the two that do not. A comment in the file calls this deliberate: an internal enterprise tool, shared-read, no owner-scoping. A coordinator reads the protocol, the site list and the document binder, and creates or updates the three operational entities. The sponsor's role, tenant_admin, can write all six and can delete trials. Sites are rows here, too. Eight are seeded in six countries, from US-101 Boston to AU-520 Melbourne, inside a single tenant, whereas Helix, the other trial app in the repo, makes each site a tenant, and reading the two config.py files side by side is the quickest way to decide which model your own product needs.One schema is public.
trial is listed in PUBLIC_SCHEMAS, and the front end uses it for a Find a study page that a visitor can filter by therapeutic area and by phase without signing in.Escalating a serious adverse event
Six adverse events are seeded. Two are flagged
serious: a Grade 3 neutropenia in Boston and a pneumonitis in London.The sponsor console's Safety tab puts an Escalate button on any event that is serious and still
reported. The coordinator's view renders the same table with canEscalate: false. Both seeded serious events are already past reported, so the button shows up once you log a new serious event. The button runs ae_escalation, a workflow declared with on_error: "compensate". Its first step updates ae_state to under_review and carries an automatic compensation that writes reported back. Its second step emails the safety team. Its third posts to the Slack channel #safety.Order matters in that list. The state change comes first and is the one step with an undo. A reviewer can read the whole escalation path, record and alerts together, in under thirty lines of Python.
Two more workflows ship with it.
participant_enrolled is bound to the event @create:trialcore:participant, and its single step is a send_email call. enrollment_milestone is run from the console with the sponsor's address as input.The clinical trial management console, and running it yourself
The sponsor console has seven tabs, from Dashboard to Documents. Its charts are plain styled elements drawn in the same 898-line file: enrollment against target per trial, the participant funnel, adverse events by severity, enrollment by phase. Six trials, sixteen participants and twelve visits are seeded, so every chart has something in it on first load.
bash
git clone https://github.com/supero-platform/supero-apps.git
cd supero-apps/apps/life-sciences/trialcore
cp .env.example .env # set SUPERO_DOMAIN (any free name) + SUPERO_PASSWORD
./run.shClone, enter the folder, copy the env file and start it. There is no account to create first:
run.sh registers the domain named in .env. It should be up and running in about two minutes at http://localhost:5668, and the sign-in form shows two demo logins, a CRA with the admin role and a site coordinator.The app in the trialcore folder is MIT. The platform underneath is not open source: it is a hosted service. Its README describes TrialCore as a reference app and tells you not to put real patient data in it.
Look around the live demo first. Does your study track something TrialCore lacks? Add a seventh schema with its own
_state field, then run ./run.sh --reset. To do the same in a project of your own, start at supero.dev.On this page