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:
@@ -20,6 +20,8 @@ class FitParser:
|
||||
sub_sport: str | None = None
|
||||
device: str | None = None
|
||||
|
||||
has_baro_alt = False # True if any record used enhanced_altitude
|
||||
|
||||
with fitdecode.FitReader(io.BytesIO(raw_bytes)) as fit:
|
||||
for frame in fit:
|
||||
if not isinstance(frame, fitdecode.FitDataMessage):
|
||||
@@ -60,7 +62,9 @@ class FitParser:
|
||||
# enhanced_altitude is written by barometric altimeters (most
|
||||
# modern Garmins). Fall back to GPS-derived altitude if absent.
|
||||
_alt = _get(frame, "enhanced_altitude")
|
||||
if _alt is None:
|
||||
if _alt is not None:
|
||||
has_baro_alt = True
|
||||
else:
|
||||
_alt = _get(frame, "altitude")
|
||||
|
||||
dp = DataPoint(
|
||||
@@ -100,6 +104,8 @@ class FitParser:
|
||||
if not points:
|
||||
raise ValueError(f"No record messages found in {path.name}")
|
||||
|
||||
altitude_source = "barometric" if has_baro_alt else "gps"
|
||||
|
||||
return ParsedActivity(
|
||||
points=points,
|
||||
sport=sport,
|
||||
@@ -109,6 +115,7 @@ class FitParser:
|
||||
laps=laps,
|
||||
source_file=path.name,
|
||||
source_hash="",
|
||||
altitude_source=altitude_source,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ class GpxParser(BaseParser):
|
||||
started_at=started_at,
|
||||
source_file=path.name,
|
||||
source_hash="", # set by factory
|
||||
altitude_source="gps",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ class TcxParser:
|
||||
started_at=points[0].timestamp,
|
||||
source_file=path.name,
|
||||
source_hash="",
|
||||
altitude_source="gps",
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user