fix low level issues

This commit is contained in:
Davide Scaini
2026-03-31 23:22:12 +02:00
parent 8f91503cf7
commit 81438231b4
19 changed files with 126 additions and 44 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ def _resolve_data_dir(explicit: Optional[str], config_path: Optional[str]) -> Pa
if config_path and Path(config_path).exists():
import yaml
raw = yaml.safe_load(Path(config_path).read_text())
raw = yaml.safe_load(Path(config_path).read_text()) or {}
out = raw.get("output", {}).get("dir")
if out:
return Path(out).expanduser().resolve()
+1 -6
View File
@@ -506,12 +506,7 @@ async def save_athlete(payload: dict[str, Any]) -> JSONResponse:
encoding="utf-8",
)
# Patch athlete.json in-place (preserves power_curve, updated_at, etc.)
data = json.loads(athlete_path.read_text(encoding="utf-8"))
data.update(overrides)
athlete_path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
# Re-merge so _merged/athlete.json symlink stays valid
# Re-merge — merge_all() applies edits/athlete.yaml on top of athlete.json
from bincio.render.merge import merge_all
merge_all(dd)
+3 -2
View File
@@ -346,5 +346,6 @@ def _patch_duplicate_of(output_dir: Path, activity_id: str, canonical_id: str) -
data = json.loads(p.read_text())
data["duplicate_of"] = canonical_id
p.write_text(json.dumps(data, indent=2, ensure_ascii=False))
except Exception:
pass
except Exception as e:
import logging
logging.getLogger(__name__).warning("_patch_duplicate_of failed for %s: %s", activity_id, e)
+18 -8
View File
@@ -237,19 +237,29 @@ def _fastest_time_for_distance(speed_1hz: list[float], target_km: float) -> Opti
def _best_climb(ele_1hz: list[Optional[float]]) -> Optional[float]:
"""Maximum net elevation gain over any contiguous window (Kadane's on deltas).
Ignores samples where elevation is None. Returns None if fewer than two
valid elevation samples exist.
None samples are treated as breaks between segments — the Kadane window is
reset to 0 at each gap so non-contiguous elevation data is never joined.
Returns None if fewer than two non-None samples exist.
"""
valid = [e for e in ele_1hz if e is not None]
if len(valid) < 2:
non_null = sum(1 for e in ele_1hz if e is not None)
if non_null < 2:
return None
max_gain = 0.0
current = 0.0
for a, b in zip(valid, valid[1:]):
current = max(0.0, current + (b - a))
if current > max_gain:
max_gain = current
prev: Optional[float] = None
for e in ele_1hz:
if e is None:
# Gap — reset window so we don't bridge the discontinuity
current = 0.0
prev = None
continue
if prev is not None:
current = max(0.0, current + (e - prev))
if current > max_gain:
max_gain = current
prev = e
return round(max_gain, 1) if max_gain > 0 else None
+4 -4
View File
@@ -43,11 +43,11 @@ def preview_coords(
mask = rdp(coords, epsilon=0.001, return_mask=True)
reduced = [gps[i] for i, keep in enumerate(mask) if keep]
# Subsample if still too many
# Subsample if still too many — always include last point without exceeding max_points
if len(reduced) > max_points:
step = len(reduced) / max_points
reduced = [reduced[int(i * step)] for i in range(max_points)]
reduced.append(gps[-1]) # always include the last point
step = len(reduced) / (max_points - 1)
reduced = [reduced[int(i * step)] for i in range(max_points - 1)]
reduced.append(gps[-1])
return [[round(lat, 5), round(lon, 5)] for lat, lon in reduced]
+2 -2
View File
@@ -29,8 +29,8 @@ def build_timeseries(
t = int((p.timestamp - started_at).total_seconds())
if t < 0:
continue
if last_t is not None and t == last_t:
continue # skip sub-second duplicates
if last_t is not None and t <= last_t:
continue # skip sub-second duplicates and non-monotonic points
sampled.append(p)
last_t = t
+2 -2
View File
@@ -38,7 +38,7 @@ def _find_data_dir(explicit: Optional[str], config_path: Optional[str]) -> Path:
if config_path and Path(config_path).exists():
import yaml
raw = yaml.safe_load(Path(config_path).read_text())
raw = yaml.safe_load(Path(config_path).read_text()) or {}
out = raw.get("output", {}).get("dir")
if out:
return Path(out).expanduser().resolve()
@@ -47,7 +47,7 @@ def _find_data_dir(explicit: Optional[str], config_path: Optional[str]) -> Path:
auto_config = Path.cwd() / "extract_config.yaml"
if auto_config.exists():
import yaml
raw = yaml.safe_load(auto_config.read_text())
raw = yaml.safe_load(auto_config.read_text()) or {}
out = raw.get("output", {}).get("dir")
if out:
return Path(out).expanduser().resolve()
+16 -2
View File
@@ -140,13 +140,27 @@ def merge_all(data_dir: Path) -> int:
if not dest_img.exists():
dest_img.symlink_to(img_dir.resolve())
# Symlink athlete.json if present
# Produce merged athlete.json — base from extract overlaid with edits/athlete.yaml
athlete_src = data_dir / "athlete.json"
athlete_dest = merged_dir / "athlete.json"
if athlete_dest.exists() or athlete_dest.is_symlink():
athlete_dest.unlink()
if athlete_src.exists():
athlete_dest.symlink_to(athlete_src.resolve())
athlete_edits_path = data_dir / "edits" / "athlete.yaml"
if athlete_edits_path.exists():
try:
import yaml as _yaml
edits = _yaml.safe_load(athlete_edits_path.read_text(encoding="utf-8")) or {}
except Exception:
edits = {}
else:
edits = {}
if edits:
athlete_data = json.loads(athlete_src.read_text(encoding="utf-8"))
athlete_data.update(edits)
athlete_dest.write_text(json.dumps(athlete_data, indent=2, ensure_ascii=False))
else:
athlete_dest.symlink_to(athlete_src.resolve())
# Write merged index.json (private filtered, highlight sorted)
index_path = data_dir / "index.json"