# Bincio Segments — Design Plan ## Overview Segments are named GPS stretches that can be defined by any user. Every time an activity passes through a segment, an "effort" is recorded automatically. Users can track their personal history and PRs on each segment. --- ## Data Model ### Segment (global, shared) File: `/var/bincio/segments/{segment-id}.json` ```json { "id": "climb-to-passo-rolle-a3f2", "name": "Climb to Passo Rolle", "sport": "cycling", "polyline": [[46.123, 11.456], [46.124, 11.457], "..."], "distance_m": 4320.5, "bbox": [11.456, 46.120, 11.502, 46.135], "created_by": "brut", "created_at": "2026-05-13T10:00:00Z" } ``` Segment ID: slugified name + 4-char hash to avoid collisions. ### SegmentEffort (per user) File: `/var/bincio/data/{handle}/segment_efforts/{segment-id}.json` Array of efforts, newest first: ```json [ { "activity_id": "2026-04-26T053013Z-ccbas-...", "started_at": "2026-04-26T06:12:33Z", "elapsed_s": 1842, "avg_speed_kmh": 12.4, "avg_hr_bpm": 158, "avg_power_w": 245, "np_power_w": 261, "detected_at": "2026-05-13T10:05:00Z" } ] ``` --- ## Detection Algorithm **Input:** activity GPS track (lat/lon, ~1 Hz), segment polyline **Steps:** 1. **Bbox pre-filter** — skip if activity bbox doesn't overlap segment bbox (padded by ~50 m). O(1) per segment. 2. **Start proximity** — find all activity points within 25 m of `polyline[0]`. Each is a candidate entry point. 3. **End proximity** — from each candidate entry, scan forward (never backward) for an activity point within 25 m of `polyline[-1]`. Enforces correct direction. 4. **Path conformance** — between entry and exit indices, project each activity point onto the nearest segment polyline segment and compute perpendicular distance. If more than 30% of points exceed 50 m deviation, reject the match. Configurable thresholds. 5. **Effort extraction** — from the matched entry/exit indices, slice the timeseries and compute `elapsed_s` and avg metrics. 6. **Multiple efforts** — after a successful match, continue scanning from the exit point to detect repeat efforts in the same activity (e.g. repeat climbs). **Configurable constants** (easy to tune): - `MATCH_RADIUS_M = 25` — proximity to start/end points - `CONFORMANCE_MAX_DEVIATION_M = 50` — max perpendicular distance - `CONFORMANCE_MAX_FRACTION = 0.30` — fraction of points allowed to exceed max deviation --- ## Server-side Code Structure ``` bincio/segments/ __init__.py models.py — Segment, SegmentEffort dataclasses store.py — read/write segments and efforts to/from /var/bincio detect.py — matching algorithm cli.py — bincio segments detect [--activity X] [--segment Y] [--user U] [--all] ``` Detection is called: - **At ingest time** — after each new activity is processed, reads all segment definitions (bbox pre-filter makes this fast), writes matches to the user's `segment_efforts/` - **Retroactively** — via CLI or API endpoint, for one user re-running all activities against all (or one) segment(s) --- ## API Endpoints All new, added to `server.py`: ``` GET /api/segments?bbox=lon_min,lat_min,lon_max,lat_max list segments in viewport POST /api/segments create segment (auth required) DELETE /api/segments/{id} delete (created_by or admin only) GET /api/segments/{id}/efforts current user's efforts (auth) POST /api/segments/{id}/detect retrigger detection for current user ``` Bbox filtering is done in memory at request time. All segment definitions are read from disk on startup (or lazily cached). Works fine for hundreds of segments. --- ## Frontend ### New pages / routes | Route | Purpose | |---|---| | `/segments/` | Map + list of all segments; entry point for browsing and creating | | `/segments/new/` | Segment creation flow | | `/segments/{id}/` | Segment detail: polyline on map + user's effort history | ### `/segments/` page - Full-width map showing segment polylines for segments whose bbox overlaps the current map viewport - Segment list below (or sidebar): name, sport, distance, creator - "New segment" button → `/segments/new/` - Clicking a segment → `/segments/{id}/` ### `/segments/new/` — creation flow 1. Searchable list of the user's own activities with GPS → select one 2. Activity track shown on map 3. Dual-handle range slider below the map, one position per GPS point in the track - Moving handles highlights the selected portion on the map in a contrasting colour - Start and end coordinates are the GPS points at the slider handle positions 4. Name input + optional sport selector 5. Confirm → `POST /api/segments` ### `/segments/{id}/` — segment detail - Segment polyline on map - Metadata: name, sport, distance, created by - Effort history table (sorted by date, newest first): - Date, elapsed time, avg speed, avg HR, avg power, NP - Delta vs. personal best highlighted - "Retrigger detection" button → `POST /api/segments/{id}/detect` ### Activity detail page (existing) Below the stats table: if any segment efforts were detected for this activity, show a compact list — segment name + elapsed time + Δ vs. PR. ### Athlete page (existing) New "Segments" section: list of segments the user has efforts on, showing best time and effort count for each. --- ## Segment Creation UX (refined) Standalone `/segments/new/` page with an activity picker. Additionally, the activity detail page gets a **"Create segment from this activity"** button that links to `/segments/new/?activity={id}`, pre-selecting the activity and skipping the picker step. Rationale: users will typically think "I want to turn this climb I just did into a segment" — the shortcut from activity detail makes that fast, while the standalone page remains the canonical entry point for discovery. --- ## Implementation Strategy UI-first, so users can start populating the segment collection immediately. Detection comes after. **Phase 1 — Minimal backend to unblock UI** ✅ 1. `bincio/segments/models.py` — Segment and SegmentEffort dataclasses ✅ 2. `bincio/segments/store.py` — read/write segments to/from disk ✅ 3. `POST /api/segments` + `GET /api/segments?bbox=...` + `DELETE /api/segments/{id}` ✅ **Phase 2 — Creation UI** ✅ 4. `/segments/new/` page — activity picker → map + dual-handle slider → name/save ✅ 5. `/segments/` page — map + list of existing segments, bbox-filtered ✅ 6. Activity detail — "Create segment from this activity" shortcut button ✅ **Phase 3 — Detection** ✅ 7. `bincio/segments/detect.py` — matching algorithm, tested in isolation ✅ 8. `bincio/segments/cli.py` — retroactive detection CLI (`bincio segments detect`) ✅ 9. Ingest hook — run detection at end of each ingest ✅ 10. `POST /api/segments/{id}/detect` + `GET /api/segments/{id}/efforts` ✅ **Phase 4 — Display** (next) 11. `/segments/{id}/` page — segment detail + effort history table 12. Activity detail — show matched segment efforts below stats 13. Athlete page — segments section