fix(train): a day's skin temperature in time order, and its heart rate a point a minute without lost contact
The skin temperature's line ran backwards in places: the band's samples aren't always in time order, and neither the Loop's lines, nor the kept day, nor the chart sorted them. They are sorted now, a time given twice kept once, as the lines are read and as a kept day is opened, and WorkoutChart draws any points in the order of their x, from the least to the most. The heart rate spiked down: the band writes 0 where it lost contact, and each run's mean, the chart's one point a run, counted those in. A value that isn't a heart's rate is 0, none read, in its place (so the samples after keep their times), left out of every mean, days kept before included; the chart draws the samples a point a minute, averaged across runs, rather than one mean a run, which zigzagged where runs lay close. ADR 0030, D65. Whats-New: A day's skin temperature no longer runs backwards, and its heart rate no longer drops where the band lost contact. Whats-New-De: Die Hauttemperatur eines Tages läuft nicht mehr rückwärts, und sein Puls fällt nicht mehr ab, wo das Band den Kontakt verlor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
1c67310c93
commit
ae3121310a
4 changed files with 232 additions and 16 deletions
|
|
@ -0,0 +1,63 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/day/model/day.dart';
|
||||
|
||||
/// A day's 24/7 heart rate as its chart draws it (ADR 0030, D37, D65): the
|
||||
/// samples a point a minute, what the band couldn't read left out.
|
||||
void main() {
|
||||
test("a run's mean leaves out what the band couldn't read", () {
|
||||
const run = HeartRun(
|
||||
at: Duration.zero,
|
||||
trigger: HeartTrigger.timed,
|
||||
bpm: [70, 0, 72, 0],
|
||||
);
|
||||
const none = HeartRun(
|
||||
at: Duration.zero,
|
||||
trigger: HeartTrigger.timed,
|
||||
bpm: [0, 0],
|
||||
);
|
||||
|
||||
expect(run.mean, 71);
|
||||
expect(none.mean, isNull);
|
||||
});
|
||||
|
||||
test('the samples come a point a minute, averaged across runs, in time '
|
||||
'order, half a run lost contact or not', () {
|
||||
const at = Duration(hours: 7, minutes: 30);
|
||||
final points = heartByMinute([
|
||||
// Later, a run of its own.
|
||||
HeartRun(
|
||||
at: at + const Duration(minutes: 10),
|
||||
trigger: HeartTrigger.highActivity,
|
||||
bpm: const [140, 142],
|
||||
),
|
||||
// Two minutes a sample a second, every other one not read.
|
||||
HeartRun(
|
||||
at: at,
|
||||
trigger: HeartTrigger.timed,
|
||||
bpm: [
|
||||
for (var i = 0; i < 120; i++)
|
||||
if (i.isOdd) 0 else if (i < 60) 60 else 64,
|
||||
],
|
||||
),
|
||||
// Within the first minute too.
|
||||
HeartRun(
|
||||
at: at + const Duration(seconds: 50),
|
||||
trigger: HeartTrigger.manual,
|
||||
bpm: const [90],
|
||||
),
|
||||
// Nothing read at all.
|
||||
HeartRun(
|
||||
at: at + const Duration(minutes: 20),
|
||||
trigger: HeartTrigger.timed,
|
||||
bpm: const [0, 0, 0],
|
||||
),
|
||||
]);
|
||||
|
||||
expect(points, [
|
||||
// 30 samples of 60 and one of 90.
|
||||
(at, 61),
|
||||
(at + const Duration(minutes: 1), 64),
|
||||
(at + const Duration(minutes: 10), 141),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
|
@ -65,6 +65,25 @@ void main() {
|
|||
expect(decodeHeart(encodeHeart(heart)), heart);
|
||||
});
|
||||
|
||||
test('a day kept with its skin temperature out of order comes back in '
|
||||
'order, a time given twice once (D65)', () {
|
||||
final kept = DayContent(
|
||||
date: date,
|
||||
skin: const [
|
||||
(Duration(minutes: 2), 33.5),
|
||||
(Duration.zero, 33.1),
|
||||
(Duration(minutes: 1), 33.3),
|
||||
(Duration.zero, 33.1),
|
||||
],
|
||||
);
|
||||
|
||||
expect(decodeDay(encodeDay(kept))!.skin, const [
|
||||
(Duration.zero, 33.1),
|
||||
(Duration(minutes: 1), 33.3),
|
||||
(Duration(minutes: 2), 33.5),
|
||||
]);
|
||||
});
|
||||
|
||||
test('a day with nothing but its date comes back so', () {
|
||||
final bare = DayContent(date: date);
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,48 @@ void main() {
|
|||
},
|
||||
);
|
||||
|
||||
test('skin temperature comes in time order, a time given twice once '
|
||||
'(D65)', () {
|
||||
final lines = LoopLines()
|
||||
..add(const {
|
||||
'kind': 'skin',
|
||||
'date': '2026-10-28',
|
||||
'samples': [
|
||||
[120000, 33.5],
|
||||
[0, 33.1],
|
||||
[60000, 33.3],
|
||||
[0, 40.0],
|
||||
[60000, 33.3],
|
||||
],
|
||||
});
|
||||
|
||||
expect(lines.skins[today], const [
|
||||
(Duration.zero, 33.1),
|
||||
(Duration(minutes: 1), 33.3),
|
||||
(Duration(minutes: 2), 33.5),
|
||||
]);
|
||||
});
|
||||
|
||||
test("heart rate the band couldn't read, 0 or beyond a heart's, counts "
|
||||
'as none read, in its place (D65)', () {
|
||||
final lines = LoopLines()
|
||||
..add(const {
|
||||
'kind': 'heart',
|
||||
'date': '2026-10-27',
|
||||
'runs': [
|
||||
{
|
||||
'at': 600,
|
||||
'trigger': 'timed',
|
||||
'bpm': [60, 0, 62, 0, 300, -1],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
final run = lines.hearts[yesterday]!.single;
|
||||
expect(run.bpm, [60, 0, 62, 0, 0, 0]);
|
||||
expect(run.mean, 61);
|
||||
});
|
||||
|
||||
test('a later line of a kind and date replaces an earlier one', () {
|
||||
final lines = LoopLines()
|
||||
..add(summary)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,21 @@ void main() {
|
|||
),
|
||||
);
|
||||
|
||||
test('its points are drawn in the order of their x, never backwards '
|
||||
'(D65); points in order are taken as they are', () {
|
||||
const sorted = [(x: 0.0, value: 1.0), (x: 1.0, value: 2.0)];
|
||||
|
||||
expect(
|
||||
chartInOrder(const [
|
||||
(x: 2.0, value: 3.0),
|
||||
(x: 0.0, value: 1.0),
|
||||
(x: 1.0, value: 2.0),
|
||||
]),
|
||||
[(x: 0.0, value: 1.0), (x: 1.0, value: 2.0), (x: 2.0, value: 3.0)],
|
||||
);
|
||||
expect(identical(chartInOrder(sorted), sorted), isTrue);
|
||||
});
|
||||
|
||||
testWidgets('held, the value and its moment under the finger show beside '
|
||||
'the title, following the finger, and go when it lifts', (
|
||||
tester,
|
||||
|
|
|
|||
Loading…
Reference in a new issue