fix high priority issues
This commit is contained in:
+26
-11
@@ -112,10 +112,11 @@ def compute_mmp(pts: list[DataPoint], started_at: datetime) -> Optional[list[lis
|
|||||||
[duration_s, avg_watts] pairs (integers), or None when the activity has no
|
[duration_s, avg_watts] pairs (integers), or None when the activity has no
|
||||||
power data. Only durations shorter than the total activity are included.
|
power data. Only durations shorter than the total activity are included.
|
||||||
"""
|
"""
|
||||||
# 1 Hz downsample: at most one sample per second, skip sub-second duplicates.
|
# Build a dense 1 Hz power array with gaps zero-filled.
|
||||||
# Seconds without a recorded sample are omitted (not zero-filled) so that
|
# Zero-filling is the standard approach (matches GoldenCheetah / WKO):
|
||||||
# paused-recording gaps don't silently lower power averages.
|
# a recording gap counts as 0 W so windows cannot silently span pauses
|
||||||
power_1hz: list[int] = []
|
# and inflate MMP values.
|
||||||
|
sparse: dict[int, int] = {}
|
||||||
last_t = -1
|
last_t = -1
|
||||||
for p in pts:
|
for p in pts:
|
||||||
t = int((p.timestamp - started_at).total_seconds())
|
t = int((p.timestamp - started_at).total_seconds())
|
||||||
@@ -123,11 +124,15 @@ def compute_mmp(pts: list[DataPoint], started_at: datetime) -> Optional[list[lis
|
|||||||
continue
|
continue
|
||||||
last_t = t
|
last_t = t
|
||||||
if p.power_w is not None:
|
if p.power_w is not None:
|
||||||
power_1hz.append(p.power_w)
|
sparse[t] = p.power_w
|
||||||
|
|
||||||
if len(power_1hz) < 2:
|
if len(sparse) < 2:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
t_min = min(sparse)
|
||||||
|
t_max = max(sparse)
|
||||||
|
power_1hz: list[int] = [sparse.get(t, 0) for t in range(t_min, t_max + 1)]
|
||||||
|
|
||||||
n = len(power_1hz)
|
n = len(power_1hz)
|
||||||
results: list[list[int]] = []
|
results: list[list[int]] = []
|
||||||
|
|
||||||
@@ -166,17 +171,27 @@ def compute_best_efforts(
|
|||||||
"""
|
"""
|
||||||
targets = BEST_EFFORT_DISTANCES.get(sport, [])
|
targets = BEST_EFFORT_DISTANCES.get(sport, [])
|
||||||
|
|
||||||
# Build 1 Hz speed (km/h) and elevation (m) arrays — same downsampling as timeseries.py
|
# Build dense 1 Hz speed (km/h) and elevation (m) arrays with gap zero-filling.
|
||||||
speed_1hz: list[float] = []
|
# Zero-filling speed gaps (0 km/h) prevents best-effort windows from spanning
|
||||||
ele_1hz: list[Optional[float]] = []
|
# recording pauses and producing artificially fast times.
|
||||||
|
sparse_speed: dict[int, float] = {}
|
||||||
|
sparse_ele: dict[int, Optional[float]] = {}
|
||||||
last_t = -1
|
last_t = -1
|
||||||
for p in pts:
|
for p in pts:
|
||||||
t = int((p.timestamp - started_at).total_seconds())
|
t = int((p.timestamp - started_at).total_seconds())
|
||||||
if t < 0 or t == last_t:
|
if t < 0 or t == last_t:
|
||||||
continue
|
continue
|
||||||
last_t = t
|
last_t = t
|
||||||
speed_1hz.append(p.speed_kmh if p.speed_kmh is not None else 0.0)
|
sparse_speed[t] = p.speed_kmh if p.speed_kmh is not None else 0.0
|
||||||
ele_1hz.append(p.elevation_m)
|
sparse_ele[t] = p.elevation_m
|
||||||
|
|
||||||
|
if not sparse_speed:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
t_min = min(sparse_speed)
|
||||||
|
t_max = max(sparse_speed)
|
||||||
|
speed_1hz: list[float] = [sparse_speed.get(t, 0.0) for t in range(t_min, t_max + 1)]
|
||||||
|
ele_1hz: list[Optional[float]] = [sparse_ele.get(t) for t in range(t_min, t_max + 1)]
|
||||||
|
|
||||||
best_efforts: Optional[list[list[float]]] = None
|
best_efforts: Optional[list[list[float]]] = None
|
||||||
if targets and speed_1hz:
|
if targets and speed_1hz:
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ def write_activity(
|
|||||||
}
|
}
|
||||||
|
|
||||||
json_path = acts_dir / f"{activity_id}.json"
|
json_path = acts_dir / f"{activity_id}.json"
|
||||||
|
# Collision guard: if a *different* activity already has this ID, append a
|
||||||
|
# short hash suffix to disambiguate (same hash = idempotent re-extract).
|
||||||
|
if json_path.exists():
|
||||||
|
existing = json.loads(json_path.read_text(encoding="utf-8"))
|
||||||
|
if existing.get("source_hash") != activity.source_hash:
|
||||||
|
activity_id = f"{activity_id}-{activity.source_hash[-6:]}"
|
||||||
|
json_path = acts_dir / f"{activity_id}.json"
|
||||||
json_path.write_text(json.dumps(detail, indent=2, ensure_ascii=False))
|
json_path.write_text(json.dumps(detail, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
# ── GeoJSON track ────────────────────────────────────────────────────────
|
# ── GeoJSON track ────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -85,13 +85,15 @@
|
|||||||
$: if (map && MarkerClass && timeseries && !markersAdded) {
|
$: if (map && MarkerClass && timeseries && !markersAdded) {
|
||||||
markersAdded = true;
|
markersAdded = true;
|
||||||
const add = () => {
|
const add = () => {
|
||||||
const lats = (timeseries!.lat ?? []).filter(v => v != null) as number[];
|
// Filter lat/lon together so indices stay aligned
|
||||||
const lons = (timeseries!.lon ?? []).filter(v => v != null) as number[];
|
const pts = (timeseries!.lat ?? [])
|
||||||
if (!lats.length) return;
|
.map((lat, i) => ({ lat, lon: (timeseries!.lon ?? [])[i] }))
|
||||||
|
.filter(p => p.lat != null && p.lon != null) as { lat: number; lon: number }[];
|
||||||
|
if (!pts.length) return;
|
||||||
new MarkerClass({ element: makeDot('#4ade80'), anchor: 'center' })
|
new MarkerClass({ element: makeDot('#4ade80'), anchor: 'center' })
|
||||||
.setLngLat([lons[0], lats[0]]).addTo(map);
|
.setLngLat([pts[0].lon, pts[0].lat]).addTo(map);
|
||||||
new MarkerClass({ element: makeDot('#f87171'), anchor: 'center' })
|
new MarkerClass({ element: makeDot('#f87171'), anchor: 'center' })
|
||||||
.setLngLat([lons[lons.length - 1], lats[lats.length - 1]]).addTo(map);
|
.setLngLat([pts[pts.length - 1].lon, pts[pts.length - 1].lat]).addTo(map);
|
||||||
};
|
};
|
||||||
map.loaded() ? add() : map.once('load', add);
|
map.loaded() ? add() : map.once('load', add);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user