"""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