mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(workouts): read a day with activity data and no workout as rest
The strain run writes no impulse row on a day without a scorable workout or active energy, so a rest day for someone without daily active energy was never rated: fitness and fatigue held for months instead of decaying, and the figures named the last stored day while the coverage count ended there too. A day without a stored impulse is now classified from the underlying data: a workout on the day leaves it unrated, steps, heart rate or active energy without a workout make it rest, and no data at all leaves it unrated. The series runs to the day the strain run scores, so a stretch without data after the last rated day shows in the coverage count, and the card names the last rated day when it lies further back than the day before.
This commit is contained in:
1 parent
0918451e02
commit
29d6029999
15 files changed
+286
-66
No files matched your search
@@ -44,7 +44,19 @@ describe("<TrainingLoadCard>", () => {
|
||||
it("says how many recent days were rated when some were not", () => {
|
||||
const html = render({ ...OK, recentRatedDays: 25, recentDays: 42 });
|
||||
expect(html).toContain('data-slot="training-load-coverage"');
|
||||
expect(html).toContain("25 of the last 42 days have heart-rate data");
|
||||
expect(html).toContain("25 of the last 42 days could be rated");
|
||||
});
|
||||
|
||||
it("names the last rated day when it lies behind the series end", () => {
|
||||
const series = [
|
||||
{ day: "2026-10-19", ctl: 54, atl: 61, tsb: -7, rated: false },
|
||||
{ day: "2026-10-20", ctl: 54, atl: 61, tsb: -7, rated: false },
|
||||
];
|
||||
const stale = render({ ...OK, asOf: "2026-09-02", series });
|
||||
expect(stale).toContain('data-slot="training-load-as-of"');
|
||||
expect(stale).toMatch(/As of 0?9\/0?2|As of 02\/09|As of Sep/);
|
||||
const fresh = render({ ...OK, asOf: "2026-10-19", series });
|
||||
expect(fresh).not.toContain('data-slot="training-load-as-of"');
|
||||
});
|
||||
|
||||
it("signs a positive form and drops the warm-up note once covered", () => {
|
||||
|
||||
@@ -6,11 +6,14 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
import { useFormatters, useTranslations } from "@/lib/i18n/context";
|
||||
import type { TrainingLoad } from "@/lib/insights/training-load";
|
||||
import { shiftDateKey } from "@/lib/tz/format";
|
||||
|
||||
/**
|
||||
* v1.44 — fitness, fatigue and form from the stored training impulse, the
|
||||
* server's figures as given. Renders nothing until the model has 28 days and
|
||||
* three training days to stand on.
|
||||
* three training days to stand on. The series ends on the day the strain run
|
||||
* scores; when the last rated day lies more than a day before that, the card
|
||||
* names it, so figures held through weeks without data never pass as fresh.
|
||||
*/
|
||||
export function TrainingLoadCard({
|
||||
trainingLoad,
|
||||
@@ -21,6 +24,10 @@ export function TrainingLoadCard({
|
||||
const fmt = useFormatters();
|
||||
if (!trainingLoad || trainingLoad.status !== "ok") return null;
|
||||
|
||||
const seriesEnd = trainingLoad.series.at(-1)?.day;
|
||||
const stale =
|
||||
seriesEnd !== undefined && trainingLoad.asOf < shiftDateKey(seriesEnd, -1);
|
||||
|
||||
const stats = [
|
||||
{ key: "fitness", value: trainingLoad.ctl },
|
||||
{ key: "fatigue", value: trainingLoad.atl },
|
||||
@@ -47,6 +54,16 @@ export function TrainingLoadCard({
|
||||
))}
|
||||
</dl>
|
||||
<p className="text-sm">{t("insights.workouts.load.explainer")}</p>
|
||||
{stale ? (
|
||||
<p
|
||||
className="text-muted-foreground text-xs"
|
||||
data-slot="training-load-as-of"
|
||||
>
|
||||
{t("insights.workouts.load.asOf", {
|
||||
date: fmt.dateShortSmartCalendar(trainingLoad.asOf),
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
{trainingLoad.recentRatedDays < trainingLoad.recentDays ? (
|
||||
<p
|
||||
className="text-muted-foreground text-xs"
|
||||
|
||||
Reference in new issue
Block a user