OpenAthlete
AGPLv3 endurance training platform: EU-oriented hosting, transparent CTL/ATL/TSB logic in code, self-hosting, and full export. Built in Grenoble.
翻译准备中
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Hang Gliding and Paragliding competition task score and flight analysis
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
GlideComp scores hanggliding and paragliding competitions — and analyses the whole field's flights, so pilots can learn from the people they flew the day with.查看完整 README ↗GlideComp scores hanggliding and paragliding competitions — and analyses the whole field's flights, so pilots can learn from the people they flew the day with.
What it does:
How it's built:
@glidecomp/engine package handles geo calculations (WGS84 ellipsoid math, Vincenty formulas) and flight analysisKey design principles:
The app is live at glidecomp.com.
glidecomp.com and localhost:3000 are in the allowed URLs list.bun install
# Copy .env.example and add your MapBox token
cp .env.example .env
# Edit .env and set VITE_MAPBOX_TOKEN=your_token_here
Start the frontend and all API workers together:
bun run dev
This starts two processes:
wrangler dev session, reached through the dev-router Worker at
http://localhost:8790Every worker shares a single Miniflare instance, so the D1 database has one
writer — running them as separate processes on separate ports raced on the
shared SQLite file (issue #477). Only the router's port is exposed; it
dispatches /api/auth/*, /api/comp/*, /api/airscore/* and friends to the
right worker over service bindings, exactly as the Pages Functions do in
production. Vite proxies all of /api there, so the browser only ever talks to
:3000. To start the workers on their own: bun run dev:workers.
The auth worker requires a .dev.vars file in web/workers/auth-api/ — see docs/auth.md for setup.
To start only the frontend (without any workers):
bun run dev:frontend
AirScore features are served by the same router; nothing extra to start. If you'd rather use the production AirScore API instead of the local worker:
VITE_AIRSCORE_URL=https://glidecomp.com/api/airscore bun run dev
bun run preview builds the site and serves it through wrangler pages dev, so
you exercise the SSR Functions, the sitemap and the Astro pages exactly as
production does — no HMR, so a code change needs a re-run.
bun run preview # http://localhost:3000
bun run preview binds ports on your Mac (pages dev, the Workers' dev-router,
a workerd inspector) and keeps D1 + R2 in web/.wrangler/state, so a second copy
collides with the first. To run one that can't, put it in a container — the
stock ports stay inside the VM and only one is published:
bun run preview:container # http://localhost:3200
PORT=3201 bun run preview:container # a second, wholly independent instance
Requires Apple's container (macOS 26+,
Apple silicon): brew install container. Each PORT gets its own named volume
for D1 + R2, so instances never see each other's data — or your host checkout's.
Your checkout is bind-mounted read-only and staged into the container on
start, so a source edit needs a restart (Ctrl-C, re-run), not an image rebuild.
There's no HMR — preview serves a production build, same as on the host. Use
bun run dev for a fast edit loop.
The image holds only the dependency tree, and the script rebuilds it for you
when it needs to: it hashes the files that layer is built from (bun.lock, the
workspace manifests, patches/) and compares against a stamp beside the image,
printing the reason when it rebuilds. Editing source never triggers one. Even a
missed rebuild is only ever slower, not wrong — the entrypoint runs
bun install --frozen-lockfile after staging, which reconciles node_modules
to whatever lockfile the mount brought in.
| variable | default | when you need it |
|---|---|---|
PORT | 3200 | run more than one instance |
SKIP_SEED | 0 | keep an instance's existing data instead of reseeding |
REBUILD | 0 | force an image rebuild — normally detected for you |
CPUS / MEMORY | 6 / 8G | vite build is OOM-killed below ~4G |
The default port is 3200 because 3000 (vite), 3100 (SSR e2e), 4321 (astro),
8790 (the Workers' dev-router) and 9232 (its inspector) are all already spoken
for — and
because a native bun run dev binds Vite to [::1]:3000 while a published
container port binds 127.0.0.1:3000. macOS treats those as different sockets,
so both bind happily and localhost quietly serves you whichever one the
resolver picked. The script refuses a port anything already holds, on either
stack, rather than let that happen.
The browser only ever talks to the published port. The one thing that doesn't
work in the container is the analysis page's AirScore import — it calls
same-origin /api/airscore, and there's no Pages Function proxying that, so it
404s here exactly as it does in production.
bun run test # bun test sweep (root, engine, airscore-api, dev-router, scripts) + type check
bun run test:all # the above, then the three vitest suites concurrently
bun run test:comp # Competition API tests only
bun run test:auth # Auth API tests only
bun run test:frontend # Frontend tests only
bun run test:e2e # Playwright end-to-end tests
bun run test:e2e:ssr # Playwright suite against the server-rendered build
bun run db:migrate # Apply D1 migrations to the local state
bun run typecheck # Type check the root project
bun run typecheck:all # Type check everything (frontend + engine + all four workers)
The e2e suite writes to the persistent local D1 state in web/.wrangler/state.
Specs clean up after themselves and each run sweeps anything a killed run left
behind, so this should stay tidy on its own — but if local e2e results ever look
inexplicable, bun run kill-state resets the database to empty (then
bun run seed to put the sample comps back).
Read https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session
master → deploys to productionbun run deploy # Manual deploy to Cloudflare Pages
bun run deploy:worker # Manual deploy AirScore API Worker
bun run deploy:comp # Manual deploy Competition API Worker
bun run deploy:auth # Manual deploy Auth API Worker
bun run deploy:all # Deploy Pages + AirScore & Competition Workers (auth-api is separate: deploy:auth)
URLs:
detect-events - Detect flight events from an IGC file, outputting CSV:
bun run detect-events -- <flight.igc> [task.xctsk]
# Example with sample data
bun run detect-events -- \
web/frontend/public/data/tracks/2025-01-05-Tushar-Corryong.igc \
web/frontend/public/data/tasks/buje.xctsk
get-xcontest-task - Download a task from XContest by code:
bun run get-xcontest-task -- face
bun run get-xcontest-task -- --file task.json
score-task - Score multiple pilots against a task using CIVL GAP (FAI Sporting Code Section 7F):
bun run score-task -- <task.xctsk> <igc-file-or-folder>... [options]
# Scores identically to the web app. --wing (HG or PG) is REQUIRED for GAP
# scoring — the CLI has no comp record and won't guess. Given it, the run starts
# from the official per-category FAI/S7F defaults and each flag overrides one
# parameter. Flag names are the kebab-case of the gap_params keys the UI saves;
# units are the engine's (metres / seconds / 0-1 ratios). Grouped by function:
#
# Wing (required for GAP):
# --wing <HG|PG> Competition wing (`scoring`)
# Task mode:
# --open-distance Score as open distance (GAP options ignored)
# --comp <slug-or-dir> Score a whole bundled comp (every task per class,
# plus a per-class cross-task aggregate); wing comes
# from the comp.json manifest. Implies --analysis.
# Task analysis:
# --analysis After the scores, print the behavioural task
# analysis — per-pilot metrics (climbing, gliding,
# decisions, gaggle, race craft, day profile/wind)
# led by the metric-separation ranking (Spearman ρ
# vs GAP rank). See docs/2026-07-18-field-analysis-plan.md.
# Nominal parameters:
# --nominal-distance <m> `nominalDistance` (default: 70% of task distance)
# --nominal-distance-pct <%> That percentage, when you'd rather not do the sum
# --nominal-goal <ratio> `nominalGoal` 0-1 (default: 0.3)
# --nominal-time <s> `nominalTime` in seconds (default: 5400)
# --minimum-distance <m> `minimumDistance` in metres (default: 5000)
# Scoring terms (per-wing defaults; each has a --use-… form to turn it back on):
# --no-use-leading Disable leading (departure) points (`useLeading`)
# --no-use-arrival Disable arrival points (`useArrival`)
# --no-use-distance-difficulty Disable difficulty points (`useDistanceDifficulty`)
# Stopped tasks (S7F §13.4):
# --stop-time <iso-datetime> Announce a stop, e.g. 2026-01-15T03:45:00Z
# --score-back-time <s> PG score-back window (default: 300)
# Formula & advanced:
# --leading-formula <weighted|classic> `leadingFormula` (default: classic HG / weighted PG)
# --leading-weight-formula <gap2020|s7f2024> PG leading weight (default: gap2020)
# --time-points-exponent <5/6|2/3> `timePointsExponent` (default: 5/6)
# --ess-not-goal-factor <ratio> `essNotGoalFactor` (default: 0.8 HG / 0 PG)
# (see --help for the full list, incl. nominal-launch, distance-origin,
# jump-the-gun-factor, jump-the-gun-max-seconds, leading-time-ratio)
# Output:
# --json Output as JSON
# Example: score Corryong Cup 2026 Open Task 1 as HG with the official defaults
bun run score-task \
web/samples/comps/corryong-cup-2026-open-t1/task.xctsk \
web/samples/comps/corryong-cup-2026-open-t1/ \
--wing HG
# Example: score the whole bundled comp, with task analysis per task and a
# per-class cross-task aggregate
bun run score-task -- --comp corryong-cup-2026
Example output:
=== Task Scoring Results (CIVL GAP) ===
Scoring config:
Sport: HG
Leading: on (classic)
Time exponent: 5/6
Arrival: on
Difficulty: on
Nominal: dist 55.2 km / time 90 min / goal 30% / launch 96%
Task distance: 78.8 km
Pilots: 32 flying / 32 present
In goal: 12 (37.5%)
Best time: 1:37:55
Task Validity: 100.0%
Available Points: 1000 (dist: 486, time: 360, lead: 90, arr: 64)
# Pilot Dist SS Time Dist Pts Diff Pts Time Pts Lead Pts Arr Pts Total
-----------------------------------------------------------------------------------------------------------------------
1 Rohan Holtkamp 78.8 km 1:47:55 485.6 242.8 294.1 90.0 64.3 934
2 Jon Durand 78.8 km 1:37:55 485.6 242.8 360.1 21.2 44.2 911
3 Peter Burkitt 78.8 km 1:50:13 485.6 242.8 281.7 89.4 53.4 910
...
13 Rich Reinauer 76.9 km LO 479.7 242.8 0.0 0.0 0.0 480
The full HG table also shows a leading-coefficient (
LC) column; it's elided here for width.
seed - Seed the bundled sample competitions into local D1 + R2 (idempotent; --remote targets production):
bun run seed # every bundled comp
bun run seed big-chip kosci-loop # just these slugs
civl-rankings - Import the FAI/CIVL monthly world pilot rankings into D1 (all ten disciplines; idempotent, and a no-op on a day CIVL hasn't published anything new). Runs daily in CI — see docs/civl-rankings.md:
bun run civl-rankings
build-3dvis - Pack a task's tracks into a 3D replay asset for offline inspection (the replay itself loads it from the Worker):
bun run build-3dvis
bench-task / bench-analysis - Benchmark the scoring and analysis paths (see the benchmark-engine skill).
functions/ - Cloudflare Pages Functions (SSR for the public /comp pages,
/api/* proxies to the Workers, sitemap, middleware)
web/
frontend/ - Cloudflare Pages frontend (Vite + TypeScript)
static/ - Astro app: the prerendered content pages (home, about,
legal, the scoring guides)
engine/ - Shared analysis library (IGC parsing, event detection, GAP scoring,
task analysis, track quality, weather)
cli/ - CLI utilities (detect-events, get-xcontest-task, score-task, build-3dvis, benchmarks)
workers/
auth-api/ - Authentication API (Cloudflare Worker + D1)
competition-api/ - Competition management API (Cloudflare Worker + D1 + R2)
airscore-api/ - AirScore caching proxy (Cloudflare Worker)
dev-router/ - Dev-only router: owns :8790 and dispatches to the three above
db/
migrations/ - D1 schema migrations (shared by auth-api and competition-api)
samples/ - Bundled sample competitions, CIVL ranking fixtures, reference data
scripts/ - Operational scripts (comp seeding/download/generation, dev helpers, secrets)
e2e/ - Playwright end-to-end tests
docs/ - Feature and architecture specifications
patches/ - Patched dependencies (applied by bun)