Last active
March 11, 2026 19:07
-
-
Save bojanrajkovic/9641e4f4e3cb43da3276734f0f1e8f5c to your computer and use it in GitHub Desktop.
Olivia CPAP Sleep Analysis Dashboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Olivia — CPAP Sleep Analysis Dashboard</title> | |
| <style> | |
| :root { | |
| --bg: #1a1a2e; | |
| --bg2: #16213e; | |
| --bg3: #0f3460; | |
| --text: #e0e0e0; | |
| --text2: #a0a0b0; | |
| --accent: #e94560; | |
| --accent2: #533483; | |
| --good: #4ecca3; | |
| --warn: #f0a500; | |
| --bad: #e94560; | |
| --border: #2a2a4a; | |
| --card: #1e1e3a; | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; | |
| background: var(--bg); | |
| color: var(--text); | |
| line-height: 1.5; | |
| min-height: 100vh; | |
| } | |
| .header { | |
| background: linear-gradient(135deg, var(--bg2), var(--accent2)); | |
| padding: 24px 32px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .header h1 { font-size: 1.5rem; font-weight: 600; } | |
| .header .subtitle { color: var(--text2); font-size: 0.9rem; margin-top: 4px; } | |
| .header .meta { display: flex; gap: 24px; margin-top: 12px; flex-wrap: wrap; } | |
| .header .meta-item { | |
| background: rgba(0,0,0,0.3); | |
| padding: 6px 14px; | |
| border-radius: 6px; | |
| font-size: 0.8rem; | |
| font-family: 'SF Mono', 'Cascadia Code', monospace; | |
| } | |
| .header .meta-item span { color: var(--text2); } | |
| .nav { | |
| display: flex; | |
| gap: 0; | |
| background: var(--bg2); | |
| border-bottom: 1px solid var(--border); | |
| overflow-x: auto; | |
| } | |
| .nav button { | |
| background: none; | |
| border: none; | |
| color: var(--text2); | |
| padding: 12px 20px; | |
| font-size: 0.85rem; | |
| cursor: pointer; | |
| border-bottom: 2px solid transparent; | |
| white-space: nowrap; | |
| font-family: inherit; | |
| } | |
| .nav button:hover { color: var(--text); background: rgba(255,255,255,0.03); } | |
| .nav button.active { color: var(--accent); border-bottom-color: var(--accent); } | |
| .content { padding: 24px 32px; max-width: 1400px; margin: 0 auto; } | |
| .section { display: none; } | |
| .section.active { display: block; } | |
| .card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| padding: 20px; | |
| margin-bottom: 20px; | |
| } | |
| .card h2 { font-size: 1.1rem; margin-bottom: 4px; } | |
| .card h3 { font-size: 0.95rem; margin-bottom: 8px; color: var(--text2); font-weight: 400; } | |
| .card .desc { font-size: 0.85rem; color: var(--text2); margin-bottom: 16px; } | |
| .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } | |
| .grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; } | |
| @media (max-width: 900px) { .grid2, .grid3 { grid-template-columns: 1fr; } } | |
| .stat-row { display: flex; gap: 16px; margin-bottom: 20px; flex-wrap: wrap; } | |
| .stat { | |
| background: rgba(0,0,0,0.2); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 14px 18px; | |
| flex: 1; | |
| min-width: 140px; | |
| } | |
| .stat .label { font-size: 0.75rem; color: var(--text2); text-transform: uppercase; letter-spacing: 0.5px; } | |
| .stat .value { font-size: 1.6rem; font-weight: 700; margin-top: 2px; font-family: 'SF Mono', monospace; } | |
| .stat .sub { font-size: 0.75rem; color: var(--text2); margin-top: 2px; } | |
| .stat.good .value { color: var(--good); } | |
| .stat.warn .value { color: var(--warn); } | |
| .stat.bad .value { color: var(--bad); } | |
| canvas { width: 100%; height: auto; display: block; border-radius: 6px; } | |
| .heatmap-container { overflow-x: auto; } | |
| .heatmap { border-collapse: collapse; width: 100%; min-width: 700px; font-size: 0.8rem; font-family: 'SF Mono', monospace; } | |
| .heatmap th { padding: 6px 8px; text-align: center; color: var(--text2); font-weight: 400; font-size: 0.75rem; } | |
| .heatmap td { padding: 6px 8px; text-align: center; border-radius: 3px; } | |
| .heatmap .date-cell { text-align: left; color: var(--text2); white-space: nowrap; font-size: 0.75rem; } | |
| .rec-list { list-style: none; } | |
| .rec-list li { | |
| padding: 16px; | |
| margin-bottom: 12px; | |
| border-radius: 8px; | |
| border-left: 4px solid; | |
| background: rgba(0,0,0,0.2); | |
| } | |
| .rec-list li.tonight { border-color: var(--good); } | |
| .rec-list li.specialist { border-color: var(--warn); } | |
| .rec-list li.monitor { border-color: var(--accent2); } | |
| .rec-list .rec-tag { | |
| display: inline-block; | |
| font-size: 0.7rem; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| margin-bottom: 6px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| font-weight: 600; | |
| } | |
| .rec-list .tonight .rec-tag { background: rgba(78,204,163,0.2); color: var(--good); } | |
| .rec-list .specialist .rec-tag { background: rgba(240,165,0,0.2); color: var(--warn); } | |
| .rec-list .monitor .rec-tag { background: rgba(83,52,131,0.3); color: #b080e0; } | |
| .rec-list .rec-title { font-weight: 600; margin-bottom: 4px; } | |
| .rec-list .rec-body { font-size: 0.85rem; color: var(--text2); } | |
| .timeline-row { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 3px; | |
| font-size: 0.75rem; | |
| font-family: 'SF Mono', monospace; | |
| } | |
| .timeline-label { width: 80px; color: var(--text2); flex-shrink: 0; } | |
| .timeline-bar-container { flex: 1; height: 18px; position: relative; background: rgba(0,0,0,0.2); border-radius: 3px; } | |
| .timeline-bar { | |
| position: absolute; | |
| height: 100%; | |
| border-radius: 2px; | |
| min-width: 1px; | |
| } | |
| .legend { display: flex; gap: 16px; margin: 12px 0; flex-wrap: wrap; } | |
| .legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.75rem; color: var(--text2); } | |
| .legend-swatch { width: 12px; height: 12px; border-radius: 2px; flex-shrink: 0; } | |
| .finding-box { | |
| background: rgba(233,69,96,0.08); | |
| border: 1px solid rgba(233,69,96,0.2); | |
| border-radius: 8px; | |
| padding: 16px; | |
| margin-bottom: 16px; | |
| } | |
| .finding-box.info { | |
| background: rgba(78,204,163,0.08); | |
| border-color: rgba(78,204,163,0.2); | |
| } | |
| .finding-box h4 { font-size: 0.9rem; margin-bottom: 6px; } | |
| .finding-box p { font-size: 0.85rem; color: var(--text2); } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>Olivia — CPAP Sleep Analysis</h1> | |
| <div class="subtitle">ResMed AirSense 10 AutoSet · Feb 21 – Mar 11, 2026 · 8 weeks post-op</div> | |
| <div class="meta"> | |
| <div class="meta-item"><span>Mode:</span> AutoSet 6–11 cmH2O</div> | |
| <div class="meta-item"><span>EPR:</span> Level 3</div> | |
| <div class="meta-item"><span>Ramp:</span> 4 cmH2O start</div> | |
| <div class="meta-item"><span>Mask:</span> Nasal Pillow</div> | |
| </div> | |
| </div> | |
| <div class="nav"> | |
| <button class="active" data-tab="overview">Overview</button> | |
| <button data-tab="heatmaps">Event Heatmaps</button> | |
| <button data-tab="trend">Trend Analysis</button> | |
| <button data-tab="worst">Night Explorer</button> | |
| <button data-tab="findings">Key Findings</button> | |
| <button data-tab="recs">Recommendations</button> | |
| </div> | |
| <div class="content"> | |
| <!-- OVERVIEW --> | |
| <div class="section active" id="sec-overview"> | |
| <div class="stat-row"> | |
| <div class="stat warn"> | |
| <div class="label">Avg AHI (last 7d)</div> | |
| <div class="value">2.6</div> | |
| <div class="sub">Up from 1.5 two weeks ago</div> | |
| </div> | |
| <div class="stat bad"> | |
| <div class="label">Worst Night OAs</div> | |
| <div class="value">53</div> | |
| <div class="sub">Mar 10 — 49 in 2-hr window</div> | |
| </div> | |
| <div class="stat good"> | |
| <div class="label">Best Night AHI</div> | |
| <div class="value">0.6</div> | |
| <div class="sub">Feb 21 — proves settings can work</div> | |
| </div> | |
| <div class="stat warn"> | |
| <div class="label">Pressure Ceiling Hits</div> | |
| <div class="value">6</div> | |
| <div class="sub">Nights where machine maxed at 11</div> | |
| </div> | |
| </div> | |
| <div class="grid2"> | |
| <div class="card"> | |
| <h2>Nightly AHI</h2> | |
| <h3>Apnea-Hypopnea Index over time</h3> | |
| <canvas id="ahi-chart" height="220"></canvas> | |
| </div> | |
| <div class="card"> | |
| <h2>Obstructive Apnea Events</h2> | |
| <h3>Total OA count per night</h3> | |
| <canvas id="oa-chart" height="220"></canvas> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Session Fragmentation</h2> | |
| <h3>Mask-on/off cycles per night — more = worse sleep</h3> | |
| <canvas id="sessions-chart" height="180"></canvas> | |
| </div> | |
| </div> | |
| <!-- HEATMAPS --> | |
| <div class="section" id="sec-heatmaps"> | |
| <div class="card"> | |
| <h2>Obstructive Apneas by Hour</h2> | |
| <p class="desc">Each cell = OA count in that hour of the sleep period (7 PM to 7 AM). Darker red = more events. Look for where clusters land each night.</p> | |
| <div class="heatmap-container"> | |
| <table class="heatmap" id="oa-heatmap"></table> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>All Respiratory Events by Hour</h2> | |
| <p class="desc">Includes obstructive apneas, central apneas, unclassified apneas, and arousals.</p> | |
| <div class="heatmap-container"> | |
| <table class="heatmap" id="all-events-heatmap"></table> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Mask-On Sessions by Hour</h2> | |
| <p class="desc">Each count = mask was removed and replaced. More sessions in an hour = more disrupted sleep. Evening (19-22) fragmentation is normal settling-in.</p> | |
| <div class="heatmap-container"> | |
| <table class="heatmap" id="sessions-heatmap"></table> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Average Apnea Duration by Hour (seconds)</h2> | |
| <p class="desc">Longer apneas = more severe airway collapse. Darker = longer events.</p> | |
| <div class="heatmap-container"> | |
| <table class="heatmap" id="duration-heatmap"></table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- TREND --> | |
| <div class="section" id="sec-trend"> | |
| <div class="card"> | |
| <h2>Worsening Trend</h2> | |
| <p class="desc">Sleep improved after the Jan 14 surgery, but has been progressively worsening since late February.</p> | |
| <div class="stat-row"> | |
| <div class="stat good"> | |
| <div class="label">Week 1 (Feb 21-27)</div> | |
| <div class="value">1.5</div> | |
| <div class="sub">Avg AHI · 5-6 wks post-op</div> | |
| </div> | |
| <div class="stat warn"> | |
| <div class="label">Week 2 (Feb 28-Mar 6)</div> | |
| <div class="value">2.8</div> | |
| <div class="sub">Avg AHI · 6-7 wks post-op</div> | |
| </div> | |
| <div class="stat warn"> | |
| <div class="label">Week 3 (Mar 7-11)</div> | |
| <div class="value">2.3</div> | |
| <div class="sub">Avg AHI · 7-8 wks post-op</div> | |
| </div> | |
| </div> | |
| <canvas id="trend-chart" height="260"></canvas> | |
| <div class="legend"> | |
| <div class="legend-item"><div class="legend-swatch" style="background:#e94560"></div> OA events (left axis)</div> | |
| <div class="legend-item"><div class="legend-swatch" style="background:#4ecca3"></div> AHI (right axis)</div> | |
| <div class="legend-item"><div class="legend-swatch" style="background:rgba(240,165,0,0.3)"></div> Weekly avg AHI</div> | |
| </div> | |
| </div> | |
| <div class="finding-box"> | |
| <h4>The "spike" nights are getting worse</h4> | |
| <p>Feb 25: 43 OAs → Mar 2: 36 → Mar 6: 49 → Mar 10: 53. The bad nights are almost entirely obstructive (OAI ≈ AHI), indicating airway collapse — not a central breathing drive problem.</p> | |
| </div> | |
| <div class="card"> | |
| <h2>Possible Causes of Worsening</h2> | |
| <div class="grid2"> | |
| <div class="finding-box" style="margin:0"> | |
| <h4>Growth Spurt</h4> | |
| <p>Eating more lately. Growth-related changes in soft tissue or airway dimensions may be narrowing the airway. Adenoids peak growth at ages 2-8.</p> | |
| </div> | |
| <div class="finding-box" style="margin:0"> | |
| <h4>Room / Bed Change</h4> | |
| <p>Recently moved rooms. Hose threaded through headboard creates fixed anchor point — may increase mask-off events and discourage lateral sleep.</p> | |
| </div> | |
| <div class="finding-box info" style="margin:0"> | |
| <h4>Post-Surgical Changes</h4> | |
| <p>Residual adenoid tissue (couldn't be fully removed due to palate risk) may be undergoing changes at 6-8 weeks post-op.</p> | |
| </div> | |
| <div class="finding-box info" style="margin:0"> | |
| <h4>Seasonal / Cold</h4> | |
| <p>Recent mild cold + late Feb to March seasonal transition. Nasal congestion increases upper airway resistance.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- NIGHT EXPLORER --> | |
| <div class="section" id="sec-worst"> | |
| <div class="card"> | |
| <h2>How Did the Night Go?</h2> | |
| <p class="desc">Select a night to see the session timeline, pressure levels, and event summary.</p> | |
| <div style="margin-top:12px;"> | |
| <select id="night-select" style="background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px 14px;font-size:0.9rem;font-family:'SF Mono',monospace;cursor:pointer;min-width:240px;"> | |
| </select> | |
| </div> | |
| </div> | |
| <div id="night-stats" class="stat-row" style="margin-top:0;"></div> | |
| <div class="card"> | |
| <h2>Session Timeline</h2> | |
| <p class="desc">Each bar = one CPAP session. Color indicates average pressure level. Gaps = mask off.</p> | |
| <div class="legend"> | |
| <div class="legend-item"><div class="legend-swatch" style="background:#e94560"></div> High pressure (avg ≥9 cmH2O)</div> | |
| <div class="legend-item"><div class="legend-swatch" style="background:#f0a500"></div> Mid-range (avg 6-9 cmH2O)</div> | |
| <div class="legend-item"><div class="legend-swatch" style="background:#533483"></div> Ramp / low (avg <6 cmH2O)</div> | |
| <div class="legend-item"><div class="legend-swatch" style="background:rgba(255,255,255,0.1)"></div> Gap (mask off)</div> | |
| </div> | |
| <div id="night-timeline" style="margin-top:12px;"></div> | |
| </div> | |
| <div id="night-callout"></div> | |
| </div> | |
| <!-- FINDINGS --> | |
| <div class="section" id="sec-findings"> | |
| <div class="card"> | |
| <h2>Key Findings from CPAP Data Analysis</h2> | |
| <h3>Feb 21 – Mar 11, 2026 · Post-op from partial adenoidectomy + turbinate reduction (Jan 14)</h3> | |
| </div> | |
| <div class="finding-box"> | |
| <h4>1. OA Clusters Shift From Night to Night — Tied to REM, Not the Clock</h4> | |
| <p>Obstructive apnea clusters aren't fixed at 2-4 AM. They appear at different hours each night (sometimes 01:00, sometimes 03:00, sometimes 05:00), consistent with REM-linked events. Whenever the cluster hits, it's dense and concentrated — suggesting a state change (entering REM or rolling supine) rather than gradual worsening.</p> | |
| </div> | |
| <div class="finding-box"> | |
| <h4>2. Strong Positional Component</h4> | |
| <p>AHI swings from 0.6 to 5.6 between nights — too variable for fixed anatomy alone. Good nights prove current pressures work. Two position-dependent collapse mechanisms: pharyngeal (residual adenoids) + tracheal (tracheomalacia). Both worsen supine. The repeat sleep study should include positional monitoring.</p> | |
| </div> | |
| <div class="finding-box"> | |
| <h4>3. Machine Hitting 11 cmH2O Pressure Ceiling</h4> | |
| <p>During OA clusters, pressure is frequently pegged at exactly 11.00 cmH2O (the maximum). The AutoSet algorithm would go higher if allowed but can't. For a 6-year-old, 11 is already on the higher end — but the data shows it's not enough during supine + REM conditions.</p> | |
| </div> | |
| <div class="finding-box"> | |
| <h4>4. Ramp Restart Creates Vicious Cycle</h4> | |
| <p>Power-cycling the machine after each wakeup drops pressure to 4 cmH2O ramp start. The airway immediately collapses at this pressure, causing more apneas before the machine ramps back up. Even SmartStart restarts at ramp pressure if ramp is enabled.</p> | |
| </div> | |
| <div class="finding-box info"> | |
| <h4>5. Progressive Worsening Trend</h4> | |
| <p>Avg AHI rose from 1.5 (week of Feb 21) to 2.8 (week of Feb 28). "Spike" nights getting progressively worse: 43 → 36 → 49 → 53 OA events. Something is actively changing — growth spurt, room change, post-surgical changes, or seasonal factors.</p> | |
| </div> | |
| <div class="finding-box info"> | |
| <h4>6. Mask Leaks Are NOT the Problem</h4> | |
| <p>Leak rates consistently near zero across all time periods. Median leak 0.00 L/s. Mask fit/seal is not a contributing factor.</p> | |
| </div> | |
| <div class="finding-box info"> | |
| <h4>7. Clonidine-REM Interaction</h4> | |
| <p>Current under-dosing of clonidine ER (0.12-0.13mg vs prescribed 0.15mg) means less REM suppression → more REM sleep → more opportunity for REM-related OA clusters. Full dose suppresses REM by ~40%. However, higher dose also raises arousal threshold. Needs coordinated specialist discussion.</p> | |
| </div> | |
| </div> | |
| <!-- RECOMMENDATIONS --> | |
| <div class="section" id="sec-recs"> | |
| <div class="card"> | |
| <h2>Recommendations</h2> | |
| <h3>Organized by urgency: tonight, specialist visit, ongoing monitoring</h3> | |
| </div> | |
| <ul class="rec-list"> | |
| <li class="tonight"> | |
| <div class="rec-tag">Do Tonight</div> | |
| <div class="rec-title">Stop power-cycling the machine</div> | |
| <div class="rec-body">Leave the machine on when she disconnects the hose. Power-cycling forces a full restart from 4 cmH2O ramp pressure. Leaving it on won't fully solve the ramp issue (SmartStart still applies ramp), but it's less disruptive.</div> | |
| </li> | |
| <li class="tonight"> | |
| <div class="rec-tag">Do Tonight</div> | |
| <div class="rec-title">Fix the hose routing</div> | |
| <div class="rec-body">Unthread the hose from the headboard. Mount the 3D-printed hook on the wall above her pillow and route the hose from overhead. The current through-headboard routing creates a fixed anchor that pulls on the mask during position changes, potentially causing mask-off events and discouraging lateral sleep.</div> | |
| </li> | |
| <li class="tonight"> | |
| <div class="rec-tag">Do Tonight</div> | |
| <div class="rec-title">Encourage lateral sleeping</div> | |
| <div class="rec-body">Place a firm pillow or rolled blanket behind her back after she falls asleep on her side. A body pillow she can hug in front naturally encourages side positioning. Pool noodle under the fitted sheet creates a ridge that discourages supine without waking her.</div> | |
| </li> | |
| <li class="specialist"> | |
| <div class="rec-tag">Specialist Visit</div> | |
| <div class="rec-title">Share this data — especially the worsening trend</div> | |
| <div class="rec-body">The progressive worsening (avg AHI 1.5 → 2.8 over two weeks, spike nights getting worse) suggests something is actively changing. Bring the nightly AHI table and hourly heatmaps to show the pattern.</div> | |
| </li> | |
| <li class="specialist"> | |
| <div class="rec-tag">Specialist Visit</div> | |
| <div class="rec-title">Ramp start pressure — raise or disable</div> | |
| <div class="rec-body">The 4 cmH2O ramp restart is clearly counterproductive during mid-night wakeups. Ask about raising ramp start to 6 cmH2O (the minimum therapeutic pressure) or disabling ramp entirely. This addresses the vicious restart cycle.</div> | |
| </li> | |
| <li class="specialist"> | |
| <div class="rec-tag">Specialist Visit</div> | |
| <div class="rec-title">Repeat sleep study with positional monitoring</div> | |
| <div class="rec-body">Already planned. The AHI variability (0.6 to 5.6) strongly suggests a positional component. Body position tracking during the study would confirm whether supine position triggers the OA clusters and determine the true pressure needed for supine + REM conditions.</div> | |
| </li> | |
| <li class="specialist"> | |
| <div class="rec-tag">Specialist Visit</div> | |
| <div class="rec-title">Pressure ceiling discussion</div> | |
| <div class="rec-body">The machine is at 11 cmH2O max and still failing during OA clusters. For a 6-year-old this is already high — the right answer might be raising pressure OR addressing the underlying cause (growth, positional factors). Let the sleep study guide this.</div> | |
| </li> | |
| <li class="specialist"> | |
| <div class="rec-tag">Specialist Visit</div> | |
| <div class="rec-title">Coordinate clonidine dosing with sleep specialist</div> | |
| <div class="rec-body">Current underdose (0.12-0.13mg vs prescribed 0.15mg) provides less REM suppression. Full dose may reduce REM-related OA clusters, but also raises arousal threshold. The prescribing physician and sleep specialist should discuss together.</div> | |
| </li> | |
| <li class="specialist"> | |
| <div class="rec-tag">Specialist Visit</div> | |
| <div class="rec-title">Airway re-evaluation</div> | |
| <div class="rec-body">Is residual adenoid tissue enlarging with the growth spurt? The progressive worsening at 6-8 weeks post-op warrants a look at the airway anatomy, especially given her age (peak adenoid growth: 2-8 years) and increased appetite.</div> | |
| </li> | |
| <li class="monitor"> | |
| <div class="rec-tag">Ongoing</div> | |
| <div class="rec-title">Track whether positional changes help</div> | |
| <div class="rec-body">After implementing pillow/noodle/hose changes, compare AHI and session counts for the next week against the current baseline. If the positional hypothesis is correct, you should see fewer spike nights.</div> | |
| </li> | |
| <li class="monitor"> | |
| <div class="rec-tag">Ongoing</div> | |
| <div class="rec-title">Watch for the trend to continue or stabilize</div> | |
| <div class="rec-body">If the worsening trend continues despite the mechanical fixes, that points more strongly toward anatomical changes (growth) that need clinical intervention.</div> | |
| </li> | |
| </ul> | |
| </div> | |
| </div> | |
| <script> | |
| // ============================================================ | |
| // DATA | |
| // ============================================================ | |
| const DATES = [ | |
| '02-21','02-22','02-23','02-24','02-25','02-26','02-27', | |
| '02-28','03-01','03-02','03-03','03-04','03-05','03-06', | |
| '03-07','03-08','03-09','03-10' | |
| ]; | |
| const LABELS = DATES.map(d => d.replace('-','/')); | |
| const AHI = [0.6,1.1,1.2,1.2,4.3,1.0,1.1, 2.8,1.4,4.0,1.1,1.8,3.2,5.6, 1.7,1.2,1.2,5.2]; | |
| const OAI = [0.1,0.8,1.0,1.1,4.3,0.4,1.0, 1.5,1.3,3.6,0.8,1.5,2.6,5.6, 1.2,1.1,1.0,5.0]; | |
| const OA_CT = [2,10,9,12,43,5,10, 12,13,36,9,13,22,49, 15,12,11,53]; | |
| const SESSIONS = [1,18,18,28,25,11,17, 6,16,14,11,15,6,22, 8,18,11,20]; | |
| const HOURS = ['19','20','21','22','23','00','01','02','03','04','05','06']; | |
| const HOUR_LABELS = HOURS.map(h => h + ':00'); | |
| const HM_DATES = ['02-28','03-01','03-02','03-03','03-04','03-05','03-06','03-07','03-08','03-09','03-10']; | |
| const OA_BY_HOUR = [ | |
| [0,9,1,0,1,0,1,0,0,0,0,0], | |
| [0,0,0,1,0,0,1,0,9,1,1,0], | |
| [0,8,5,1,0,1,3,0,7,0,11,0], | |
| [0,4,0,0,0,0,0,3,2,0,0,0], | |
| [0,0,0,0,0,0,0,0,1,11,0,1], | |
| [2,0,0,1,0,0,0,0,0,6,8,5], | |
| [2,0,4,0,0,9,1,4,3,0,15,11], | |
| [0,0,0,0,7,0,0,1,1,0,6,0], | |
| [0,0,0,0,1,1,5,0,3,0,0,0], | |
| [0,0,0,0,0,1,3,0,0,0,7,0], | |
| [0,0,0,0,0,0,1,34,15,3,0,0], | |
| ]; | |
| const ALL_EV_BY_HOUR = [ | |
| [0,9,2,0,2,0,2,1,0,4,1,0], | |
| [0,0,0,2,1,2,1,0,10,2,2,0], | |
| [1,9,5,1,0,1,4,0,7,0,13,0], | |
| [0,6,0,1,0,0,0,3,2,0,0,1], | |
| [0,0,1,0,0,0,0,0,2,11,0,1], | |
| [2,0,0,2,0,0,2,1,0,6,9,6], | |
| [2,0,4,0,0,9,1,5,3,0,15,11], | |
| [0,1,0,0,10,0,0,2,3,0,6,2], | |
| [0,0,1,1,1,1,6,0,3,0,0,0], | |
| [1,0,0,0,0,1,3,0,1,0,9,0], | |
| [1,0,0,0,1,0,1,34,17,3,0,0], | |
| ]; | |
| const SESS_BY_HOUR = [ | |
| [0,3,1,0,0,0,0,0,0,1,0,0], | |
| [2,3,3,4,0,0,1,0,3,0,0,0], | |
| [3,3,4,1,0,0,1,0,1,1,0,0], | |
| [2,3,0,0,0,1,1,1,2,0,0,1], | |
| [1,0,1,0,2,2,0,0,5,4,0,0], | |
| [1,0,0,0,0,1,0,0,0,2,2,0], | |
| [4,1,3,1,1,2,0,3,1,0,5,1], | |
| [0,0,5,0,0,0,0,0,0,0,2,0], | |
| [1,0,0,1,2,1,7,1,0,0,2,0], | |
| [1,3,0,0,0,1,5,0,0,0,0,0], | |
| [2,1,0,0,1,0,0,6,9,0,0,0], | |
| ]; | |
| const DUR_BY_HOUR = [ | |
| [0,44,92,0,12,0,14,11,0,38,120,0], | |
| [0,0,0,10,0,0,13,0,42,45,10,0], | |
| [0,38,38,11,0,12,30,0,44,0,34,0], | |
| [0,65,0,14,0,0,0,13,10,0,0,0], | |
| [0,0,120,0,0,0,0,0,80,17,0,11], | |
| [53,0,0,68,0,0,12,10,0,30,73,23], | |
| [76,0,18,0,0,35,10,11,15,0,25,24], | |
| [0,0,0,0,33,0,0,11,10,0,13,0], | |
| [0,0,0,0,10,24,33,0,10,0,0,0], | |
| [10,0,0,0,0,12,17,0,10,0,13,0], | |
| [0,0,0,0,0,0,24,29,42,42,0,0], | |
| ]; | |
| var NIGHTS_DATA = null; | |
| const NIGHTS_DATA_URL = 'https://gist.githubusercontent.com/bojanrajkovic/9641e4f4e3cb43da3276734f0f1e8f5c/raw/nights-data.json'; | |
| // ============================================================ | |
| // NAVIGATION | |
| // ============================================================ | |
| document.querySelectorAll('.nav button').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| document.querySelectorAll('.nav button').forEach(b => b.classList.remove('active')); | |
| document.querySelectorAll('.section').forEach(s => s.classList.remove('active')); | |
| btn.classList.add('active'); | |
| document.getElementById('sec-' + btn.dataset.tab).classList.add('active'); | |
| }); | |
| }); | |
| // ============================================================ | |
| // CHART HELPERS | |
| // ============================================================ | |
| function setupCanvas(id) { | |
| const canvas = document.getElementById(id); | |
| const dpr = window.devicePixelRatio || 1; | |
| const rect = canvas.getBoundingClientRect(); | |
| canvas.width = rect.width * dpr; | |
| canvas.height = rect.height * dpr; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.scale(dpr, dpr); | |
| return { ctx, w: rect.width, h: rect.height }; | |
| } | |
| function drawBarChart(id, data, { color, thresholds, yMax }) { | |
| const { ctx, w, h } = setupCanvas(id); | |
| const pad = { top: 20, right: 16, bottom: 40, left: 40 }; | |
| const cw = w - pad.left - pad.right; | |
| const ch = h - pad.top - pad.bottom; | |
| const max = yMax || Math.max(...data) * 1.2; | |
| const barW = Math.min(cw / data.length * 0.7, 24); | |
| const gap = cw / data.length; | |
| ctx.strokeStyle = 'rgba(255,255,255,0.06)'; | |
| ctx.lineWidth = 1; | |
| for (let i = 0; i <= 4; i++) { | |
| const y = pad.top + ch - (ch * i / 4); | |
| ctx.beginPath(); ctx.moveTo(pad.left, y); ctx.lineTo(w - pad.right, y); ctx.stroke(); | |
| ctx.fillStyle = '#606080'; | |
| ctx.font = '10px monospace'; | |
| ctx.textAlign = 'right'; | |
| ctx.fillText((max * i / 4).toFixed(max > 10 ? 0 : 1), pad.left - 6, y + 3); | |
| } | |
| if (thresholds) { | |
| thresholds.forEach(t => { | |
| const y = pad.top + ch - (ch * t.value / max); | |
| ctx.strokeStyle = t.color || 'rgba(233,69,96,0.4)'; | |
| ctx.setLineDash([4, 4]); | |
| ctx.beginPath(); ctx.moveTo(pad.left, y); ctx.lineTo(w - pad.right, y); ctx.stroke(); | |
| ctx.setLineDash([]); | |
| ctx.fillStyle = t.color || 'rgba(233,69,96,0.6)'; | |
| ctx.font = '10px sans-serif'; | |
| ctx.textAlign = 'left'; | |
| ctx.fillText(t.label, w - pad.right - 60, y - 4); | |
| }); | |
| } | |
| data.forEach((val, i) => { | |
| const x = pad.left + i * gap + (gap - barW) / 2; | |
| const barH = (val / max) * ch; | |
| const y = pad.top + ch - barH; | |
| let c = typeof color === 'function' ? color(val) : color; | |
| ctx.fillStyle = c; | |
| ctx.beginPath(); | |
| ctx.roundRect(x, y, barW, barH, [3, 3, 0, 0]); | |
| ctx.fill(); | |
| ctx.fillStyle = '#606080'; | |
| ctx.font = '9px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.save(); | |
| ctx.translate(x + barW / 2, pad.top + ch + 14); | |
| ctx.rotate(-0.5); | |
| ctx.fillText(LABELS[i], 0, 0); | |
| ctx.restore(); | |
| }); | |
| } | |
| // ============================================================ | |
| // RENDER CHARTS | |
| // ============================================================ | |
| function renderOverview() { | |
| drawBarChart('ahi-chart', AHI, { | |
| color: v => v >= 5 ? '#e94560' : v >= 3 ? '#f0a500' : '#4ecca3', | |
| yMax: 7, | |
| thresholds: [{ value: 5, label: 'AHI >= 5', color: 'rgba(233,69,96,0.4)' }] | |
| }); | |
| drawBarChart('oa-chart', OA_CT, { | |
| color: v => v >= 40 ? '#e94560' : v >= 20 ? '#f0a500' : '#4ecca3', | |
| yMax: 60 | |
| }); | |
| drawBarChart('sessions-chart', SESSIONS, { | |
| color: v => v >= 20 ? '#e94560' : v >= 15 ? '#f0a500' : 'rgba(78,204,163,0.6)', | |
| yMax: 35 | |
| }); | |
| } | |
| // ============================================================ | |
| // HEATMAPS | |
| // ============================================================ | |
| function renderHeatmap(tableId, data, dates, { maxVal, colorFn, emptyChar }) { | |
| const table = document.getElementById(tableId); | |
| emptyChar = emptyChar || '\u00B7'; | |
| let html = '<tr><th></th>'; | |
| HOUR_LABELS.forEach(h => html += '<th>' + h + '</th>'); | |
| html += '<th>Total</th></tr>'; | |
| data.forEach((row, ri) => { | |
| const total = row.reduce((a, b) => a + b, 0); | |
| html += '<tr><td class="date-cell">' + dates[ri] + '</td>'; | |
| row.forEach(val => { | |
| if (val === 0) { | |
| html += '<td style="color:rgba(255,255,255,0.15)">' + emptyChar + '</td>'; | |
| } else { | |
| const intensity = Math.min(val / maxVal, 1); | |
| const bg = colorFn(intensity); | |
| html += '<td style="background:' + bg + ';color:#fff;font-weight:' + (intensity > 0.5 ? 600 : 400) + '">' + val + '</td>'; | |
| } | |
| }); | |
| html += '<td style="color:' + (total > maxVal * 2 ? '#e94560' : '#a0a0b0') + ';font-weight:600">' + total + '</td>'; | |
| html += '</tr>'; | |
| }); | |
| table.innerHTML = html; | |
| } | |
| function renderHeatmaps() { | |
| renderHeatmap('oa-heatmap', OA_BY_HOUR, HM_DATES, { | |
| maxVal: 15, | |
| colorFn: function(i) { return 'rgba(233,69,96,' + (0.15 + i * 0.75) + ')'; } | |
| }); | |
| renderHeatmap('all-events-heatmap', ALL_EV_BY_HOUR, HM_DATES, { | |
| maxVal: 15, | |
| colorFn: function(i) { return 'rgba(240,165,0,' + (0.15 + i * 0.7) + ')'; } | |
| }); | |
| renderHeatmap('sessions-heatmap', SESS_BY_HOUR, HM_DATES, { | |
| maxVal: 7, | |
| colorFn: function(i) { return 'rgba(83,52,131,' + (0.2 + i * 0.7) + ')'; } | |
| }); | |
| renderHeatmap('duration-heatmap', DUR_BY_HOUR, HM_DATES, { | |
| maxVal: 80, | |
| colorFn: function(i) { return 'rgba(240,165,0,' + (0.15 + i * 0.7) + ')'; } | |
| }); | |
| } | |
| // ============================================================ | |
| // TREND CHART | |
| // ============================================================ | |
| function renderTrend() { | |
| const { ctx, w, h } = setupCanvas('trend-chart'); | |
| const pad = { top: 20, right: 50, bottom: 40, left: 50 }; | |
| const cw = w - pad.left - pad.right; | |
| const ch = h - pad.top - pad.bottom; | |
| const oaMax = 60; | |
| const ahiMax = 7; | |
| ctx.strokeStyle = 'rgba(255,255,255,0.06)'; | |
| for (let i = 0; i <= 4; i++) { | |
| const y = pad.top + ch - (ch * i / 4); | |
| ctx.beginPath(); ctx.moveTo(pad.left, y); ctx.lineTo(w - pad.right, y); ctx.stroke(); | |
| ctx.fillStyle = '#e94560'; | |
| ctx.font = '10px monospace'; | |
| ctx.textAlign = 'right'; | |
| ctx.fillText(Math.round(oaMax * i / 4), pad.left - 6, y + 3); | |
| ctx.fillStyle = '#4ecca3'; | |
| ctx.textAlign = 'left'; | |
| ctx.fillText((ahiMax * i / 4).toFixed(1), w - pad.right + 6, y + 3); | |
| } | |
| var weekRanges = [[0, 7], [7, 14], [14, 18]]; | |
| var weekAvgs = [1.5, 2.8, 2.3]; | |
| weekRanges.forEach(function(range, wi) { | |
| var s = range[0], e = range[1]; | |
| var x1 = pad.left + (s / (DATES.length - 1)) * cw; | |
| var x2 = pad.left + ((e - 1) / (DATES.length - 1)) * cw; | |
| var y = pad.top + ch - (weekAvgs[wi] / ahiMax) * ch; | |
| ctx.fillStyle = 'rgba(240,165,0,0.08)'; | |
| ctx.fillRect(x1, y, x2 - x1, pad.top + ch - y); | |
| ctx.strokeStyle = 'rgba(240,165,0,0.4)'; | |
| ctx.setLineDash([3, 3]); | |
| ctx.beginPath(); ctx.moveTo(x1, y); ctx.lineTo(x2, y); ctx.stroke(); | |
| ctx.setLineDash([]); | |
| ctx.fillStyle = 'rgba(240,165,0,0.6)'; | |
| ctx.font = '9px sans-serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('avg ' + weekAvgs[wi], (x1 + x2) / 2, y - 4); | |
| }); | |
| var gap = cw / DATES.length; | |
| var barW = Math.min(gap * 0.5, 18); | |
| OA_CT.forEach(function(val, i) { | |
| var x = pad.left + i * gap + (gap - barW) / 2; | |
| var barH = (val / oaMax) * ch; | |
| var y = pad.top + ch - barH; | |
| ctx.fillStyle = val >= 40 ? 'rgba(233,69,96,0.8)' : 'rgba(233,69,96,0.4)'; | |
| ctx.beginPath(); | |
| ctx.roundRect(x, y, barW, barH, [3, 3, 0, 0]); | |
| ctx.fill(); | |
| }); | |
| ctx.strokeStyle = '#4ecca3'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| AHI.forEach(function(val, i) { | |
| var x = pad.left + i * gap + gap / 2; | |
| var y = pad.top + ch - (val / ahiMax) * ch; | |
| if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); | |
| }); | |
| ctx.stroke(); | |
| AHI.forEach(function(val, i) { | |
| var x = pad.left + i * gap + gap / 2; | |
| var y = pad.top + ch - (val / ahiMax) * ch; | |
| ctx.fillStyle = val >= 5 ? '#e94560' : '#4ecca3'; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, 4, 0, Math.PI * 2); | |
| ctx.fill(); | |
| }); | |
| LABELS.forEach(function(lbl, i) { | |
| var x = pad.left + i * gap + gap / 2; | |
| ctx.fillStyle = '#606080'; | |
| ctx.font = '9px monospace'; | |
| ctx.textAlign = 'center'; | |
| ctx.save(); | |
| ctx.translate(x, pad.top + ch + 14); | |
| ctx.rotate(-0.5); | |
| ctx.fillText(lbl, 0, 0); | |
| ctx.restore(); | |
| }); | |
| ctx.fillStyle = '#e94560'; | |
| ctx.font = '10px sans-serif'; | |
| ctx.save(); | |
| ctx.translate(12, pad.top + ch / 2); | |
| ctx.rotate(-Math.PI / 2); | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('OA Events', 0, 0); | |
| ctx.restore(); | |
| ctx.fillStyle = '#4ecca3'; | |
| ctx.save(); | |
| ctx.translate(w - 8, pad.top + ch / 2); | |
| ctx.rotate(-Math.PI / 2); | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('AHI', 0, 0); | |
| ctx.restore(); | |
| } | |
| // ============================================================ | |
| // NIGHT EXPLORER | |
| // ============================================================ | |
| function timeToMin(t) { | |
| var parts = t.split(':'); | |
| var h = parseInt(parts[0]), m = parseInt(parts[1]); | |
| if (h < 12) h += 24; | |
| return (h - 18) * 60 + m; | |
| } | |
| function sessionDurationMin(s) { | |
| var startM = timeToMin(s.start); | |
| var endM = timeToMin(s.end); | |
| if (endM <= startM) endM += 24 * 60; | |
| return endM - startM; | |
| } | |
| function pressColor(avgPress) { | |
| if (avgPress < 6) return '#533483'; | |
| if (avgPress < 9) return '#f0a500'; | |
| return '#e94560'; | |
| } | |
| function populateNightSelect() { | |
| var select = document.getElementById('night-select'); | |
| if (!NIGHTS_DATA) return; | |
| select.innerHTML = ''; | |
| DATES.forEach(function(d) { | |
| if (!NIGHTS_DATA[d]) return; | |
| var opt = document.createElement('option'); | |
| opt.value = d; | |
| var dateIdx = DATES.indexOf(d); | |
| var ahi = dateIdx >= 0 ? AHI[dateIdx] : '?'; | |
| var oa = dateIdx >= 0 ? OA_CT[dateIdx] : '?'; | |
| opt.textContent = NIGHTS_DATA[d].label + ' — AHI ' + ahi + ', ' + oa + ' OAs, ' + NIGHTS_DATA[d].sessions.length + ' sessions'; | |
| select.appendChild(opt); | |
| }); | |
| // Default to worst night | |
| var worst = DATES.reduce(function(best, d, i) { return AHI[i] > AHI[best] ? i : best; }, 0); | |
| select.value = DATES[worst]; | |
| select.addEventListener('change', function() { renderNight(select.value); }); | |
| renderNight(DATES[worst]); | |
| } | |
| function renderNight(nightKey) { | |
| if (!NIGHTS_DATA || !NIGHTS_DATA[nightKey]) return; | |
| var night = NIGHTS_DATA[nightKey]; | |
| var dateIdx = DATES.indexOf(nightKey); | |
| var ahi = dateIdx >= 0 ? AHI[dateIdx] : null; | |
| var oaCount = dateIdx >= 0 ? OA_CT[dateIdx] : night.totalOA; | |
| var sessions = night.sessions; | |
| // Compute stats | |
| var longestMin = 0; | |
| var totalSleepMin = 0; | |
| sessions.forEach(function(s) { | |
| var dur = sessionDurationMin(s); | |
| totalSleepMin += dur; | |
| if (dur > longestMin) longestMin = dur; | |
| }); | |
| var maxPressure = 0; | |
| sessions.forEach(function(s) { if (s.maxPress > maxPressure) maxPressure = s.maxPress; }); | |
| // Render stats | |
| var statsHtml = ''; | |
| function statClass(val, warnThresh, badThresh, invert) { | |
| if (invert) return val >= badThresh ? 'good' : val >= warnThresh ? 'warn' : 'bad'; | |
| return val >= badThresh ? 'bad' : val >= warnThresh ? 'warn' : 'good'; | |
| } | |
| statsHtml += '<div class="stat ' + statClass(ahi, 3, 5, false) + '"><div class="label">AHI</div><div class="value">' + (ahi !== null ? ahi : '—') + '</div></div>'; | |
| statsHtml += '<div class="stat ' + statClass(oaCount, 15, 30, false) + '"><div class="label">Obstructive Apneas</div><div class="value">' + oaCount + '</div></div>'; | |
| statsHtml += '<div class="stat ' + statClass(sessions.length, 10, 18, false) + '"><div class="label">Sessions</div><div class="value">' + sessions.length + '</div></div>'; | |
| var longestStr = longestMin >= 60 ? Math.floor(longestMin / 60) + 'h ' + (longestMin % 60) + 'm' : longestMin + 'm'; | |
| statsHtml += '<div class="stat ' + statClass(longestMin, 120, 240, true) + '"><div class="label">Longest Session</div><div class="value">' + longestStr + '</div></div>'; | |
| var totalStr = totalSleepMin >= 60 ? Math.floor(totalSleepMin / 60) + 'h ' + (totalSleepMin % 60) + 'm' : totalSleepMin + 'm'; | |
| statsHtml += '<div class="stat ' + statClass(totalSleepMin, 360, 480, true) + '"><div class="label">Total Mask-On</div><div class="value">' + totalStr + '</div></div>'; | |
| document.getElementById('night-stats').innerHTML = statsHtml; | |
| // Render timeline | |
| var container = document.getElementById('night-timeline'); | |
| var totalMin = 14 * 60; // 18:00 to 08:00 | |
| var html = '<div style="position:relative;height:20px;margin-left:80px;margin-bottom:4px;">'; | |
| for (var hh = 18; hh <= 32; hh++) { | |
| var displayH = hh >= 24 ? hh - 24 : hh; | |
| var pct = ((hh - 18) / 14) * 100; | |
| if (hh % 2 === 0 && pct <= 100) { | |
| html += '<span style="position:absolute;left:' + pct + '%;transform:translateX(-50%);font-size:10px;color:#606080;font-family:monospace">' + String(displayH).padStart(2, '0') + ':00</span>'; | |
| } | |
| } | |
| html += '</div>'; | |
| // Single continuous timeline row | |
| html += '<div class="timeline-row" style="height:28px;">'; | |
| html += '<div class="timeline-label">Sessions</div>'; | |
| html += '<div class="timeline-bar-container" style="height:28px;">'; | |
| sessions.forEach(function(s) { | |
| var startM = timeToMin(s.start); | |
| var endM = timeToMin(s.end); | |
| if (endM <= startM) endM += 24 * 60; | |
| var left = Math.max(0, (startM / totalMin) * 100); | |
| var width = Math.max(((endM - startM) / totalMin) * 100, 0.3); | |
| if (left + width > 100) width = 100 - left; | |
| var color = pressColor(s.avgPress); | |
| var dur = endM - startM; | |
| var durStr = dur >= 60 ? Math.floor(dur / 60) + 'h ' + (dur % 60) + 'm' : dur + 'm'; | |
| html += '<div class="timeline-bar" style="left:' + left + '%;width:' + width + '%;background:' + color + ';border-radius:2px;" title="' + s.start + ' – ' + s.end + ' (' + durStr + ') avg ' + s.avgPress + ' max ' + s.maxPress + ' cmH₂O"></div>'; | |
| }); | |
| html += '</div></div>'; | |
| // Per-session detail rows for fragmented nights | |
| if (sessions.length > 1 && sessions.length <= 30) { | |
| html += '<div style="margin-top:16px;font-size:0.75rem;font-family:\'SF Mono\',monospace;color:var(--text2);">'; | |
| html += '<div style="display:grid;grid-template-columns:70px 70px 60px 65px 65px 1fr;gap:4px 12px;padding:4px 0;border-bottom:1px solid var(--border);font-weight:600;color:var(--text);">'; | |
| html += '<span>Start</span><span>End</span><span>Dur</span><span>Avg P</span><span>Max P</span><span></span></div>'; | |
| sessions.forEach(function(s) { | |
| var dur = sessionDurationMin(s); | |
| var durStr = dur >= 60 ? Math.floor(dur / 60) + 'h' + (dur % 60 ? ' ' + (dur % 60) + 'm' : '') : dur + 'm'; | |
| var color = pressColor(s.avgPress); | |
| var barWidth = Math.min((s.maxPress / 11) * 100, 100); | |
| html += '<div style="display:grid;grid-template-columns:70px 70px 60px 65px 65px 1fr;gap:4px 12px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.04);align-items:center;">'; | |
| html += '<span>' + s.start + '</span>'; | |
| html += '<span>' + s.end + '</span>'; | |
| html += '<span>' + durStr + '</span>'; | |
| html += '<span style="color:' + color + '">' + s.avgPress + '</span>'; | |
| html += '<span style="color:' + (s.maxPress >= 10.5 ? '#e94560' : '#a0a0b0') + '">' + s.maxPress + '</span>'; | |
| html += '<div style="height:6px;background:rgba(255,255,255,0.05);border-radius:3px;overflow:hidden;"><div style="width:' + barWidth + '%;height:100%;background:' + color + ';border-radius:3px;"></div></div>'; | |
| html += '</div>'; | |
| }); | |
| html += '</div>'; | |
| } | |
| container.innerHTML = html; | |
| // Callout | |
| var callout = document.getElementById('night-callout'); | |
| var rampSessions = sessions.filter(function(s) { return s.avgPress <= 5; }); | |
| var ceilingHits = sessions.filter(function(s) { return s.maxPress >= 10.8; }); | |
| var calloutHtml = ''; | |
| if (rampSessions.length >= 3 && oaCount > 10) { | |
| calloutHtml += '<div class="finding-box"><h4>Ramp restart cycle detected</h4>'; | |
| calloutHtml += '<p>' + rampSessions.length + ' sessions started at ramp pressure (≤5 cmH₂O). '; | |
| calloutHtml += 'Combined with ' + oaCount + ' obstructive apneas, this suggests the vicious cycle: apnea cluster → wake → mask off → mask on → ramp at 4 cmH₂O → immediate collapse → more apneas.</p></div>'; | |
| } | |
| if (ceilingHits.length >= 3) { | |
| calloutHtml += '<div class="finding-box"><h4>Pressure ceiling reached repeatedly</h4>'; | |
| calloutHtml += '<p>' + ceilingHits.length + ' sessions hit max pressure (≥10.8 cmH₂O). The machine wanted to go higher but is capped at 11. This night needed more pressure than available.</p></div>'; | |
| } | |
| if (sessions.length <= 3 && oaCount <= 5) { | |
| calloutHtml += '<div class="finding-box info"><h4>Good night</h4>'; | |
| calloutHtml += '<p>Only ' + sessions.length + ' session' + (sessions.length > 1 ? 's' : '') + ' with ' + oaCount + ' OA' + (oaCount !== 1 ? 's' : '') + '. Low fragmentation and few events suggest the position and pressure were working well.</p></div>'; | |
| } | |
| callout.innerHTML = calloutHtml; | |
| } | |
| async function loadNightsData() { | |
| try { | |
| var resp = await fetch(NIGHTS_DATA_URL); | |
| NIGHTS_DATA = await resp.json(); | |
| populateNightSelect(); | |
| } catch(e) { | |
| document.getElementById('night-timeline').innerHTML = '<p style="color:var(--bad)">Failed to load session data: ' + e.message + '</p>'; | |
| } | |
| } | |
| // ============================================================ | |
| // INIT | |
| // ============================================================ | |
| function init() { | |
| renderOverview(); | |
| renderHeatmaps(); | |
| renderTrend(); | |
| loadNightsData(); | |
| } | |
| var resizeTimeout; | |
| window.addEventListener('resize', function() { | |
| clearTimeout(resizeTimeout); | |
| resizeTimeout = setTimeout(init, 200); | |
| }); | |
| init(); | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "02-21": { | |
| "label": "Feb 21 \u2192 Feb 22", | |
| "sessions": [ | |
| { | |
| "start": "19:40", | |
| "end": "07:06", | |
| "avgPress": 7.4, | |
| "maxPress": 10.8 | |
| } | |
| ], | |
| "totalOA": 2, | |
| "totalEvents": 9, | |
| "totalSessions": 1 | |
| }, | |
| "02-22": { | |
| "label": "Feb 22 \u2192 Feb 23", | |
| "sessions": [ | |
| { | |
| "start": "19:18", | |
| "end": "20:50", | |
| "avgPress": 5.6, | |
| "maxPress": 8.6 | |
| }, | |
| { | |
| "start": "20:51", | |
| "end": "20:58", | |
| "avgPress": 9.4, | |
| "maxPress": 9.5 | |
| }, | |
| { | |
| "start": "21:00", | |
| "end": "21:02", | |
| "avgPress": 9.5, | |
| "maxPress": 9.5 | |
| }, | |
| { | |
| "start": "21:06", | |
| "end": "21:10", | |
| "avgPress": 9.4, | |
| "maxPress": 9.5 | |
| }, | |
| { | |
| "start": "21:13", | |
| "end": "21:15", | |
| "avgPress": 9.3, | |
| "maxPress": 9.3 | |
| }, | |
| { | |
| "start": "21:21", | |
| "end": "21:34", | |
| "avgPress": 9.8, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "21:36", | |
| "end": "21:44", | |
| "avgPress": 9.8, | |
| "maxPress": 9.9 | |
| }, | |
| { | |
| "start": "21:46", | |
| "end": "21:48", | |
| "avgPress": 10.4, | |
| "maxPress": 10.5 | |
| }, | |
| { | |
| "start": "21:49", | |
| "end": "21:53", | |
| "avgPress": 10.3, | |
| "maxPress": 10.5 | |
| }, | |
| { | |
| "start": "22:02", | |
| "end": "22:06", | |
| "avgPress": 9.9, | |
| "maxPress": 10.2 | |
| }, | |
| { | |
| "start": "22:08", | |
| "end": "22:12", | |
| "avgPress": 9.5, | |
| "maxPress": 9.8 | |
| }, | |
| { | |
| "start": "22:32", | |
| "end": "22:43", | |
| "avgPress": 10.6, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "22:46", | |
| "end": "22:48", | |
| "avgPress": 10.7, | |
| "maxPress": 10.7 | |
| }, | |
| { | |
| "start": "22:53", | |
| "end": "02:43", | |
| "avgPress": 7.8, | |
| "maxPress": 10.7 | |
| }, | |
| { | |
| "start": "02:44", | |
| "end": "05:31", | |
| "avgPress": 7.1, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "05:32", | |
| "end": "07:33", | |
| "avgPress": 7.7, | |
| "maxPress": 9.7 | |
| }, | |
| { | |
| "start": "07:34", | |
| "end": "08:24", | |
| "avgPress": 9.3, | |
| "maxPress": 10.7 | |
| } | |
| ], | |
| "totalOA": 10, | |
| "totalEvents": 16, | |
| "totalSessions": 17 | |
| }, | |
| "02-23": { | |
| "label": "Feb 23 \u2192 Feb 24", | |
| "sessions": [ | |
| { | |
| "start": "20:37", | |
| "end": "21:10", | |
| "avgPress": 7.0, | |
| "maxPress": 7.4 | |
| }, | |
| { | |
| "start": "21:12", | |
| "end": "21:36", | |
| "avgPress": 6.5, | |
| "maxPress": 6.8 | |
| }, | |
| { | |
| "start": "21:39", | |
| "end": "21:44", | |
| "avgPress": 6.4, | |
| "maxPress": 6.4 | |
| }, | |
| { | |
| "start": "21:50", | |
| "end": "22:39", | |
| "avgPress": 9.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "22:40", | |
| "end": "22:41", | |
| "avgPress": 7.6, | |
| "maxPress": 7.6 | |
| }, | |
| { | |
| "start": "22:54", | |
| "end": "22:56", | |
| "avgPress": 7.6, | |
| "maxPress": 7.6 | |
| }, | |
| { | |
| "start": "22:58", | |
| "end": "23:04", | |
| "avgPress": 7.6, | |
| "maxPress": 7.6 | |
| }, | |
| { | |
| "start": "23:07", | |
| "end": "00:40", | |
| "avgPress": 7.7, | |
| "maxPress": 10.5 | |
| }, | |
| { | |
| "start": "00:42", | |
| "end": "00:47", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "00:48", | |
| "end": "00:49", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "00:55", | |
| "end": "03:37", | |
| "avgPress": 6.9, | |
| "maxPress": 8.9 | |
| }, | |
| { | |
| "start": "03:39", | |
| "end": "03:49", | |
| "avgPress": 6.1, | |
| "maxPress": 10.8 | |
| }, | |
| { | |
| "start": "03:50", | |
| "end": "04:00", | |
| "avgPress": 10.7, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "04:27", | |
| "end": "04:28", | |
| "avgPress": 10.5, | |
| "maxPress": 10.5 | |
| }, | |
| { | |
| "start": "04:35", | |
| "end": "05:23", | |
| "avgPress": 8.2, | |
| "maxPress": 10.5 | |
| }, | |
| { | |
| "start": "05:24", | |
| "end": "05:25", | |
| "avgPress": 8.6, | |
| "maxPress": 8.6 | |
| }, | |
| { | |
| "start": "05:33", | |
| "end": "05:34", | |
| "avgPress": 8.6, | |
| "maxPress": 8.6 | |
| }, | |
| { | |
| "start": "06:02", | |
| "end": "06:48", | |
| "avgPress": 9.2, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 9, | |
| "totalEvents": 15, | |
| "totalSessions": 18 | |
| }, | |
| "02-24": { | |
| "label": "Feb 24 \u2192 Feb 25", | |
| "sessions": [ | |
| { | |
| "start": "19:31", | |
| "end": "19:34", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:36", | |
| "end": "19:41", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:43", | |
| "end": "19:45", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:47", | |
| "end": "19:48", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:00", | |
| "end": "20:02", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:04", | |
| "end": "20:08", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:09", | |
| "end": "20:39", | |
| "avgPress": 5.5, | |
| "maxPress": 6.6 | |
| }, | |
| { | |
| "start": "20:40", | |
| "end": "20:44", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:44", | |
| "end": "20:59", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "21:05", | |
| "end": "21:06", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "21:08", | |
| "end": "21:09", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "21:12", | |
| "end": "22:54", | |
| "avgPress": 6.4, | |
| "maxPress": 7.2 | |
| }, | |
| { | |
| "start": "22:55", | |
| "end": "22:56", | |
| "avgPress": 6.4, | |
| "maxPress": 6.4 | |
| }, | |
| { | |
| "start": "23:10", | |
| "end": "23:12", | |
| "avgPress": 6.4, | |
| "maxPress": 6.4 | |
| }, | |
| { | |
| "start": "23:18", | |
| "end": "01:20", | |
| "avgPress": 7.4, | |
| "maxPress": 9.2 | |
| }, | |
| { | |
| "start": "01:21", | |
| "end": "01:27", | |
| "avgPress": 6.6, | |
| "maxPress": 6.6 | |
| }, | |
| { | |
| "start": "01:31", | |
| "end": "01:32", | |
| "avgPress": 6.6, | |
| "maxPress": 6.6 | |
| }, | |
| { | |
| "start": "01:34", | |
| "end": "02:31", | |
| "avgPress": 7.1, | |
| "maxPress": 9.4 | |
| }, | |
| { | |
| "start": "02:34", | |
| "end": "03:46", | |
| "avgPress": 9.8, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:49", | |
| "end": "03:53", | |
| "avgPress": 10.7, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:57", | |
| "end": "04:03", | |
| "avgPress": 10.1, | |
| "maxPress": 10.7 | |
| }, | |
| { | |
| "start": "04:12", | |
| "end": "04:13", | |
| "avgPress": 9.9, | |
| "maxPress": 9.9 | |
| }, | |
| { | |
| "start": "04:15", | |
| "end": "04:16", | |
| "avgPress": 9.9, | |
| "maxPress": 9.9 | |
| }, | |
| { | |
| "start": "04:18", | |
| "end": "05:37", | |
| "avgPress": 8.0, | |
| "maxPress": 9.9 | |
| }, | |
| { | |
| "start": "05:38", | |
| "end": "05:46", | |
| "avgPress": 8.3, | |
| "maxPress": 9.0 | |
| }, | |
| { | |
| "start": "05:48", | |
| "end": "05:49", | |
| "avgPress": 9.0, | |
| "maxPress": 9.0 | |
| }, | |
| { | |
| "start": "05:51", | |
| "end": "06:21", | |
| "avgPress": 7.8, | |
| "maxPress": 9.0 | |
| }, | |
| { | |
| "start": "06:22", | |
| "end": "06:38", | |
| "avgPress": 9.7, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 12, | |
| "totalEvents": 14, | |
| "totalSessions": 28 | |
| }, | |
| "02-25": { | |
| "label": "Feb 25 \u2192 Feb 26", | |
| "sessions": [ | |
| { | |
| "start": "19:14", | |
| "end": "20:15", | |
| "avgPress": 8.5, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "20:21", | |
| "end": "20:29", | |
| "avgPress": 7.2, | |
| "maxPress": 7.6 | |
| }, | |
| { | |
| "start": "20:35", | |
| "end": "21:52", | |
| "avgPress": 6.9, | |
| "maxPress": 7.9 | |
| }, | |
| { | |
| "start": "21:53", | |
| "end": "21:54", | |
| "avgPress": 6.9, | |
| "maxPress": 6.9 | |
| }, | |
| { | |
| "start": "21:55", | |
| "end": "23:35", | |
| "avgPress": 7.5, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "23:36", | |
| "end": "00:25", | |
| "avgPress": 9.8, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "00:39", | |
| "end": "02:00", | |
| "avgPress": 8.2, | |
| "maxPress": 10.2 | |
| }, | |
| { | |
| "start": "02:04", | |
| "end": "02:05", | |
| "avgPress": 10.0, | |
| "maxPress": 10.0 | |
| }, | |
| { | |
| "start": "02:06", | |
| "end": "02:10", | |
| "avgPress": 10.1, | |
| "maxPress": 10.2 | |
| }, | |
| { | |
| "start": "02:11", | |
| "end": "02:13", | |
| "avgPress": 10.1, | |
| "maxPress": 10.1 | |
| }, | |
| { | |
| "start": "02:17", | |
| "end": "02:19", | |
| "avgPress": 10.1, | |
| "maxPress": 10.1 | |
| }, | |
| { | |
| "start": "02:25", | |
| "end": "02:59", | |
| "avgPress": 9.1, | |
| "maxPress": 10.1 | |
| }, | |
| { | |
| "start": "03:06", | |
| "end": "03:10", | |
| "avgPress": 8.6, | |
| "maxPress": 9.1 | |
| }, | |
| { | |
| "start": "03:16", | |
| "end": "03:17", | |
| "avgPress": 10.9, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:29", | |
| "end": "03:31", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:34", | |
| "end": "03:35", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:44", | |
| "end": "03:46", | |
| "avgPress": 10.8, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:49", | |
| "end": "04:37", | |
| "avgPress": 8.7, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "04:40", | |
| "end": "04:46", | |
| "avgPress": 10.5, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "04:46", | |
| "end": "04:47", | |
| "avgPress": 10.3, | |
| "maxPress": 10.3 | |
| }, | |
| { | |
| "start": "04:53", | |
| "end": "04:57", | |
| "avgPress": 9.4, | |
| "maxPress": 10.3 | |
| }, | |
| { | |
| "start": "04:59", | |
| "end": "05:00", | |
| "avgPress": 9.2, | |
| "maxPress": 9.2 | |
| }, | |
| { | |
| "start": "05:04", | |
| "end": "05:40", | |
| "avgPress": 9.5, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:45", | |
| "end": "06:31", | |
| "avgPress": 10.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "06:34", | |
| "end": "06:39", | |
| "avgPress": 10.0, | |
| "maxPress": 10.8 | |
| } | |
| ], | |
| "totalOA": 43, | |
| "totalEvents": 43, | |
| "totalSessions": 25 | |
| }, | |
| "02-26": { | |
| "label": "Feb 26 \u2192 Feb 27", | |
| "sessions": [ | |
| { | |
| "start": "19:18", | |
| "end": "19:20", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:22", | |
| "end": "19:27", | |
| "avgPress": 6.8, | |
| "maxPress": 7.5 | |
| }, | |
| { | |
| "start": "19:30", | |
| "end": "19:33", | |
| "avgPress": 7.2, | |
| "maxPress": 7.5 | |
| }, | |
| { | |
| "start": "19:53", | |
| "end": "19:54", | |
| "avgPress": 7.2, | |
| "maxPress": 7.2 | |
| }, | |
| { | |
| "start": "20:02", | |
| "end": "20:03", | |
| "avgPress": 6.3, | |
| "maxPress": 7.2 | |
| }, | |
| { | |
| "start": "20:08", | |
| "end": "20:12", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:19", | |
| "end": "00:04", | |
| "avgPress": 6.7, | |
| "maxPress": 9.1 | |
| }, | |
| { | |
| "start": "00:05", | |
| "end": "00:09", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "00:11", | |
| "end": "00:41", | |
| "avgPress": 4.5, | |
| "maxPress": 6.1 | |
| }, | |
| { | |
| "start": "00:42", | |
| "end": "00:43", | |
| "avgPress": 6.1, | |
| "maxPress": 6.1 | |
| }, | |
| { | |
| "start": "00:45", | |
| "end": "06:41", | |
| "avgPress": 7.8, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 5, | |
| "totalEvents": 11, | |
| "totalSessions": 11 | |
| }, | |
| "02-27": { | |
| "label": "Feb 27 \u2192 Feb 28", | |
| "sessions": [ | |
| { | |
| "start": "19:58", | |
| "end": "20:02", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:03", | |
| "end": "20:05", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:10", | |
| "end": "22:58", | |
| "avgPress": 6.9, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "23:08", | |
| "end": "23:49", | |
| "avgPress": 7.5, | |
| "maxPress": 8.9 | |
| }, | |
| { | |
| "start": "23:49", | |
| "end": "01:04", | |
| "avgPress": 6.4, | |
| "maxPress": 9.0 | |
| }, | |
| { | |
| "start": "01:06", | |
| "end": "01:09", | |
| "avgPress": 8.9, | |
| "maxPress": 8.9 | |
| }, | |
| { | |
| "start": "01:11", | |
| "end": "01:12", | |
| "avgPress": 8.9, | |
| "maxPress": 8.9 | |
| }, | |
| { | |
| "start": "01:16", | |
| "end": "01:22", | |
| "avgPress": 9.5, | |
| "maxPress": 10.3 | |
| }, | |
| { | |
| "start": "01:24", | |
| "end": "01:37", | |
| "avgPress": 10.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "01:38", | |
| "end": "01:44", | |
| "avgPress": 10.6, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "02:19", | |
| "end": "02:20", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "02:37", | |
| "end": "02:38", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "02:44", | |
| "end": "02:45", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "02:55", | |
| "end": "02:56", | |
| "avgPress": 7.8, | |
| "maxPress": 7.8 | |
| }, | |
| { | |
| "start": "02:59", | |
| "end": "03:00", | |
| "avgPress": 7.8, | |
| "maxPress": 7.8 | |
| }, | |
| { | |
| "start": "03:18", | |
| "end": "04:26", | |
| "avgPress": 8.2, | |
| "maxPress": 10.5 | |
| }, | |
| { | |
| "start": "04:27", | |
| "end": "07:27", | |
| "avgPress": 8.9, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 10, | |
| "totalEvents": 13, | |
| "totalSessions": 17 | |
| }, | |
| "02-28": { | |
| "label": "Feb 28 \u2192 Mar 1", | |
| "sessions": [ | |
| { | |
| "start": "20:10", | |
| "end": "20:15", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:16", | |
| "end": "20:37", | |
| "avgPress": 9.7, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "20:39", | |
| "end": "20:40", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "21:42", | |
| "end": "04:56", | |
| "avgPress": 8.2, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "04:57", | |
| "end": "04:58", | |
| "avgPress": 8.3, | |
| "maxPress": 8.3 | |
| }, | |
| { | |
| "start": "07:21", | |
| "end": "07:23", | |
| "avgPress": 8.2, | |
| "maxPress": 8.3 | |
| } | |
| ], | |
| "totalOA": 12, | |
| "totalEvents": 22, | |
| "totalSessions": 6 | |
| }, | |
| "03-01": { | |
| "label": "Mar 1 \u2192 Mar 2", | |
| "sessions": [ | |
| { | |
| "start": "19:35", | |
| "end": "19:42", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:45", | |
| "end": "19:50", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:24", | |
| "end": "20:25", | |
| "avgPress": 6.0, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "20:32", | |
| "end": "20:37", | |
| "avgPress": 6.0, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "20:41", | |
| "end": "20:45", | |
| "avgPress": 6.0, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "21:17", | |
| "end": "21:25", | |
| "avgPress": 6.0, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "21:31", | |
| "end": "21:33", | |
| "avgPress": 6.1, | |
| "maxPress": 6.1 | |
| }, | |
| { | |
| "start": "21:36", | |
| "end": "22:02", | |
| "avgPress": 6.7, | |
| "maxPress": 7.0 | |
| }, | |
| { | |
| "start": "22:03", | |
| "end": "22:29", | |
| "avgPress": 5.6, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "22:31", | |
| "end": "22:32", | |
| "avgPress": 6.0, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "22:33", | |
| "end": "22:35", | |
| "avgPress": 6.0, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "22:39", | |
| "end": "01:22", | |
| "avgPress": 8.8, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "01:22", | |
| "end": "03:19", | |
| "avgPress": 8.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:21", | |
| "end": "03:40", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "03:44", | |
| "end": "03:46", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "03:47", | |
| "end": "06:44", | |
| "avgPress": 8.5, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 13, | |
| "totalEvents": 20, | |
| "totalSessions": 16 | |
| }, | |
| "03-02": { | |
| "label": "Mar 2 \u2192 Mar 3", | |
| "sessions": [ | |
| { | |
| "start": "19:02", | |
| "end": "19:05", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:09", | |
| "end": "19:52", | |
| "avgPress": 8.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "19:53", | |
| "end": "20:33", | |
| "avgPress": 9.9, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "20:36", | |
| "end": "20:38", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "20:41", | |
| "end": "20:49", | |
| "avgPress": 10.8, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "20:57", | |
| "end": "20:59", | |
| "avgPress": 10.6, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "21:09", | |
| "end": "21:11", | |
| "avgPress": 9.5, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "21:25", | |
| "end": "21:26", | |
| "avgPress": 10.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "21:28", | |
| "end": "21:54", | |
| "avgPress": 9.9, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "21:56", | |
| "end": "22:12", | |
| "avgPress": 5.6, | |
| "maxPress": 7.1 | |
| }, | |
| { | |
| "start": "22:21", | |
| "end": "01:16", | |
| "avgPress": 7.9, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "01:17", | |
| "end": "03:50", | |
| "avgPress": 7.6, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:58", | |
| "end": "04:36", | |
| "avgPress": 9.6, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "04:37", | |
| "end": "05:47", | |
| "avgPress": 8.4, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 36, | |
| "totalEvents": 41, | |
| "totalSessions": 14 | |
| }, | |
| "03-03": { | |
| "label": "Mar 3 \u2192 Mar 4", | |
| "sessions": [ | |
| { | |
| "start": "19:17", | |
| "end": "19:22", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:23", | |
| "end": "19:31", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "20:16", | |
| "end": "20:23", | |
| "avgPress": 6.0, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "20:29", | |
| "end": "20:31", | |
| "avgPress": 8.7, | |
| "maxPress": 8.7 | |
| }, | |
| { | |
| "start": "20:36", | |
| "end": "00:47", | |
| "avgPress": 8.2, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "00:49", | |
| "end": "01:43", | |
| "avgPress": 8.3, | |
| "maxPress": 10.9 | |
| }, | |
| { | |
| "start": "01:53", | |
| "end": "02:04", | |
| "avgPress": 8.2, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "02:08", | |
| "end": "03:05", | |
| "avgPress": 8.5, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "03:08", | |
| "end": "03:10", | |
| "avgPress": 10.4, | |
| "maxPress": 10.5 | |
| }, | |
| { | |
| "start": "03:14", | |
| "end": "06:05", | |
| "avgPress": 8.2, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "06:07", | |
| "end": "06:40", | |
| "avgPress": 8.5, | |
| "maxPress": 9.1 | |
| } | |
| ], | |
| "totalOA": 9, | |
| "totalEvents": 13, | |
| "totalSessions": 11 | |
| }, | |
| "03-04": { | |
| "label": "Mar 4 \u2192 Mar 5", | |
| "sessions": [ | |
| { | |
| "start": "19:32", | |
| "end": "21:02", | |
| "avgPress": 7.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "21:02", | |
| "end": "21:17", | |
| "avgPress": 4.8, | |
| "maxPress": 6.0 | |
| }, | |
| { | |
| "start": "23:31", | |
| "end": "23:34", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "23:37", | |
| "end": "00:22", | |
| "avgPress": 6.6, | |
| "maxPress": 8.1 | |
| }, | |
| { | |
| "start": "00:27", | |
| "end": "00:32", | |
| "avgPress": 8.1, | |
| "maxPress": 8.1 | |
| }, | |
| { | |
| "start": "00:33", | |
| "end": "03:15", | |
| "avgPress": 7.6, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:18", | |
| "end": "03:44", | |
| "avgPress": 10.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:47", | |
| "end": "03:48", | |
| "avgPress": 10.6, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "03:50", | |
| "end": "03:51", | |
| "avgPress": 10.6, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "03:54", | |
| "end": "03:57", | |
| "avgPress": 10.6, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "03:59", | |
| "end": "04:00", | |
| "avgPress": 10.6, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "04:12", | |
| "end": "04:25", | |
| "avgPress": 9.9, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "04:27", | |
| "end": "04:30", | |
| "avgPress": 10.8, | |
| "maxPress": 10.8 | |
| }, | |
| { | |
| "start": "04:32", | |
| "end": "04:46", | |
| "avgPress": 10.5, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "04:57", | |
| "end": "06:40", | |
| "avgPress": 8.7, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 13, | |
| "totalEvents": 15, | |
| "totalSessions": 15 | |
| }, | |
| "03-05": { | |
| "label": "Mar 5 \u2192 Mar 6", | |
| "sessions": [ | |
| { | |
| "start": "19:34", | |
| "end": "22:30", | |
| "avgPress": 8.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "00:32", | |
| "end": "04:07", | |
| "avgPress": 7.2, | |
| "maxPress": 10.4 | |
| }, | |
| { | |
| "start": "04:09", | |
| "end": "04:10", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "04:14", | |
| "end": "05:12", | |
| "avgPress": 8.7, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:20", | |
| "end": "05:41", | |
| "avgPress": 10.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:50", | |
| "end": "06:10", | |
| "avgPress": 10.5, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 22, | |
| "totalEvents": 28, | |
| "totalSessions": 6 | |
| }, | |
| "03-06": { | |
| "label": "Mar 6 \u2192 Mar 7", | |
| "sessions": [ | |
| { | |
| "start": "19:37", | |
| "end": "19:39", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:42", | |
| "end": "19:44", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:48", | |
| "end": "19:50", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "19:52", | |
| "end": "19:57", | |
| "avgPress": 4.3, | |
| "maxPress": 5.9 | |
| }, | |
| { | |
| "start": "20:00", | |
| "end": "20:57", | |
| "avgPress": 9.0, | |
| "maxPress": 10.9 | |
| }, | |
| { | |
| "start": "21:35", | |
| "end": "21:41", | |
| "avgPress": 8.9, | |
| "maxPress": 10.7 | |
| }, | |
| { | |
| "start": "21:44", | |
| "end": "21:51", | |
| "avgPress": 10.5, | |
| "maxPress": 10.6 | |
| }, | |
| { | |
| "start": "21:52", | |
| "end": "21:55", | |
| "avgPress": 10.9, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "22:02", | |
| "end": "22:40", | |
| "avgPress": 9.8, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "23:43", | |
| "end": "00:40", | |
| "avgPress": 7.1, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "00:40", | |
| "end": "00:47", | |
| "avgPress": 10.6, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "00:51", | |
| "end": "02:22", | |
| "avgPress": 8.7, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "02:23", | |
| "end": "02:47", | |
| "avgPress": 8.6, | |
| "maxPress": 10.2 | |
| }, | |
| { | |
| "start": "02:50", | |
| "end": "02:54", | |
| "avgPress": 10.4, | |
| "maxPress": 10.7 | |
| }, | |
| { | |
| "start": "02:56", | |
| "end": "02:58", | |
| "avgPress": 10.5, | |
| "maxPress": 10.7 | |
| }, | |
| { | |
| "start": "03:02", | |
| "end": "05:21", | |
| "avgPress": 9.9, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:22", | |
| "end": "05:32", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:33", | |
| "end": "05:35", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:36", | |
| "end": "05:45", | |
| "avgPress": 10.8, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:47", | |
| "end": "05:51", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:58", | |
| "end": "05:59", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "06:06", | |
| "end": "06:37", | |
| "avgPress": 10.6, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 49, | |
| "totalEvents": 50, | |
| "totalSessions": 22 | |
| }, | |
| "03-07": { | |
| "label": "Mar 7 \u2192 Mar 8", | |
| "sessions": [ | |
| { | |
| "start": "18:56", | |
| "end": "21:05", | |
| "avgPress": 8.2, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "21:08", | |
| "end": "21:10", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "21:10", | |
| "end": "21:19", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "21:21", | |
| "end": "21:22", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "21:24", | |
| "end": "21:29", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "21:29", | |
| "end": "05:34", | |
| "avgPress": 8.1, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:35", | |
| "end": "05:56", | |
| "avgPress": 10.1, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "05:59", | |
| "end": "06:45", | |
| "avgPress": 8.8, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 15, | |
| "totalEvents": 24, | |
| "totalSessions": 8 | |
| }, | |
| "03-08": { | |
| "label": "Mar 8 \u2192 Mar 9", | |
| "sessions": [ | |
| { | |
| "start": "18:43", | |
| "end": "18:47", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "18:53", | |
| "end": "18:54", | |
| "avgPress": 8.5, | |
| "maxPress": 10.0 | |
| }, | |
| { | |
| "start": "18:55", | |
| "end": "19:49", | |
| "avgPress": 9.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "19:50", | |
| "end": "22:03", | |
| "avgPress": 8.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "22:06", | |
| "end": "23:58", | |
| "avgPress": 8.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "23:59", | |
| "end": "00:52", | |
| "avgPress": 6.6, | |
| "maxPress": 7.7 | |
| }, | |
| { | |
| "start": "00:55", | |
| "end": "01:08", | |
| "avgPress": 9.6, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "01:11", | |
| "end": "01:14", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "01:15", | |
| "end": "01:16", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "01:17", | |
| "end": "01:25", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "01:26", | |
| "end": "01:28", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "01:30", | |
| "end": "01:39", | |
| "avgPress": 4.1, | |
| "maxPress": 5.2 | |
| }, | |
| { | |
| "start": "01:40", | |
| "end": "01:47", | |
| "avgPress": 7.0, | |
| "maxPress": 8.6 | |
| }, | |
| { | |
| "start": "01:49", | |
| "end": "01:52", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "02:02", | |
| "end": "05:32", | |
| "avgPress": 7.6, | |
| "maxPress": 10.2 | |
| }, | |
| { | |
| "start": "05:34", | |
| "end": "05:35", | |
| "avgPress": 9.5, | |
| "maxPress": 9.5 | |
| }, | |
| { | |
| "start": "05:40", | |
| "end": "05:41", | |
| "avgPress": 9.5, | |
| "maxPress": 9.5 | |
| } | |
| ], | |
| "totalOA": 12, | |
| "totalEvents": 15, | |
| "totalSessions": 17 | |
| }, | |
| "03-09": { | |
| "label": "Mar 9 \u2192 Mar 10", | |
| "sessions": [ | |
| { | |
| "start": "18:31", | |
| "end": "19:17", | |
| "avgPress": 5.2, | |
| "maxPress": 6.8 | |
| }, | |
| { | |
| "start": "19:18", | |
| "end": "20:22", | |
| "avgPress": 8.2, | |
| "maxPress": 10.8 | |
| }, | |
| { | |
| "start": "20:22", | |
| "end": "20:26", | |
| "avgPress": 7.2, | |
| "maxPress": 7.2 | |
| }, | |
| { | |
| "start": "20:28", | |
| "end": "20:29", | |
| "avgPress": 7.2, | |
| "maxPress": 7.2 | |
| }, | |
| { | |
| "start": "20:51", | |
| "end": "00:58", | |
| "avgPress": 7.3, | |
| "maxPress": 9.2 | |
| }, | |
| { | |
| "start": "00:59", | |
| "end": "01:22", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "01:24", | |
| "end": "01:30", | |
| "avgPress": 7.2, | |
| "maxPress": 9.8 | |
| }, | |
| { | |
| "start": "01:32", | |
| "end": "01:43", | |
| "avgPress": 9.5, | |
| "maxPress": 9.8 | |
| }, | |
| { | |
| "start": "01:44", | |
| "end": "01:49", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "01:53", | |
| "end": "01:55", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "01:56", | |
| "end": "05:44", | |
| "avgPress": 7.4, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 11, | |
| "totalEvents": 15, | |
| "totalSessions": 11 | |
| }, | |
| "03-10": { | |
| "label": "Mar 10 \u2192 Mar 11", | |
| "sessions": [ | |
| { | |
| "start": "18:38", | |
| "end": "19:34", | |
| "avgPress": 6.5, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "19:35", | |
| "end": "19:38", | |
| "avgPress": 10.6, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "19:39", | |
| "end": "20:49", | |
| "avgPress": 7.2, | |
| "maxPress": 10.4 | |
| }, | |
| { | |
| "start": "20:51", | |
| "end": "23:16", | |
| "avgPress": 7.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "23:17", | |
| "end": "02:16", | |
| "avgPress": 8.3, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "02:17", | |
| "end": "02:29", | |
| "avgPress": 7.4, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "02:31", | |
| "end": "02:37", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "02:38", | |
| "end": "02:39", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "02:41", | |
| "end": "02:47", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "02:49", | |
| "end": "02:52", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "02:58", | |
| "end": "03:02", | |
| "avgPress": 11.0, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:04", | |
| "end": "03:07", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "03:10", | |
| "end": "03:14", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "03:14", | |
| "end": "03:21", | |
| "avgPress": 6.8, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:39", | |
| "end": "03:41", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "03:41", | |
| "end": "03:42", | |
| "avgPress": 4.0, | |
| "maxPress": 4.0 | |
| }, | |
| { | |
| "start": "03:44", | |
| "end": "03:45", | |
| "avgPress": 4.5, | |
| "maxPress": 4.9 | |
| }, | |
| { | |
| "start": "03:48", | |
| "end": "03:58", | |
| "avgPress": 10.4, | |
| "maxPress": 11.0 | |
| }, | |
| { | |
| "start": "03:59", | |
| "end": "05:39", | |
| "avgPress": 8.7, | |
| "maxPress": 11.0 | |
| } | |
| ], | |
| "totalOA": 53, | |
| "totalEvents": 57, | |
| "totalSessions": 19 | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment