From 63caf9dee99bc1f1a743423c80849f3e699f612c Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Tue, 10 Nov 2026 15:41:55 +0100 Subject: [PATCH] Match RunnerUp TCX: Calories, walk as Other Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WzxdWZ9CKZsNxtD5Jr9bYD --- .../RunnerUp_2026-11-09-09-15-00_Walking.tcx | 3 ++- .../RunnerUp_2026-11-09-17-30-00_Running.tcx | 1 + screen_locker/tests/test_run_tcx_golden.py | 12 +++++----- .../workout_app/lib/models/run_track.dart | 22 +++++++++++++------ .../workout_app/lib/services/tcx_codec.dart | 7 ++++-- .../test/services/tcx_codec_test.dart | 16 ++++++++++++++ 6 files changed, 44 insertions(+), 17 deletions(-) diff --git a/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-09-15-00_Walking.tcx b/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-09-15-00_Walking.tcx index db26651..082d0c2 100644 --- a/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-09-15-00_Walking.tcx +++ b/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-09-15-00_Walking.tcx @@ -1,11 +1,12 @@ - + 2026-11-09T08:15:00Z 1665 2331.004 + 0 Active Manual diff --git a/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-17-30-00_Running.tcx b/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-17-30-00_Running.tcx index a0bddf5..bf6d10e 100644 --- a/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-17-30-00_Running.tcx +++ b/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-17-30-00_Running.tcx @@ -6,6 +6,7 @@ 1560 4212.014 + 0 Active Manual diff --git a/screen_locker/tests/test_run_tcx_golden.py b/screen_locker/tests/test_run_tcx_golden.py index eb12699..d0366f8 100644 --- a/screen_locker/tests/test_run_tcx_golden.py +++ b/screen_locker/tests/test_run_tcx_golden.py @@ -135,20 +135,18 @@ class TestGoldenWalk: ) assert data["activity_id"] == str(spec["started_at"]) - def test_walk_is_flagged_by_name_even_if_sport_is_not_walking( - self, inbox: Path - ) -> None: - """The app also writes ``Sport="Walking"``; either signal alone suffices.""" + def test_walk_is_marked_by_file_name_as_with_runnerup(self, inbox: Path) -> None: + """Like RunnerUp, the app writes ``Sport="Other"``; the name says walk.""" spec = _golden()["walking"] name = str(spec["file"]) body = (FIXTURES / name).read_text(encoding="utf-8") - assert 'Sport="Walking"' in body + assert 'Sport="Other"' in body renamed = inbox / "activity.tcx" - renamed.write_text(body.replace('Sport="Walking"', 'Sport="Other"')) + renamed.write_text(body) data = Verifier()._pull_and_parse_tcx(str(renamed)) assert data is not None assert data["walking"] is False - (inbox / name).write_text(body.replace('Sport="Walking"', 'Sport="Other"')) + (inbox / name).write_text(body) by_name = Verifier()._pull_and_parse_tcx(str(inbox / name)) assert by_name is not None assert by_name["walking"] is True diff --git a/stronglift_replacement/workout_app/lib/models/run_track.dart b/stronglift_replacement/workout_app/lib/models/run_track.dart index e2d5656..40676ab 100644 --- a/stronglift_replacement/workout_app/lib/models/run_track.dart +++ b/stronglift_replacement/workout_app/lib/models/run_track.dart @@ -4,21 +4,29 @@ library; import 'package:flutter/foundation.dart'; -/// The sports the app records. The name doubles as the TCX `Sport` attribute -/// and the file-name suffix the PC keys on (`..._Walking.tcx`). +/// The sports the app records. enum RunSport { /// A run; credited on distance OR duration by the PC. - running('Running'), + running('Running', 'Running'), /// A walk; the PC sums a day's walks and credits their moving time. - walking('Walking'); + /// + /// Written as `Sport="Other"` exactly like RunnerUp does (the TCX schema + /// only knows Running, Biking and Other); the `_Walking.tcx` file-name + /// suffix is what marks it a walk. + walking('Walking', 'Other'); - const RunSport(this.tcxName); + const RunSport(this.tcxName, this.tcxSport); - /// `Sport="..."` on the TCX `Activity` and `_.tcx` in the file name. + /// The sport's name: the `_.tcx` file-name suffix the PC keys on, and + /// the `runs.sport` column. final String tcxName; - /// The sport named [tcxName], or null for anything the app does not record. + /// The schema-valid `Sport="..."` attribute the encoder writes. + final String tcxSport; + + /// The sport named [tcxName] (also accepts the Strava-style + /// `Sport="Walking"`), or null for anything the app does not record. static RunSport? fromTcxName(String? tcxName) { for (final sport in values) { if (sport.tcxName == tcxName) return sport; diff --git a/stronglift_replacement/workout_app/lib/services/tcx_codec.dart b/stronglift_replacement/workout_app/lib/services/tcx_codec.dart index 532fb33..bc32cf8 100644 --- a/stronglift_replacement/workout_app/lib/services/tcx_codec.dart +++ b/stronglift_replacement/workout_app/lib/services/tcx_codec.dart @@ -5,7 +5,8 @@ /// /// * File name `RunnerUp_YYYY-MM-DD-HH-MM-SS_.tcx`, stamp in LOCAL /// time (the PC globs today's local date in the name), suffix `Running` or -/// `Walking` (a walk is recognised by that suffix or `Sport="Walking"`). +/// `Walking` (a walk is recognised by that suffix or `Sport="Walking"`; the +/// encoder writes `Sport="Other"` for it, as RunnerUp does). /// * `` and every `