尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A real-time sport science dashboard for the University of Florida Football Strength & Conditioning staff, tracking GPS load (Catapult), force plate/strength testing (VALD), and derived metrics (ACWR, load ramp-up, leaderboards, session comparisons, period/drill breakdowns) for the full roster.
Note: An earlier version of this README described a different, planned architecture (Supabase Storage hosting, R "go-live" import scripts, role-based auth). That plan was superseded by what's documented below — this file now reflects the system as it actually runs in production.
main on every pushpages/dashboard.htmlpages/athlete.html| Layer | Technology |
|---|---|
| Frontend | Single-file vanilla HTML/CSS/JS per page, no build step |
| Charts | Chart.js 4.4 via CDN |
| Hosting | Vercel, auto-deploy from GitHub main branch |
| Database | Supabase Postgres |
| Backend logic | Supabase Edge Functions (Deno/TypeScript) + pg_cron scheduling |
| GPS pipeline | Catapult OpenField → webhook → Supabase Edge Function |
| Force/strength pipeline | VALD ForceDecks/NordBord → nightly R script via GitHub Actions |
| Auth | Username-based login via Supabase (not Supabase's role-based auth system) |
LoadMonitoring/
├── pages/
│ ├── dashboard.html ← Main staff dashboard (single large file: HTML+CSS+JS)
│ └── athlete.html ← Athlete-facing login/view
├── scripts/
│ ├── vald_sync.R ← Nightly VALD sync (runs via GitHub Actions)
│ ├── vald_backfill_metrics.R ← One-off historical VALD metric backfill
│ └── create_athletes.mjs ← Admin script for creating athlete accounts
├── .github/workflows/
│ └── vald-sync.yml ← Nightly cron trigger for vald_sync.R
└── README.md ← This file
Note: Supabase Edge Functions (the Catapult integration) live in the Supabase dashboard under Edge Functions — they are not stored in this GitHub repo. See "Supabase Edge Functions" below for what exists and what each one does.
These are managed directly in the Supabase dashboard (Project → Edge Functions), not in Git.
| Function | Purpose | Trigger |
|---|---|---|
catapult-webhook | Core GPS sync. Fires on every Catapult activity update. Writes to catapult_sessions and catapult_period_stats. Captures participation tags. Filters out Catapult's auto-created junk activities (named Activity <digits>). | Catapult webhook subscription |
catapult-athlete-profiles-sync | Pulls each athlete's profile max velocity from Catapult /athletes, writes to athletes.max_velocity_mph and athletes.catapult_athlete_id. | pg_cron, nightly |
catapult-diagnostic | Debug/inspection tool for testing Catapult API responses directly. Not part of the live pipeline. | Manual only |
catapult-sessions-recheck | Re-pulls fresh stats for existing activities into a separate comparison table (catapult_sessions_recheck), used to catch/diagnose sessions that synced with incomplete data. | Manual only |
catapult-period-backfill | One-time historical backfill of catapult_period_stats. Already run. | Manual only |
catapult-tag-backfill | One-time historical backfill of catapult_sessions.participation_tag. Already run. | Manual only |
To check pg_cron job status/history:
select * from cron.job;
select * from cron.job_run_details order by start_time desc limit 10;
| Table | Contents |
|---|---|
athletes | Roster. Includes catapult_athlete_id, max_velocity_mph (synced nightly) |
catapult_sessions | GPS session totals per athlete per activity. Includes participation_tag (e.g. 'Full', 'Rehab') |
catapult_period_stats | Per-athlete, per-drill/period breakdown within each practice session |
cmj_tests | VALD ForceDecks CMJ jump testing data |
nordbord_tests | VALD NordBord hamstring testing data |
catapult_sessions_recheck | One-time comparison table, not used by the live dashboard |
Important: session_date is stored as text (MM/DD/YYYY), not a real date
column. Any SQL sorting/filtering by date must use to_date(session_date, 'MM/DD/YYYY')
or it will sort alphabetically instead of chronologically.
Secrets live in two separate places and must be kept in sync manually — there is no automatic sharing between them:
vald-sync.yml| Secret | Used by | Notes |
|---|---|---|
CATAPULT_BASE_URL | Edge Functions | e.g. https://connect-us.catapultsports.com/api/v6 |
CATAPULT_API_TOKEN | Edge Functions | Generated in OpenField Cloud → Settings → API Tokens. Must have full scope (Summary Data, Sensor Data, Activities, Athletes, Tags, etc.) |
VALD_CLIENT_ID / VALD_CLIENT_PASSWORD / VALD_DUENDE_ID | GitHub Actions (vald_sync.R) | OAuth client credentials for VALD's API |
SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY | Edge Functions | Server-side Supabase access |
SUPABASE_URL / SUPABASE_SERVICE_KEY | GitHub Actions | Same purpose, different secret store |
Secret values cannot be viewed again once saved — only rotated. If a token needs regenerating, it must be recreated from scratch with the provider (Catapult/VALD) and updated in both places above.
GitHub Actions cannot successfully call Catapult's /athletes endpoint — it
returns a persistent 401 even with a correct token, likely due to IP allowlisting
on Catapult's side. Supabase Edge Functions work fine for the identical call. Any
new Catapult integration should be built as a Supabase Edge Function, not a GitHub
Actions/R script.
Catapult's velocity_max field (profile max velocity) is returned in meters/second,
not mph. Multiply by 2.23694 to match what OpenField's own UI shows.
Catapult sometimes auto-creates junk "activities" (default name pattern:
Activity <digits>) when a unit is powered on but not assigned to a tracked
period. catapult-webhook filters these out by name pattern before syncing —
if this starts happening under a different naming convention, the filter in
catapult-webhook will need updating.
Catapult webhook events can fire multiple times per activity as stats get
recalculated, but can also stop firing before a session finishes processing,
leaving partial/incomplete data synced. There's no automatic re-check for this —
catapult-sessions-recheck exists as a manual tool to re-pull and compare if
numbers look wrong for a specific date.
Athlete names differ across systems (Catapult, VALD, the athletes table) —
e.g. "Ace Ciongoli" in Catapult = "Austin Ciongoli" in Supabase. Every sync script
has a NAME_MAP object handling known mismatches. When a name doesn't match, it
logs as "unmatched" — check sync logs periodically, especially after roster changes,
and add new mappings as needed.
Participation tags: athletes tagged Rehab in Catapult's Activity Editor are
captured into catapult_sessions.participation_tag. Load Monitor and Session
Compare pages exclude Rehab-tagged rows from averages to match what Catapult's
own reports show.
New tables need explicit Row Level Security (RLS) policies, or the dashboard's anon key silently returns zero rows (no error thrown). Copy this pattern for any new table:
alter table [new_table] enable row level security;
create policy "Service role full access" on [new_table] for all using (true);
create policy "Authenticated users can read [new_table]" on [new_table] for select using (true);
Supabase queries cap at 1000 rows by default. Scripts/functions pulling large tables must paginate with or will silently undercount.
No build step or bundler — these are static files. To test locally with a live connection to the real Supabase project:
cd pages
python3 -m http.server 8000
Then open http://localhost:8000/dashboard.html in your browser. Edits to the file
take effect on refresh — no server restart needed. Push to main when ready to deploy.
GPS_ROSTER in dashboard.html, and check
NAME_MAP objects in vald_sync.R and the catapult-athlete-profiles-sync Edge
Function for new name mismatches./athletes 401 issue and any
future account-level module/scope questions.University of Florida Football Strength & Conditioning — Internal Use Only
.range()Vercel's auto-deploy from GitHub occasionally fails silently with no new
deployment triggered. Fix: git commit --allow-empty -m "Trigger Vercel redeploy" && git push.
GPS_ROSTER — a hardcoded JS array inside dashboard.html — controls which
athletes appear in GPS-related views. This needs manual updates when the roster
changes; it does not automatically sync from the athletes table.