diff --git a/bincio/segments/detect.py b/bincio/segments/detect.py index af1626c..828d879 100644 --- a/bincio/segments/detect.py +++ b/bincio/segments/detect.py @@ -21,6 +21,15 @@ MATCH_RADIUS_M = 25 # max distance to segment start/end to open/close a CONFORMANCE_MAX_DEV_M = 50 # max allowed deviation for each interior segment point CONFORMANCE_MAX_FRAC = 0.30 # max fraction of interior points allowed to deviate +# Minimum geometric speed (segment_distance / elapsed_s) per sport, in m/s. +# Rejects false matches from long circuit rides where the track passes the +# segment start early and the segment end hours later. +_MIN_SPEED_MS: dict[str, float] = { + 'cycling': 1.0, # ~3.6 km/h — slowest realistic climb + 'running': 0.5, # ~1.8 km/h +} +_MIN_SPEED_DEFAULT = 0.2 # hiking / walking / unknown + # ── fast distance approximation ─────────────────────────────────────────────── _R = 6_371_000.0 # Earth radius in metres @@ -250,6 +259,15 @@ def detect_one(track: ActivityTrack, seg: Segment) -> list[SegmentEffort]: # No end found — no more efforts possible starting at or after start_idx. break + # Reject implausibly slow matches (e.g. circuit rides matching start + # early and end hours later on a second pass through the area). + elapsed = track.times[end_idx] - track.times[start_idx] + if elapsed > 0: + min_speed = _MIN_SPEED_MS.get(track.sport, _MIN_SPEED_DEFAULT) + if seg.distance_m / elapsed < min_speed: + search_from = start_idx + 1 + continue + if _conformance_ok(track, seg, start_idx, end_idx): efforts.append(_extract_effort(track, seg, start_idx, end_idx)) search_from = end_idx + 1