segments: reject false efforts via geometric speed check
Long circuit rides were matching a segment START early and finding the segment END hours later on a second pass, producing effort times of ~17000s on a 4.7km segment. The conformance check passed because the full-circuit track covers all interior points within 50m over 5 hours. Add a per-sport minimum geometric speed (segment_distance / elapsed_s): cycling ≥ 1.0 m/s, running ≥ 0.5 m/s, default ≥ 0.2 m/s. When the check fails, advance past the current start candidate and retry, so a legitimate later match (e.g. a second lap done at real speed) is still detected.
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user