Showing
6 changed files
with
77 additions
and
47 deletions
| @@ -178,6 +178,13 @@ class ApiHrvReportDataSource implements HrvReportDataSource { | @@ -178,6 +178,13 @@ class ApiHrvReportDataSource implements HrvReportDataSource { | ||
| 178 | if (_stressLevelFromApiId(item.hrvLevel) case final level?) | 178 | if (_stressLevelFromApiId(item.hrvLevel) case final level?) |
| 179 | date: level, | 179 | date: level, |
| 180 | }; | 180 | }; |
| 181 | + final dailyStressScoresByDate = <DateTime, double>{ | ||
| 182 | + for (final item | ||
| 183 | + in data.dailyDistributionList ?? const <DailyDistributionList>[]) | ||
| 184 | + if (_parseApiDate(item.date) case final date?) | ||
| 185 | + if (item.comprehensiveStressScore case final score?) | ||
| 186 | + date: score.toDouble(), | ||
| 187 | + }; | ||
| 181 | final distributionCounts = _distributionCounts(data.hrvDistributionList); | 188 | final distributionCounts = _distributionCounts(data.hrvDistributionList); |
| 182 | 189 | ||
| 183 | return YearlyHrvReport( | 190 | return YearlyHrvReport( |
| @@ -189,6 +196,7 @@ class ApiHrvReportDataSource implements HrvReportDataSource { | @@ -189,6 +196,7 @@ class ApiHrvReportDataSource implements HrvReportDataSource { | ||
| 189 | trendsByDate, | 196 | trendsByDate, |
| 190 | levelsByDate, | 197 | levelsByDate, |
| 191 | preferDailyLevel: true, | 198 | preferDailyLevel: true, |
| 199 | + dailyStressScoresByDate: dailyStressScoresByDate, | ||
| 192 | ), | 200 | ), |
| 193 | ], | 201 | ], |
| 194 | distributionCounts: | 202 | distributionCounts: |
| @@ -203,6 +211,7 @@ class ApiHrvReportDataSource implements HrvReportDataSource { | @@ -203,6 +211,7 @@ class ApiHrvReportDataSource implements HrvReportDataSource { | ||
| 203 | Map<DateTime, HrvTrendList> trendsByDate, | 211 | Map<DateTime, HrvTrendList> trendsByDate, |
| 204 | Map<DateTime, HrvStressLevel> levelsByDate, { | 212 | Map<DateTime, HrvStressLevel> levelsByDate, { |
| 205 | bool preferDailyLevel = false, | 213 | bool preferDailyLevel = false, |
| 214 | + Map<DateTime, double> dailyStressScoresByDate = const {}, | ||
| 206 | }) { | 215 | }) { |
| 207 | final trend = trendsByDate[date]; | 216 | final trend = trendsByDate[date]; |
| 208 | final trendLevel = _stressLevelFromApiId(trend?.state); | 217 | final trendLevel = _stressLevelFromApiId(trend?.state); |
| @@ -215,6 +224,7 @@ class ApiHrvReportDataSource implements HrvReportDataSource { | @@ -215,6 +224,7 @@ class ApiHrvReportDataSource implements HrvReportDataSource { | ||
| 215 | averageHeartRate: trend?.hrAverage?.round(), | 224 | averageHeartRate: trend?.hrAverage?.round(), |
| 216 | lastRestingHrValue: trend?.lastRestingHrValue, | 225 | lastRestingHrValue: trend?.lastRestingHrValue, |
| 217 | comprehensiveStressScore: trend?.comprehensiveStressScore?.toDouble(), | 226 | comprehensiveStressScore: trend?.comprehensiveStressScore?.toDouble(), |
| 227 | + dailyComprehensiveStressScore: dailyStressScoresByDate[date], | ||
| 218 | trendLevel: trendLevel, | 228 | trendLevel: trendLevel, |
| 219 | level: level, | 229 | level: level, |
| 220 | ); | 230 | ); |
| @@ -418,6 +428,16 @@ class MockHrvReportDataSource implements HrvReportDataSource { | @@ -418,6 +428,16 @@ class MockHrvReportDataSource implements HrvReportDataSource { | ||
| 418 | if (year == 2025) return YearlyHrvReport.empty(year); | 428 | if (year == 2025) return YearlyHrvReport.empty(year); |
| 419 | 429 | ||
| 420 | const monthlyValues = [58.0, 82.0, 64.0, 74.0, 31.0, 45.0, 31.0]; | 430 | const monthlyValues = [58.0, 82.0, 64.0, 74.0, 31.0, 45.0, 31.0]; |
| 431 | + const monthlyStressScores = [42.0, 29.0, 48.0, 33.0, 72.0, 86.0, 55.0]; | ||
| 432 | + const monthlyTrendLevels = [ | ||
| 433 | + HrvStressLevel.normal, | ||
| 434 | + HrvStressLevel.normal, | ||
| 435 | + HrvStressLevel.normal, | ||
| 436 | + HrvStressLevel.normal, | ||
| 437 | + HrvStressLevel.attention, | ||
| 438 | + HrvStressLevel.overload, | ||
| 439 | + HrvStressLevel.normal, | ||
| 440 | + ]; | ||
| 421 | final days = <HrvDayReport>[]; | 441 | final days = <HrvDayReport>[]; |
| 422 | var dataIndex = 0; | 442 | var dataIndex = 0; |
| 423 | final levels = <HrvStressLevel>[ | 443 | final levels = <HrvStressLevel>[ |
| @@ -437,10 +457,26 @@ class MockHrvReportDataSource implements HrvReportDataSource { | @@ -437,10 +457,26 @@ class MockHrvReportDataSource implements HrvReportDataSource { | ||
| 437 | averageHrv: hasData ? monthlyValues[month - 1] : null, | 457 | averageHrv: hasData ? monthlyValues[month - 1] : null, |
| 438 | averageHeartRate: hasData ? 67 : null, | 458 | averageHeartRate: hasData ? 67 : null, |
| 439 | level: level, | 459 | level: level, |
| 460 | + comprehensiveStressScore: | ||
| 461 | + day == 1 && month <= monthlyStressScores.length | ||
| 462 | + ? monthlyStressScores[month - 1] | ||
| 463 | + : null, | ||
| 464 | + dailyComprehensiveStressScore: | ||
| 465 | + hasData ? _mockDailyStressScore(level!) : null, | ||
| 466 | + trendLevel: day == 1 && month <= monthlyTrendLevels.length | ||
| 467 | + ? monthlyTrendLevels[month - 1] | ||
| 468 | + : null, | ||
| 440 | ), | 469 | ), |
| 441 | ); | 470 | ); |
| 442 | } | 471 | } |
| 443 | } | 472 | } |
| 444 | return YearlyHrvReport(year: year, days: days, validDayCount: 133); | 473 | return YearlyHrvReport(year: year, days: days, validDayCount: 133); |
| 445 | } | 474 | } |
| 475 | + | ||
| 476 | + double _mockDailyStressScore(HrvStressLevel level) => switch (level) { | ||
| 477 | + HrvStressLevel.excellent => 10, | ||
| 478 | + HrvStressLevel.normal => 40, | ||
| 479 | + HrvStressLevel.attention => 70, | ||
| 480 | + HrvStressLevel.overload => 90, | ||
| 481 | + }; | ||
| 446 | } | 482 | } |
| @@ -38,6 +38,7 @@ class HrvDayReport { | @@ -38,6 +38,7 @@ class HrvDayReport { | ||
| 38 | this.averageHeartRate, | 38 | this.averageHeartRate, |
| 39 | this.lastRestingHrValue, | 39 | this.lastRestingHrValue, |
| 40 | this.comprehensiveStressScore, | 40 | this.comprehensiveStressScore, |
| 41 | + this.dailyComprehensiveStressScore, | ||
| 41 | this.trendLevel, | 42 | this.trendLevel, |
| 42 | this.level, | 43 | this.level, |
| 43 | }); | 44 | }); |
| @@ -47,6 +48,7 @@ class HrvDayReport { | @@ -47,6 +48,7 @@ class HrvDayReport { | ||
| 47 | final int? averageHeartRate; | 48 | final int? averageHeartRate; |
| 48 | final int? lastRestingHrValue; | 49 | final int? lastRestingHrValue; |
| 49 | final double? comprehensiveStressScore; | 50 | final double? comprehensiveStressScore; |
| 51 | + final double? dailyComprehensiveStressScore; | ||
| 50 | final HrvStressLevel? trendLevel; | 52 | final HrvStressLevel? trendLevel; |
| 51 | final HrvStressLevel? level; | 53 | final HrvStressLevel? level; |
| 52 | 54 |
| @@ -592,51 +592,40 @@ class _DailyDistributionCard extends StatelessWidget { | @@ -592,51 +592,40 @@ class _DailyDistributionCard extends StatelessWidget { | ||
| 592 | 592 | ||
| 593 | ({List<HrvDayReport> mostStressed, List<HrvDayReport> mostRelaxed}) | 593 | ({List<HrvDayReport> mostStressed, List<HrvDayReport> mostRelaxed}) |
| 594 | _dailyStressExtremeDays(YearlyHrvReport report) { | 594 | _dailyStressExtremeDays(YearlyHrvReport report) { |
| 595 | - final days = report.days.where((day) => day.level != null).toList() | ||
| 596 | - ..sort(_compareByStressDescending); | ||
| 597 | - | ||
| 598 | - final mostStressed = <HrvDayReport>[]; | ||
| 599 | - final mostRelaxed = <HrvDayReport>[]; | ||
| 600 | - if (days.isEmpty) { | ||
| 601 | - return (mostStressed: mostStressed, mostRelaxed: mostRelaxed); | ||
| 602 | - } | ||
| 603 | - | ||
| 604 | - if (days.length == 1) { | ||
| 605 | - final day = days.single; | ||
| 606 | - if (_isStressedLevel(day.level!)) { | ||
| 607 | - mostStressed.add(day); | ||
| 608 | - } else { | ||
| 609 | - mostRelaxed.add(day); | ||
| 610 | - } | ||
| 611 | - return (mostStressed: mostStressed, mostRelaxed: mostRelaxed); | ||
| 612 | - } | ||
| 613 | - | ||
| 614 | - mostStressed.add(days.first); | ||
| 615 | - mostRelaxed.add(days.last); | ||
| 616 | - | ||
| 617 | - if (days.length == 3) { | ||
| 618 | - final middle = days[1]; | ||
| 619 | - if (_isStressedLevel(middle.level!)) { | ||
| 620 | - mostStressed.add(middle); | ||
| 621 | - } else { | ||
| 622 | - mostRelaxed.add(middle); | ||
| 623 | - } | ||
| 624 | - } else if (days.length >= 4) { | ||
| 625 | - mostStressed.add(days[1]); | ||
| 626 | - mostRelaxed.add(days[days.length - 2]); | ||
| 627 | - } | 595 | + final daysWithScore = report.days.where((day) { |
| 596 | + return day.level != null && day.dailyComprehensiveStressScore != null; | ||
| 597 | + }); | ||
| 598 | + final mostStressed = daysWithScore | ||
| 599 | + .where((day) => _isStressedLevel(day.level!)) | ||
| 600 | + .toList() | ||
| 601 | + ..sort(_compareByDailyStressDescending); | ||
| 602 | + final mostRelaxed = daysWithScore | ||
| 603 | + .where((day) => !_isStressedLevel(day.level!)) | ||
| 604 | + .toList() | ||
| 605 | + ..sort(_compareByDailyStressAscending); | ||
| 606 | + | ||
| 607 | + return ( | ||
| 608 | + mostStressed: _takeTwoAndSortByDate(mostStressed), | ||
| 609 | + mostRelaxed: _takeTwoAndSortByDate(mostRelaxed), | ||
| 610 | + ); | ||
| 611 | + } | ||
| 628 | 612 | ||
| 629 | - mostStressed.sort(_compareByDate); | ||
| 630 | - mostRelaxed.sort(_compareByDate); | ||
| 631 | - return (mostStressed: mostStressed, mostRelaxed: mostRelaxed); | 613 | + int _compareByDailyStressDescending(HrvDayReport a, HrvDayReport b) { |
| 614 | + final valueComparison = b.dailyComprehensiveStressScore! | ||
| 615 | + .compareTo(a.dailyComprehensiveStressScore!); | ||
| 616 | + return valueComparison == 0 ? a.date.compareTo(b.date) : valueComparison; | ||
| 632 | } | 617 | } |
| 633 | 618 | ||
| 634 | - int _compareByStressDescending(HrvDayReport a, HrvDayReport b) { | ||
| 635 | - final valueComparison = | ||
| 636 | - _stressRank(b.level!).compareTo(_stressRank(a.level!)); | 619 | + int _compareByDailyStressAscending(HrvDayReport a, HrvDayReport b) { |
| 620 | + final valueComparison = a.dailyComprehensiveStressScore! | ||
| 621 | + .compareTo(b.dailyComprehensiveStressScore!); | ||
| 637 | return valueComparison == 0 ? a.date.compareTo(b.date) : valueComparison; | 622 | return valueComparison == 0 ? a.date.compareTo(b.date) : valueComparison; |
| 638 | } | 623 | } |
| 639 | 624 | ||
| 625 | + List<HrvDayReport> _takeTwoAndSortByDate(List<HrvDayReport> days) { | ||
| 626 | + return days.take(2).toList()..sort(_compareByDate); | ||
| 627 | + } | ||
| 628 | + | ||
| 640 | int _compareByDate(HrvDayReport a, HrvDayReport b) { | 629 | int _compareByDate(HrvDayReport a, HrvDayReport b) { |
| 641 | return a.date.compareTo(b.date); | 630 | return a.date.compareTo(b.date); |
| 642 | } | 631 | } |
| @@ -646,14 +635,6 @@ class _DailyDistributionCard extends StatelessWidget { | @@ -646,14 +635,6 @@ class _DailyDistributionCard extends StatelessWidget { | ||
| 646 | level == HrvStressLevel.overload; | 635 | level == HrvStressLevel.overload; |
| 647 | } | 636 | } |
| 648 | 637 | ||
| 649 | - int _stressRank(HrvStressLevel level) { | ||
| 650 | - return switch (level) { | ||
| 651 | - HrvStressLevel.excellent => 0, | ||
| 652 | - HrvStressLevel.normal => 1, | ||
| 653 | - HrvStressLevel.attention => 2, | ||
| 654 | - HrvStressLevel.overload => 3, | ||
| 655 | - }; | ||
| 656 | - } | ||
| 657 | } | 638 | } |
| 658 | 639 | ||
| 659 | class _MonthDots extends StatelessWidget { | 640 | class _MonthDots extends StatelessWidget { |
| @@ -395,6 +395,7 @@ class LocalHealthDataConvert { | @@ -395,6 +395,7 @@ class LocalHealthDataConvert { | ||
| 395 | DailyDistributionList( | 395 | DailyDistributionList( |
| 396 | date: dateKey(item.day), | 396 | date: dateKey(item.day), |
| 397 | hrvLevel: item.state, | 397 | hrvLevel: item.state, |
| 398 | + comprehensiveStressScore: item.comprehensiveStressScore, | ||
| 398 | ), | 399 | ), |
| 399 | ] | 400 | ] |
| 400 | : null, | 401 | : null, |
| @@ -78,20 +78,26 @@ class DailyDistributionList { | @@ -78,20 +78,26 @@ class DailyDistributionList { | ||
| 78 | DailyDistributionList({ | 78 | DailyDistributionList({ |
| 79 | this.date, | 79 | this.date, |
| 80 | this.hrvLevel, | 80 | this.hrvLevel, |
| 81 | + this.comprehensiveStressScore, | ||
| 81 | }); | 82 | }); |
| 82 | 83 | ||
| 83 | DailyDistributionList.fromJson(dynamic json) { | 84 | DailyDistributionList.fromJson(dynamic json) { |
| 84 | date = json['date'] ?? json['time_key']; | 85 | date = json['date'] ?? json['time_key']; |
| 85 | hrvLevel = json['hrv_level'] ?? json['stress_state'] ?? json['stress_id']; | 86 | hrvLevel = json['hrv_level'] ?? json['stress_state'] ?? json['stress_id']; |
| 87 | + comprehensiveStressScore = json['comprehensive_stress_score']; | ||
| 86 | } | 88 | } |
| 87 | 89 | ||
| 88 | Object? date; | 90 | Object? date; |
| 89 | Object? hrvLevel; | 91 | Object? hrvLevel; |
| 92 | + num? comprehensiveStressScore; | ||
| 90 | 93 | ||
| 91 | Map<String, dynamic> toJson() { | 94 | Map<String, dynamic> toJson() { |
| 92 | final map = <String, dynamic>{}; | 95 | final map = <String, dynamic>{}; |
| 93 | map['date'] = date; | 96 | map['date'] = date; |
| 94 | map['hrv_level'] = hrvLevel; | 97 | map['hrv_level'] = hrvLevel; |
| 98 | + if (comprehensiveStressScore != null) { | ||
| 99 | + map['comprehensive_stress_score'] = comprehensiveStressScore; | ||
| 100 | + } | ||
| 95 | return map; | 101 | return map; |
| 96 | } | 102 | } |
| 97 | } | 103 | } |
| @@ -724,6 +724,10 @@ void main() { | @@ -724,6 +724,10 @@ void main() { | ||
| 724 | expect(statistics.dailyDistributionList, hasLength(1)); | 724 | expect(statistics.dailyDistributionList, hasLength(1)); |
| 725 | expect(statistics.dailyDistributionList?.single.date, 20260512); | 725 | expect(statistics.dailyDistributionList?.single.date, 20260512); |
| 726 | expect( | 726 | expect( |
| 727 | + statistics.dailyDistributionList?.single.comprehensiveStressScore, | ||
| 728 | + 77, | ||
| 729 | + ); | ||
| 730 | + expect( | ||
| 727 | statistics.dailyDistributionList?.map((e) => e.date), | 731 | statistics.dailyDistributionList?.map((e) => e.date), |
| 728 | isNot(contains(20260101)), | 732 | isNot(contains(20260101)), |
| 729 | ); | 733 | ); |
-
Please register or login to post a comment