Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion lib/src/onehz/wellness/temp_circadian.dart
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,13 @@ CircadianNonparam? _nonparam(
for (final v in profile) {
profVar += (v - grand) * (v - grand);
}
profVar /= profile.length;
// Divide by the FIXED epochsPerDay, not profile.length (the count of
// hour-of-day bins that happen to have >=1 sample). A phase-locked gap
// (e.g. charging at the same hour every day, see file header) leaves a
// bin permanently unpopulated, shrinking profile.length below
// epochsPerDay and inflating IS by epochsPerDay/profile.length. Matches
// circadian_np.dart's `p = epochsPerDay` normalization.
profVar /= epochsPerDay;
}
final double is_ = varTot > 0 ? (profVar / (varTot / p)).clamp(0, 1) : 0.0;

Expand Down
28 changes: 28 additions & 0 deletions test/onehz/wellness_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ void main() {
expect(np.interdailyStability, closeTo(0.5582, 0.0005));
});

test(
'IS divisor is epochsPerDay, not the count of populated hour-of-day '
'bins (phase-locked charging-gap regression)', () {
// Hour-of-day 3 is NEVER sampled across the whole window (e.g. the band
// charges at the same time every day) -> profile.length == 23 while
// epochsPerDay == 24. Dividing profVar by profile.length instead of
// epochsPerDay inflates IS by 24/23.
final samples = <AdcSample>[];
for (var day = 0; day < 14; day++) {
for (var h = 0; h < 24; h++) {
if (h == 3) continue;
for (var k = 0; k < 6; k++) {
final tHours = day * 24.0 + h + k / 6.0;
final adc =
2000 + 150 * math.cos(2 * math.pi / 24 * (tHours - 4));
samples.add(AdcSample(tHours * 3.6e6, adc));
}
}
}
final np = tempCircadian(samples, deviceFamily: 'gen4', epochMin: 60)
.value!
.nonparam!;
// Correct value with divisor = epochsPerDay; strictly less than the
// pre-fix value (which clamped to 1.0).
expect(np.interdailyStability, lessThan(0.999));
expect(np.interdailyStability, closeTo(0.9583, 0.001));
});

test('M10/L5/RA are WITHHELD, not merely null (MT-09)', () {
final samples = <AdcSample>[];
for (var i = 0; i < 3 * 24 * 6; i++) {
Expand Down
Loading