Everything on this page is generated when the site is built, from the metric registry in packages/protocol and the method notes that sit beside the kernel code. If the code changes, this page changes with it. It describes the method. It never shows anyone's data.
Engines store and compute in SI units. Transcoding happens only at display. A range names its unit on both ends. Nightly metrics are keyed by wake date in the athlete's time zone at the time. A metric a source lacks is absent, never zero.
Overnight HRV (RMSSD) HRV
Root mean square of successive RR-interval differences during sleep. Vendors window it differently (night average vs slow-wave weighted); Sensor Court translates between them.
Identifier
hrv_rmssd_night
Engine unit (SI)
ms
Shown as
ms (metric), ms (imperial), 0 decimals
Scale
ratio
Translation
log_ratio: multiplicative bias, estimated as a median log ratio; fused on the natural log
Plausible range
5 ms to 250 ms; shown as 5 ms to 250 ms
Widest useful 80% band
20 ms
Cross-vendor fusion
Allowed: readings from different vendors are translated and fused.
Resting heart rate RHR
Lowest sustained heart rate for the night, as each vendor defines it. Stored as beats per minute (count/min), not Hz.
Identifier
resting_hr
Engine unit (SI)
count/min
Shown as
bpm (metric), bpm (imperial), 0 decimals
Scale
ratio
Translation
log_ratio: multiplicative bias, estimated as a median log ratio; fused on the natural log
Plausible range
25 count/min to 120 count/min; shown as 25 bpm to 120 bpm
Widest useful 80% band
8 count/min
Cross-vendor fusion
Allowed: readings from different vendors are translated and fused.
Sleep duration Sleep
Total time asleep in the main sleep period ending on the wake date (excludes awake time in bed).
Identifier
sleep_duration
Engine unit (SI)
s
Shown as
h (metric), h (imperial), 1 decimal
Scale
ratio
Translation
log_ratio: multiplicative bias, estimated as a median log ratio; fused on the natural log
Plausible range
0 s to 64800 s; shown as 0 h to 18 h
Widest useful 80% band
full log width 0.17 (−8.1% / +8.9% of the value)
Cross-vendor fusion
Allowed: readings from different vendors are translated and fused.
Sleep efficiency Efficiency
Time asleep divided by time in bed for the main sleep period, as a ratio from 0 to 1.
Identifier
sleep_efficiency
Engine unit (SI)
1 (dimensionless ratio)
Shown as
% (metric), % (imperial), 0 decimals
Scale
ratio
Translation
difference: additive bias, estimated as a median difference; fused in SI units
Plausible range
0 (ratio) to 1 (ratio); shown as 0 % to 100 %
Widest useful 80% band
0.1 (ratio)
Cross-vendor fusion
Not allowed: vendors define the denominator differently (Garmin: its own sleep window; Oura and WHOOP: time in bed) and their co-worn readings barely rank-correlate, so a bias translation would be invented. Nights with two or more vendors are refused as incommensurable_definitions.
Skin temperature deviation Skin temp
Night skin temperature minus the vendor's own personal baseline. A temperature difference, so kelvin and degrees Celsius are numerically equal.
Identifier
skin_temp_deviation
Engine unit (SI)
K
Shown as
Δ°C (metric), Δ°F (imperial), 2 decimals
Scale
interval
Translation
difference: additive bias, estimated as a median difference; fused in SI units
Plausible range
−5 K to 5 K; shown as −5 Δ°C to 5 Δ°C
Widest useful 80% band
1 K
Cross-vendor fusion
Allowed: readings from different vendors are translated and fused.
Overnight respiratory rate Resp. rate
Average breaths per minute during sleep.
Identifier
respiratory_rate_night
Engine unit (SI)
count/min
Shown as
br/min (metric), br/min (imperial), 1 decimal
Scale
ratio
Translation
log_ratio: multiplicative bias, estimated as a median log ratio; fused on the natural log
Plausible range
4 count/min to 40 count/min; shown as 4 br/min to 40 br/min
Widest useful 80% band
3 count/min
Cross-vendor fusion
Allowed: readings from different vendors are translated and fused.
How Sensor Court judges a night
For each metric and wake date, every source that reported the metric is a witness. The court translates the witnesses onto one scale, weighs them by how noisy each source is, fuses them into one number with an 80% band, overrules a witness that disagrees too much, and refuses (with a typed reason) when it cannot give a useful answer.
Scales and admissibility
Fusion scale. The registry field translation decides it. For log_ratio metrics (HRV, resting HR, sleep duration, respiratory rate) all work happens on the natural log, y = ln(value), so a bias is a ratio and a noise σ is roughly a fraction (0.05 ≈ 5%). For difference metrics (sleep efficiency, skin temperature deviation) y = value in SI units. Results go back to SI at the end, so a log-scale band comes out asymmetric in SI.
Admissible reading. Right metric, right wake date, unit equal to the registry's SI unit, inside the registry's plausible range, and, on the log scale, strictly positive (a 0 s sleep cannot be logged and is set aside, never fused as zero). Everything else is listed in the receipt's params.excluded with a reason.
Nights are joined by wake date, exactly. Each vendor's wake date comes from its own wake instant. Two vendors' instants for one night that fall either side of local midnight give two single-witness sittings. findSplitNightCandidates finds same-metric readings from different vendors within 180 min on different wake dates where neither vendor reported on the other's date; the import counts them per metric and vendor pair. It does not re-key readings (a wake date is part of observation identity).
One witness per vendor. If a vendor gave several readings for one wake date, the median-value reading testifies and the rest are listed as duplicate_vendor_reading.
Tenants never mix. An observation from another tenant is a programming error and throws.
Translate: bias per source and era
A vendor's reading is modelled as
y[n, g] = c[n] + b[g] + e[n, g]
where n is a co-worn night (two or more vendors), g is a (vendor, era) group, c[n] is the consensus that night, b[g] the group's systematic bias and e its noise. Era is part of the group because firmware and hardware changes move the bias (the old SpO₂ "finding" was an era artefact).
Estimation, robust. Start with b = 0 and repeat until no bias moves by more than 1e-9:
r[n, g] = y[n, g] − median over the OTHER vendors that night of ( y[n, h] − b[h] )
b[g] ← ( b[g] + median over n of r[n, g] ) / 2
b ← b − Σ_g N_g b[g] / Σ_g N_g (N_g = co-worn nights of g)
In words: a group's bias is the median of how far it sits above the other sources on the nights they were worn together. For log-ratio metrics this is a median log ratio; for difference metrics a median difference. The last line fixes the scale: biases average to zero, weighted by nights, so the consensus scale is "the average co-worn source". Only differences between biases are truly identifiable; the centring is a convention.
Noise per source: the three-cornered hat
If three sources A, B, C measure the same thing with independent errors, the variance of a pairwise difference is the sum of the two sources' variances, V_AB = σ²_A + σ²_B. Three pairs, three unknowns:
σ²_A = ( V_AB + V_AC − V_BC ) / 2
Each V is a robust variance of the translated pairwise differences over the window:
V_AB = ( 1.4826 × MAD( y'_A − y'_B ) )²
where MAD is the median absolute deviation about the median and 1.4826 makes it match the standard deviation for normal data. A few wild nights therefore do not inflate the noise.
Strength and fallback. Nothing is silently clamped; every fallback writes a note built from the numbers, and the noise claim's receipt carries the raw 3CH variance and an outcome code.
The hat assumes the sources' errors are independent. Two devices that share a sensing principle or an algorithm look closer together than they are; the negative-variance fallback catches the extreme case, not the milder one.
Fusion and dissent
With k usable witnesses (translated y_i, noise σ_i):
w_i = 1 / σ_i²
ȳ = Σ w_i y_i / Σ w_i fixed-effect mean
Q = Σ w_i (y_i − ȳ)² Cochran's Q
C = Σ w_i − Σ w_i² / Σ w_i
τ² = max( 0, (Q − (k − 1)) / C ) between-source variance (0 when k = 1)
w*_i = 1 / (σ_i² + τ²)
μ = Σ w*_i y_i / Σ w*_i the verdict (fusion scale)
SE = √( 1 / Σ w*_i )
band = μ ± q · SE, q = Φ⁻¹(0.9) = 1.2816 80% band
In words: each source counts in proportion to its precision. If the sources disagree more than their own noise explains, the excess becomes τ², which is added to every source's variance: weights even out and the band widens. τ² before the max(0, ·) is kept in the receipt (tau2InitialRaw, tau2FinalRaw); truncation at zero is part of the DerSimonian–Laird estimator, not a hidden clamp. Weights reported are w*_i / Σ w* and sum to 1 over witnesses that were fused.
Dissent: leave-one-out residuals
For each witness i (when k ≥ 2), fit section 3's model to the other witnesses, giving μ₋ᵢ, τ²₋ᵢ and SE²₋ᵢ, and compute
z_i = ( y_i − μ₋ᵢ ) / √( σ_i² + τ²₋ᵢ + SE²₋ᵢ )
A witness whose |z| is above the bar is overruled: its weight drops to zero, and the reason on the card is assembled from the readings, the consensus of the others and the bar it crossed. The verdict is then recomputed once, without it. Leaving the witness out of its own consensus matters: an outlier included in the mean drags the mean towards itself and inflates the between-source variance until its own residual looks ordinary.
Two witnesses. With k = 2 both residuals have the same size, |y₁ − y₂| / √(σ₁² + σ₂²), so the court cannot tell which one is wrong. When that exceeds 2.5 both are overruled and the court refuses with all_witnesses_overruled, saying the disagreement cannot be attributed.
Student-t bands
Real pairwise differences between devices have heavier tails than a normal distribution: integer-valued readings, the occasional bad night. A Gaussian band and a fixed |z| > 2.5 rule would then overrule honest witnesses too often. The court fits the tails instead.
Model. The court's leave-one-out residual is modelled as a scaled Student-t, z ≈ s · T_ν, fitted per noise-model epoch (vendor, era, calibration epoch) and per night class: pair (exactly two usable witnesses: z is one difference) and multi (three or more: τ² and the others' mean enter). Both fits travel on the noise model as tail: {pair, multi} with df, scale, source, residuals and residualKind.
Held-out residuals. The noise stage replays the court's arithmetic on every history night with two or more usable witnesses (same translation, same σ, same leaveOneOutResiduals code as the court): each witness's z is computed against the consensus of the others, so it never judges itself. Each z goes to its own epoch's class list (a pair night's two residuals have the same size; the metric-pooled list takes one).
Fit. Maximum likelihood with the location fixed at 0: for each ν on a 49-point log grid in [1, 1000], the scale solves the EM fixed point s² = Σ wᵢ zᵢ² / n, wᵢ = (ν + 1)/(ν + zᵢ²/s²); the best ν is refined by golden section on ln ν. An epoch needs 100 residuals in a class for its own fit (source: epoch); with fewer it takes the metric's pooled fit (pooled); with fewer than 100 anywhere, or on a metric the registry never fuses across vendors, the documented prior ν = 4, s = 1 (prior, heavier than Gaussian, so it can only widen). Every fit writes a note with ν, s, the residual count, and the t and Gaussian log-likelihoods.
Band.band = μ ± q · SE with
fused witnesses
q
1
max(Φ⁻¹(0.9), s·Q_ν(0.9)) of its pair tail: the tail may widen a lone band, never narrow it (a pair's scale cannot be attributed to one source)
2
the larger s·Q_ν(0.9) of the two pair tails
3 or more
max(Φ⁻¹(0.9), the largest s·Q_ν(0.9) of their multi tails): never below Gaussian, because τ² already absorbs part of the spread z measures
Dissent. The Gaussian rule promised a false-dissent probability α = P(|Z| > 2.5) = 1.24%. A witness is overruled when |zᵢ| > s · Q_ν(1 − α/2), which has that same probability under the fitted tail. Pair nights use the larger of the two witnesses' pair thresholds (neither witness's tail may lower the bar for an unattributable disagreement); three or more use each witness's own multi tail. dissentThreshold (default 2.5) keeps its meaning as the Gaussian-equivalent bar; Infinity still disables dissent. The reason string quotes the bar in the form |z| = <z> > <bar> (Student-t ν = <ν>, scale <s>, <source>: the same 1.24% false-dissent rate as |Z| > 2.5). Receipts carry tailRule, nominalFalseDissentRate, bandQuantile and per witness witnessTails[{class, tail, zThreshold}].
The proportional sleep band
Every verdict has a widest useful 80% band in the registry. Wider than that, and the court refuses with band_wider_than_useful rather than show a number that cannot support a decision.
Sleep duration is refused when ln(upper / lower) > 0.17, a full log width of 0.17, about −8.1% / +8.9% of the value. Its band is built on the log scale, so a lone reading's band is a fixed fraction of the value, and the cap is a fixed fraction too.
an illustrative 4 h night: a band up to about 41 min wide
an illustrative 9 h night: a band up to about 92 min wide
Two nights from the same device with the same measured noise get the same verdict whatever their length. An absolute cap (say, one hour) would refuse long lone nights and admit short ones, so anything that averages admitted nights would see a sample censored from above.
Display unit h. Generated from maxUsefulBand in the registry.
Refusal reasons
A refusal is a typed answer, with a receipt listing every reading that was looked at. The court checks in this order:
Reason
When
no_witness
no reading of this metric for this wake date was supplied
unit_mismatch
readings exist but every one has the wrong unit
implausible_input
readings exist but none is plausible (or positive on the log scale)
single_witness_no_noise_model
exactly one witness and it has no usable noise model
insufficient_history
two or more witnesses and none has a noise model
all_witnesses_overruled
every fused witness was overruled (always the case for a disagreeing pair)
band_wider_than_useful
the 80% band is wider than the registry's maxUsefulBand: upper − lower (SI) above an absolute width, or ln(upper / lower) above a full log width
What the athlete reads
Each reason has one plain-language lead sentence, shared by the web and iPhone apps. Placeholders in italics are filled from the verdict's own fields.
Reason
Lead sentence
no_witness
No device reported noun last night.
single_witness_no_noise_model
Too uncertain to show: only witnesses reported noun, with nothing yet to check it against.
band_wider_than_useful
Too uncertain to show: witnesses, and pronoun likely range (lower to upper) is wider than the maxBand Intero allows for noun.
implausible_input
Too uncertain to show: every reading of noun last night was outside the range Intero trusts.
insufficient_history
Too uncertain to show: witnesses reported noun, but none yet has enough shared history to judge how reliable it is.
unit_mismatch
Too uncertain to show: every reading of noun last night used units Intero doesn't expect.
zone_unknown
Too uncertain to show: Intero couldn't tell which night this reading of noun belongs to.
all_witnesses_overruled
Too uncertain to show: witnesses measuring noun disagreed too much with each other to be trusted.
incommensurable_definitions
witnesses reported noun, but they define it too differently to combine into one number.
settling_window
nounCap is withheld until the settling window after the event closes.
Own baseline and effective sample size
Window. The 60 calendar days before the as-of wake date (the as-of night and anything later are never read).
Refusals are skipped, never scored. A refused night contributes nothing; the count is kept in the receipt.
Centre and spread.centre = median(x), spread = 1.4826 × MAD(x), in SI units. The receipt also gives the centre's approximate standard error 1.2533 × spread / √n_eff.
Effective sample size. Nights next to each other are not independent (HRV runs in streaks), so 60 nights carry less information than 60 independent readings. With gaps allowed, lag-k autocorrelation uses pairs of readings exactly k days apart:
ρ_k = [ Σ (x_i − x̄)(x_j − x̄) / m_k ] / [ Σ (x − x̄)² / n ] (m_k pairs at lag k)
τ_B = 1 + 2 Σ_{k=1..M} (1 − k/(M+1)) ρ_k, M = ⌊√n⌋ Bartlett lag window
τ_AR = (1 + ρ₁) / (1 − ρ₁) when ρ₁ > 0 AR(1) floor
τ = max(τ_B, τ_AR)
n_eff = min(n, n / τ)
The AR(1) floor exists because the truncated Bartlett sum misses most of a slowly decaying autocorrelation in a short series: for AR(1) with φ = 0.9 and 20 nights (true n_eff about 1) the Bartlett sum alone gave a median n_eff of 6.9 and refused only 80% of series; with the floor the median is 3.3 and 96.5% refuse. The cost is that 7.5% of independent 20-night series also refuse (0% at 60 nights). n_eff is capped at n (negative autocorrelation never claims more information than there are readings); the uncapped value is recorded.
Refuses (insufficient_history) with fewer than 14 estimates, zero robust spread, more than half of the lags lacking 3 date pairs, or n_eff < 10.
Student-t by maximum likelihood, ν ∈ [1, 1000]; own fit ≥ 100 residuals per class, else pooled, else prior ν = 4, s = 1; leave-one-out residuals, no time cross-fitting
calibration: min co-worn nights
14
widest useful band (1.5.0)
registry maxUsefulBand: absolute SI width, or full log width (sleep duration 0.17)
placement eras (calibration 1.3.0, noise 1.5.0)
athlete device_placement events split a (vendor, era) with readings on both sides of the date, before the changepoint search
noise: window / min three-source nights / floor
365 nights / 60 / ¼ prior
single-witness transfer (1.4.0)
never-co-worn eras only; nearest measured era of the same vendor; every reading between with one known device and firmware; carried model weak
baseline: window / min estimates / min n_eff / min pairs per lag
60 days / 14 / 10 / 3
What doesn't work yet
Intero publishes its misses. This list is partly written by hand and partly pulled from the "known limits" in the method notes.
Déjà Vu is mostly a null result
"The last ten nights most resemble these earlier windows; here is what happened next." Descriptive only. Every number is a mission ledger claim receipted by analogs-deja-vu 1.1.0 (1.0.0 claims stay on record, superseded).
In backtests so far, the analogs mostly fail to beat two simple references: the last value seen, and the athlete's own seasonal average. For most metrics the honest answer is "no better than persistence". The page says so, shows how often it answered, and is never used as advice.
Sensor Court
HRV's widest useful band is still an absolute width on a log-scale metric, so a lone high reading is refused before a lone low one. The proportional form exists; switching HRV to it is an open decision.
Sleep efficiency cannot be fused across vendors: they define the denominator differently. Each vendor's reading stands alone.
Respiratory rate is not stationary: a device can drift relative to the others inside one era, which no tail shape fixes.
Skin temperature readings sit on a coarse lattice, and the extreme tail of their differences is heavier than the fitted Student-t, so dissent is slightly over-called.
Calibration and noise models are fitted on the whole history, so verdicts are not strictly leave-future-out.
Forecast
Gaussian on the fusion scale. Heavy-tailed metrics (sleep duration, skin temperature) come out too wide on average.
Events (races, travel, illness) are not inputs. The level only learns about them after they happen.
Parameter uncertainty in Q and S is not propagated into the interval. The Q interval is published instead.
A night refused by the court is skipped, and the forecast widens through gap until the next estimate.
Drift Radar
Only raw shifts are visible; a change a vendor applied retroactively to its whole history is not.
Before a third device was worn, shifts can only be dated between two devices unless a passport names the change.
Elsewhere
Off-site encrypted backups are designed, not built. Backups today sit on the same server volume as the data.
The iPhone app is a private TestFlight beta, not an App Store release.
Training load covers heart-rate load and cycling work only; running power is refused as a device model.