S
supero.docs
Documentation/Reference apps/One argument hides the arm in Helix, a multi-site clinical trial app

One argument hides the arm in Helix, a multi-site clinical trial app

Helix is an open-source multi-site clinical trial app that hides the treatment arm from the investigator role. Source on GitHub, with a live demo.

  • apps
  • life-sciences
  • clinical-trials
  • blinding
  • multi-tenant

Overview

Treatment or placebo is a single attribute on the participant, and in this open-source multi-site clinical trial app one role's reads come back without it.
In a blinded trial, the investigator who assesses a participant is not supposed to know whether that participant is taking the drug or the placebo. Helix stores the answer in an attribute called arm on its Participant schema. Then its access policy, in setup.py, gives the investigator role this rule:
python
PolicyRule(entity="participant", can_read=True, can_create=True, can_update=True,
           hidden_fields=["arm"]),
Seven apps in the supero-apps repo hide fields from a role. The other six hide things written about a record: staff notes, a diagnosis, a fraud score, a lab flag. Helix is the only one that hides the variable the study itself depends on. You can read all of it in apps/life-sciences/helix.

Who is blinded, and where it happens

Helix has two roles per site. tenant_admin is the site coordinator. tenant_user is the investigator. The coordinator's rule for participant has no hidden_fields. The investigator's is the one above. For that role arm is absent from the response body, removed server-side.
The roster in ui/app.js shows what that buys you in the front end. Its arm column is one expression: if p.arm has a value, print it in a coloured label, and if it does not, print a dash. No role check. The same table component serves the coordinator and the investigator, and the difference between their two screens is made entirely by which fields arrived in the response, so a developer who adds a second roster view next month cannot forget to apply the blind. Fourteen participants are seeded across three sites, each with an arm of treatment or placebo. The sign-in screen in the same file has quick-login buttons for the HQ admin, the Boston coordinator and the Austin investigator.
The blind covers reads and nothing else. The enrolment form in the same file carries an Arm selector for whoever enrols a subject, the investigator included, so in a real study that choice would belong to the coordinator or to a randomisation step. Helix models the shape of that access control, and nobody has certified it for running a trial.

A multi-site clinical trial app where each site is a tenant

config.py lists four tenants (a tenant is the platform's unit of data separation): Helix HQ as default-tenant, then three sites: Boston, Austin, Denver. Seven users are seeded, an HQ admin plus a coordinator and an investigator for each site.
Look for filter_field in the policy and you will not find it. Inside a site the clinical records are shared by the team, and a comment in setup.py explains that tenant isolation already scopes the data to the user's site. The seed function follows the same split. Nine studies are written once, at HQ. Each site then gets a site record and a staff profile, then its own participants, and every participant is linked to its HQ study with a call to ref_link.
For a user the client reports as able to switch tenants, which the code's comments reserve for the HQ admin, the shell renders a TenantSwitcher. Picking a site calls client.setTenantOverride, and the sidebar changes with it: HQ opens on a Portfolio dashboard, a site opens on an Overview with a roster snapshot. .env.example ships with SUPERO_IS_MULTI_TENANT=true and names the tenant noun Site.
Nothing in Helix is public. PUBLIC_SCHEMAS is an empty list with a five-word comment beside it: clinical data is never public.
The sister app TrialCore takes the other road, with sites as rows in one tenant and a public study registry.

Visits borrow a lifecycle; serious events send mail

schemas.py has six schemas and only 36 attributes. It stays that small partly because Visit inherits:
python
Visit = {
    "schema_type": "object", "name": "Visit", "namespace": "helixclinicalns",
    "parent_type": "tenant", "extends": "appointment:base_appointment",
The base brings status, start_time and end_time, and the comment above the schema notes that every create must set all three. Helix adds a visit_type, a visit_window string such as "Week 2 (+/- 3)" and a notes field. Each seeded participant gets three visits, 42 in total, one in each of three lifecycle states.
Adverse events are where the one workflow lives. The form in app.js creates the record with a reference to its participant, and only when is_serious is true does it then run serious_ae_reported, a two-step workflow that emails the safety team and stamps workflow_status and processed_at on the record. EVENT_BINDINGS is empty. No event fires it. The decision to alert is made in the form's submit handler, in plain sight. There is also a protocol assistant tab, a thin chat over services.ai.complete with a fixed preamble telling the model to answer conservatively for trial coordinators.

Try the blind yourself

bash
git clone https://github.com/supero-platform/supero-apps.git
cd supero-apps/apps/life-sciences/helix
cp .env.example .env      # set SUPERO_DOMAIN (any free name) + SUPERO_PASSWORD
./run.sh
Clone the repo, step into the Helix folder, copy the env file and launch. You do not sign up for anything. The domain you type into .env is registered on first run, and the app answers on port 5707, running in about two minutes.
Then, on your own copy, sign in twice for the same site, once as its coordinator and once as its investigator, and open the participant roster in both.
The contents of the helix folder, including the 1,078 lines of ui/app.js, are MIT. The platform that stores the data and applies the policy is hosted for you, and is not open source.
If your protocol blinds more than the arm, the list takes more names.
The hosted copy at helix.supero.live is there to look at before you clone, and for a trial app shaped by your own protocol the place to begin is supero.dev.