From c2544c4a9c55cbb0bb66d4c8abd38aa2d145478b Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Tue, 10 Nov 2026 15:37:41 +0100 Subject: [PATCH] Add GPS run foundation: TCX codec, DB, deps Pure-Dart TCX codec pinned to the PC's RunnerUp parser by a shared golden fixture (Dart byte-compare + pytest through the real verifier), GPS filters and moving-time maths mirroring the PC walk rule, runs / run_points tables (schema v9), PositionSource seam over geolocator, location permissions and foreground service type, map/cache deps. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WzxdWZ9CKZsNxtD5Jr9bYD --- .../RunnerUp_2026-08-12-17-42-11_Running.tcx | 190 +++ .../RunnerUp_2026-11-09-09-15-00_Walking.tcx | 1071 +++++++++++++ .../RunnerUp_2026-11-09-17-30-00_Running.tcx | 747 +++++++++ .../tests/fixtures/run_tcx/golden_tracks.json | 1391 +++++++++++++++++ screen_locker/tests/test_run_tcx_golden.py | 169 ++ .../workout_app/DOCS-workout-app-context.md | 15 + .../android/app/src/main/AndroidManifest.xml | 20 +- .../workout_app/lib/models/run_record.dart | 51 + .../workout_app/lib/models/run_track.dart | 155 ++ .../workout_app/lib/services/gps_filter.dart | 135 ++ .../lib/services/position_source.dart | 38 + .../services/position_source_geolocator.dart | 57 + .../services/position_source_scripted.dart | 43 + .../workout_app/lib/services/run_math.dart | 74 + .../lib/services/storage_service.dart | 5 +- .../lib/services/storage_service_runs.dart | 185 +++ .../lib/services/storage_service_sandbox.dart | 2 + .../lib/services/storage_service_schema.dart | 9 + .../workout_app/lib/services/tcx_codec.dart | 240 +++ .../workout_app/pubspec.lock | 292 +++- .../workout_app/pubspec.yaml | 23 +- .../test/models/run_track_test.dart | 80 + .../test/services/gps_filter_test.dart | 131 ++ .../test/services/position_source_test.dart | 164 ++ .../test/services/run_math_test.dart | 71 + .../services/storage_service_runs_test.dart | 188 +++ .../test/services/tcx_codec_test.dart | 124 ++ .../test/services/tcx_decode_test.dart | 151 ++ .../services/tcx_golden_fixture_test.dart | 83 + 29 files changed, 5857 insertions(+), 47 deletions(-) create mode 100644 screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-08-12-17-42-11_Running.tcx create mode 100644 screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-09-15-00_Walking.tcx create mode 100644 screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-17-30-00_Running.tcx create mode 100644 screen_locker/tests/fixtures/run_tcx/golden_tracks.json create mode 100644 screen_locker/tests/test_run_tcx_golden.py create mode 100644 stronglift_replacement/workout_app/lib/models/run_record.dart create mode 100644 stronglift_replacement/workout_app/lib/models/run_track.dart create mode 100644 stronglift_replacement/workout_app/lib/services/gps_filter.dart create mode 100644 stronglift_replacement/workout_app/lib/services/position_source.dart create mode 100644 stronglift_replacement/workout_app/lib/services/position_source_geolocator.dart create mode 100644 stronglift_replacement/workout_app/lib/services/position_source_scripted.dart create mode 100644 stronglift_replacement/workout_app/lib/services/run_math.dart create mode 100644 stronglift_replacement/workout_app/lib/services/storage_service_runs.dart create mode 100644 stronglift_replacement/workout_app/lib/services/tcx_codec.dart create mode 100644 stronglift_replacement/workout_app/test/models/run_track_test.dart create mode 100644 stronglift_replacement/workout_app/test/services/gps_filter_test.dart create mode 100644 stronglift_replacement/workout_app/test/services/position_source_test.dart create mode 100644 stronglift_replacement/workout_app/test/services/run_math_test.dart create mode 100644 stronglift_replacement/workout_app/test/services/storage_service_runs_test.dart create mode 100644 stronglift_replacement/workout_app/test/services/tcx_codec_test.dart create mode 100644 stronglift_replacement/workout_app/test/services/tcx_decode_test.dart create mode 100644 stronglift_replacement/workout_app/test/services/tcx_golden_fixture_test.dart diff --git a/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-08-12-17-42-11_Running.tcx b/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-08-12-17-42-11_Running.tcx new file mode 100644 index 0000000..3b8bd3e --- /dev/null +++ b/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-08-12-17-42-11_Running.tcx @@ -0,0 +1,190 @@ + + + + + 2026-08-12T15:42:11Z + + 624 + 983.6306355595589 + 0 + Active + Manual + + + + + 48.858438076 + 2.294584871 + + 110.06611511572041 + 2.142966628074646 + + + 54 + + + + + + + 48.858442867 + 2.294574705 + + 109.65041623678366 + 3.0187163949012756 + + + 53 + + + + + + + 48.858578090 + 2.294260282 + + 102.71014718222068 + 31.116633534431458 + + + 65 + + + + + + + 48.858744365 + 2.294188828 + + 101.26072111921606 + 50.64840912818909 + + + 61 + + + + + + + 48.858792955 + 2.294100028 + + 100.58585072961351 + 58.97427588701248 + + + 58 + + + + + + + 48.858859857 + 2.292925949 + + 99.60869064520372 + 140.32738709449768 + + + 57 + + + + + + + + 626 + 980.7304027080536 + 0 + Active + Manual + + + + + 48.859968549 + 2.282023519 + + 99.76934097304894 + 1003.5486820391379 + + + 59 + + + + + + + 48.859990492 + 2.282025531 + + 100.06562741186055 + 1005.9941445044242 + + + 57 + + + + + + + 48.861645306 + 2.281974721 + + 99.66943152941295 + 1190.9687945595942 + + + 55 + + + + + + + 48.861872213 + 2.282046494 + + 99.35286804810477 + 1217.0878333947621 + + + 58 + + + + + + + + RunnerUp Pixel 6a with barometer + 0 + 0 + + 1 + 0 + 0 + 0 + + + + + + org.runnerup - 2.11.0.1 - 23000372 + + + 1 + 0 + + + en + 000-00000-00 + + \ No newline at end of file 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 new file mode 100644 index 0000000..db26651 --- /dev/null +++ b/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-09-15-00_Walking.tcx @@ -0,0 +1,1071 @@ + + + + + 2026-11-09T08:15:00Z + + 1665 + 2331.004 + Active + Manual + + + + + 48.8584000 + 2.2945000 + + 35.0 + 0.000 + + + + + 48.8585447 + 2.2946845 + + 35.0 + 21.002 + + + + + 48.8586833 + 2.2948794 + + 35.3 + 41.997 + + + + + 48.8588157 + 2.2950842 + + 35.5 + 63.003 + + + + + 48.8589416 + 2.2952981 + + 35.8 + 83.999 + + + + + 48.8590611 + 2.2955204 + + 36.1 + 105.000 + + + + + 48.8591742 + 2.2957502 + + 36.3 + 125.995 + + + + + 48.8592813 + 2.2959867 + + 36.6 + 146.999 + + + + + 48.8593827 + 2.2962289 + + 36.8 + 168.001 + + + + + 48.8594788 + 2.2964760 + + 37.0 + 189.000 + + + + + 48.8595702 + 2.2967272 + + 37.2 + 210.000 + + + + + 48.8596577 + 2.2969816 + + 37.4 + 231.001 + + + + + 48.8597420 + 2.2972385 + + 37.5 + 252.002 + + + + + 48.8598239 + 2.2974971 + + 37.7 + 272.998 + + + + + 48.8599043 + 2.2977569 + + 37.8 + 294.002 + + + + + 48.8599841 + 2.2980170 + + 37.9 + 314.997 + + + + + 48.8600643 + 2.2982770 + + 37.9 + 336.004 + + + + + 48.8601457 + 2.2985360 + + 38.0 + 357.002 + + + + + 48.8602292 + 2.2987934 + + 38.0 + 377.997 + + + + + 48.8603158 + 2.2990486 + + 38.0 + 399.003 + + + + + 48.8604061 + 2.2993007 + + 38.0 + 420.002 + + + + + 48.8605008 + 2.2995490 + + 37.9 + 440.998 + + + + + 48.8606007 + 2.2997927 + + 37.8 + 462.003 + + + + + 48.8607062 + 2.3000308 + + 37.7 + 483.003 + + + + + 48.8608176 + 2.3002625 + + 37.6 + 503.997 + + + + + 48.8609354 + 2.3004870 + + 37.5 + 525.004 + + + + + 48.8610595 + 2.3007033 + + 37.3 + 545.999 + + + + + 48.8611901 + 2.3009107 + + 37.1 + 567.001 + + + + + 48.8613271 + 2.3011084 + + 36.9 + 588.006 + + + + + 48.8614701 + 2.3012958 + + 36.7 + 609.001 + + + + + 48.8616190 + 2.3014725 + + 36.5 + 630.006 + + + + + 48.8617732 + 2.3016381 + + 36.2 + 651.000 + + + + + 48.8619324 + 2.3017925 + + 36.0 + 671.999 + + + + + 48.8620961 + 2.3019358 + + 35.7 + 693.004 + + + + + 48.8622636 + 2.3020682 + + 35.4 + 713.997 + + + + + 48.8624346 + 2.3021902 + + 35.2 + 735.002 + + + + + 48.8626084 + 2.3023024 + + 34.9 + 755.998 + + + + + 48.8627846 + 2.3024057 + + 34.6 + 776.997 + + + + + 48.8629628 + 2.3025010 + + 34.3 + 798.003 + + + + + 48.8631425 + 2.3025894 + + 34.1 + 819.005 + + + + + 48.8633233 + 2.3026723 + + 33.8 + 840.004 + + + + + 48.8635049 + 2.3027509 + + 33.6 + 860.999 + + + + + 48.8636871 + 2.3028267 + + 33.3 + 882.004 + + + + + 48.8638695 + 2.3029011 + + 33.1 + 903.004 + + + + + 48.8640519 + 2.3029757 + + 32.9 + 924.007 + + + + + 48.8642340 + 2.3030518 + + 32.7 + 945.007 + + + + + 48.8644155 + 2.3031311 + + 32.6 + 966.006 + + + + + 48.8645961 + 2.3032149 + + 32.4 + 987.002 + + + + + 48.8647755 + 2.3033044 + + 32.3 + 1007.997 + + + + + 48.8649534 + 2.3034010 + + 32.2 + 1029.003 + + + + + 48.8651292 + 2.3035058 + + 32.1 + 1050.001 + + + + + 48.8653026 + 2.3036197 + + 32.0 + 1071.005 + + + + + 48.8654730 + 2.3037435 + + 32.0 + 1092.005 + + + + + 48.8656399 + 2.3038778 + + 32.0 + 1113.004 + + + + + 48.8658028 + 2.3040231 + + 32.0 + 1134.005 + + + + + 48.8659611 + 2.3041795 + + 32.1 + 1154.998 + + + + + 48.8661145 + 2.3043471 + + 32.1 + 1176.004 + + + + + 48.8662623 + 2.3045258 + + 32.2 + 1197.003 + + + + + 48.8664043 + 2.3047151 + + 32.3 + 1218.004 + + + + + 48.8665401 + 2.3049146 + + 32.5 + 1239.003 + + + + + 48.8666696 + 2.3051236 + + 32.6 + 1260.005 + + + + + 48.8667926 + 2.3053414 + + 32.8 + 1281.001 + + + + + 48.8667926 + 2.3053414 + + 33.0 + 1281.001 + + + + + 48.8667926 + 2.3053414 + + 33.2 + 1281.001 + + + + + 48.8671059 + 2.3060590 + + 33.4 + 1281.001 + + + + + 48.8672049 + 2.3063036 + + 33.7 + 1302.008 + + + + + 48.8672988 + 2.3065527 + + 33.9 + 1323.008 + + + + + 48.8673884 + 2.3068054 + + 34.2 + 1344.006 + + + + + 48.8674743 + 2.3070611 + + 34.4 + 1365.007 + + + + + 48.8675574 + 2.3073189 + + 34.7 + 1386.006 + + + + + 48.8676385 + 2.3075782 + + 35.0 + 1407.007 + + + + + 48.8677186 + 2.3078382 + + 35.2 + 1428.006 + + + + + 48.8677984 + 2.3080984 + + 35.5 + 1449.005 + + + + + 48.8678790 + 2.3083580 + + 35.8 + 1470.002 + + + + + 48.8679613 + 2.3086165 + + 36.0 + 1491.008 + + + + + 48.8680461 + 2.3088730 + + 36.3 + 1512.006 + + + + + 48.8681342 + 2.3091270 + + 36.5 + 1533.009 + + + + + 48.8682264 + 2.3093775 + + 36.8 + 1554.005 + + + + + 48.8683234 + 2.3096238 + + 37.0 + 1575.002 + + + + + 48.8684258 + 2.3098651 + + 37.2 + 1596.005 + + + + + 48.8685339 + 2.3101005 + + 37.3 + 1617.004 + + + + + 48.8686482 + 2.3103291 + + 37.5 + 1638.007 + + + + + 48.8687688 + 2.3105500 + + 37.6 + 1659.004 + + + + + 48.8688959 + 2.3107624 + + 37.8 + 1680.006 + + + + + 48.8690294 + 2.3109656 + + 37.9 + 1701.012 + + + + + 48.8691691 + 2.3111587 + + 37.9 + 1722.007 + + + + + 48.8693148 + 2.3113414 + + 38.0 + 1743.008 + + + + + 48.8694661 + 2.3115131 + + 38.0 + 1764.002 + + + + + 48.8696227 + 2.3116737 + + 38.0 + 1785.007 + + + + + 48.8697839 + 2.3118231 + + 38.0 + 1806.000 + + + + + 48.8699494 + 2.3119615 + + 37.9 + 1827.003 + + + + + 48.8701186 + 2.3120892 + + 37.8 + 1848.008 + + + + + 48.8702909 + 2.3122067 + + 37.7 + 1869.006 + + + + + 48.8704659 + 2.3123147 + + 37.6 + 1890.007 + + + + + 48.8706430 + 2.3124143 + + 37.5 + 1911.004 + + + + + 48.8708219 + 2.3125063 + + 37.3 + 1932.004 + + + + + 48.8710021 + 2.3125921 + + 37.1 + 1953.001 + + + + + 48.8711834 + 2.3126729 + + 36.9 + 1974.009 + + + + + 48.8713653 + 2.3127501 + + 36.7 + 1995.009 + + + + + 48.8715476 + 2.3128251 + + 36.5 + 2016.009 + + + + + 48.8717300 + 2.3128994 + + 36.2 + 2037.006 + + + + + 48.8719123 + 2.3129745 + + 36.0 + 2058.008 + + + + + 48.8720942 + 2.3130519 + + 35.7 + 2079.012 + + + + + 48.8722753 + 2.3131330 + + 35.5 + 2100.005 + + + + + 48.8724555 + 2.3132193 + + 35.2 + 2121.012 + + + + + 48.8726342 + 2.3133119 + + 34.9 + 2142.005 + + + + + 48.8728112 + 2.3134121 + + 34.6 + 2163.007 + + + + + 48.8729860 + 2.3135209 + + 34.4 + 2184.010 + + + + + 48.8731581 + 2.3136392 + + 34.1 + 2205.011 + + + + + 48.8733270 + 2.3137677 + + 33.8 + 2226.012 + + + + + 48.8734921 + 2.3139069 + + 33.6 + 2247.005 + + + + + 48.8736530 + 2.3140572 + + 33.4 + 2268.003 + + + + + 48.8738092 + 2.3142187 + + 33.1 + 2289.007 + + + + + 48.8739601 + 2.3143914 + + 32.9 + 2310.009 + + + + + 48.8741053 + 2.3145749 + + 32.8 + 2331.004 + + + + + kuhy workout_app + 0 + 0 + + 1 + 0 + + + + + + kuhy workout_app + + + 1 + 0 + + + en + 000-00000-00 + + \ No newline at end of file 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 new file mode 100644 index 0000000..a0bddf5 --- /dev/null +++ b/screen_locker/tests/fixtures/run_tcx/RunnerUp_2026-11-09-17-30-00_Running.tcx @@ -0,0 +1,747 @@ + + + + + 2026-11-09T16:30:00Z + + 1560 + 4212.014 + Active + Manual + + + + + 48.8584000 + 2.2945000 + + 35.0 + 0.000 + + + + + 48.8587720 + 2.2949745 + + 35.0 + 54.000 + + + + + 48.8591285 + 2.2954757 + + 35.3 + 107.999 + + + + + 48.8594688 + 2.2960023 + + 35.5 + 161.998 + + + + + 48.8597926 + 2.2965524 + + 35.8 + 215.997 + + + + + 48.8600999 + 2.2971240 + + 36.1 + 269.998 + + + + + 48.8603909 + 2.2977150 + + 36.3 + 324.001 + + + + + 48.8606663 + 2.2983230 + + 36.6 + 378.001 + + + + + 48.8609269 + 2.2989459 + + 36.8 + 432.002 + + + + + 48.8611740 + 2.2995813 + + 37.0 + 485.998 + + + + + 48.8614092 + 2.3002272 + + 37.2 + 540.003 + + + + + 48.8616342 + 2.3008814 + + 37.4 + 594.005 + + + + + 48.8618509 + 2.3015420 + + 37.5 + 648.004 + + + + + 48.8620615 + 2.3022071 + + 37.7 + 702.000 + + + + + 48.8622683 + 2.3028751 + + 37.8 + 756.006 + + + + + 48.8624735 + 2.3035441 + + 37.9 + 810.002 + + + + + 48.8626796 + 2.3042125 + + 37.9 + 864.001 + + + + + 48.8628890 + 2.3048786 + + 38.0 + 918.005 + + + + + 48.8631038 + 2.3055406 + + 38.0 + 972.001 + + + + + 48.8633263 + 2.3061968 + + 38.0 + 1026.003 + + + + + 48.8635585 + 2.3068452 + + 38.0 + 1080.006 + + + + + 48.8638022 + 2.3074837 + + 37.9 + 1134.004 + + + + + 48.8640589 + 2.3081103 + + 37.8 + 1188.001 + + + + + 48.8643301 + 2.3087227 + + 37.7 + 1242.002 + + + + + 48.8646168 + 2.3093186 + + 37.6 + 1296.005 + + + + + 48.8649196 + 2.3098958 + + 37.5 + 1350.008 + + + + + 48.8652388 + 2.3104521 + + 37.3 + 1404.004 + + + + + 48.8655746 + 2.3109853 + + 37.1 + 1457.999 + + + + + 48.8659267 + 2.3114938 + + 36.9 + 1512.002 + + + + + 48.8662946 + 2.3119758 + + 36.7 + 1566.007 + + + + + 48.8666773 + 2.3124302 + + 36.5 + 1620.004 + + + + + 48.8670740 + 2.3128561 + + 36.2 + 1674.006 + + + + + 48.8674834 + 2.3132533 + + 36.0 + 1728.010 + + + + + 48.8679042 + 2.3136218 + + 35.7 + 1782.009 + + + + + 48.8683351 + 2.3139623 + + 35.4 + 1836.009 + + + + + 48.8687747 + 2.3142760 + + 35.2 + 1890.008 + + + + + 48.8692217 + 2.3145646 + + 34.9 + 1944.009 + + + + + 48.8696748 + 2.3148301 + + 34.6 + 1998.004 + + + + + 48.8701329 + 2.3150752 + + 34.3 + 2052.005 + + + + + 48.8705949 + 2.3153027 + + 34.1 + 2106.005 + + + + + 48.8710599 + 2.3155157 + + 33.8 + 2160.006 + + + + + 48.8715270 + 2.3157179 + + 33.6 + 2214.010 + + + + + 48.8719954 + 2.3159128 + + 33.3 + 2268.009 + + + + + 48.8724644 + 2.3161042 + + 33.1 + 2322.006 + + + + + 48.8729334 + 2.3162960 + + 32.9 + 2376.010 + + + + + 48.8734016 + 2.3164919 + + 32.7 + 2430.007 + + + + + 48.8738683 + 2.3166958 + + 32.6 + 2484.002 + + + + + 48.8743329 + 2.3169112 + + 32.4 + 2538.012 + + + + + 48.8747943 + 2.3171416 + + 32.3 + 2592.013 + + + + + 48.8752516 + 2.3173901 + + 32.2 + 2646.013 + + + + + 48.8757037 + 2.3176595 + + 32.1 + 2700.007 + + + + + 48.8761495 + 2.3179525 + + 32.0 + 2754.011 + + + + + 48.8765876 + 2.3182709 + + 32.0 + 2808.004 + + + + + 48.8770168 + 2.3186164 + + 32.0 + 2862.005 + + + + + 48.8774357 + 2.3189900 + + 32.0 + 2916.006 + + + + + 48.8778429 + 2.3193923 + + 32.1 + 2970.003 + + + + + 48.8782372 + 2.3198234 + + 32.1 + 3024.005 + + + + + 48.8786174 + 2.3202829 + + 32.2 + 3078.009 + + + + + 48.8789825 + 2.3207698 + + 32.3 + 3132.008 + + + + + 48.8793318 + 2.3212829 + + 32.5 + 3186.012 + + + + + 48.8796647 + 2.3218205 + + 32.6 + 3240.010 + + + + + 48.8799810 + 2.3223808 + + 32.8 + 3294.008 + + + + + 48.8802809 + 2.3229617 + + 33.0 + 3348.013 + + + + + 48.8805647 + 2.3235609 + + 33.2 + 3402.011 + + + + + 48.8808333 + 2.3241761 + + 33.4 + 3456.009 + + + + + 48.8810877 + 2.3248052 + + 33.7 + 3510.013 + + + + + 48.8813292 + 2.3254458 + + 33.9 + 3564.008 + + + + + 48.8815595 + 2.3260960 + + 34.2 + 3618.011 + + + + + 48.8817805 + 2.3267536 + + 34.4 + 3672.013 + + + + + 48.8819942 + 2.3274167 + + 34.7 + 3726.010 + + + + + 48.8822028 + 2.3280836 + + 35.0 + 3780.011 + + + + + 48.8824086 + 2.3287525 + + 35.2 + 3834.012 + + + + + 48.8826139 + 2.3294217 + + 35.5 + 3888.008 + + + + + 48.8828212 + 2.3300896 + + 35.8 + 3942.013 + + + + + 48.8830327 + 2.3307543 + + 36.0 + 3996.008 + + + + + 48.8832508 + 2.3314142 + + 36.3 + 4050.012 + + + + + 48.8834774 + 2.3320674 + + 36.5 + 4104.014 + + + + + 48.8837145 + 2.3327118 + + 36.8 + 4158.007 + + + + + 48.8839640 + 2.3333455 + + 37.0 + 4212.014 + + + + + kuhy workout_app + 0 + 0 + + 1 + 0 + + + + + + kuhy workout_app + + + 1 + 0 + + + en + 000-00000-00 + + \ No newline at end of file diff --git a/screen_locker/tests/fixtures/run_tcx/golden_tracks.json b/screen_locker/tests/fixtures/run_tcx/golden_tracks.json new file mode 100644 index 0000000..1586631 --- /dev/null +++ b/screen_locker/tests/fixtures/run_tcx/golden_tracks.json @@ -0,0 +1,1391 @@ +{ + "running": { + "file": "RunnerUp_2026-11-09-17-30-00_Running.tcx", + "sport": "Running", + "started_at": "2026-11-09T16:30:00Z", + "utc_offset_minutes": 60, + "total_seconds": 1560, + "distance_m": 4212.014, + "points": [ + [ + "2026-11-09T16:30:00Z", + 48.8584, + 2.2945, + 35.0, + 0.0 + ], + [ + "2026-11-09T16:30:20Z", + 48.858772, + 2.2949745, + 35.0, + 54.0 + ], + [ + "2026-11-09T16:30:40Z", + 48.8591285, + 2.2954757, + 35.3, + 107.999 + ], + [ + "2026-11-09T16:31:00Z", + 48.8594688, + 2.2960023, + 35.5, + 161.998 + ], + [ + "2026-11-09T16:31:20Z", + 48.8597926, + 2.2965524, + 35.8, + 215.997 + ], + [ + "2026-11-09T16:31:40Z", + 48.8600999, + 2.297124, + 36.1, + 269.998 + ], + [ + "2026-11-09T16:32:00Z", + 48.8603909, + 2.297715, + 36.3, + 324.001 + ], + [ + "2026-11-09T16:32:20Z", + 48.8606663, + 2.298323, + 36.6, + 378.001 + ], + [ + "2026-11-09T16:32:40Z", + 48.8609269, + 2.2989459, + 36.8, + 432.002 + ], + [ + "2026-11-09T16:33:00Z", + 48.861174, + 2.2995813, + 37.0, + 485.998 + ], + [ + "2026-11-09T16:33:20Z", + 48.8614092, + 2.3002272, + 37.2, + 540.003 + ], + [ + "2026-11-09T16:33:40Z", + 48.8616342, + 2.3008814, + 37.4, + 594.005 + ], + [ + "2026-11-09T16:34:00Z", + 48.8618509, + 2.301542, + 37.5, + 648.004 + ], + [ + "2026-11-09T16:34:20Z", + 48.8620615, + 2.3022071, + 37.7, + 702.0 + ], + [ + "2026-11-09T16:34:40Z", + 48.8622683, + 2.3028751, + 37.8, + 756.006 + ], + [ + "2026-11-09T16:35:00Z", + 48.8624735, + 2.3035441, + 37.9, + 810.002 + ], + [ + "2026-11-09T16:35:20Z", + 48.8626796, + 2.3042125, + 37.9, + 864.001 + ], + [ + "2026-11-09T16:35:40Z", + 48.862889, + 2.3048786, + 38.0, + 918.005 + ], + [ + "2026-11-09T16:36:00Z", + 48.8631038, + 2.3055406, + 38.0, + 972.001 + ], + [ + "2026-11-09T16:36:20Z", + 48.8633263, + 2.3061968, + 38.0, + 1026.003 + ], + [ + "2026-11-09T16:36:40Z", + 48.8635585, + 2.3068452, + 38.0, + 1080.006 + ], + [ + "2026-11-09T16:37:00Z", + 48.8638022, + 2.3074837, + 37.9, + 1134.004 + ], + [ + "2026-11-09T16:37:20Z", + 48.8640589, + 2.3081103, + 37.8, + 1188.001 + ], + [ + "2026-11-09T16:37:40Z", + 48.8643301, + 2.3087227, + 37.7, + 1242.002 + ], + [ + "2026-11-09T16:38:00Z", + 48.8646168, + 2.3093186, + 37.6, + 1296.005 + ], + [ + "2026-11-09T16:38:20Z", + 48.8649196, + 2.3098958, + 37.5, + 1350.008 + ], + [ + "2026-11-09T16:38:40Z", + 48.8652388, + 2.3104521, + 37.3, + 1404.004 + ], + [ + "2026-11-09T16:39:00Z", + 48.8655746, + 2.3109853, + 37.1, + 1457.999 + ], + [ + "2026-11-09T16:39:20Z", + 48.8659267, + 2.3114938, + 36.9, + 1512.002 + ], + [ + "2026-11-09T16:39:40Z", + 48.8662946, + 2.3119758, + 36.7, + 1566.007 + ], + [ + "2026-11-09T16:40:00Z", + 48.8666773, + 2.3124302, + 36.5, + 1620.004 + ], + [ + "2026-11-09T16:40:20Z", + 48.867074, + 2.3128561, + 36.2, + 1674.006 + ], + [ + "2026-11-09T16:40:40Z", + 48.8674834, + 2.3132533, + 36.0, + 1728.01 + ], + [ + "2026-11-09T16:41:00Z", + 48.8679042, + 2.3136218, + 35.7, + 1782.009 + ], + [ + "2026-11-09T16:41:20Z", + 48.8683351, + 2.3139623, + 35.4, + 1836.009 + ], + [ + "2026-11-09T16:41:40Z", + 48.8687747, + 2.314276, + 35.2, + 1890.008 + ], + [ + "2026-11-09T16:42:00Z", + 48.8692217, + 2.3145646, + 34.9, + 1944.009 + ], + [ + "2026-11-09T16:42:20Z", + 48.8696748, + 2.3148301, + 34.6, + 1998.004 + ], + [ + "2026-11-09T16:42:40Z", + 48.8701329, + 2.3150752, + 34.3, + 2052.005 + ], + [ + "2026-11-09T16:43:00Z", + 48.8705949, + 2.3153027, + 34.1, + 2106.005 + ], + [ + "2026-11-09T16:43:20Z", + 48.8710599, + 2.3155157, + 33.8, + 2160.006 + ], + [ + "2026-11-09T16:43:40Z", + 48.871527, + 2.3157179, + 33.6, + 2214.01 + ], + [ + "2026-11-09T16:44:00Z", + 48.8719954, + 2.3159128, + 33.3, + 2268.009 + ], + [ + "2026-11-09T16:44:20Z", + 48.8724644, + 2.3161042, + 33.1, + 2322.006 + ], + [ + "2026-11-09T16:44:40Z", + 48.8729334, + 2.316296, + 32.9, + 2376.01 + ], + [ + "2026-11-09T16:45:00Z", + 48.8734016, + 2.3164919, + 32.7, + 2430.007 + ], + [ + "2026-11-09T16:45:20Z", + 48.8738683, + 2.3166958, + 32.6, + 2484.002 + ], + [ + "2026-11-09T16:45:40Z", + 48.8743329, + 2.3169112, + 32.4, + 2538.012 + ], + [ + "2026-11-09T16:46:00Z", + 48.8747943, + 2.3171416, + 32.3, + 2592.013 + ], + [ + "2026-11-09T16:46:20Z", + 48.8752516, + 2.3173901, + 32.2, + 2646.013 + ], + [ + "2026-11-09T16:46:40Z", + 48.8757037, + 2.3176595, + 32.1, + 2700.007 + ], + [ + "2026-11-09T16:47:00Z", + 48.8761495, + 2.3179525, + 32.0, + 2754.011 + ], + [ + "2026-11-09T16:47:20Z", + 48.8765876, + 2.3182709, + 32.0, + 2808.004 + ], + [ + "2026-11-09T16:47:40Z", + 48.8770168, + 2.3186164, + 32.0, + 2862.005 + ], + [ + "2026-11-09T16:48:00Z", + 48.8774357, + 2.31899, + 32.0, + 2916.006 + ], + [ + "2026-11-09T16:48:20Z", + 48.8778429, + 2.3193923, + 32.1, + 2970.003 + ], + [ + "2026-11-09T16:48:40Z", + 48.8782372, + 2.3198234, + 32.1, + 3024.005 + ], + [ + "2026-11-09T16:49:00Z", + 48.8786174, + 2.3202829, + 32.2, + 3078.009 + ], + [ + "2026-11-09T16:49:20Z", + 48.8789825, + 2.3207698, + 32.3, + 3132.008 + ], + [ + "2026-11-09T16:49:40Z", + 48.8793318, + 2.3212829, + 32.5, + 3186.012 + ], + [ + "2026-11-09T16:50:00Z", + 48.8796647, + 2.3218205, + 32.6, + 3240.01 + ], + [ + "2026-11-09T16:50:20Z", + 48.879981, + 2.3223808, + 32.8, + 3294.008 + ], + [ + "2026-11-09T16:50:40Z", + 48.8802809, + 2.3229617, + 33.0, + 3348.013 + ], + [ + "2026-11-09T16:51:00Z", + 48.8805647, + 2.3235609, + 33.2, + 3402.011 + ], + [ + "2026-11-09T16:51:20Z", + 48.8808333, + 2.3241761, + 33.4, + 3456.009 + ], + [ + "2026-11-09T16:51:40Z", + 48.8810877, + 2.3248052, + 33.7, + 3510.013 + ], + [ + "2026-11-09T16:52:00Z", + 48.8813292, + 2.3254458, + 33.9, + 3564.008 + ], + [ + "2026-11-09T16:52:20Z", + 48.8815595, + 2.326096, + 34.2, + 3618.011 + ], + [ + "2026-11-09T16:52:40Z", + 48.8817805, + 2.3267536, + 34.4, + 3672.013 + ], + [ + "2026-11-09T16:53:00Z", + 48.8819942, + 2.3274167, + 34.7, + 3726.01 + ], + [ + "2026-11-09T16:53:20Z", + 48.8822028, + 2.3280836, + 35.0, + 3780.011 + ], + [ + "2026-11-09T16:53:40Z", + 48.8824086, + 2.3287525, + 35.2, + 3834.012 + ], + [ + "2026-11-09T16:54:00Z", + 48.8826139, + 2.3294217, + 35.5, + 3888.008 + ], + [ + "2026-11-09T16:54:20Z", + 48.8828212, + 2.3300896, + 35.8, + 3942.013 + ], + [ + "2026-11-09T16:54:40Z", + 48.8830327, + 2.3307543, + 36.0, + 3996.008 + ], + [ + "2026-11-09T16:55:00Z", + 48.8832508, + 2.3314142, + 36.3, + 4050.012 + ], + [ + "2026-11-09T16:55:20Z", + 48.8834774, + 2.3320674, + 36.5, + 4104.014 + ], + [ + "2026-11-09T16:55:40Z", + 48.8837145, + 2.3327118, + 36.8, + 4158.007 + ], + [ + "2026-11-09T16:56:00Z", + 48.883964, + 2.3333455, + 37.0, + 4212.014 + ] + ], + "expect": { + "status": "verified", + "distance_km": 4.2, + "minutes": 26 + } + }, + "walking": { + "file": "RunnerUp_2026-11-09-09-15-00_Walking.tcx", + "sport": "Walking", + "started_at": "2026-11-09T08:15:00Z", + "utc_offset_minutes": 60, + "total_seconds": 1665, + "distance_m": 2331.004, + "points": [ + [ + "2026-11-09T08:15:00Z", + 48.8584, + 2.2945, + 35.0, + 0.0 + ], + [ + "2026-11-09T08:15:15Z", + 48.8585447, + 2.2946845, + 35.0, + 21.002 + ], + [ + "2026-11-09T08:15:30Z", + 48.8586833, + 2.2948794, + 35.3, + 41.997 + ], + [ + "2026-11-09T08:15:45Z", + 48.8588157, + 2.2950842, + 35.5, + 63.003 + ], + [ + "2026-11-09T08:16:00Z", + 48.8589416, + 2.2952981, + 35.8, + 83.999 + ], + [ + "2026-11-09T08:16:15Z", + 48.8590611, + 2.2955204, + 36.1, + 105.0 + ], + [ + "2026-11-09T08:16:30Z", + 48.8591742, + 2.2957502, + 36.3, + 125.995 + ], + [ + "2026-11-09T08:16:45Z", + 48.8592813, + 2.2959867, + 36.6, + 146.999 + ], + [ + "2026-11-09T08:17:00Z", + 48.8593827, + 2.2962289, + 36.8, + 168.001 + ], + [ + "2026-11-09T08:17:15Z", + 48.8594788, + 2.296476, + 37.0, + 189.0 + ], + [ + "2026-11-09T08:17:30Z", + 48.8595702, + 2.2967272, + 37.2, + 210.0 + ], + [ + "2026-11-09T08:17:45Z", + 48.8596577, + 2.2969816, + 37.4, + 231.001 + ], + [ + "2026-11-09T08:18:00Z", + 48.859742, + 2.2972385, + 37.5, + 252.002 + ], + [ + "2026-11-09T08:18:15Z", + 48.8598239, + 2.2974971, + 37.7, + 272.998 + ], + [ + "2026-11-09T08:18:30Z", + 48.8599043, + 2.2977569, + 37.8, + 294.002 + ], + [ + "2026-11-09T08:18:45Z", + 48.8599841, + 2.298017, + 37.9, + 314.997 + ], + [ + "2026-11-09T08:19:00Z", + 48.8600643, + 2.298277, + 37.9, + 336.004 + ], + [ + "2026-11-09T08:19:15Z", + 48.8601457, + 2.298536, + 38.0, + 357.002 + ], + [ + "2026-11-09T08:19:30Z", + 48.8602292, + 2.2987934, + 38.0, + 377.997 + ], + [ + "2026-11-09T08:19:45Z", + 48.8603158, + 2.2990486, + 38.0, + 399.003 + ], + [ + "2026-11-09T08:20:00Z", + 48.8604061, + 2.2993007, + 38.0, + 420.002 + ], + [ + "2026-11-09T08:20:15Z", + 48.8605008, + 2.299549, + 37.9, + 440.998 + ], + [ + "2026-11-09T08:20:30Z", + 48.8606007, + 2.2997927, + 37.8, + 462.003 + ], + [ + "2026-11-09T08:20:45Z", + 48.8607062, + 2.3000308, + 37.7, + 483.003 + ], + [ + "2026-11-09T08:21:00Z", + 48.8608176, + 2.3002625, + 37.6, + 503.997 + ], + [ + "2026-11-09T08:21:15Z", + 48.8609354, + 2.300487, + 37.5, + 525.004 + ], + [ + "2026-11-09T08:21:30Z", + 48.8610595, + 2.3007033, + 37.3, + 545.999 + ], + [ + "2026-11-09T08:21:45Z", + 48.8611901, + 2.3009107, + 37.1, + 567.001 + ], + [ + "2026-11-09T08:22:00Z", + 48.8613271, + 2.3011084, + 36.9, + 588.006 + ], + [ + "2026-11-09T08:22:15Z", + 48.8614701, + 2.3012958, + 36.7, + 609.001 + ], + [ + "2026-11-09T08:22:30Z", + 48.861619, + 2.3014725, + 36.5, + 630.006 + ], + [ + "2026-11-09T08:22:45Z", + 48.8617732, + 2.3016381, + 36.2, + 651.0 + ], + [ + "2026-11-09T08:23:00Z", + 48.8619324, + 2.3017925, + 36.0, + 671.999 + ], + [ + "2026-11-09T08:23:15Z", + 48.8620961, + 2.3019358, + 35.7, + 693.004 + ], + [ + "2026-11-09T08:23:30Z", + 48.8622636, + 2.3020682, + 35.4, + 713.997 + ], + [ + "2026-11-09T08:23:45Z", + 48.8624346, + 2.3021902, + 35.2, + 735.002 + ], + [ + "2026-11-09T08:24:00Z", + 48.8626084, + 2.3023024, + 34.9, + 755.998 + ], + [ + "2026-11-09T08:24:15Z", + 48.8627846, + 2.3024057, + 34.6, + 776.997 + ], + [ + "2026-11-09T08:24:30Z", + 48.8629628, + 2.302501, + 34.3, + 798.003 + ], + [ + "2026-11-09T08:24:45Z", + 48.8631425, + 2.3025894, + 34.1, + 819.005 + ], + [ + "2026-11-09T08:25:00Z", + 48.8633233, + 2.3026723, + 33.8, + 840.004 + ], + [ + "2026-11-09T08:25:15Z", + 48.8635049, + 2.3027509, + 33.6, + 860.999 + ], + [ + "2026-11-09T08:25:30Z", + 48.8636871, + 2.3028267, + 33.3, + 882.004 + ], + [ + "2026-11-09T08:25:45Z", + 48.8638695, + 2.3029011, + 33.1, + 903.004 + ], + [ + "2026-11-09T08:26:00Z", + 48.8640519, + 2.3029757, + 32.9, + 924.007 + ], + [ + "2026-11-09T08:26:15Z", + 48.864234, + 2.3030518, + 32.7, + 945.007 + ], + [ + "2026-11-09T08:26:30Z", + 48.8644155, + 2.3031311, + 32.6, + 966.006 + ], + [ + "2026-11-09T08:26:45Z", + 48.8645961, + 2.3032149, + 32.4, + 987.002 + ], + [ + "2026-11-09T08:27:00Z", + 48.8647755, + 2.3033044, + 32.3, + 1007.997 + ], + [ + "2026-11-09T08:27:15Z", + 48.8649534, + 2.303401, + 32.2, + 1029.003 + ], + [ + "2026-11-09T08:27:30Z", + 48.8651292, + 2.3035058, + 32.1, + 1050.001 + ], + [ + "2026-11-09T08:27:45Z", + 48.8653026, + 2.3036197, + 32.0, + 1071.005 + ], + [ + "2026-11-09T08:28:00Z", + 48.865473, + 2.3037435, + 32.0, + 1092.005 + ], + [ + "2026-11-09T08:28:15Z", + 48.8656399, + 2.3038778, + 32.0, + 1113.004 + ], + [ + "2026-11-09T08:28:30Z", + 48.8658028, + 2.3040231, + 32.0, + 1134.005 + ], + [ + "2026-11-09T08:28:45Z", + 48.8659611, + 2.3041795, + 32.1, + 1154.998 + ], + [ + "2026-11-09T08:29:00Z", + 48.8661145, + 2.3043471, + 32.1, + 1176.004 + ], + [ + "2026-11-09T08:29:15Z", + 48.8662623, + 2.3045258, + 32.2, + 1197.003 + ], + [ + "2026-11-09T08:29:30Z", + 48.8664043, + 2.3047151, + 32.3, + 1218.004 + ], + [ + "2026-11-09T08:29:45Z", + 48.8665401, + 2.3049146, + 32.5, + 1239.003 + ], + [ + "2026-11-09T08:30:00Z", + 48.8666696, + 2.3051236, + 32.6, + 1260.005 + ], + [ + "2026-11-09T08:30:15Z", + 48.8667926, + 2.3053414, + 32.8, + 1281.001 + ], + [ + "2026-11-09T08:30:30Z", + 48.8667926, + 2.3053414, + 33.0, + 1281.001 + ], + [ + "2026-11-09T08:30:45Z", + 48.8667926, + 2.3053414, + 33.2, + 1281.001 + ], + [ + "2026-11-09T08:31:30Z", + 48.8671059, + 2.306059, + 33.4, + 1281.001 + ], + [ + "2026-11-09T08:31:45Z", + 48.8672049, + 2.3063036, + 33.7, + 1302.008 + ], + [ + "2026-11-09T08:32:00Z", + 48.8672988, + 2.3065527, + 33.9, + 1323.008 + ], + [ + "2026-11-09T08:32:15Z", + 48.8673884, + 2.3068054, + 34.2, + 1344.006 + ], + [ + "2026-11-09T08:32:30Z", + 48.8674743, + 2.3070611, + 34.4, + 1365.007 + ], + [ + "2026-11-09T08:32:45Z", + 48.8675574, + 2.3073189, + 34.7, + 1386.006 + ], + [ + "2026-11-09T08:33:00Z", + 48.8676385, + 2.3075782, + 35.0, + 1407.007 + ], + [ + "2026-11-09T08:33:15Z", + 48.8677186, + 2.3078382, + 35.2, + 1428.006 + ], + [ + "2026-11-09T08:33:30Z", + 48.8677984, + 2.3080984, + 35.5, + 1449.005 + ], + [ + "2026-11-09T08:33:45Z", + 48.867879, + 2.308358, + 35.8, + 1470.002 + ], + [ + "2026-11-09T08:34:00Z", + 48.8679613, + 2.3086165, + 36.0, + 1491.008 + ], + [ + "2026-11-09T08:34:15Z", + 48.8680461, + 2.308873, + 36.3, + 1512.006 + ], + [ + "2026-11-09T08:34:30Z", + 48.8681342, + 2.309127, + 36.5, + 1533.009 + ], + [ + "2026-11-09T08:34:45Z", + 48.8682264, + 2.3093775, + 36.8, + 1554.005 + ], + [ + "2026-11-09T08:35:00Z", + 48.8683234, + 2.3096238, + 37.0, + 1575.002 + ], + [ + "2026-11-09T08:35:15Z", + 48.8684258, + 2.3098651, + 37.2, + 1596.005 + ], + [ + "2026-11-09T08:35:30Z", + 48.8685339, + 2.3101005, + 37.3, + 1617.004 + ], + [ + "2026-11-09T08:35:45Z", + 48.8686482, + 2.3103291, + 37.5, + 1638.007 + ], + [ + "2026-11-09T08:36:00Z", + 48.8687688, + 2.31055, + 37.6, + 1659.004 + ], + [ + "2026-11-09T08:36:15Z", + 48.8688959, + 2.3107624, + 37.8, + 1680.006 + ], + [ + "2026-11-09T08:36:30Z", + 48.8690294, + 2.3109656, + 37.9, + 1701.012 + ], + [ + "2026-11-09T08:36:45Z", + 48.8691691, + 2.3111587, + 37.9, + 1722.007 + ], + [ + "2026-11-09T08:37:00Z", + 48.8693148, + 2.3113414, + 38.0, + 1743.008 + ], + [ + "2026-11-09T08:37:15Z", + 48.8694661, + 2.3115131, + 38.0, + 1764.002 + ], + [ + "2026-11-09T08:37:30Z", + 48.8696227, + 2.3116737, + 38.0, + 1785.007 + ], + [ + "2026-11-09T08:37:45Z", + 48.8697839, + 2.3118231, + 38.0, + 1806.0 + ], + [ + "2026-11-09T08:38:00Z", + 48.8699494, + 2.3119615, + 37.9, + 1827.003 + ], + [ + "2026-11-09T08:38:15Z", + 48.8701186, + 2.3120892, + 37.8, + 1848.008 + ], + [ + "2026-11-09T08:38:30Z", + 48.8702909, + 2.3122067, + 37.7, + 1869.006 + ], + [ + "2026-11-09T08:38:45Z", + 48.8704659, + 2.3123147, + 37.6, + 1890.007 + ], + [ + "2026-11-09T08:39:00Z", + 48.870643, + 2.3124143, + 37.5, + 1911.004 + ], + [ + "2026-11-09T08:39:15Z", + 48.8708219, + 2.3125063, + 37.3, + 1932.004 + ], + [ + "2026-11-09T08:39:30Z", + 48.8710021, + 2.3125921, + 37.1, + 1953.001 + ], + [ + "2026-11-09T08:39:45Z", + 48.8711834, + 2.3126729, + 36.9, + 1974.009 + ], + [ + "2026-11-09T08:40:00Z", + 48.8713653, + 2.3127501, + 36.7, + 1995.009 + ], + [ + "2026-11-09T08:40:15Z", + 48.8715476, + 2.3128251, + 36.5, + 2016.009 + ], + [ + "2026-11-09T08:40:30Z", + 48.87173, + 2.3128994, + 36.2, + 2037.006 + ], + [ + "2026-11-09T08:40:45Z", + 48.8719123, + 2.3129745, + 36.0, + 2058.008 + ], + [ + "2026-11-09T08:41:00Z", + 48.8720942, + 2.3130519, + 35.7, + 2079.012 + ], + [ + "2026-11-09T08:41:15Z", + 48.8722753, + 2.313133, + 35.5, + 2100.005 + ], + [ + "2026-11-09T08:41:30Z", + 48.8724555, + 2.3132193, + 35.2, + 2121.012 + ], + [ + "2026-11-09T08:41:45Z", + 48.8726342, + 2.3133119, + 34.9, + 2142.005 + ], + [ + "2026-11-09T08:42:00Z", + 48.8728112, + 2.3134121, + 34.6, + 2163.007 + ], + [ + "2026-11-09T08:42:15Z", + 48.872986, + 2.3135209, + 34.4, + 2184.01 + ], + [ + "2026-11-09T08:42:30Z", + 48.8731581, + 2.3136392, + 34.1, + 2205.011 + ], + [ + "2026-11-09T08:42:45Z", + 48.873327, + 2.3137677, + 33.8, + 2226.012 + ], + [ + "2026-11-09T08:43:00Z", + 48.8734921, + 2.3139069, + 33.6, + 2247.005 + ], + [ + "2026-11-09T08:43:15Z", + 48.873653, + 2.3140572, + 33.4, + 2268.003 + ], + [ + "2026-11-09T08:43:30Z", + 48.8738092, + 2.3142187, + 33.1, + 2289.007 + ], + [ + "2026-11-09T08:43:45Z", + 48.8739601, + 2.3143914, + 32.9, + 2310.009 + ], + [ + "2026-11-09T08:44:00Z", + 48.8741053, + 2.3145749, + 32.8, + 2331.004 + ] + ], + "expect": { + "status": "verified", + "legs": 1, + "distance_km": 2.3, + "moving_minutes": 28 + } + } +} \ No newline at end of file diff --git a/screen_locker/tests/test_run_tcx_golden.py b/screen_locker/tests/test_run_tcx_golden.py new file mode 100644 index 0000000..eb12699 --- /dev/null +++ b/screen_locker/tests/test_run_tcx_golden.py @@ -0,0 +1,169 @@ +"""Cross-language golden test: the app's TCX is accepted by the real verifier. + +The workout app (Dart, ``lib/services/tcx_codec.dart``) writes the TCX files +the PC credits. Each language's own round trip passes happily while the other +side's parser disagrees, so the Dart twin +(``stronglift_replacement/workout_app/test/services/tcx_golden_fixture_test.dart``) +encodes ``fixtures/run_tcx/golden_tracks.json`` and compares the result byte +for byte with the ``.tcx`` files checked in next to it. This test feeds those +SAME files to the real ``RunnerUpVerificationMixin`` -- date glob, parser, +run validation and walk summing -- and asserts the km / minutes the JSON +promises. Everything runs on a temporary drop directory: nothing here reads +or writes ``~/data/cloud/RunnerUp``. +""" + +from __future__ import annotations + +import json +from pathlib import Path +import shutil +from unittest.mock import MagicMock, patch + +import pytest + +from screen_locker._runnerup_verification import RunnerUpVerificationMixin + +FIXTURES = Path(__file__).parent / "fixtures" / "run_tcx" +SAMPLE = "RunnerUp_2026-08-12-17-42-11_Running.tcx" +# ``_TCX_SPORT_TO_INT`` code for "Running" in screen_locker/_runnerup_tcx.py. +SPORT_RUNNING = 0 + + +def _golden() -> dict[str, dict[str, object]]: + return json.loads((FIXTURES / "golden_tracks.json").read_text(encoding="utf-8")) + + +class Verifier(RunnerUpVerificationMixin): + """The real mixin with no phone: only the WebDAV drop directory is read.""" + + def __init__(self) -> None: + self.adb = MagicMock(return_value=(False, "")) + + def _has_adb_device(self) -> bool: + return False + + def _run_adb(self, args: list[str]) -> tuple[bool, str]: + return self.adb(args) + + +@pytest.fixture(name="inbox") +def inbox_fixture(tmp_path: Path) -> Path: + """A temporary WebDAV inbox standing in for ``~/data/cloud/RunnerUp``.""" + inbox = tmp_path / "cloud" / "RunnerUp" + inbox.mkdir(parents=True) + return inbox + + +def _verify(inbox: Path, today: str) -> tuple[str, str] | None: + """Run the real file-based verification on ``inbox`` as if it were ``today``.""" + with ( + patch("screen_locker._runnerup_verification.RUNNERUP_WEBDAV_DIRS", (inbox,)), + patch("screen_locker._runnerup_verification.today_str", return_value=today), + ): + return Verifier()._verify_runnerup_via_files() + + +def _drop(inbox: Path, name: str) -> None: + shutil.copyfile(FIXTURES / name, inbox / name) + + +class TestGoldenRun: + """The app's run file passes the PC's run validation unchanged.""" + + def test_run_is_verified_with_the_promised_km_and_minutes( + self, inbox: Path + ) -> None: + """Date glob, parser and validation accept the app's run file.""" + spec = _golden()["running"] + _drop(inbox, str(spec["file"])) + expect = spec["expect"] + assert isinstance(expect, dict) + result = _verify(inbox, "2026-11-09") + assert result == ( + expect["status"], + f"Running: {expect['distance_km']} km in {expect['minutes']} min", + ) + + def test_parsed_fields_match_the_json(self, inbox: Path) -> None: + """The parser reads sport, duration and distance as the app wrote them.""" + spec = _golden()["running"] + _drop(inbox, str(spec["file"])) + data = Verifier()._pull_and_parse_tcx(str(inbox / str(spec["file"]))) + assert data is not None + assert data["sport"] == SPORT_RUNNING + assert data["walking"] is False + assert data["duration_seconds"] == spec["total_seconds"] + assert data["distance_m"] == pytest.approx(float(str(spec["distance_m"]))) + assert data["activity_id"] == str(spec["started_at"]) + + def test_the_local_date_in_the_name_is_what_is_globbed(self, inbox: Path) -> None: + """Another day does not see the file: the stamp is local, the glob is too.""" + spec = _golden()["running"] + _drop(inbox, str(spec["file"])) + assert _verify(inbox, "2026-11-10") is None + + +class TestGoldenWalk: + """The app's walk file is a walk, and its moving time is what the PC sums.""" + + def test_walk_is_verified_with_the_promised_moving_minutes( + self, inbox: Path + ) -> None: + """A walk is summed per day: legs, km and moving minutes as promised.""" + spec = _golden()["walking"] + _drop(inbox, str(spec["file"])) + expect = spec["expect"] + assert isinstance(expect, dict) + result = _verify(inbox, "2026-11-09") + assert result == ( + expect["status"], + ( + f"Walking: {expect['legs']} walk(s), {expect['distance_km']} km, " + f"{expect['moving_minutes']} min moving" + ), + ) + + def test_pc_moving_time_equals_the_apps_total_seconds(self, inbox: Path) -> None: + """Dart's moving-time rule and the PC's ``track_summary`` agree exactly.""" + spec = _golden()["walking"] + _drop(inbox, str(spec["file"])) + data = Verifier()._pull_and_parse_tcx(str(inbox / str(spec["file"]))) + assert data is not None + assert data["walking"] is True + assert data["moving_seconds"] == pytest.approx( + float(str(spec["total_seconds"])) + ) + 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.""" + spec = _golden()["walking"] + name = str(spec["file"]) + body = (FIXTURES / name).read_text(encoding="utf-8") + assert 'Sport="Walking"' in body + renamed = inbox / "activity.tcx" + renamed.write_text(body.replace('Sport="Walking"', 'Sport="Other"')) + 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"')) + by_name = Verifier()._pull_and_parse_tcx(str(inbox / name)) + assert by_name is not None + assert by_name["walking"] is True + + +class TestRunnerUpSample: + """The trimmed real RunnerUp export parses to the numbers Dart's decoder sees.""" + + def test_parser_sums_both_laps(self, inbox: Path) -> None: + """Same literals as the Dart decoder test: 624 + 626 s, two laps' metres.""" + _drop(inbox, SAMPLE) + data = Verifier()._pull_and_parse_tcx(str(inbox / SAMPLE)) + assert data is not None + assert data["sport"] == SPORT_RUNNING + assert data["walking"] is False + assert data["duration_seconds"] == 1250 + assert data["distance_m"] == pytest.approx(983.6306 + 980.7304, abs=1e-3) + assert data["activity_id"] == "2026-08-12T15:42:11Z" diff --git a/stronglift_replacement/workout_app/DOCS-workout-app-context.md b/stronglift_replacement/workout_app/DOCS-workout-app-context.md index ec52cd6..3c893ae 100644 --- a/stronglift_replacement/workout_app/DOCS-workout-app-context.md +++ b/stronglift_replacement/workout_app/DOCS-workout-app-context.md @@ -14,3 +14,18 @@ Implementing design_v2.md improvements. workout and shows the status-bar notification; notification presses go through a durable prefs queue, never straight into sqflite (the CRDT sync layer owns that database's ordering) + +## GPS runs (foundation, 2026-11-10) +- The app records runs/walks itself and uploads a TCX the PC credits unchanged. + Contract = the PC parser, pinned by `screen_locker/tests/fixtures/run_tcx/` + (Dart `test/services/tcx_golden_fixture_test.dart` <-> pytest + `screen_locker/tests/test_run_tcx_golden.py`). Regenerate after a deliberate + codec change: `UPDATE_GOLDEN=1 flutter test test/services/tcx_golden_fixture_test.dart`. +- File name `RunnerUp_YYYY-MM-DD-HH-MM-SS_.tcx`, stamp in + LOCAL time; ``/`