import copy import math from types import SimpleNamespace from app.analytics import analyse from app.display import ( ANALYSIS_VERSION, CHART_LIMIT, compact_routes, persist_analysis, stored_analysis, ) from app.services import activity_data, route_data def long_run(): return SimpleNamespace( sport="running", distance_m=21000, samples=[ { "point": [-33 + i / 1000000, 151 + math.sin(i / 100) / 1000], "distance": i * 3, "time": i, "speed": 3, "elevation": 100 + math.sin(i / 100), "heart_rate": 150, } for i in range(7001) ], laps=[{"distance": 21000, "duration": 7000}], ) def test_persisted_summaries_equal_full_resolution_and_raw_data_is_untouched(): row = long_run() original = copy.deepcopy(row.samples) expected = analyse(row) persist_analysis(row) assert row.analysis_version == ANALYSIS_VERSION for key in ("terrain", "laps"): assert row.analysis_summary[key] == expected[key] for key, value in expected["summary"].items(): assert row.analysis_summary["summary"][key] == value assert row.analysis_summary["segments"] assert "heart_rate_drift" in row.analysis_summary["summary"] assert row.samples == original assert len(row.display_data["chart"]) == CHART_LIMIT assert row.display_data["chart"][0]["distance"] == 0 assert row.display_data["chart"][-1]["distance"] == 21000 assert sum(map(len, row.route_geometry)) <= 1400 assert row.route_geometry[0][0] == [round(v, 6) for v in original[0]["point"]] assert row.route_geometry[-1][-1] == [round(v, 6) for v in original[-1]["point"]] assert set(route_data(row)) == {"segments"} assert set(row.display_data["route_segments"][0][0]) == { "point", "distance", "speed", "grade", "heart_rate", } assert "point" not in row.display_data["chart"][0] def test_simplification_retains_sharp_turns_and_does_not_bridge_gps_gaps(): points = [[0, i / 10000] for i in range(100)] points += [[i / 10000, 0.0099] for i in range(1, 100)] samples = [{"point": point} for point in points] samples += [{"point": None}, {"point": [1, 1]}, {"point": [1, 1.01]}] routes = compact_routes(samples) assert len(routes) == 2 assert [0, 0.0099] in [row["point"] for row in routes[0]] assert routes[0][0]["point"] == points[0] assert routes[0][-1]["point"] == points[-1] assert routes[1][0]["point"] == [1, 1] assert routes[1][-1]["point"] == [1, 1.01] def test_incomplete_and_non_gps_data_and_refresh_clear_stored_routes(): row = long_run() persist_analysis(row) row.samples = [ {"point": None, "distance": 0, "time": 0}, {"point": None, "distance": 100, "time": 30}, ] persist_analysis(row) assert row.route_geometry == [] assert activity_data(row)["route_segments"] == [] assert len(activity_data(row)["chart"]) == 2 assert stored_analysis(row)["summary"]["samples"] == 2 row.analysis_version = 0 assert route_data(row) == {"segments": []} assert activity_data(row) == {"segments": [], "route_segments": [], "chart": []} def test_fragmented_routes_preserve_each_singleton(): samples = [{"point": [i / 1000, 0]} if i % 2 else {"point": None} for i in range(20)] assert len(compact_routes(samples)) == 10