Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0725b69
RR correction and rhythm screening replay settled beats on each sync
mikenrafter Nov 1, 2026
f118248
HRV and respiration updates rescan completed windows
mikenrafter Nov 1, 2026
f9bb3ee
Merge branch 'split/area-1-framework' into split/area-2-rr-rhythm
mikenrafter Nov 3, 2026
51d33e9
Validate cap-only gap labels across streaming and checkpoint paths
mikenrafter Nov 3, 2026
0171a3c
Merge branch 'split/area-2-rr-rhythm' into split/area-3-hrv-respiration
mikenrafter Nov 3, 2026
ecbff93
Merge branch 'split/area-1-framework' into split/area-2-rr-rhythm
mikenrafter Nov 3, 2026
e21147a
Merge branch 'split/area-2-rr-rhythm' into split/area-3-hrv-respiration
mikenrafter Nov 3, 2026
b1fdba5
Merge branch 'split/area-1-framework' into split/area-2-rr-rhythm
mikenrafter Nov 4, 2026
59207de
Merge branch 'split/area-2-rr-rhythm' into split/area-3-hrv-respiration
mikenrafter Nov 4, 2026
e1ee3fb
merge main
abdulsaheel Nov 13, 2026
4cfabe5
merge framework
abdulsaheel Nov 13, 2026
ff74e7a
merge main
abdulsaheel Nov 13, 2026
bee1396
merge main
abdulsaheel Nov 13, 2026
9ccf5b3
calculation cache snapshots sets and keeps result types, property har…
abdulsaheel Nov 13, 2026
447c95a
merge main
abdulsaheel Nov 13, 2026
cbc38fe
Fix cache copies and irregular screen checkpoints
mikenrafter Nov 13, 2026
ff7e896
Merge branch 'split/area-2-rr-rhythm' into split/area-3-hrv-respiration
mikenrafter Nov 13, 2026
25eff01
Copy RR checkpoint buffers before returning them
mikenrafter Nov 13, 2026
80a30ff
Merge branch 'split/area-2-rr-rhythm' into split/area-3-hrv-respiration
mikenrafter Nov 13, 2026
0c0ae9e
Fix HRV stream parity and checkpoint ownership
mikenrafter Nov 13, 2026
8517c4f
Update spectral fold law for omitted backward beats
mikenrafter Nov 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,9 @@ There is **one** sleep source (`segmentSleep`) and **one** headline readiness

## Tests

On Linux, `nix develop` supplies the pinned Flutter 3.41.6 / Dart 3.11.4
toolchain used by Edge, plus Git, Clang, CMake, Ninja, and pkg-config.
The interactive shell enters an FHS environment for native build tools.
For a command without an interactive shell, use `nix develop -c dart test`;
`nix develop -c analytics-fhs <command>` also supplies the FHS environment.
Use a Dart SDK matching `pubspec.yaml`'s `environment` constraint.

```bash
nix develop
dart pub get
dart analyze
dart test
Expand Down
6 changes: 3 additions & 3 deletions lib/aggregate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ export 'src/aggregate/aggregator.dart';
export 'src/aggregate/checkpoint.dart';
export 'src/aggregate/laws.dart';
export 'src/aggregate/primitives.dart';
export 'src/onehz/clinical/hrv_time_aggregator.dart' hide hrvTimeOneSlice;







export 'src/onehz/clinical/prsa_aggregator.dart';
export 'src/onehz/foundations/rr_sandwich.dart';



7 changes: 4 additions & 3 deletions lib/onehz.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export 'src/onehz/device.dart';

// Layer 0: foundations.
export 'src/onehz/foundations/rr_correction.dart';

export 'src/onehz/foundations/rr_correction_stream.dart';
export 'src/onehz/foundations/baseline.dart';
export 'src/onehz/foundations/ewma_baselines.dart';
export 'src/onehz/foundations/fusion.dart';
Expand All @@ -34,8 +34,9 @@ export 'src/onehz/clinical/cosinor.dart';
export 'src/onehz/clinical/load_trimp.dart';
export 'src/onehz/clinical/stress_si.dart';
export 'src/onehz/clinical/irregular_rhythm.dart';


export 'src/onehz/clinical/irregular_rhythm_state.dart';
export 'src/onehz/clinical/irregular_diagnostics.dart'
hide diagnosticArtifactFraction;
export 'src/onehz/clinical/cardiac_coherence.dart';
export 'src/onehz/clinical/vo2max.dart';

Expand Down
84 changes: 58 additions & 26 deletions lib/src/onehz/clinical/hrv_freq.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

import '../types.dart';
import '../util.dart';
import '../../aggregate/json_support.dart';

part 'hrv_freq_stream.dart';

class HrvFreq {
final double? ulf;
Expand Down Expand Up @@ -88,6 +91,8 @@ Metric<HrvFreq> hrvFreq(

// Times in seconds for Hz output; tachogram value = NN (ms).
var tSec = [for (final t in nnTimesMs) t / 1000.0];
// A non-finite beat time has no place on the clock, and an infinite last
// time made the segment walk endless (it never reaches infinity).
if (tSec.any((t) => !t.isFinite)) {
return const Metric<HrvFreq>.absent(
tier: Tier.high,
Expand Down Expand Up @@ -145,6 +150,22 @@ Metric<HrvFreq> hrvFreq(
final vlf = _welchBandPower(tSec, nn, 0.003, 0.04, oversample: oversample);
final lf = _welchBandPower(tSec, nn, 0.04, 0.15, oversample: oversample);
final hfRaw = _welchBandPower(tSec, nn, 0.15, 0.40, oversample: oversample);
return _hrvFreqMetric(ulf, vlf, lf, hfRaw,
artifactFraction: artifactFraction,
hfArtifactGate: hfArtifactGate,
droppedBeats: droppedBeats);
}

Metric<HrvFreq> _hrvFreqMetric(
double? ulf,
double? vlf,
double? lf,
double? hfRaw, {
required double artifactFraction,
required double hfArtifactGate,
int droppedBeats = 0,
}) {
const inputs = ['rr_cleaned', 'beat_times'];
if (lf == null && hfRaw == null) {
return const Metric<HrvFreq>.absent(
tier: Tier.high,
Expand Down Expand Up @@ -268,35 +289,46 @@ double? _welchBandPower(
var k = 0;
final step = segSec / 2; // 50 % overlap, the Welch default
for (var start = tSec.first; start + segSec <= tSec.last; start += step) {
final end = start + segSec;
final ts = <double>[];
final ys = <double>[];
for (var i = 0; i < tSec.length; i++) {
if (tSec[i] < start) continue;
if (tSec[i] >= end) break;
ts.add(tSec[i]);
ys.add(y[i]);
}
// A segment has to be BOTH beat-dense and time-complete: a dropout in
// the middle leaves few beats spanning the full window, and its
// periodogram is a window function, not a spectrum. Endpoint span alone
// misses an internal dropout that still leaves beats near both edges, so
// also reject on the single largest gap between consecutive beats.
if (ts.length < minPointsPerSegment || ts.last - ts.first < segSec * 0.8) {
continue;
}
var maxGap = 0.0;
for (var i = 1; i < ts.length; i++) {
final g = ts[i] - ts[i - 1];
if (g > maxGap) maxGap = g;
}
if (maxGap > segSec * 0.2) continue;
final ls = lombScargle(ts, ys, grid);
if (ls == null) continue;
final p = ls.bandPower(loHz, hiHz);
final p = _welchSegmentPower(tSec, y, start, segSec, loHz, hiHz,
grid: grid, minPointsPerSegment: minPointsPerSegment);
if (p == null) continue;
if (!p.isFinite) continue;
sum += p;
k++;
}
return k == 0 ? null : sum / k;
}

/// One Welch segment. Streams call this exact function when a segment closes.
double? _welchSegmentPower(
List<double> tSec,
List<double> y,
double start,
double segSec,
double loHz,
double hiHz, {
required List<double> grid,
required int minPointsPerSegment,
}) {
final end = start + segSec;
final ts = <double>[];
final ys = <double>[];
for (var i = 0; i < tSec.length; i++) {
if (tSec[i] < start) continue;
if (tSec[i] >= end) break;
ts.add(tSec[i]);
ys.add(y[i]);
}
if (ts.length < minPointsPerSegment || ts.last - ts.first < segSec * 0.8) {
return null;
}
var maxGap = 0.0;
for (var i = 1; i < ts.length; i++) {
final g = ts[i] - ts[i - 1];
if (g > maxGap) maxGap = g;
}
if (maxGap > segSec * 0.2) return null;
final ls = lombScargle(ts, ys, grid);
if (ls == null) return null;
return ls.bandPower(loHz, hiHz);
}
Loading
Loading