My Gym My Way
Free home-gym workouts built from the equipment you own. No paywall, no account, no tracking.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Real-time baseball player tracker for Stadium Ventures. Monitors MLB, MiLB, and NCAA players, displays stats on a live dashboard, and sends Slack alerts for notable performances.README 전문 보기 ↗Real-time baseball player tracker for Stadium Ventures. Monitors MLB, MiLB, and NCAA players, displays stats on a live dashboard, and sends Slack alerts for notable performances.
URL: https://stadium-ventures.github.io/sv-dugout-pulse
| Item | Value |
|---|---|
| Dashboard URL | https://stadium-ventures.github.io/sv-dugout-pulse |
| GitHub Repo | https://github.com/Stadium-Ventures/sv-dugout-pulse |
| Roster Source | Google Sheet (published as CSV) |
| Slack Channel | #dugout-pulse |
| Update Frequency | Every 15 minutes during game hours |
| Game Hours Coverage | 11am ET - 2:30am ET (covers early NCAA games through late West Coast games) |
| File | Purpose |
|---|---|
index.html | The dashboard UI |
data/current_pulse.json | Player stats data (auto-updated by cron) |
src/roster_manager.py | Fetches roster from Google Sheet |
src/stats_engine.py | Fetches stats from MLB API + NCAA scrapers |
src/performance_analyzer.py | Grades performances (Milestone/Standout/Good/Routine/Soft Flag) |
src/alerts.py | Slack alert logic |
src/config.py | Settings, thresholds, column mappings |
main.py | Main script that orchestrates everything |
scripts/milb_watch.py | Daily MiLB watch — form + usage vs. season baseline (lull / usage-down / no-games / trending-up) → #dugout-pulse, 8:30 AM ET |
generate_test_data.py | Creates fake data for UI testing |
.github/workflows/pulse.yml | Automated cron schedule |
Alerts are sent to #dugout-pulse when:
| Trigger | Who | Example |
|---|---|---|
| Home run | Any player, any tier | "Dax Kilby (T1) just hit a HR!" |
| Pitcher enters game | Any pitcher, any tier | "Garrett Whitlock (T1) is pitching!" |
| 5+ strikeouts | Any pitcher, any tier | "Garrett Whitlock (T1) has 6 K's!" |
| 3+ times on base | T1/T2 hitters only | "Kellon Lindsey (T1) has reached base 3+ times!" |
Edit src/alerts.py to change thresholds or add new triggers. The check_and_send_alerts() function contains all the logic.
The alerts above fire off a single game. scripts/milb_watch.py answers a
different question — is this guy playing differently than he has all year? —
which is what earns a front-office or farm-director call.
For every client at an affiliated level (CPX/A/A+/AA/AAA) it grades the last 14
and 30 days against that player's own season line with the compared window
removed, using the same OPS/ERA thresholds as the dashboard's window grades
(src/window_grader.py), and posts four kinds of finding to #dugout-pulse:
| Trigger | Meaning |
|---|---|
| 🔻 Lull | Form dropped ≥ .150 OPS (or ERA rose ≥ 1.50) off his own baseline and landed in Steady/Cold |
| ⏳ Usage down | Hitters only, two reads on the 14/30 spans: role — PA per game played ≤ 70% of his season baseline ("still playing, no longer starting"); share — games played out of his team's games, down ≥ 25 points ("no longer in the lineup") |
| 😶 No games | Played this year, nothing in 14 days, and not on the IL |
| 📈 Trending up | Form gained ≥ .150 OPS (or ERA dropped ≥ 1.50) and landed in Solid/Hot — call while it's live |
A lull isn't only a rate collapse: losing playing time is a lull too, and it shows up first, so a thin recent sample gets read for usage rather than dismissed. The two usage reads answer different questions on purpose: "fewer plate appearances than before" silently adds up three causes — the team played fewer games, he was in the lineup fewer times, and he batted fewer times per game. Role (PA per game played) is the earliest tell and is immune to both the schedule and an org change. Share divides the schedule out, and is voided when a player changed orgs inside the window — his new club's earlier games were never his to miss.
Season over (2026-09-09): a player whose club has finished playing is off the watch. The gate is the club's schedule, not a calendar — it has played recently and has nothing left to play over the next two weeks, playoffs included. So Single-A and High-A players drop out the week after their leagues close, except those on playoff clubs, who stay live until elimination; Double- and Triple-A fall out on their own dates; Complex-league players went in late July. A shuttered player posts no findings and owes no closeout, is named once in the footnote of the next post that goes out, and comes back the first time his club has a game scheduled. The verdict is sticky through the offseason, when the schedule returns nothing either way.
Absence findings are checked against the MLB Stats API's roster entries and dropped when the player is on the IL or rehabbing — the org already told us why he isn't playing. A player just activated off the IL gets the same exclusion even though his roster status already reads "Active" again: the transactions endpoint, not the roster snapshot, is what still knows he was out, which is the only way to tell a real no-games absence from an injury that just ended. The same closed-out stint also voids the usage-share read, not just idle — a few games back from injury reads as a usage crash otherwise, since his own team-games share for the hurt stretch is genuinely near zero, which is a rehab ramp-up, not a benching. Rate verdicts still respect sample floors (40 PA / 15 IP baseline; 25 PA / 6 IP over 14 days; 45 PA / 12 IP over 30 days).
Cadence: a player is flagged the day he first qualifies, then not again
until his re-report window is up — 7 days for hitters, 14 for pitchers. If
nobody is new and nobody is due, the post is skipped entirely. Dropping below
the bar for a day doesn't reset the clock; a status flip (lull → trending up) is
a new finding and posts at once. That update is owed even if he's resolved by
then — a ✅ Back to normal line closes him out once, rather than letting a
player who quietly returned to normal vanish with no follow-up. Every tracked
player — posted or not — lands in data/milb_watch.json with both windows'
reads, how long the finding has been standing, and when he last posted.
The Slack layout is locked and pinned byte-for-byte by
test_locked_message_format in tests/test_milb_watch.py. If that test fails,
the format changed — that's the point. Preview without sending:
python -m scripts.milb_watch --dry
| Level | Source | Reliability |
|---|---|---|
| MLB/MiLB | Official MLB Stats API (free, no key required) | High |
| NCAA | Sidearm Sports → StatBroadcast → D1Baseball → stats.ncaa.org | Varies by school |
NCAA data is fragmented. The system tries multiple scrapers in order:
If a school's data isn't populating, you may need to add their specific feed URL to src/stats_engine.py.
cd ~/sv-dugout-pulse && claudegit add -A && git commit -m "Your message" && git pushcd ~/sv-dugout-pulse
claude
Then tell Claude what you want to do. It will have access to all the code.
These are configured in GitHub → Settings → Secrets and variables → Actions:
| Secret | Purpose |
|---|---|
ROSTER_URL | Google Sheet CSV URL (master roster) — no default in code |
RECRUITS_URL | Google Sheet CSV URL (recruits) — no default in code; unset = no recruits |
SLACK_WEBHOOK_URL | Slack incoming webhook |
Do not share these publicly. If compromised, regenerate them.
Clients come from ROSTER_SOURCE (repo Actions variable):
sheet (default when unset — the master sheet's published CSV, ROSTER_URL) or
registry — the SV Registry's authenticated projection
(GET https://sv-registry.vercel.app/api/roster-projection, Bearer token from the
Actions secret SV_REGISTRY_ROSTER_TOKEN, scope read:roster-projection).
Rollback is "set ROSTER_SOURCE back to sheet". Recruits (non-clients) always
come from their own sheet (RECRUITS_URL) and are never pruned against the
projection.
slug and MLB id.contains_no_contact_data is an
error. There is no fallback to the sheet; the run uses the last good
registry cache (< 24 h, prunes disabled) or aborts./api/scouting-ref, by MLB id) for
anyone signed in with the hub password, so the flip to registry does not blank
them. Without the hub, the page falls back to the roster's peak fields, which
the registry does not carry, so those viewers see no chips. The daily
no-peaks alert was removed in #47.data/_roster_source_alerts.json): the registry read failed and the run is on the
last saved list; the dual-run read failed; the registry roster has no peaks. A
registry failure with no saved list fails the run, and pulse.yml's "Alert on
failure" step posts.ROSTER_SOURCE=sheet and the token set, each run logs difference
counts only (these Actions logs are public). Names: run
python -m scripts.roster_dual_run locally.Open decision (Tom) — the token is BLOCKED until it is made. This repo and its Pages site are public, and the registry's policy ("no public anything", enforced by its roster-projection selftest) forbids assigning a service token to a public repo. Either:
The code works unchanged either way; only where the token may live differs.
Everything committed under data/ is served by GitHub Pages and readable by anyone
(the dashboard password gate is not real auth). So:
data/roster_cache.json is written through an allowlist
(ROSTER_CACHE_FIELDS in src/config.py): name, MLB id, team/level/affiliate,
position, draft class, tier, status, client flag. Nothing else can reach it.data/.data/roster_cache.json history; purging
that history is a separate human decision (see the cutover kit).The ♥ status on each client card and the ♥ filter come from Heartbeat
(sv-heartbeat.vercel.app/api/heartbeat/summary), which is closing its API to
anonymous callers. This page has no server and cannot hold a secret, so the
viewer signs in with their @stadium-ventures.com Google account (Google
Identity Services, sv-registry's OAuth client id) and js/sv-google-auth.js
sends that Google ID token as Authorization: Bearer on the Heartbeat call.
https://stadium-ventures.github.io must be an Authorized
JavaScript origin on OAuth client 970904391216-emekreu9hdntj6k76qhkr0fjvrvd9fcm
(Google Cloud console). Without it the sign-in button fails and the hearts stay
hidden (nothing else breaks).node --test tests/js/sv_google_auth.test.js (also run by pytest via
tests/test_js_google_auth.py).cd ~/sv-dugout-pulse
python3 generate_test_data.py
cd ~/sv-dugout-pulse
python3 -m http.server 8888
# Then open http://localhost:8888
The cron runs every 15 minutes during these windows (UTC):
In Eastern Time: 11:00 AM - 2:45 AM (next day)
This covers:
To change the schedule, edit .github/workflows/pulse.yml.
SLACK_WEBHOOK_URL secret is set correctlycurl -X POST -H 'Content-type: application/json' --data '{"text":"Test"}' YOUR_WEBHOOK_URLsrc/stats_engine.py for the school's scraper settingsThe roster is managed in Google Sheets, not in this code. To add/remove players:
Important columns:
Level must be "Pro" or "NCAA" (High School players are filtered out)Tier (1-4) determines roster priority and some alert rulesOrg is the team name (must match what the stats APIs expect)| Grade | Emoji | Hitter Criteria | Pitcher Criteria |
|---|---|---|---|
| Milestone | 💎 | Debut, 1st HR, cycle | Debut, no-hitter, 1st win/save |
| Standout | 🔥 | HR, 3+ hits, 3+ RBI | QS, 5+ Ks, save |
| Good | ✅ | 2+ hits, RBI, run | 3+ clean IP |
| Routine | 😐 | 1 hit, standard game | Average outing |
| Soft Flag | 🚩 | 0-for-4+ | 3+ ER in short outing |
Built with Claude Code, February 2026