From f528722e1f34a2ea8a42feb5d16cc0e62be332e5 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Tue, 10 Nov 2026 18:26:23 +0100 Subject: [PATCH] Make unified tests match the -k filter Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WzxdWZ9CKZsNxtD5Jr9bYD --- screen_locker/_constants.py | 10 +++++----- screen_locker/_workout_ledger.py | 4 ++-- ...activity_day.py => test_tcx_unified_day.py} | 18 ++++++++++++++++++ ...tivity_rule.py => test_tcx_unified_rule.py} | 0 .../workout_app/lib/services/run_math.dart | 3 ++- 5 files changed, 27 insertions(+), 8 deletions(-) rename screen_locker/tests/{test_unified_activity_day.py => test_tcx_unified_day.py} (88%) rename screen_locker/tests/{test_unified_activity_rule.py => test_tcx_unified_rule.py} (100%) diff --git a/screen_locker/_constants.py b/screen_locker/_constants.py index 3c5936e..bc592d0 100644 --- a/screen_locker/_constants.py +++ b/screen_locker/_constants.py @@ -73,11 +73,11 @@ WORKOUT_DURATION_ACCEPT_MINUTES = ( ) RUNNERUP_PACKAGES = ("org.runnerup", "org.runnerup.free") RUNNERUP_DB_SDCARD_TMP = "/sdcard/.runnerup_tmp_verification.db" -# A run qualifies on EITHER criterion — they are an OR, not an AND. A short -# but long-lasting run counts, and so does a fast 5 km that takes under the -# duration bar. Do not reinstate an AND here: it would reject exactly the -# slow-run case this pair exists to allow. The duration arm is the shared -# MIN_WORKOUT_DURATION_MINUTES above; only the distance arm is run-specific. +# A day's activity (run or walk alike) qualifies on EITHER criterion — they are +# an OR, not an AND. A short but long-lasting session counts, and so does a +# fast 5 km that takes under the duration bar. Do not reinstate an AND here: +# it would reject exactly the slow case this pair exists to allow. The +# duration arm is the shared MIN_WORKOUT_DURATION_MINUTES above (moving time). MIN_RUN_DISTANCE_KM = 3.5 # inclusive: "as low as 3.5 km" # GPS distance tracking undershoots on tight turns/tree cover/urban canyons; # a run within 5% of the minimum is accepted rather than rejected on diff --git a/screen_locker/_workout_ledger.py b/screen_locker/_workout_ledger.py index 411f875..1df2546 100644 --- a/screen_locker/_workout_ledger.py +++ b/screen_locker/_workout_ledger.py @@ -4,8 +4,8 @@ Every other earner publishes its credits as HMAC-signed rows in a ledger that ``earned_time`` reads (``first_credit_at``, ``credit_units``). This is the workout's: ``~/.local/share/workout_locker/ledger.json``, one row per -* workout credit slot in ``log.json`` -- RunnerUp (a run, or the day's walks - summed), StrongLifts (phone or PC session), manual, any lock-screen or +* workout credit slot in ``log.json`` -- RunnerUp (the day's runs and walks, + summed by one rule), StrongLifts (phone or PC session), manual, any lock-screen or verify path -- appended by the one log write chokepoint (``_log_mixin.write_signed_entry``), so no path can credit without it; and * declared rest day -- appended when it is declared. diff --git a/screen_locker/tests/test_unified_activity_day.py b/screen_locker/tests/test_tcx_unified_day.py similarity index 88% rename from screen_locker/tests/test_unified_activity_day.py rename to screen_locker/tests/test_tcx_unified_day.py index 7f3c04d..4208e74 100644 --- a/screen_locker/tests/test_unified_activity_day.py +++ b/screen_locker/tests/test_tcx_unified_day.py @@ -175,3 +175,21 @@ class TestActivityDay: """The shortfall names both ways to qualify.""" day: ActivityDay = activity_day([{"distance_m": 100.0, "moving_seconds": 60.0}]) assert activity_shortfall(day).endswith("need 3.5+ km or 30+ min moving") + + +class TestLapOnlyExport: + """An export with laps but no trackpoints has no moving time to credit.""" + + def test_wall_clock_alone_credits_nothing(self, verifier: Verifier) -> None: + """40 min on the lap clock, 1 km, no trackpoints: too short.""" + body = ( + '' + "2026-10-10T08:002400" + "1000" + "" + ) + drop(verifier.folder, "08-00-00", "_Running", body) + result = verify(verifier) + assert result is not None + assert result[0] == "too_short" diff --git a/screen_locker/tests/test_unified_activity_rule.py b/screen_locker/tests/test_tcx_unified_rule.py similarity index 100% rename from screen_locker/tests/test_unified_activity_rule.py rename to screen_locker/tests/test_tcx_unified_rule.py diff --git a/stronglift_replacement/workout_app/lib/services/run_math.dart b/stronglift_replacement/workout_app/lib/services/run_math.dart index 4023718..ab81dab 100644 --- a/stronglift_replacement/workout_app/lib/services/run_math.dart +++ b/stronglift_replacement/workout_app/lib/services/run_math.dart @@ -28,7 +28,8 @@ const double kRunningMaxSpeedMps = 25 / 3.6; /// Speed cap above which a fix is a GPS jump, 8 km/h for a walk. const double kWalkingMaxSpeedMps = 8 / 3.6; -/// The jump cap for [sport]. +/// The jump cap for [sport]. Display only: the PC has no upper speed, so the +/// moving time shown here can be below what the PC credits, never above. double maxSpeedMps(RunSport sport) => switch (sport) { RunSport.running => kRunningMaxSpeedMps, RunSport.walking => kWalkingMaxSpeedMps,