Inspiration
Drug-induced arrhythmia — specifically Torsades de Pointes (TdP), a rare but potentially fatal heart rhythm disorder — is one of the most common reasons a promising drug gets pulled from development or, worse, from the market after approval. Terfenadine and cisapride are the textbook examples. The FDA built an entire regulatory framework, CiPA (Comprehensive in vitro Proarrhythmia Assay), specifically to replace the old, crude "does it block one channel" screen with something mechanistic. That framework is real, validated, and public — but it lives in papers, spreadsheets, and a GitHub repo most people will never open. We wanted to take that same FDA science and make it something you can actually watch happen: drag a dose slider and see a real simulated heart cell's electrical signal degrade in real time, instead of reading a risk label someone else computed for you.
What it does
Cardie takes a compound and a dose, runs it through the FDA's own ORd-CiPA v1.0 human ventricular myocyte model, and classifies its torsades risk as LOW, INTERMEDIATE, or HIGH — matching the FDA's own labels on all 12 of their CiPA training compounds. Every number on the page comes out of the simulation itself:
- A live, animated WebGL heart and single-cell view, where membrane colour, contraction, and ion-channel activity are all driven by the actual solved ODEs, not decoration
- A transmural heterogeneity panel showing three real heart-wall cell layers (endocardial, epicardial, mid-myocardial) overlaid, which is the real mechanism behind re-entrant arrhythmia, not just a prettier chart
- A cross-drug comparison panel showing all 12 compounds' simulated voltage side by side at one instant, backed by a live time-series database query
- A plain-English explanation of both the verdict and whatever you're currently looking at, regenerated every time you change the drug, the dose, or the view — plus the same explanation read aloud on request
- A natural-language box ("is terfenadine safe at twice the normal dose?") that parses your question into a structured simulation query
- A built-in glossary explaining every term on the page, for anyone who doesn't already know what hERG or qNet means
How we built it
The pipeline is: a pharmacology adapter (measured IC50s, or nearest structural analogue via RDKit/Tanimoto similarity for anything outside the 12) feeds a Myokit-based electrophysiology engine running the ORd-CiPA v1.0 model, which produces qNet and APD90 for a risk classifier fitted against the FDA's own reference table. A Flask API serves a precomputed sweep (12 drugs × 4 doses × 3 cell layers) from memory, so the live demo never waits on a simulation — the one genuinely compute-heavy step runs offline and gets cached to JSON.
On top of that sits a strict separation: the simulation computes every number, and Gemini only ever explains numbers that have already been computed — it narrates the verdict, narrates the current view, answers natural-language questions, and reads explanations aloud, but it never touches the physics. Tiger Data (TimescaleDB) stores the full native-resolution simulation output as a real hypertable and powers the live cross-drug comparison query. The whole thing is deployed on a Vultr instance running behind systemd, pulling straight from our GitHub repo.
The frontend is vanilla JS and HTML5 Canvas, with Three.js (vendored, no CDN dependency) for the WebGL heart and cell. No framework, no build step.
Challenges we ran into
The cardiac model itself hid four separate "traps" that each produce
plausible-looking wrong output: Simulation.pre() silently mutates the
model's default state between runs; the hERG drug concentration parameter
is declared in millimolar in the model file but the FDA's own fitted
parameters are in nanomolar; a channel-trapping rate constant defaults to
zero unless explicitly set; and qNet has to integrate one specific calcium
current component, not the more obvious "total" one. Every one of these
compiles fine and runs fine — they just produce a confidently wrong answer
if you don't know to check for them.
Deploying surfaced a second wave of bugs that never showed up in local development: a Python 3.13-only f-string syntax crashed the app outright on the server's stock Python 3.10, and Flask's dev server wasn't threaded, so a single slow Gemini call could block even a basic data fetch and make the whole page look broken. We also had to fully switch narration providers twice in one session — from ElevenLabs (blocked outright by their free tier's API restrictions) to the browser's own Web Speech API, and finally to Gemini's own text-to-speech model once we realized it was already live and needed no new dependency.
Restructuring the layout for independent mobile/desktop section ordering ran straight into a genuinely obscure CSS Grid behaviour: the browser's auto-placement cursor only moves forward through rows, so a short column's items get silently shoved down to match a taller column's row count unless you place every item explicitly. And once we loaded real data into Tiger Data, a query that looked fine against an empty database started returning a dozen duplicate rows per drug, because a ±2ms time window that returns nothing when the table is empty returns everything in range once it isn't.
Accomplishments that we're proud of
Reproducing the FDA's own reference table row for row, and getting 12/12 risk classifications right against their own training labels, using exactly their own published model and data. Building a visualization where we can honestly say every pixel is driven by real simulated state. Going beyond the standard single-cell endocardial view to simulate all three transmural cell layers in the same session, surfacing the actual re-entry mechanism behind the arrhythmia instead of just a flatter metric. And building something that degrades gracefully end to end — the app runs fully with zero API keys configured, and stays honest on the page itself about exactly what is and isn't validated, rather than overclaiming.
What we learned
A lot about cardiac electrophysiology modelling that we didn't know going in — what qNet actually integrates and why it beats APD90, why transmural dispersion is the real torsades mechanism, why hERG trapping behaviour matters. We also relearned, the hard way, that "works on my machine" means almost nothing once Python versions, threading defaults, and database transaction scoping change underneath you. And we came away with a strong opinion on AI integration: the moment an LLM is allowed to touch a number instead of just explaining one, you've traded a validated pipeline for a plausible-sounding one.
What's next for Cardie
Extending past the 12 CiPA training compounds to the FDA's fuller validation set, which means re-fitting dynamic hERG binding parameters for the rest — a real, multi-day undertaking on its own. We'd also like to explore actual tissue-level propagation rather than the current single-cell-per-layer view, a proper domain and HTTPS for the deployment, and validating the nearest-analogue prediction path against real measured data for compounds outside the training set.
Log in or sign up for Devpost to join the conversation.