|
...
|
...
|
@@ -348,7 +348,14 @@ class YearlyHrvReport implements HrvPeriodReport { |
|
|
|
days.where((day) => day.hasData).toList();
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool get hasData => daysWithData.isNotEmpty;
|
|
|
|
// The yearly view is a stress report. A month can have a comprehensive
|
|
|
|
// stress score even when there is no HRV average for that month.
|
|
|
|
bool get hasData => days.any(
|
|
|
|
(day) =>
|
|
|
|
day.comprehensiveStressScore != null ||
|
|
|
|
day.level != null ||
|
|
|
|
day.trendLevel != null,
|
|
|
|
);
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get validDays =>
|
|
...
|
...
|
@@ -395,22 +402,16 @@ class YearlyHrvReport implements HrvPeriodReport { |
|
|
|
}).length;
|
|
|
|
}
|
|
|
|
|
|
|
|
double? averageForMonth(int month) {
|
|
|
|
final values = daysForMonth(month)
|
|
|
|
.map((day) => day.averageHrv)
|
|
|
|
.whereType<double>()
|
|
|
|
.toList();
|
|
|
|
if (values.isEmpty) return null;
|
|
|
|
return values.reduce((sum, value) => sum + value) / values.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
double? averageStressScoreForMonth(int month) {
|
|
|
|
final values = daysForMonth(month)
|
|
|
|
.map((day) => day.comprehensiveStressScore)
|
|
|
|
.whereType<double>()
|
|
|
|
.toList();
|
|
|
|
if (values.isEmpty) return null;
|
|
|
|
return values.reduce((sum, value) => sum + value) / values.length;
|
|
|
|
/// The yearly API supplies one trend item per month. The data source places
|
|
|
|
/// it on the first day of the month, so this reads the server score directly
|
|
|
|
/// instead of aggregating daily values locally.
|
|
|
|
double? stressScoreForMonth(int month) {
|
|
|
|
for (final day in days) {
|
|
|
|
if (day.date.month == month && day.date.day == 1) {
|
|
|
|
return day.comprehensiveStressScore;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
HrvStressLevel? levelForMonth(int month) {
|
|
...
|
...
|
@@ -421,12 +422,10 @@ class YearlyHrvReport implements HrvPeriodReport { |
|
|
|
if (trendLevels.isNotEmpty) return trendLevels.first;
|
|
|
|
|
|
|
|
final levels = daysForMonth(month)
|
|
|
|
.where((day) => day.averageHrv != null)
|
|
|
|
.map((day) => day.level)
|
|
|
|
.whereType<HrvStressLevel>()
|
|
|
|
.toList();
|
|
|
|
if (levels.isEmpty) return null;
|
|
|
|
return levels.first;
|
|
|
|
return levels.isEmpty ? null : levels.first;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<int> get monthsWithLevel => [
|
|
...
|
...
|
@@ -438,10 +437,10 @@ class YearlyHrvReport implements HrvPeriodReport { |
|
|
|
final stressedValues = <MapEntry<int, double>>[];
|
|
|
|
final relaxedValues = <MapEntry<int, double>>[];
|
|
|
|
for (var month = 1; month <= 12; month++) {
|
|
|
|
final average = averageForMonth(month);
|
|
|
|
final score = stressScoreForMonth(month);
|
|
|
|
final level = levelForMonth(month);
|
|
|
|
if (average == null || level == null) continue;
|
|
|
|
final entry = MapEntry(month, average);
|
|
|
|
if (score == null || level == null) continue;
|
|
|
|
final entry = MapEntry(month, score);
|
|
|
|
if (level == HrvStressLevel.attention ||
|
|
|
|
level == HrvStressLevel.overload) {
|
|
|
|
stressedValues.add(entry);
|
|
...
|
...
|
@@ -451,32 +450,21 @@ class YearlyHrvReport implements HrvPeriodReport { |
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
mostStressed: _selectMonthsByHrv(stressedValues, lowestFirst: true),
|
|
|
|
leastStressed: _selectMonthsByHrv(relaxedValues, lowestFirst: false),
|
|
|
|
mostStressed:
|
|
|
|
_selectMonthsByStressScore(stressedValues, highestFirst: true),
|
|
|
|
leastStressed:
|
|
|
|
_selectMonthsByStressScore(relaxedValues, highestFirst: false),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<int> _selectMonthsByHrv(
|
|
|
|
List<int> _selectMonthsByStressScore(
|
|
|
|
List<MapEntry<int, double>> values, {
|
|
|
|
required bool lowestFirst,
|
|
|
|
required bool highestFirst,
|
|
|
|
}) {
|
|
|
|
values.sort((a, b) {
|
|
|
|
final valueComparison = lowestFirst
|
|
|
|
? a.value.compareTo(b.value)
|
|
|
|
: b.value.compareTo(a.value);
|
|
|
|
return valueComparison == 0 ? a.key.compareTo(b.key) : valueComparison;
|
|
|
|
});
|
|
|
|
return values.take(2).map((entry) => entry.key).toList()..sort();
|
|
|
|
}
|
|
|
|
|
|
|
|
List<int> monthsAtExtreme({required bool maximum}) {
|
|
|
|
final values = [
|
|
|
|
for (var month = 1; month <= 12; month++)
|
|
|
|
if (averageForMonth(month) case final value?) MapEntry(month, value),
|
|
|
|
];
|
|
|
|
values.sort((a, b) {
|
|
|
|
final valueComparison =
|
|
|
|
maximum ? b.value.compareTo(a.value) : a.value.compareTo(b.value);
|
|
|
|
final valueComparison = highestFirst
|
|
|
|
? b.value.compareTo(a.value)
|
|
|
|
: a.value.compareTo(b.value);
|
|
|
|
return valueComparison == 0 ? a.key.compareTo(b.key) : valueComparison;
|
|
|
|
});
|
|
|
|
return values.take(2).map((entry) => entry.key).toList()..sort();
|
...
|
...
|
|