This commit is contained in:
2026-06-12 05:48:30 -04:00
parent 9d91ac8ebc
commit 64a5ab4b7f
37 changed files with 4530 additions and 407 deletions

View File

@@ -0,0 +1,92 @@
"""Integration: manual_activities flow through into the Banister PMC.
The contract is that `daily_training_load_series(..., include_manual=True)`
unions manual rows into the same Series the PMC consumes, so adding a
strength session lifts CTL on that day and the days following — same
recursion, no special case.
"""
from __future__ import annotations
import pandas as pd
from openrun.model import banister, daily_training_load_series
def _add_activity(conn, aid: int, when: str, atype: str, training_load: float) -> None:
conn.execute(
"""INSERT INTO activities
(activity_id, start_time_local, activity_type, training_load, raw, fetched_at)
VALUES (?, ?, ?, ?, '{}', 'now')""",
(aid, when, atype, training_load),
)
def _add_manual(conn, when: str, atype: str, training_load: float) -> None:
conn.execute(
"""INSERT INTO manual_activities
(activity_date, activity_type, training_load, source, imported_at)
VALUES (?, ?, ?, 'test', datetime('now'))""",
(when, atype, training_load),
)
def test_include_manual_increases_daily_sum(tmp_conn) -> None:
_add_activity(tmp_conn, aid=1, when="2026-05-01 06:00:00", atype="running", training_load=50.0)
_add_manual(tmp_conn, when="2026-05-01", atype="running", training_load=30.0)
tmp_conn.commit()
without = daily_training_load_series(tmp_conn)
with_manual = daily_training_load_series(tmp_conn, include_manual=True)
assert without.iloc[0] == 50.0
assert with_manual.iloc[0] == 80.0
def test_manual_only_day_appears_when_included(tmp_conn) -> None:
"""A day with only a manual row is invisible without the flag and appears with it."""
_add_manual(tmp_conn, when="2026-05-02", atype="running", training_load=25.0)
tmp_conn.commit()
without = daily_training_load_series(tmp_conn)
with_manual = daily_training_load_series(tmp_conn, include_manual=True)
assert without.empty
assert with_manual.loc[pd.Timestamp("2026-05-02")] == 25.0
def test_manual_load_lifts_banister_ctl_monotonically(tmp_conn) -> None:
"""For two otherwise-identical worlds, the one with extra manual TL must
show strictly higher CTL on the day the manual session was logged and on
every following day (the EWMA carries forward)."""
_add_activity(tmp_conn, aid=1, when="2026-05-01 06:00:00", atype="running", training_load=50.0)
_add_activity(tmp_conn, aid=2, when="2026-05-03 06:00:00", atype="running", training_load=60.0)
tmp_conn.commit()
base = banister(daily_training_load_series(tmp_conn))
_add_manual(tmp_conn, when="2026-05-02", atype="running", training_load=40.0)
tmp_conn.commit()
with_manual = banister(daily_training_load_series(tmp_conn, include_manual=True))
# 2026-05-01 is unchanged (manual is in the future at that point).
assert with_manual.loc["2026-05-01", "CTL"] == base.loc["2026-05-01", "CTL"]
# 2026-05-02 onward is strictly larger.
for d in ("2026-05-02", "2026-05-03"):
assert with_manual.loc[d, "CTL"] > base.loc[d, "CTL"]
def test_activity_type_filter_excludes_other_manual_rows(tmp_conn) -> None:
"""Manual rows whose type is not in activity_types stay out of the series."""
_add_manual(tmp_conn, when="2026-05-02", atype="strength", training_load=100.0)
tmp_conn.commit()
out = daily_training_load_series(tmp_conn, include_manual=True)
assert out.empty
def test_manual_same_day_as_garmin_row_sums(tmp_conn) -> None:
"""A manual row and a Garmin row on the same day must combine, not overwrite."""
_add_activity(tmp_conn, aid=1, when="2026-05-01 18:00:00", atype="running", training_load=70.0)
_add_manual(tmp_conn, when="2026-05-01", atype="running", training_load=30.0)
tmp_conn.commit()
s = daily_training_load_series(tmp_conn, include_manual=True)
assert s.loc[pd.Timestamp("2026-05-01")] == 100.0