Your data never leaves the browser — and CI proves it on every commit.
A fleet of local-only, single-signal physiological analyzers. No upload. No accounts. No network.
One signal in, honest numbers out. Each analyzer reads one raw biosignal from a consumer device, derives metrics from it, and reports inward over a shared event bus (Ganglior) so a fusion layer (the Integrator) can read across them. Every number carries an evidence grade, so you always know which kind of number you're looking at. And "100% client-side" isn't a promise — it's a test: a headless privacy gate boots every shipped surface and fails the build if anything so much as reaches for the network.
Suite version: 1.17.0 — 22 ledger-backed releases, each computed from a green tree.
| 🧠 Analyzers | 7 live single-signal nodes · 1 planned (EEGDex) |
| 🔀 Fusion | Integrator (cross-signal) + 2 ingest front doors — Data Unifier · OverDex — all live |
| 🧪 Tests | 3,500+ assertions across 230 groups, run as 6 partitioned CI shards — green on every push |
| 🛡️ CI gates | no-network · tests · types · biome · CodeQL — the privacy claim is enforced, not asserted |
| 🎫 Honesty | every surfaced number carries an evidence grade — nothing ships unlabelled |
| 🔬 Reproducible | every preprint regenerates from its live tool; every bundle is content-addressed |
Roadmap ▰▰▰▰▰▰▰▱ 7 of 8 planned analyzers live — EEGDex (Muse) is next.
Momentum.
v1.16.0landed a fleet-wide DEEP-AUDIT-II hardening pass: cross-signal ECG↔apnea corroboration anchored to the right night, presence-gated HRV math (no morenullarithmetic publishing confident numbers), and duration-scoped norms. Full history in the changelog.
| Node | Signal | Device | Status | Flagship metric |
|---|---|---|---|---|
| OxyDex | SpO₂ / oximetry | O2Ring · Wellue · ViATOM | 🟢 Live | ODI-4 · T90 · hypoxic burden |
| ECGDex | Raw ECG | Polar H10 | 🟢 Live | QTc · rMSSD |
| PulseDex | HRV from raw RR | Polar H10 · Coospo · Wahoo | 🟢 Live | rMSSD · SDNN |
| PpgDex | Wrist PPG | Polar Verity Sense | 🟢 Live | PPI → HRV + pulse-wave morphology |
| HRVDex | HRV summaries | (exports) | 🟢 Live | additive multi-day rMSSD / SDNN ledger |
| GlucoDex | CGM | (exports) | 🟢 Live | Time in Range · GMI |
| CPAPDex | CPAP therapy | ResMed · EDF | 🟢 Live | pressure · leak · respiratory events |
| EEGDex | EEG | Muse | ⚪ Planned | — |
git clone https://github.com/Plantucha/Tepna.gitThen open index.html in any modern browser and click your device. No build step, no server, no
install — every node app is a single self-contained HTML file that runs straight from disk.
💡 Why that's remarkable: an entire overnight sleep-apnea screen, an ECG QT analysis, or a CGM report runs from a file on your disk — the raw biosignal is parsed, the math is done, and the figures are drawn, all without a single byte leaving the tab.
Most health apps say your data is private. Tepna proves it, mechanically, on every commit.
no-network.html is a self-verifying gate — the privacy analogue of a build
signature. It statically scans every shipped surface (the 8 self-contained bundles + the 2
orchestrators + their loose modules), boots each one in a trapped iframe where any cross-origin
request throws, and asserts zero network egress. A planted-canary negative control ships inside
the gate, so a vacuous "all clear" can never pass. It runs headless in CI on every push.
| CI gate | What it guarantees |
|---|---|
| no-network | No shipped surface reaches the network — privacy is enforced, not asserted |
| tests | 3,500+ assertions over the real DSP modules, run as 6 parallel shards that provably partition the suite — so the union of the shards is the whole gate, and no group can quietly go unrun |
| types | JSDoc type-checking (tsc --checkJs) across the signal contracts |
| biome | Format + house-invariant lint (frozen names, Clock Contract, SPDX, evidence vocabulary) |
| CodeQL | Static security analysis on every push |
Render-coverage (booting each real app bundle in an iframe and asserting computed values reach the
DOM) is a heavier browser gate — it lives in Dex-Test-Suite.html?full and the manual
browser-gates workflow, not on the per-push path.
Beyond CI, verify-provenance.html content-addresses every bundle
(GATE A: code identity) and every fixture (GATE B: known-answer reproducibility) — so a shipped app
is provably the code it claims to be.
Browser-enforced, too. Every bundle ships a strict Content-Security-Policy. connect-src 'none'
blocks egress at the browser layer, and script-src lists a per-inline-script sha256 hash with
no 'unsafe-inline' — so even a <script> injected into the DOM (a future untrusted→HTML
regression) cannot execute. CSP is an injection backstop, not just an egress control; a headless
negative-control test asserts an injected script stays inert, and the security · csp-strict gate
holds the invariant (no inline handler survives, every script-src is hash-based).
Tepna derives a large surface of metrics, but they are not all equally trustworthy — and we say so on every number. The honest front door is this short list of externally validated metrics, each anchored to a published standard. If you read nothing else, read these.
| Metric | Signal · App | What it is | Anchor |
|---|---|---|---|
| ODI-4 | SpO₂ · OxyDex | 4% oxygen-desaturation index — apnea-screening workhorse | AASM |
| T90 | SpO₂ · OxyDex | % of night below 90% SpO₂ — hypoxia severity | Sleep-apnea literature |
| Hypoxic burden | SpO₂ · OxyDex | Area·depth·duration of desaturations | Azarbarzin 2019 |
| rMSSD | RR/ECG · PulseDex · ECGDex · HRVDex | Short-term parasympathetic HRV | Task Force 1996 |
| SDNN | RR/ECG · PulseDex · ECGDex | Overall HRV over the window | Task Force 1996 |
| QTc | ECG · ECGDex | Rate-corrected QT (Bazett / Fridericia) | Repolarisation standard |
| Time in Range | CGM · GlucoDex | % time 70–180 mg/dL — primary CGM metric | 2019 consensus |
| GMI | CGM · GlucoDex | Glucose Management Indicator from mean glucose | Bergenstal 2018 |
Everything past this list exists, but lives lower on the evidence ladder. Treat it accordingly.
Each metric carries one of five evidence tiers. The grade is shown as a disc badge in every app — disc shape encodes trust, never hue (so the ladder reads the same to colour-blind users and in greyscale print). The grade is a per-node fact, defined once in each node's registry.
| Rank | Tier | Means | Example |
|---|---|---|---|
| 0 | measured | Read directly off the device, not derived | Mean SpO₂, Mean HR |
| 1 | validated | Established + externally validated against a published standard | ODI-4, rMSSD, TIR |
| 2 | emerging | Published, but device-dependent or less standardized | Nonlinear HRV, coupling |
| 3 | experimental | Plausible, internally consistent, not externally confirmed | Composite indices |
| 4 | heuristic | Rule-of-thumb estimate — directional signal only | Sleep-derived BP estimate |
A metric's tier is sourced from its node's registry (*-registry.js) and mirrored into the
reference guides; the badge visuals come from one canonical stylesheet. A higher rank is not a
"better" metric — it's a louder one. A heuristic that points the right direction can still be
useful; the ladder just makes sure you always know which kind of number you're looking at.
| App | Signal | Device | Reads | Reference |
|---|---|---|---|---|
| OxyDex | SpO₂ / oximetry | O2Ring / Wellue / ViATOM | Raw 1 Hz overnight SpO₂·HR·motion | Technical guide |
| PpgDex | Wrist PPG | Polar Verity Sense | Raw optical pulse → PPI → HRV + pulse-wave morphology | Technical guide |
| PulseDex | HRV from raw RR | Polar H10 (*_RR.txt; Coospo/Wahoo too) |
Beat-to-beat RR-interval streams | Technical guide |
| ECGDex | Raw ECG | Polar H10 | ECG (~130 Hz, Polar Sensor Logger) | Technical guide |
| HRVDex | HRV summaries | — | Vendor HRV exports + ECGDex exports (additive, multi-day) | Technical guide |
| GlucoDex | CGM | — | Continuous glucose traces | Technical guide |
| CPAPDex | CPAP therapy | ResMed · EDF | EDF therapy data — pressure · leak · respiratory events | Technical guide |
| EEGDex | EEG (planned) | Muse | Raw EEG | (planned) |
Each node emits a ganglior.node-export JSON that the Integrator fuses across signals. Two
ingest front-doors feed it: the Data Unifier takes individual files you drop and routes each to
the right node, and OverDex takes a whole folder of mixed exports — it walks the tree, runs the
right node on each file, and hands every result to the Integrator automatically.
HRVDex is the suite's HRV ledger. Its imports are additive: every Welltory CSV or ECGDex export (CSV or ganglior.node-export JSON, including the multi-recording array) appends to one accumulating multi-day table — drop many files or nights at once, exact-duplicate measurements are skipped, and the table is persisted in the browser between visits (clear it with Clear saved history). ECGDex's ⬇ HRVDex export writes a Welltory-style CSV with all loaded nights in one file, so a whole H10 history lands in HRVDex in a single drop.
Every non-trivial number traces to a published method, and every working preprint is regenerated
from the live tool behind it — no hand-drawn figures, no cherry-picked runs. Highlights from
papers/:
- One phone is not one clock — a single-subject methods study measuring ~48 ppm inter-device timing drift across 11 nights (145k beats), showing why beat-level fusion of two consumer wearables needs a single acquisition clock.
- Dead ends — a synthesis of the walls the suite hit and why: fixed, flagged, or fundamental. Negative results, reported honestly.
- Sensor-trio power & σ analyses, rMSSD equivalence, ODI-4 calibration, timestamp pathology — each with its live regenerating tool.
The methods overview is Science.html and the system design is
Architecture.html.
Most people never need this: open index.html and click your device. For anyone reading the
source, here's the lay of the land.
| You want… | Look at |
|---|---|
| A map of every doc | DOCS-INDEX.md — the single entry path over all ~180 briefs, audits, and READMEs, grouped by topic. Start here before opening any individual brief. |
| The app for your device | the bundled OxyDex.html, PpgDex.html, PulseDex.html, ECGDex.html, HRVDex.html, GlucoDex.html, CPAPDex.html — open them directly. Integrator.html fuses them all; Data Unifier.html routes any file you drop to the right node, and OverDex.html runs a whole folder at once. |
| The front door | index.html (the landing/device picker) — the page normal people start from. |
| How an app is built | its source modules — <node>-dsp.js (signal math), -render.js, -app.js, -registry.js — plus <App>.src.html. Edit these; never the bundled *.html, then re-bundle. |
| Shared engine | metric-registry.js (evidence badges), ganglior-provenance.js, ans-design.css. |
| Research tools | *-analysis.html — the live tool behind each paper — plus cohort-*.html, synth-gen.html. Gates: Dex-Test-Suite.html, verify-provenance.html. |
| The papers | papers/ — working preprints, each regenerated from its tool. |
| Architecture docs | wiring/ — the "How It's Wired" reference set; start at wiring/How It's Wired - the Dex Suite.html. |
| Design system | templates/ — visual-language spec, theme + evidence-badge previews. |
| House rules | CLAUDE.md (constitution) · CONTRIBUTING.md (on-ramp) · ARCHITECTURE-PRINCIPLES.md · docs/LEXICON.md. |
Yes, the suite derives well over 75 metrics across all nodes. That number describes surface area, not confidence. The validated set above is the part we'd stake a decision on; the long tail is there for exploration and is graded honestly so you can tell the difference at a glance. We lead with the ladder, not the count, on purpose.
For the why behind Tepna — the philosophy, the constraints, and the jokes — see
docs/WHY-THIS-EXISTS.md (styled mirror: Why This Exists.html). It is
narrative, not spec: it defines no formats, APIs, or behavior, and nothing in it should be read
as an instruction to implement. Authoritative rules live in CLAUDE.md, this README, and the
*-BRIEF.md documents.
Apache-2.0. Author: Michal Planicka. Product brand: Tepna. See LICENSE, NOTICE,
CITATION.cff, and THIRD-PARTY.md. User-facing surfaces carry the health intended-use
disclaimer — Tepna is not a medical device and does not diagnose, treat, or monitor any
condition.