Tepna Working Preprints

Findings from the physiological-signal suite, written up so they need not be rediscovered. Each paper regenerates its tables and figures deterministically from a named, local analysis tool: open the tool, set N, and Run. Synthetic cohorts are seeded (CohortGen.patient(k) per index; synth-gen default seed 424242), so a rerun reproduces the numbers within sampling error at the stated N — on the current generator synth-gen 2.1 / cohort-gen 1.9. Standardized format: paper.css. Drafts — not peer-reviewed, not for clinical use.

What changed in the July 2026 re-texture rerun (synth-gen 2.1 / cohort-gen 1.9). Re-running the six simulation papers on a more realistic broadband RR texture flipped two headline theses: optical rMSSD is now unbiased-but-noisier rather than “a different quantity” (rmssd-equivalence), and autonomic rMSSD now localizes the CPAP-start night better than respiratory ODI-4 (treatment-response). Read together, rmssd-equivalence (bias-inert optical error) and qrs-yield (yield-driven optical error) now tell one story: the optical arm's disagreement is a variance/yield penalty, not a bias. Full log in the open-findings section below.

Candidate papers in the backlog (forward agenda — see PAPERS-ROADMAP-2026-06-24-BRIEF.md)

The series is lopsided toward simulation scored by real detectors; the planned stack (vendor-adapter layer + multi-vendor unifier, OverDex, EEGDex, Ultrahuman/Spiro nodes) opens the real-world validation front. Each candidate is tagged with its stack dependency. Honest sim/real labelling and a named regeneration tool remain mandatory.

Buildable now — no new node

  1. Timestamp-pathology benchmark. A deterministic corpus of consumer-export timestamp failure modes (DMY/MDY ambiguity, zoned vs floating, time-only midnight-roll, 14-digit, epoch-vs-civil) with the Clock Contract's floating tMs model stated as a citable method. The narrowest first paper already named by synthetic-data-frontier; corpus mostly exists in the parseTimestamp tests. methods/repro LOW effort · now
  2. The dead-ends paper — negative results as a deliverable. What doesn't survive scrutiny, with evidence: optical PRV is unbiased-but-noisier vs RR/ECG HRV (bias ≈0, r 0.93, but ~22× wider limits of agreement — a precision penalty; the earlier “+32% bias” proved a texture artifact, see the July rerun); Welltory wellness composites collapse to driver effects (and emit a fake 0 when inputs are absent); daily CGM-CV ICC≈0; SQI green while beat-yield fails under apnea; rolling-mean ODI self-suppression. A citable "map of the walls in the maze" — the manifesto made into an artifact. synthesis LOW–MED · now
  3. Cross-signal plausibility as automated QC. Encode physiological coupling laws (apnea→desat→HR surge; exertion→HRV drop; glucose↔HRV) as a forward model, run it backward to flag impossible combinations as artifacts or mis-routed files — QC no single-signal SQI or filename sniffer can do. Also the intelligence layer OverDex wants. sim + real MED · now

Unlocked by the planned stack — the real-validation front

  1. Real-data multi-vendor HRV agreement — the adapter-layer flagship. The real-world sequel to rmssd-equivalence: pool real co-recorded consumer devices across vendors (Polar, Coospo, Wahoo, Garmin…) and test rMSSD/SDNN agreement reference-free via the three-cornered hat. A hat across N consumer vendors is genuinely rare. real data needs adapter Phase 1 · MED
  2. EEG-anchored validation of cardiorespiratory sleep proxies — highest scientific payoff. EEGDex (Muse) supplies a near-PSG sleep-stage reference; ask how well ODI / HRV / movement recover EEG-defined sleep architecture. Upgrades the suite from "synthetic ground truth" toward a validated anchor. real data needs EEGDex · HIGH
  3. Longitudinal reference-free σ drift — OverDex-enabled metrology. sigma-no-reference pinned σ from one ~2 h hat; OverDex auto-detects every co-recording in an archive → track each device's σ over months (does consumer-sensor error drift, caught with no reference?). sensor-trio-nights already did the power analysis. real data needs OverDex · MED
  4. Real-data metabolic-autonomic coupling — UltrahumanDex sequel to cgm-hrv-coupling: the first real test of the synthetically-predicted glucose↔HRV shared-driver effect on co-worn data. real data needs UltrahumanDex · MED
  5. OverDex archive-fusion demonstration — point OverDex at one real, nested folder of mixed multi-vendor exports; show route→run→fuse recovers a coherent multi-signal picture with per-file provenance. The capstone that proves the architecture. real data needs OverDex + migrations · MED
  6. Consumer spirometry repeatability — SpiroDex single-node methods paper: implement ATS/ERS acceptability + repeatability criteria locally, report test-retest (the pulmonary analogue of nights-icc). real data needs SpiroDex · MED–HIGH
  1. Backlog populated June 2026 from PAPERS-ROADMAP-2026-06-24-BRIEF.md (above). The earlier batched re-bundle pass (Integrator sample fix + OxyDex hardening) is DONE — both gates green; see the resolved findings below. (buildHash has since been retired as a provenance signal — manifestHash, a deterministic projection of the actual inlined code, is now the sole executed-code identity, so the once-mooted “what does buildHash certify?” note is moot.)

Open findings flagged during analysis (not yet written up)

Material conclusion changes from the synth-gen 2.1 / cohort-gen 1.9 re-texture rerun (July 2026)

Earlier fixes & resolutions (resolved — click to expand)
  • FIXED · detector OxyDex ODI-4 severity-proportional under-count — traced to trailing-mean baseline self-suppression and corrected (v22.36, June 2026). The cohort robustness benchmark's one systematic failure — ODI-4 under-counting desaturations in proportion to OSA severity (severe-stratum mean bias ≈−31 events·h⁻¹ on the 20k v1.6 run, with calibration R² rising 0.77→0.92, i.e. deterministic, not noisy) — was localized to detectODI's baseline. It measured each dip against a trailing 5-min MEAN SpO₂ (computeBaselineArr); in severe OSA the closely-spaced dips drag that mean down, sinking the baseline−4% threshold so later events of equal depth fall below it and go uncounted — the worse the apnea, the more it self-suppresses. Fix: a new O(n) trailing p90 ceiling baseline (computeCeilingBaselineArr in oxydex-util.js, sliding 101-bin SpO₂ histogram) wired into detectODI only; brief dips sit in the lower tail and can't suppress a high percentile, so the ceiling tracks the resting SpO₂ a desaturation is clinically defined against. Representative re-run (N=220, same generator, identical SpO₂ both ways): severe bias −30.6→−15.7, gradient flattened (none −1.3→−0.9, mild −7.9→−6.2, mod −14.2→−10.3), ODI↔AHI slope 0.42→0.69, non-apneic stratum not inflated. The full-pipeline SubjectA pilot rises in step (severe night ODI-4 7.6→14.9). This is a genuine real-world oximetry improvement (documented trailing-mean-baseline behavior), not overfitting to the simulator — the percentile/window are defensible clinical constants (p90, 5-min), untuned. The ODI-4 × 1.1 AHI surrogate was re-examined and retained (corrected ODI still under-represents AHI, slope-to-truth ≈1.4 > 1, so ×1.1 does not over-shoot). JS-only change → buildHash unchanged (09c77b53517c), verify-provenance.html all-green; Dex-Test-Suite green (545/34) with a new ceiling-baseline contract assertion. Papers updated: odi4-ahi-bias (characterized→corrected), robustness-benchmark §3.3. Re-bundled OxyDex.html. See OXYDEX-ODI-CEILING-FIX-BRIEF.md.
  • METHOD · scale-as-test Pushing pilots to 100k patients was itself a robustness test — and is how several generator artifacts were found. Re-running the simulation pilots at very large N (the hrv-confound pilot at 100,000 patients / 558,787 nights) stressed the generator and the rendering far harder than the original pilot Ns, and that scale is exactly what surfaced the defects since fixed: the clamp-pileup lines (CPAP residual AHI piling at 15, the AHI ceiling at 90, the rMSSD floor/ceiling bands) only became visible as hard lines once hundreds of thousands of points were plotted, and the integer-age clustering (vertical stripes from whole-year ages) likewise only showed at high density. Smaller pilot runs masked all of them inside ordinary scatter. Large-N runs also confirmed the streaming/worker engine stays memory-flat and that results are stable far past the point of diminishing statistical returns — so the big runs paid for themselves twice: as a stress test of the pipeline and as a magnifying glass on the synthetic data. (Each artifact + fix is logged below; generator is now v1.7.)
  • FIXED · generator CGM was the least-covered signal — unrealistic for a continuously-worn OTC sensor; fixed June 2026 (user-spotted). The synthetic device-coverage model gave normoglycemic patients only a 45% chance of carrying a CGM (vs OxyDex 93%, RR 90%), so GlucoDex analyses ran on far fewer subjects than the cardiac signals — backwards for a sensor that, once on the arm, records continuously. Raised CGM adoption to 70% (normoglycemic) / 92% (dysglycemic), reflecting the OTC consumer-CGM market the corpus already uses (a Lingo sensor). CohortGen.VERSION1.6-pilot. Because chance() consumes one RNG draw regardless of its probability, the RNG stream is unchanged → rMSSD / ODI / age / AHI are byte-identical to v1.5 (the hrv and treatment-response results are untouched); only which patients carry a CGM shifts. nights-icc re-run on v1.6: CGM-CV now has 4,059 subjects (was 1,818), near parity with ODI/rMSSD — and its ICC₁ is still ≈0, confirming the “daily glycemic variability is a state, not a trait” finding was never a coverage artifact.
  • FIXED · harness Scaling a pilot to ≥1k synthetic patients OOM-crashed the browser — fixed June 2026. Re-running the pilots at N≥1000 crashed the tab during generation. Root cause was isolated to hrv-confound-analysis.js: its progress-counting first pass built the entire cohort up front — patients.push(CohortGen.patient(s)) with no only filter, so every patient's OxyDex CSV + RR + CGM + HRV files for all 1–12 nights were rendered and retained in memory simultaneously. Fine at the ~250-patient default; fatal at 1k+. Fix: stream. Count nights from sampleProfile alone (no rendering), then generate one patient at a time with only:['rr'] (PulseDex's sole input), score it, and discard before the next. Memory is now flat at any N. Verified: 1000 patients (5699 nights) in ~3 min, no crash — and the tighter estimates replace the 626-night pilot (see hrv-age-confound.html).
  • AUDIT · harness Swept every cohort tool for the same "materialize-the-whole-cohort" pattern — 20k-readiness pass, June 2026. Only hrv-confound had the fatal pattern. The other FAST analysis tools (nights-icc, treatment-response, cgm-hrv-coupling) already stream one patient per loop iteration and accumulate only small numeric per-night/per-subject records — no crash at scale. cohort-runner.html persists each result to IndexedDB as it goes (refresh/crash-resumable) and was already built for N≤20000. The FULL-lane workers (qrs-yield-worker.js, qrs-equiv-worker.js) keep the 176 Hz waveforms inside the worker realm, returning only per-window stats — memory-safe per job (though FULL-lane runtime at 20k is hours, not minutes). The only remaining 20k blocker was non-memory: hard input caps of 5000 and profile-scan ceilings (100k–200k) that bind before a selective-arc target (flat / intervention) can fill. Raised all six analysis tools to max=20000 and scan CAP=2,000,000. The suite can now be driven to 20k without code changes; the practical limit is wall-clock (serial real-detector round-trips), not memory.
  • FIXED · generator Synthetic CPAP residual AHI collapsed to a spike at exactly 15 — a spurious vertical line in every rMSSD-vs-AHI plot; fixed June 2026 (user-spotted). The per-night AHI model in cohort-gen.js applied therapy residual as ahi = Math.min(ahi, clamp(ahi*0.6, 0, 15)). For any treated night with baseline AHI ≥ 25, ahi*0.6 ≥ 15, so the clamp returned exactly 15.0 — pinning every such night onto one x-value. Measured over 2,480 sampled nights: 9.7% sat at exactly AHI = 15.0 (≈22% of all CPAP nights), drawing a hard vertical line with a sharp right edge on the low-AHI block (a secondary cause of the dense left wall is per-patient vertical streaks — a flat-arc patient's nights share a near-constant AHI while rMSSD varies night-to-night). A generator artifact, not a detector/render bug, but it distorted the AHI distribution (an unrealistic therapy-night spike that also carried the +6 ms CPAP rMSSD bump). Fix: proportional, jittered residual ahi = clamp(ahi*(0.15 + rng()*0.25), 0, ahi) — 15–40% of baseline, never above it, no pileup — and bumped CohortGen.VERSION 1.0-pilot → 1.1-pilot. Ripple: changes every synthetic-pilot AHI distribution, so all simulation pilots are being re-run on v1.1; buildHash provenance is unaffected (it fingerprints each app's bundle template, and cohort-gen.js is in no app bundle), and Dex-Test-Suite stays green (no test asserts an exact cohort AHI or the generator version).
  • FIXED · generator Two more clamp-pileup lines (AHI ceiling, rMSSD floor/ceiling) — fixed June 2026 (surfaced at high-N / high-res figure rendering). Once the AHI=15 line (v1.1) was gone and the figure was re-rendered at higher N and resolution, the same artifact class showed up at the other hard clamps in cohort-gen.js: (v1.2) worsening-arc severe nights overshot the hard clamp(ahi·jitter, 0, 90) and stacked a vertical line at exactly AHI=90 → replaced with a jittered ceiling (0 … 80+rng()·12, i.e. 80–92) so overshoot fades out instead of piling; (v1.3) the per-night rMSSD clamp(…, 9, 72) and per-patient baseline clamp(…, 12, 62) stacked horizontal lines at the rMSSD floor (~13 ms, low-HRV nights) and ceiling → replaced with jittered bounds (floor 5+rng()·5, ceiling 70+rng()·6; baseline 9+rng()·5 … 58+rng()·8). General rule established: a hard clamp() to a constant in a rendered quantity draws a pileup line on any scatter of that quantity — jitter the bound. CohortGen.VERSION1.3-pilot. Effect on results is trivial (the clamps bind only in the distribution tails; the age/AHI slopes and AUCs move within rounding), provenance unaffected (cohort-gen is in no app bundle), and the suite stays green. Figures re-rendered at high resolution.
  • FIXED · generator AHI-ceiling pileup persisted as a vertical red streak in the ODI–AHI calibration — fixed June 2026 (v1.7). Even after v1.2's jittered ceiling, the ODI-vs-truth-AHI scatter still showed a dense vertical cluster of severe-stratum (red) nights at the right edge: severe patients (baseAHI 30–80) on worsening arcs reach baseAHI·(1+0.7·t) ≈ 136, and the hard clamp clamp(ahi·jitter, 0, 80+rng()·12) collapsed that whole over-shooting mass into the narrow 80–92 band. The jitter softened the original razor line but not the pileup. Fix: replaced the hard ceiling with a soft asymptotic saturation (same approach as the v1.5 rMSSD floor): ahi = 95·(1 − exp(−jit/95)), so AHI fades smoothly toward ~95 instead of stacking at the cap. CohortGen.VERSION1.7-pilot. Cosmetic for the fit (R²≈0.98 unchanged) but removes the last clamp-pileup; AHI-dependent pilots are re-run on v1.7.
  • FIXED · app Integrator "Load bundled samples" referenced deleted fixtures — fixed June 2026. integrator-app.js bindSamples() fetched two hardcoded paths — uploads/ecgdex_2026-06-07 (4).json and uploads/OxyDex_2026-06-09_0529_summary.json — neither of which existed any more, so both 404'd, a warning was pushed, and the button silently did nothing. Fix: repointed the two filenames at the surviving exports uploads/ecgdex-2026-06-12.node-export.json + uploads/oxydex-2026-06-12.summary.json (no Integrator.src.html change needed — the paths live only in the app JS). Re-bundled Integrator.html. Verified post-fix: both load through the real normalizeFile path and fuse — 360 min overlap across 2 recordings, confirmed-events panel populated, desat match rate 100%.
  • FIXED · detector OxyDex cleanArtifactHR defensive hardening — applied June 2026. The processNight hang originally surfaced while bootstrapping the ODI pool does not reproduce through the real pipeline: parseCSV already drops --,--,0 rows (line ~727), so processNight never sees them, and the one infinite-loop path in cleanArtifactHR (i = j with a non-advancing inner while) is unreachable with the shipped constants (HARD 20 · RECOV 10 · SOFT 15 → a soft rise always exceeds RECOV, so j always advances). It stays gated by tests/oxy-hang.worker.js (real parseCSV→processNight on a heavy-dropout pool, watchdog-timed — green, 15 nights, worst 316 ms). The previously-deferred defensive 1-liner is now applied: the index advance is i = j > i ? j : i + 1, guaranteeing progress even if SOFT ≤ RECOV is ever configured. Signature and return shape unchanged (the shared assertions in tests/dex-tests.js are the public contract — pure internal guard). Re-bundled OxyDex.html; suite green pre- and post-bundle (508 passed / 31 groups).
  • FIXED · harness FULL-lane synthetic PPG (renderPPG) drove the real detector into 2:1 beat-halving — fixed June 2026. On the ≤500 FULL lane the real PPGDSP recovered only ~0.60× of the beats ECGDex did (which is ~1.00× truth) at meanSQI 0.92–0.98 — a 2:1 lock (every other equal-amplitude pulse), not perfusion loss. Bisected to a single ingredient (clean-room control: clean pulses, real buildRR timing, wander, hi-freq noise, and motion bursts all tracked 1:1; only the contact-loss dropout halved). The dropout jumped the channel from its ≈−481000 DC baseline to ≈−10 — a ~490k step (≈200× the 2300-unit pulse) that, after the detector's 0.5–8 Hz band-pass, rang as a low-frequency transient large enough to hijack the beat-detector's autocorrelation period estimate (locking T≈274 samp / 39 bpm vs the true 162/65) → oversized search window → halving. The shipped PPGDSP was exonerated throughout (tracks REAL Polar PPG cleanly). Fix: model contact-loss as the pulsatile AC vanishing at the held DC baseline (a true flatline — still trips the SQI/flatline QC gate) instead of a 490k step. Result: beat recovery 0.60→1.03–1.10, recall 96–98%, real PPG unaffected, Dex-Test-Suite green (495/495). Residual: PpgDex mildly over-detected (~3–10% on synthetic vs ~1% on real). That residual is now FIXED (June 2026, renderPPG tuning — see the PpgDex-over-detection entry below): FULL-lane beat recovery 1.06→1.05, precision 0.90→0.92, synthetic PPG rMSSD inflation +38%→+16%. The genuine apnea-perfusion yield signal (low-amplitude apnea beats missed while SQI stays high) is now cleanly isolated → written up in qrs-yield.html.
  • FIXED · harness PpgDex over-detection on synthetic PPG (renderPPG) — fixed June 2026. The optical detector recovered slightly too many beats on the FULL-lane synthetic PPG (~6% net / ~10% false-positive), inflating PPG rMSSD ≈+38% and contaminating any cross-modality HRV comparison. Diagnosed with the new qrs-yield-analysis.html harness (beat-level recall/precision vs ground truth): the clean-window excess was the dicrotic shoulder being re-detected at faster heart rates, and the apnea-window excess was an oversized motion artifact (≈4× pulse amplitude) leaking through (the FULL lane runs PPGDSP without its ACC motion gate). Fix (all in renderPPG, synth-gen.js — cheap, no bundle/provenance impact): dicrotic 0.12→0.04 and blended onto the diastolic decay; per-sample optical noise 60→30; motion artifact 9000→950 (realistic ≈1× pulse, no longer detector-dominating); apnea perfusion attenuation deepened 0.55→0.30 so a genuine low-amplitude yield dip survives (SQI is correlation-based, so it stays green — the real effect the QRS-yield paper reports). Result: FULL-lane beat recovery 1.06→1.05, precision 0.90→0.92, synthetic PPG rMSSD inflation +38%→+16% (PAT-jitter-dominated). Dex-Test-Suite.html green; FULL-lane fidelity gate seeds 1.03 / 1.07.
  • FIXED · harness Synthetic ECG (renderECGInt16) under-rendered beat-to-beat timing — fixed June 2026. The renderer measured pulse phase from each beat's ONSET with the R lobe at template phase 0.41, so every R landed at bcur.tMs + 0.41·RR and the rendered R-to-R interval became RR + 0.41·ΔRR — a low-pass of the true tachogram that attenuated reconstructed ECG rMSSD ≈26% (surfaced by qrs-yield-analysis.html; it is a renderer artifact, not an ECGDex property — ECGDex is validated on real ECG). Fix (in cohort-full.js — cheap, no bundle/provenance impact): pick the NEAREST beat per sample and offset phase so template 0.41 sits exactly on nb.tMs, placing every R at its true instant. Result: ECG beat recovery now exactly 1.00, ECG rMSSD bias −26%→≈0 (faithful). This unblocked the three-way rMSSD equivalence (rmssd-equivalence.html), where ECGDex now matches PulseDex to ±0.6 ms (r 0.9997). Dex-Test-Suite.html green.
  • RESOLVED · provenance buildHash certifies the bundle template, not the code that runs — surfaced June 2026 during the re-bundle pass; the resulting gate gap is now closed. The re-bundle brief (and CLAUDE.md) assumed re-bundling an app changes its buildHash and so flips its committed uploads/*.json provenance fixtures to stale. It did not: editing only the external oxydex-dsp.js / integrator-app.js (leaving each *.src.html untouched) and re-bundling left OxyDex.html at 09c77b53517c and Integrator.html at eb0454c3431b — unchanged — so verify-provenance.html stayed all-green with no fixture regeneration needed. Mechanism: the inliner stores the app as a <script type="__bundler/template"> (pristine HTML skeleton, assets referenced by stable UUID) plus a <script type="__bundler/manifest"> (the actual *.js/fonts, gzip+base64 — which is why cleanArtifactHR appears nowhere as plain text in the 453 KB bundle). ganglior-provenance.js buildSource() hashes only the template; the manifest where the executable code lives is excluded. So a code-only re-bundle is invisible to buildHash. This contradicts the gate's stated guarantee ("the hash always tracks the shipped code … forces a re-bundle whenever source changes"): provenance currently certifies the bundle skeleton, not the executed code, and cannot detect external-*.js drift. Resolved by decision (June 2026): buildHash is kept deliberately coarse (a true fix needs owning the inliner — re-hashing to include the manifest would flip every committed fixture at once), and executed-code provenance was moved off it onto a stronger signal. verify-provenance.html GATE A now compares each bundle's statically-computed manifestHash (SHA-256 of the __bundler/manifest — which does move on any bundled-module change) against the committed BUILD-MANIFEST.json, and GATE B reads a FIXTURE-PROVENANCE.json sidecar that code-gates each fixture to its producing bundle's manifestHash, turning red the moment the code that made it changes. So buildHash still only certifies the skeleton, but the gate no longer relies on it for code identity. Standing guidance: regenerate a node's fixtures (re-run + re-export) whenever you change its code, and record the producing manifestHash in the sidecar.
  • RESOLVED · detector GlucoDex's cleaned-series nocturnalHypo flag drops sharp nocturnal hypos — root cause re-diagnosed (June 2026): it's compression-artifact rejection, not slice-truncation. First seen as the coupling pilot's hypo flag firing on ~0/4 sleep-window slices and assumed to be loss of full-day context. Probing the actual slice (seed 6) disproved that: the planted hypo genuinely reaches ~56 mg/dL for ~50 min (10 cells < 70) in the raw CGM — the dip is right there. The flag misses it because a sharp drop-and-recover excursion (insulin hypo + Somogyi rebound) carries the same bracketing signature as a positional sensor-pressure artifact, so GlucoDex's computeBaselineArr/compression rejection flags those cells (f===3) and excludes them from hypo events. Fix (pilot + fusion): score nocturnal hypos with a flag-independent window-local time-below-70 on the raw slice (any ≥15-min run < 70 mg/dL). In the Integrator this is glucoseMetricsInWindow() (integrator-dsp.js); in the coupling tool it's the new raw-CSV scorer in cohort-worker.js's cgmcouple branch, exposed as a hypo-enrichment knob. Hypo-enriched re-run (479 planted hypo-nights): window-local recall 1.00 (479/479) vs the cleaned-series flag 0.008 — a clean before/after, written up in cgm-hrv-coupling.html §3.3. Now fixed in-detector (June 2026): the shipped GlucoDex detector itself is fixed too, not just the pilot + fusion workaround. glucodex-dsp.js gained a _looksLikeGenuineHypo() discriminator that guards the compression-rejection pass: a sustained sub-70 run (≥15 min) that reaches a real nadir (≤~60 mg/dL) and is entered/left gradually (descent & rebound over multiple cells) is no longer marked f===3, so it survives into nocturnalHypo() — while a near-vertical single-cell drop-and-recover (true positional artifact) is still rejected. A permanent two-directional assertion (GlucoDex hypo ≠ compression artifact) locks both in tests/dex-tests.js: a hand-built ~56 mg/dL / ~50 min Somogyi-rebound hypo fires nocturnalHypo (was 0), and a vertical-edged plateau still does not. Signatures/returns unchanged (back-compat; new logic is additive).
T © 2026 Michal Planicka ·Tepna v1.0.0 ·Apache-2.0 ·◈ Asheville, NC ·not a medical device