Skip to content
Back to About

How AirwayLab computes its metrics

Every number AirwayLab shows you comes from code you can read. The analysis engine is open source under GPL-3.0 at github.com/airwaylab-app/airwaylab. This page walks through each metric: what goes in, what the formula does, and where the colour bands sit. It also says what the number can and cannot tell you.

Two rules apply to everything below. Every metric carries a plain-language “what this means and does it matter” line. And nothing here is a medical conclusion or a therapy instruction. Your clinician can help interpret these findings in context.

This page documents the public engine snapshot, version 0.11.0 for the flow engines and 0.9.0 for oximetry, as recorded in lib/engine-version.ts. The hosted app at airwaylab.app runs engine 0.13.0. When a formula changes, the version bumps and your nights get re-analysed.

Where the hosted app is ahead of the public snapshot

Four changes are live in the hosted app and have not yet been published to the engine repository. Everything else on this page matches both.

  • FL Score colour bands sit at 60 and 70 instead of 30 and 50, because the night-average FL Score clusters around 54 even for unobstructed breathing. The thresholds table below already shows the live bands.
  • IFL Symptom Risk rescales the FL Score against that floor of 54 before weighting, so unobstructed breathing adds no risk. This is version 2 of the composite formula.
  • BiPAP premature and late cycling are scored from when pressure leaves the IPAP plateau relative to the flow zero-crossing: late when more than 50 ms after it, premature when more than 300 ms before it. The public snapshot uses the pressure-threshold rule described below.
  • Breath detection in the NED engine gained an onset floor and an amplitude gate against a 120-second baseline, which drops fewer real breaths on noisy nights.

Disclaimer

AirwayLab is not a medical device. It is not FDA or CE cleared. All analysis is for informational and educational purposes only. Always discuss results with your sleep physician before making therapy changes.

Which numbers come from the device and which does AirwayLab compute?

Your machine already reports a set of numbers in its summary file (STR.edf on ResMed). AirwayLab shows those unchanged. Everything else is computed from the raw flow waveform, the pressure channel, or your oximeter file.

SourceMetrics
Reported by the device, shown unchangedAHI, hypopnea index, obstructive and central apnea indices, machine RERA index, Cheyne-Stokes %, leak (50th/70th/95th percentile and max), minute ventilation, respiratory rate, tidal volume, inspiratory time, I:E ratio, mask pressure, usage hours, mask on/off events, prescribed settings (mode, pressures, EPR, rise time, trigger and cycle sensitivity). Reader: lib/parsers/machine-summary-extractor.ts.
Computed by AirwayLab from the 25 Hz flow waveformGlasgow Index, WAT scores (FL Score, Regularity, Periodicity), NED, Flatness Index, RERA-like sequence index, Estimated Arousal Index, algorithm hypopneas and brief obstructions, amplitude stability, IFL Symptom Risk, Estimated RDI.
Computed by AirwayLab from flow plus pressure (bilevel only)Detected EPAP and IPAP, trigger delay, auto-trigger %, time at IPAP, premature and late cycling, tidal-volume proxy and its variability, end-expiratory pressure.
Computed by AirwayLab from a device signalSpontaneous % and Timed % on AirCurve bilevel machines. Read from the per-breath trigger/cycle channel and aggregated by us. The event-code mapping comes from the OSCAR project and has not been validated against an independent reference.
Computed by AirwayLab from your oximeter CSVODI-3, ODI-4, time below 90% and 94%, mean and minimum SpO2, heart-rate surges, coupled desaturation-and-surge events.

Plain-language line: If a number matches your machine’s own report, it is the device’s number. If it is new to you, we computed it and the sections below say how.

How does AirwayLab find each breath?

Every flow-based metric starts by cutting the night into breaths. The flow signal is sampled 25 times per second. An inspiration starts where flow crosses zero going up and ends where it crosses zero going down. Breaths shorter than about 0.3 to 0.4 seconds, or with almost no flow, are dropped as noise. Gaps in the recording, for example mask-off periods, are kept as gaps and never bridged.

Each engine keeps its own segmenter with slightly different minimums, so per-breath counts can differ by a few breaths between engines. Code: glasgow-index.ts, wat-engine.ts, ned-engine.ts.

Plain-language line: Breath detection is the foundation. If your night has heavy leak or a lot of mask-off time, fewer clean breaths get scored and every metric on this page gets noisier.

How is the Glasgow Index computed?

Ported from the open-source GlasgowIndex tool by DaveSkvn (GPL-3.0). Each breath is checked for nine shape traits. Each trait becomes a score from 0 to 1, the share of breaths that show it. The nine scores are summed, so the overall index runs from 0 to 9. The original tool summarised on a 0 to 3 scale. We keep the full sum for granularity, which is why typical nights score 0 to 3 and scores above 3 are rare.

ComponentWhat it checksFlags a breath when
SkewLeft/right volume asymmetryleft-half volume share is below 45% or above 55%
Top heavyVolume in the top 90% bandmore than 40% of the breath sits in the top band
Flat topVariance of the middle 50% of the inspirationvariance below 0.75
SpikeSharp early peakless than 20% of the breath sits in the top 90% band
Multi-peakExtra bumps on the inspiratory curvea second peak bump is found
No pauseRest between expiration end and next inspirationfewer than 10 samples of rest
Inspiratory rateBreaths per minutemore than 20
Multi-breathInspiration without a matching exhaleno linked exhale found
Variable amplitude5-breath rolling amplitude variancevariance above 4

Colour bands: green up to 1.0, amber up to 2.0, red above. Multi-session nights are averaged by duration.

Plain-language line: The Glasgow Index counts how many of your breaths look distorted. A higher score means more breaths with an odd shape. We now rank it below the direct NED analysis, because NED reads the same thing straight from the 25 Hz signal. Treat Glasgow as a second opinion, not the headline.

How are the WAT scores computed?

WAT stands for Wobble Analysis Tool. It produces three scores, each 0 to 100.

MetricComputed fromHowRangeWhat this means and does it matter
FL ScoreEach inspiration’s top half (samples above 50% of peak)Variance of that top region. Score = (0.05 - variance) / 0.05 x 100, clamped 0 to 100, then averaged over the night. A flat top gives low variance and a high score.0-100An uncalibrated relative index. Scores cluster between roughly 50 and 65 even for unobstructed breathing. It is not a percentage of flow-limited breaths. Compare your own nights, never the absolute number.
Regularity ScoreMinute ventilation in 60-second windows stepped every 5 secondsSample Entropy (m = 2, r = 0.2 x SD) of that series. Score = 100 - (SampEn / 2.5) x 100, clamped.0-100Higher means a steadier breathing rhythm through the night. No established clinical threshold exists for it.
Periodicity IndexSame minute-ventilation series, detrendedFFT power in the 0.01 to 0.03 Hz band (30 to 100 second cycles) as a share of total power, x 200, capped at 100.0-100Higher means more of your ventilation swings in slow waves, the pattern seen in periodic breathing. No established clinical threshold exists for it.

Both series metrics return zero when the night is too short for four windows. Colour bands for the FL Score are in the thresholds table below. Regularity and Periodicity share the same shape at 30/50 and 20/40. Reference: Richman and Moorman (2000) for Sample Entropy. Code: wat-engine.ts.

How are NED, Flatness Index and RERA-like sequences computed?

NED is negative effort dependence: more effort, less air. For each breath, AirwayLab compares peak inspiratory flow with the flow at the midpoint of inspiration.

MetricUnitHowCut-offs
NED%(Qpeak - Qmid) / Qpeak x 100, where Qmid is the flow at 50% of inspiratory timeClear flow limitation at 34% or more, borderline 10 to 34%
Flatness Index (FI)ratio 0-1Mean inspiratory flow divided by peak flow0.85 or more counts as flow limited
Combined flow limitation %% of breathsNED at or above 34% OR FI at or above 0.85green up to 20%, amber up to 40%
M-shape %% of breathsA dip below 80% of peak between 25% and 75% of inspiration, with flow above threshold on both sidesNo established clinical threshold
Early-peak % (Tpeak/Ti)% of breathsPeak reached before 45% of inspiratory timeContext only, see limitations
RERA-like sequence indexevents per hourRuns of 3 to 15 consecutive breaths with NED above 20% or FI at or above 0.85. A run counts when any one holds: NED rises along the run (slope above 0.5). The run ends in a recovery breath (NED below 10%) plus a sigh (peak above 1.5 x mean). Any breath in the run passes 34%.green up to 5, amber up to 10

Colour bands for NED mean: green up to 15%, amber up to 25%. Code: ned-engine.ts.

Plain-language line: NED tells you how often the airway narrows mid-breath. The RERA-like index counts how often that narrowing builds up over several breaths and then releases. These are the metrics we trust most from the flow signal, with one big caveat: they move when your machine’s Rise Time setting changes, so compare nights on the same settings.

How is the Estimated Arousal Index computed?

The Estimated Arousal Index (EAI) looks for the breathing signature of an arousal without any EEG. An event needs all of the following:

  • At least 2 of the previous 5 breaths were flow limited (NED 20% or more, or FI 0.85 or more).
  • Breathing rate jumps more than 35% above its 120-second rolling baseline.
  • Breath volume jumps more than 50% above that baseline.

After an event, 30 seconds pass before another can count. Index = events per hour.

Colour bands: green up to 5, amber up to 10.

Plain-language line: EAI is a secondary marker. Flow-based estimates like this one typically read 2 to 3 times higher than an in-lab arousal index measured with EEG (Mansour et al. 2019, Jordan et al. 2011). Use it for trend, not for comparison with a sleep-study report.

How are hypopneas and the Estimated RDI computed?

Hypopnea index has two paths. If your SD card carries the machine’s event file (EVE.edf), AirwayLab uses the device’s own hypopnea count. Otherwise it falls back to an algorithm: peak flow is compared with a rolling 30-breath median, and a drop of 30% or more that lasts 10 seconds or longer counts as a hypopnea. A drop of more than 40% lasting only one or two breaths is logged separately as a brief obstruction. Each event is also checked for whether NED saw it, and events with NED below 34% are flagged as “NED-invisible”.

Estimated RDI = RERA-like index + hypopnea index. The AASM definition of RDI also includes apneas. AirwayLab cannot identify apneas from flow alone, so this is a conservative lower bound. It is closest to a full RDI when apneas are rare.

Colour bands: hypopnea index green up to 2, amber up to 5. Brief obstructions green up to 3, amber up to 6. Estimated RDI green up to 5, amber up to 15.

Plain-language line: Estimated RDI is the floor, not the ceiling. If your machine reports apneas, add them mentally. Code: derived-metrics.ts.

How is IFL Symptom Risk computed?

IFL Symptom Risk is a weighted blend of four flow metrics on a 0 to 100 scale:

0.35 x FL Score + 0.30 x NED mean + 0.20 x normalised FI + 0.15 x normalised Glasgow

In the hosted app the FL Score term is first floor-rescaled, see the version note at the top of this page.

Normalised FI = (FI mean - 0.5) / 0.5 x 100, floored at 0. Normalised Glasgow = Glasgow / 9 x 100. The weights follow the inspiratory-flow-limitation literature (Gold’s IFL model, Mann et al. 2024 on flow limitation and daytime sleepiness). Colour bands: green up to 20, amber up to 45. Code: ifl-risk.ts.

Plain-language line: This is one composite number that rolls up “how flow limited was the night”. It inherits every limitation of its four inputs, including the uncalibrated FL Score. When IFL Risk and EAI disagree, the app says so and your clinician can help interpret these findings in context.

How are the oximetry metrics computed?

Oximetry is optional and needs a separate CSV from a Viatom / Wellue ring (Checkme O2 Max, O2Ring). The recording is cleaned first:

  • The first 15 and last 5 minutes are trimmed.
  • Samples with motion above 5 are dropped.
  • SpO2 outside 50 to 100 is dropped.
  • Heart-rate values that look like double-counting are corrected or dropped.

A night needs at least 60 clean samples or it is reported as not analysed. The app shows how many samples survived.

MetricUnitHowGreen / amber
ODI-3 and ODI-4events per hourSpO2 drops of 3% (or 4%) below a rolling 2-minute baseline peak, with a 30-second cooldown5 / 15 and 3 / 10
Time below 90% and below 94%% of nightShare of clean samples under the threshold5 / 15 and 10 / 30
Mean and minimum SpO2%Plain mean and minimum of clean samplesmean green at 95 or above, amber at 92
Heart-rate surges (8, 10, 12, 15 bpm)events per hourHR above a 30-second rolling baseline by the threshold, 30-second cooldownHR surge 10 bpm: 10 / 20
Coupled eventsevents per hourA desaturation with a heart-rate surge (6 or 10 bpm) within 30 seconds either sidecontext
Coupling with RERA-like sequences%Clock offset between machine and ring is estimated by testing shifts of up to 120 seconds and a 5 to 45 second physiological delay. Needs at least 10 RERA-like events.context

Code: oximetry-engine.ts, cross-device-engine.ts.

Plain-language line: ODI and time below 90% follow standard definitions and are the closest thing on this page to numbers a sleep lab would recognise. The ring is a consumer sensor and the cleaning steps are ours, so the values are comparable across your nights, not with a lab study. The clock alignment between ring and machine is a best-guess match and gets a “low confidence” label when fewer than a quarter of events line up.

How are the BiPAP settings metrics computed?

On bilevel machines AirwayLab reads the pressure channel next to flow. EPAP and IPAP are detected as the 10th and 90th percentile of pressure across the night. The engine only runs when the gap between them is at least 1 cmH2O. Breaths are re-segmented on a smoothed flow signal and kept when they last 1 to 10 seconds. Trigger delay is the time from flow onset until pressure passes 15% of the way from EPAP to IPAP. A breath is auto-triggered when pressure is already rising before flow starts. Time at IPAP counts samples at or above 90% of pressure support. Premature cycling is pressure dropping below the halfway point while flow is still above 25% of peak. Tidal volume is the integral of inspiratory flow.

The hosted app scores cycle timing from plateau departure instead, see the version note at the top of this page.

Colour bands: trigger delay green up to 300 ms, amber up to 500 ms. Auto-trigger green up to 2%, amber up to 5%. Premature and late cycling green up to 2%, amber up to 10%. Tidal-volume variability green up to 25%, amber up to 30%. Code: settings-engine.ts.

Plain-language line: These describe how well your breaths and the machine’s pressure changes line up. Absolute millilitre values depend on your device’s flow calibration, so use the night-over-night change and the variability percentage, not the raw volume.

What do the green, amber and red bands mean?

Every metric gets a directional label: lower, typical, or higher. The cut-offs are working thresholds we set from the research literature and our own data. They are not clinical reference ranges and no regulator has reviewed them. The full table lives in lib/thresholds.ts. The table below is generated from the same thresholds file this site runs, so it always matches the hosted app. You can override any threshold in the app settings, which stores the override in your browser only.

Outcome

MetricGreen up toAmber up toDirection
HR surge, 10 bpm (events/hr)1020lower is better
ODI-3 (events/hr)515lower is better
ODI-4 (events/hr)310lower is better
Time below 90% SpO2 (%)515lower is better
Time below 94% SpO2 (%)1030lower is better
Mean SpO2 (%)9592higher is better
Coupled desaturation-and-surge events (%)3050lower is better
RERA-like coupling, night 2nd half (%)1025lower is better

Pattern

MetricGreen up toAmber up toDirection
IFL Symptom Risk (0-100)2045lower is better
NED mean (%)1525lower is better
NED 95th percentile (%)3050lower is better
NED clear flow limitation (%)25lower is better
Combined flow limitation (%)2040lower is better
RERA-like sequence index (events/hr)510lower is better
Estimated Arousal Index (events/hr)510lower is better
Brief obstructions (events/hr)36lower is better
Hypopnea index (events/hr)25lower is better
Amplitude variability, CV (%)2030lower is better
Unstable amplitude epochs (%)1525lower is better
Estimated RDI (events/hr)515lower is better

Context

MetricGreen up toAmber up toDirection
Glasgow Index (0-9)12lower is better
FL Score, WAT (0-100)6070lower is better
Regularity Score, WAT (0-100)3050lower is better
Periodicity Index, WAT (0-100)2040lower is better

Device

MetricGreen up toAmber up toDirection
Machine-reported AHI (events/hr)510lower is better
Leak, 95th percentile (L/min)2440lower is better
Spontaneous cycling (%)8060higher is better
Trigger delay (ms)300500lower is better
Auto-trigger rate (%)25lower is better
Inspiratory time, Ti (ms)12001000higher is better
I:E ratio1.21higher is better
Time at IPAP (ms)600400higher is better
IPAP dwell (%)4535higher is better
Premature cycling (%)210lower is better
Late cycling (%)210lower is better
Tidal-volume variability, CV (%)2530lower is better
EPAP stability, delta (cmH2O)0.51lower is better

Plain-language line: A colour is a reading aid, not a verdict. Two greens and one red on the same night means “look at the red one with your clinician”, nothing more.

Are these metrics calibrated or clinically validated?

No. This is the honest answer we owe you, and the one we gave on r/SleepApnea.

  • None of the flow-derived scores have been calibrated against polysomnography or against a scored sleep study.
  • The thresholds are our working cut-offs, not clinical reference ranges.
  • Oximetry metrics follow standard definitions but come from a consumer ring and our cleaning pipeline.
  • Machine AHI is the device’s own number. It is reliable when elevated and misses flow limitation and RERA-like events when normal.
  • The engines are deterministic: the same night always scores the same. That makes them a consistent ruler, which is why trends across your own nights carry more meaning than any absolute value.
  • When we fix a scoring error, it goes in the public changelog and the engine version bumps.

We grade our own metrics by reliability in lib/metric-registry.ts. Tier 1 is the oximetry set. Tier 2 is the NED family. Tier 3 is context only: Glasgow, the WAT scores, M-shape and Tpeak/Ti. That file is the same grading the app uses.

Plain-language line: Use AirwayLab to see patterns in your own data and to bring better questions to your appointment. Your clinician can help interpret these findings in context.

What are the known limitations?

  • Rise Time changes the numbers.NED, FI and the RERA-like index shift when your machine’s Rise Time setting changes, because Rise Time changes peak flow. Compare nights on the same settings.
  • Tpeak/Ti is a BiPAP artifact. Across 385,000 bilevel breaths, 94% peaked before 45% of inspiration. On bilevel machines this metric describes the pressure profile, not your airway.
  • Glasgow is superseded. It predates the direct NED analysis and stays for continuity.
  • FL Score is uncalibrated. It sits around 50 to 65 on unobstructed breathing and is not a percentage of anything.
  • EAI over-counts versus EEG. Expect 2 to 3 times a lab arousal index.
  • Estimated RDI is a lower bound. Apneas are not identified from flow.
  • Hypopneas depend on the file set. With EVE.edf we use the device count, without it the algorithm.
  • Spontaneous and Timed percentages rely on an event-code mapping borrowed from OSCAR that we have not validated independently.
  • Ring-to-machine clock alignment is a heuristic and is labelled low confidence when the match is weak.
  • Tidal volume in millilitresdepends on the device’s flow calibration.
  • Heavy leak or long mask-off periods reduce the number of clean breaths and make everything noisier.
  • Device support differs. Supported devices and their known gaps are listed in the device guides.

How can I check the code myself?

The engine is public. Start at the repository, then:

Found a scoring error? Open an issue. We fix it in the open.

Frequently asked questions

No. The flow-derived scores have not been calibrated against polysomnography. The thresholds are working cut-offs from the literature and our own data, not clinical reference ranges. The engines are deterministic, so trends across your own nights are the reliable signal.
They measure different things. FL Score looks at how flat the top of each breath is and sits around 50 to 65 even for unobstructed breathing. Glasgow counts nine specific shape distortions. A moderately flat breath can score high on one and low on the other.
A lab scores arousals from EEG. AirwayLab estimates them from breathing-rate and volume jumps after flow-limited breaths. Flow-based methods typically read 2 to 3 times higher. Use it for trend, not for comparison with a lab report.
The oximetry set: ODI-3, ODI-4 and time below 90% follow standard definitions. Machine AHI is the device's own number. Everything derived from the flow waveform is AirwayLab's own analysis.
The thresholds do not, but some metrics do. NED, Flatness Index and the RERA-like index shift when Rise Time changes. Compare nights recorded on the same settings.
In the public engine repository at github.com/airwaylab-app/airwaylab under GPL-3.0. Each section on this page links to the file that computes the metric.
No. AirwayLab is not a medical device and does not provide medical advice. The numbers describe patterns in your data. Your clinician can help interpret these findings in context.
Not from the flow signal. Apnea counts shown in the app are the device's own numbers. Estimated RDI therefore excludes apneas and is a lower bound.