8.2 KiB
Developer Guide
This guide is for developers contributing to BincioActivity.
Prerequisites
- Python 3.12+ with uv
- Node 20+ with npm
- Git
Local Setup
git clone https://github.com/brutsalvadi/bincio-activity.git
cd bincio-activity
# Install Python dependencies
uv sync
# Install optional extras for multi-user development
uv sync --extra serve --extra edit
# Install Node dependencies (for the site)
cd site && npm install && cd ..
Running Locally
Single-user (fastest for testing extract logic)
# Configure where to find your test activities
cp extract_config.example.yaml extract_config.yaml
$EDITOR extract_config.yaml # set input.dirs and output.dir
# Extract activities
uv run bincio extract
# Start the dev server (no login, no API server)
uv run bincio dev --data-dir ~/bincio_data
# → http://localhost:4321/u/{handle}/
Multi-user (for testing auth, write API, admin features)
# Create a test instance with an admin user
uv run bincio init --data-dir /tmp/bincio_test --handle testadmin
# Extract activities
uv run bincio extract --output /tmp/bincio_test
# Start everything (bincio serve + astro dev)
uv run bincio dev --data-dir /tmp/bincio_test
# → http://localhost:4321 (login with testadmin/{password})
Ctrl+C stops both servers.
Running Tests
# All tests
uv run pytest
# Specific test file
uv run pytest tests/extract/test_parsers.py
# Specific test function
uv run pytest tests/extract/test_parsers.py::test_gpx_parser
# With verbose output
uv run pytest -vv
# With coverage report
uv run pytest --cov=bincio
Tests are in tests/ and use pytest + fixtures for DRY test data.
Project Structure
bincio/
extract/ Python package for GPX/FIT/TCX parsing
models.py DataPoint, ParsedActivity, LapData
parsers/ GPX, FIT, TCX parsers + factory
sport.py Sport name normalization
metrics.py Haversine-based stats (distance, elevation)
timeseries.py 1Hz downsampling → BAS timeseries object
simplify.py RDP track simplification (no external deps)
dedup.py Exact + fuzzy duplicate detection
strava_csv.py Strava activities.csv importer
writer.py BAS JSON + GeoJSON output
config.py extract_config.yaml loader
cli.py `bincio extract` command
render/
cli.py `bincio render` command
merge.py Sidecar edit overlay (produces _merged/)
edit/
cli.py `bincio edit` FastAPI server
server.py Edit API endpoints
serve/
cli.py `bincio serve` command
server.py Multi-user FastAPI server (auth, invites, admin)
db.py SQLite data layer
init_cmd.py `bincio init` bootstrap
shared/ (if needed)
site/ Astro + Svelte + Tailwind frontend
src/
layouts/ Base.astro (auth wall, nav)
pages/ Routes (activity feed, detail, login, etc.)
components/ Svelte components (maps, charts, edit drawer)
lib/ TypeScript utilities (types, format, dataloader)
tests/ pytest test suite
extract/
render/
serve/
fixtures/ Shared test data
Key Concepts
BAS (BincioActivity Schema)
Activity data flows as BAS JSON files in {user}/activities/. The format is specified in SCHEMA.md.
Key files:
{id}.json— activity metadata + timeseries_merged/symlink — sidecar edits overlaid on activitiesedits/{id}.md— user-created sidecar (optional)
Shard model
Multi-user instances use a shard manifest (root index.json) that lists per-user shards. The browser fetches all shards concurrently and merges them. This allows:
- Federation (remote shard URLs)
- Yearly pagination
- No data duplication
Extract pipeline
GPX/FIT/TCX files
↓ (parse)
ParsedActivity
↓ (calculate metrics)
BAS Activity JSON
↓ (downsample to 1Hz)
Timeseries
↓ (simplify with RDP)
GeoJSON
↓ (write)
activities/{id}.json + activities/{id}.geojson
Render pipeline
{user}/
activities/*.json (extracted)
edits/*.md (user sidecars)
↓ (merge_all)
_merged/
index.json (sidecar edits applied)
activities/{id}.json
{id}.geojson
↓ (astro build)
site/dist/
Editing does not require re-extraction.
Making Changes
Adding a new endpoint
- Add a route in
bincio/serve/server.py(orbincio/edit/server.pyfor single-user) - Add Pydantic models for request/response if needed
- Add tests in
tests/serve/ - Update
docs/reference/api.mdwith the new endpoint - If admin-only, protect it with
await _require_admin(bincio_session)
Adding a parser for a new format
- Create
bincio/extract/parsers/myformat.py - Implement a parser class with
parse(file_path: Path) -> ParsedActivity - Register it in
bincio/extract/parsers/__init__.py - Add tests in
tests/extract/test_parsers.py
Modifying BAS schema
- Edit
schema/bas-v1.schema.json(JSON Schema) - Update
SCHEMA.md(human-readable spec) - Update TypeScript types in
site/src/lib/types.ts - Add a migration if the change is breaking
Frontend changes
Svelte components are in site/src/components/. Key ones:
ActivityFeed.svelte— activity grid + filtersActivityDetail.svelte— activity page (maps, charts, photos)EditDrawer.svelte— sidecar editor
Use uv run bincio dev to test changes live. The site hot-reloads on file changes.
Code Style
- Python: PEP 8, type hints where possible
- JavaScript/TypeScript: ESLint + Prettier (configured in
site/) - Svelte: No self-closing non-void tags; interactive divs need
role+ keyboard handler
Git Workflow
- Create a branch:
git checkout -b feature/my-feature - Make changes and test locally
- Commit:
git commit -m "Clear, specific commit message" - Push:
git push origin feature/my-feature - Open a pull request
Commit message style:
- Imperative mood ("add feature", not "added feature")
- Reference issues if relevant: "fix #123"
- First line ≤ 50 characters
- Blank line, then detailed explanation if needed
Performance Considerations
Extract speed
- ProcessPoolExecutor with initializer — large data (Strava lookups, hash sets) is sent once per worker, not per task
- Haversine — 10x faster than geopy for distance calculations
- Lazy parsing — FIT files decoded only once per task
Render speed
- RDP simplification — custom implementation (no external wheels for Pyodide)
- Gzip compression — activity JSON and geojson are served gzipped
- Concurrent shard fetch — browser loads all shards in parallel
Frontend
- MapLibre GL v5 — requires explicit center/zoom and workarounds
- Observable Plot — use hyphenated curve names (e.g.
"monotone-x") - Client-only for complex components — use
client:only="svelte"for activity detail to avoid hydration mismatches
Debugging
Python
# Interactive debugger
uv run python -m pdb -m bincio.extract.cli
# Or use breakpoint() in code
breakpoint()
uv run bincio extract
TypeScript
Check your editor's TypeScript integration. The site has strict tsconfig.json.
Frontend
- Open DevTools (F12)
- Check the Network tab for API calls
- Check Console for client-side errors
Database
# Inspect the SQLite database directly
sqlite3 /tmp/bincio_test/instance.db
> SELECT * FROM users;
Documentation
- User-facing docs go in
docs/ - API docs are auto-generated from FastAPI routes (and should be typed with Pydantic models)
- Code comments should explain why, not what
Known Issues & Limitations
See the GitHub repository for known issues and planned features.
Contributing
Contributions are welcome! Please:
- Check existing issues/PRs so you're not duplicating work
- Open an issue first for large changes
- Include tests for new features
- Update docs (user guide, API ref, or developer guide)
- Follow the code style guidelines
See also
- Architecture — system design and data flow
- BAS Schema — activity data format
- API Reference — all HTTP endpoints