SAS/R Monitoring & On-Leave Coverage
Turn your already-validated monitoring programs into a scheduled, self-escalating loop so the trial keeps being watched, the tracker keeps being current, and nothing falls through the cracks — especially while you’re out on leave. The monitoring itself is deterministic SAS/R, no AI required — the lowest-risk, ship-today pipeline in this whole program.
Three components, one scheduled job
Deterministic SAS/R that ingests the latest data, gates on freshness, runs the pre-specified safety + operational checks, rolls up severity, emails a digest, fires a tier-2 urgent alert on any RED, and closes with a heartbeat — watched by an independent dead-man’s switch. The %tm_* library.
The same scheduled job keeps your Smartsheet program tracker current — idempotent upsert-by-key behind an ops-only allowlist guard — so the PM dashboard is live while you’re away. The %ss_* library. No AI.
An optional on-device SLM drafts the digest prose or triages free-text the deterministic code can’t — ops-only, behind the loopback + validator + human-gate guardrails. The monitoring needs none of this; it is a convenience, not a dependency. The %slm_* library.
Below: the architecture, the checks, the alerting + escalation, the covering-while-on-leave runbook (the headline), the tracker, the optional language layer, the macro libraries, the safeguards, and IT readiness.
Watch it work — written in SAS, then a month of coverage
First, the monitoring backbone written line by line and run in SAS Enterprise Guide — in the Azure session where the study data actually lives. Then the on-leave scenario end to end: the daily job runs under a service account, a RED finding pages the covering biostatistician with an evidence packet, the tracker stays current, and the dead-man’s switch guarantees you’d know if it ever went quiet.
And the two companions in action:
Every section, demonstrated on screen
Each part of this wiki has its own live screencast — the actual SAS (or R) code written line by line in SAS Enterprise Guide 8.3 on the Azure desktop, and every icon clicked. No slides.
tm_config.sas — libraries, thresholds, recipients, schedule.
▶ The %tm_* libraryInside tm_macros.sas: the freshness gate, the roll-up, written line by line.
▶ The safety checksThe Hy’s-Law AND-gate discipline and the CTCAE labs band — thresholds, not diagnoses.
▶ Alerts & escalationThe escalation matrix written, then the tier-2 route shown in the log.
▶ The R companionPrototyping in RStudio on the laptop — synthetic, aggregate, never study data.
▶ The optional language layerThe on-device SLM boxed in: loopback, a string-only schema, a human gate.
▶ IT readinessScheduling the job under a service account in Windows Task Scheduler + the watchdog.
The interactive dashboard — the backbone made visible
A self-contained, click-through dashboard on a synthetic, longitudinal CP-101 run (22 participants × 8 visits), presented as a dark operations console (status pill, a what-if scenario simulator, cohort ranking bars, a freshness strip, and a network view). It is a teaching view of what the deterministic loop produces — not a production console, not a record. Open it in any browser; nothing leaves the file.
What it shows — seven validated sections + one exploratory, per-visit detail
A section sub-nav (Overview · Hepatic · Cardiac · Labs & Vitals · AE & DLT · Disposition · Participants) with a KPI tile strip and a standing governance ribbon. Every per-visit series is built so its worst on-treatment value equals the original summary — nothing contradicts the headline plots.
- Overview — KPI tiles; a prioritized attention worklist (every open AMBER/RED screening flag, click-to-open); a first-class data-freshness/SLA panel (stale-green → RED); and a study heatmap (participant × visit, worst flag — the “when did it start” view).
- Hepatic — the eDISH scatter now with per-visit history trails (where a participant has been, not a prediction); per-visit LFT trajectories; an R-ratio pattern strip; and a peak/baseline/trend table. The near-miss (high ALT, bilirubin under 2×) is correctly kept GREEN by the AND-gate at every visit.
- Cardiac — per-visit QTcF (Fridericia) trajectory with a cohort-median band, and an ICH E14 categorical outlier census (clearly labelled “screening census — not the E14 table”; baseline-watch shown distinctly).
- Labs & Vitals — a baseline→worst lab-shift grid (CTCAE-style screening bands, not adjudicated grades), hematology trajectories, a renal fold-rise panel (eGFR an estimate), and a vitals/orthostatic PCS board (operational context).
- AE & DLT — a 3+3 escalation state ladder (trigger / decision-pending only — the SRC decides, never the dashboard), an AE SOC×CTCAE-grade matrix (severity and seriousness kept separate), and an AE onset swimlane vs study day.
- Disposition — an enrollment/disposition funnel, RBQM KRI tiles + a protocol-deviation log, and a visit-compliance matrix (a missed visit is routed to a human, never read as “fine”).
- Participants — a sortable/filterable/searchable roster; and on click anywhere, an evidence-packet modal with per-visit sparkline trajectories.
Architecture — the daily job
One orchestrated batch job (monitor_driver.sas), scheduled under a service account, runs the loop. Order matters: freshness gates everything, and every fragile step is wrapped to fail loud.
%tm_latest)%tm_freshness)%tm_guard)%tm_chk_*)%tm_status)%tm_digest)%tm_alert)%tm_heartbeat)tm_watchdog.sh runs on a different host and account, reads the heartbeat, and screams if the job didn’t run, ran late, or ran on stale data. The thing most likely to fail silently is the scheduler itself — so the watchdog is the control that makes “no news” trustworthy. No email is itself a signal.The two branches off the same job
- Tracker (SHEETLINK): after the checks,
%ss_*upserts the run status + open flags into the Smartsheet tracker — idempotent by key, ops-only columns allowlisted — so the PM view is live. See Keeping the tracker current. - Language (LOCALMIND, optional): if enabled, an on-device SLM drafts the digest prose from the already-structured rows — never deciding what to include. See The optional language layer.
The monitoring checks — deterministic flags
Each check reads validated analysis data and writes a flagged dataset with a sev column (GREEN / AMBER / RED) against pre-specified thresholds. They are screening flags, not diagnoses — a RED is a prompt for a human, not a finding.
| Check | What it flags | Default thresholds |
|---|---|---|
%tm_chk_hyslaw | eDISH / Hy’s-Law screening: ALT or AST >×ULN and TBili >×ULN | ALT/AST 3×, TBili 2×, ALP 2× — screening flag, not a diagnosis |
%tm_chk_qtcf | QTcF absolute & change-from-baseline tiers (ICH E14) | RED abs 500 / Δ60; AMBER abs 480 / Δ30 |
%tm_chk_ae | AE/SAE running tally by cohort — participant-incidence + event counts | study-specified |
%tm_chk_dlt | Candidate-DLT tally vs the 3+3 rule (against an adjudicated DLT flag + completed window) | does not make the escalation decision |
%tm_chk_labs | Out-of-range / potentially-clinically-significant / shift flags | study-specified |
sev column against pre-specified thresholds; %tm_status takes the worst severity — a RED is a screening prompt for a human, never a diagnosis.%tm_assert wraps every fragile step; on a false condition it sets the return code, forces status to ERROR, emails the backup, and writes a FAILED heartbeat, so a broken run is never a silent green. Guard the inputs — %tm_guard asserts the expected columns exist, are numeric, and pass a range-sanity check before any threshold runs, catching a unit change or a renamed variable that would otherwise sail through. And the freshness gate runs first: a missing or dead feed flags loudest, because stale-green is the most dangerous state — it looks fine and isn’t.Alerting, paging & escalation
Two tiers, de-duplicated so a standing RED pages once, plus a heartbeat that makes silence meaningful.
- Tier 1 — the daily digest (
%tm_digest): every run emails a summary to the support alias, RED/AMBER/GREEN by section. Routine awareness. - Tier 2 — the urgent alert (
%tm_alert): on any new de-duplicated RED, page the support alias + a named backup with a one-page evidence packet (%tm_evidence— the eDISH scatter with reference lines + the participant’s lab trajectory) and the medical monitor’s name + phone. Enough to act, not to decide. - The heartbeat (
%tm_heartbeat): a run record written always, success or failure — the input to the watchdog.
Escalation matrix
| Colour | Means | Who acts |
|---|---|---|
| GREEN | Checks ran, fresh data, nothing tripped | No action — the digest is the record |
| AMBER | A tier threshold crossed, or a feed is ageing | Covering biostatistician reviews same day |
| RED | A safety screen tripped, or a feed is dead/stale | Page → covering biostatistician loops in the medical monitor, who makes the call |
| NO EMAIL | The job didn’t run (the worst case) | The watchdog pages the backup — treat as RED until proven otherwise |
Covering while out on leave — the runbook
The headline use case. Hand the covering biostatistician one page: what runs and when, what the colours mean, who they call, and what to do if the email ever stops. Everything runs under the service account SVC-BIOSTAT, never a personal login — so it does not break the day you turn off your laptop.
SVC-BIOSTAT; the watchdog ~30 min later on a different host; the tracker upsert in the same job. The covering biostatistician is the named backup on every tier-2 alert and every watchdog page for the leave window — one line in tm_config.sas, not a code change.A worked three-week leave
- Before you go: set
backup=to the covering biostatistician and confirm the medical monitor’s contact intm_config.sas; send them this runbook + the picture guide; run the job once and confirm the digest, a test alert, and a heartbeat all arrive. - Days 1–8: GREEN digests each morning; the tracker stays current; the covering biostatistician does nothing but glance.
- Day 9: an eDISH point trips Hy’s-Law screening — a tier-2 alert pages the covering biostatistician with the evidence packet; they review, loop in the medical monitor, who makes the call; the tracker reflects the open flag within the hour.
- Day 14: an overnight feed fails to land — the freshness gate flags it RED and the watchdog confirms the job ran but on a dead feed; IT restores the export; no silent gap.
- On return: a complete, dated trail of digests, the one alert, the resolution, and the tracker history — not a backlog to reconstruct.
Keeping the Smartsheet tracker current — SHEETLINK
So the PM’s program dashboard is live while you’re away, the same scheduled job upserts status into Smartsheet — deterministically, no AI.
- Idempotent upsert-by-key:
%ss_*matches each row by a stable key and updates-or-inserts, so re-runs never duplicate and a re-processed day is a no-op. - Ops-only allowlist guard: only an allowlisted set of operational columns can be written — the automation can never touch a column it shouldn’t, by construction.
- Token from a secret, never logged: the Smartsheet token comes from an environment variable / secret store, never hard-coded and never written to a log.
The optional on-device language layer — LOCALMIND
If you enable it, the %slm_* library (loopback-guard %slm_init, schema-constrained %slm_chat, allowlist %slm_validate, %slm_classify) is the engine, and the standard honesty applies: a small local model hallucinates, so its trustworthiness comes from the discipline around it, not from being local. See the dedicated on-device wiki for the full treatment.
The macro libraries
Three deterministic SAS libraries (each with an R companion), copy-paste ready, each with a per-study config so a second study or a rotating monitor is one file, not a code edit. Base SAS 9.4; no third-party packages.
| Library | Driver + key files | What it does |
|---|---|---|
TRIALMON %tm_* | tm_macros.sas · monitor_driver.sas · tm_config.sas · tm_watchdog.sh · tm_companion.R | The monitoring loop: init, latest, freshness, guard, the safety checks, status, digest, alert, evidence, heartbeat — plus the independent watchdog. |
SHEETLINK %ss_* | ss_macros.sas · tracker_update.sas · ss_config.sas · ss_companion.R | Idempotent upsert-by-key into Smartsheet behind an ops-only allowlist guard; token from a secret. |
LOCALMIND %slm_* | slm_macros.sas · triage_driver.sas · slm_config.sas · slm_companion.R | (Optional) the on-device language layer: loopback guard, schema-constrained call, allowlist validator, classify. |
tm_config.sas holds the per-study constants — paths, recipients, the medical monitor, feeds, thresholds — %included before the driver. A new study, a rotating medical monitor, or a leave-coverage backup is a one-line change, change-controlled like any study program.Safeguards & governance
The controls that make unattended automation defensible
- Deterministic, validated logic. The checks are the same threshold logic you already validate — just scheduled. Validate the threshold programs and the scheduling wrapper as study programs (GxP, risk-based CSV); version-control the config and any threshold change.
- Fail loud + guard the inputs.
%tm_assertturns any broken step into a loud ERROR + a backup email + a FAILED heartbeat;%tm_guardcatches a unit change or renamed variable before a threshold runs. A broken run is never a silent green. - Freshness first. Stale-green is the most dangerous state; a dead feed flags loudest.
- The independent watchdog. A dead-man’s switch on a different host/account makes “no email” a signal, not a guess.
- Service account, not a person. The job runs under
SVC-BIOSTATso coverage doesn’t depend on anyone’s laptop; the backup is named per leave window. - Numbers stay validated. Anything reported is independently double-programmed and comes from validated tools (Phoenix WinNonlin, Pinnacle 21, the validated pipeline). A flag is never a reported value; the optional SLM never produces one.
IT readiness — the lowest-friction path in the program
For the deterministic monitoring + tracker, the asks are small and entirely on-prem — no cloud, no AI to qualify. Hand IT this list.
- A service / group account (
SVC-BIOSTAT) to own the scheduled job and read the analysis libraries — so the automation never orphans on PTO. - Batch scheduling (Windows Task Scheduler / cron) for the daily job, and a second host/account for the watchdog dead-man’s switch.
- An internal SMTP relay for the digest + alerts (no external mail).
- Read access to the data exports + a state directory for de-duplication and heartbeats.
- For the tracker: a Smartsheet API token in a secret store (never hard-coded).
Maintenance & FAQ
Monthly upkeep (~15 min)
- Skim the heartbeat log for missed/late runs; confirm the watchdog fired in any test.
- Review alert volume — persistent AMBER noise means a threshold needs a (change-controlled) review, not silencing.
- Confirm
tm_config.sasrecipients + the medical monitor + the current leave backup are correct.
FAQ
Does it make safety decisions? No. It detects, packages, and pages pre-specified deterministic flags; humans decide. It never auto-acts on safety and never adjudicates.
Is there AI in the loop? Not in the monitoring — it is deterministic SAS/R. The on-device SLM is an optional, ops-only language convenience behind guardrails; the numbers and the flags never depend on it.
What if the job silently dies? The independent watchdog pages the backup — no email is itself a signal. That is the whole point of the heartbeat + dead-man’s switch.
Does it cost anything? The deterministic core is just scheduled SAS/R on a service account + internal SMTP — no licences beyond what you have. Only the optional SLM adds a real (validation + ownership) cost.
Is it validated? Validate the threshold logic + the scheduling wrapper as study programs, risk-based; version-control thresholds; double-program anything reported. The automation is decision-support and a logistics aid, not a record generator.
How does coverage actually work while I’m out? See the coverage runbook — service-account scheduling, a named backup per leave window, the escalation matrix, and the dead-man’s switch, all on one page you hand to your cover.