metrics: replace naive elevation accumulation with hysteresis dead-band

GPS jitter and barometric quantization noise caused systematic overestimation
of elevation gain — in extreme cases 100% of reported gain was sub-1m noise.

Implements source-aware hysteresis: elevation is only committed when it
deviates from the last committed value by ≥5m (barometric) or ≥10m (GPS/GPX/TCX).

- ParsedActivity gains `altitude_source` field ("barometric"/"gps"/"unknown")
- FIT parser sets "barometric" when enhanced_altitude is present, else "gps"
- GPX and TCX parsers always set "gps"
- metrics._elevation() uses the threshold matching the source
- 5 new parametric tests covering flat GPS noise, threshold differences, and real climbs
This commit is contained in:
Davide Scaini
2026-04-20 20:29:20 +02:00
parent 696f538f56
commit 872651f471
6 changed files with 117 additions and 9 deletions
+4
View File
@@ -57,3 +57,7 @@ class ParsedActivity:
strava_id: Optional[str] = None
privacy: Optional[str] = None # "public", "private", or None (caller decides)
laps: list[LapData] = field(default_factory=list)
# "barometric" = device has a barometric altimeter (FIT enhanced_altitude present)
# "gps" = altitude derived from GPS triangulation (GPX, TCX, FIT altitude-only)
# "unknown" = source not detected (treated as gps for threshold purposes)
altitude_source: str = "unknown"