o-sumo is a static web app for publishing sumo banzuke, torikumi, and rikishi, gyoji, and yobidashi directories. It is built with React 19, TypeScript, and Vite, and serves both a static site and static JSON APIs from Cloudflare Pages.
- README:
README.md/README_en.md - Development guide:
DEVELOPMENT.md/DEVELOPMENT_en.md - Skills index:
SKILLS.md/SKILLS_en.md - API spec:
docs/api/v1.md/docs/api/v1.en.md - API policy:
docs/api/policy.md/docs/api/policy.en.md - API changelog:
docs/api/changelog.md/docs/api/changelog.en.md - Rikishi profile and matchup refresh runbook:
docs/rikishi-profile-refresh-runbook.md - Gyoji and yobidashi refresh runbook:
docs/official-profile-refresh-runbook.md
- Web routes:
- Homepage:
/ - Archives:
/archives - Rikishi list:
/rikishi - Rikishi profile:
/rikishi/{id} - My Rikishi:
/my-rikishi/ - Compare Rikishi:
/compare/?ids={id1},{id2} - Gyoji directory:
/gyoji/ - Gyoji profile:
/gyoji/{id}/ - Yobidashi directory:
/yobidashi/ - Yobidashi profile:
/yobidashi/{id}/ - Banzuke:
/{YYYYMM}-banzuke/ - Results hub:
/{YYYYMM}-torikumi - Schedule hub:
/{YYYYMM}-yotei - Daily result:
/{YYYYMMDD}-torikumi - Daily schedule:
/{YYYYMMDD}-yotei - Kimarite:
/kimarite - Basho analytics:
/analytics/
- Homepage:
- Current route examples:
/202609-banzuke//202609-torikumi//20260913-yotei//compare/?ids=3842,4227/kimarite
- Stories:
https://blog.osada.us/ - The legacy banzuke URL
/{YYYYMM}-o-sumo/redirects to the current banzuke URL. - Public APIs:
/api/v1/banzuke.json/api/v1/torikumi.json/api/v1/rikishi.json/api/v1/rikishi/{id}.json/api/v1/rikishi-matchups.json/api/v1/gyoji.json/api/v1/gyoji/{id}.json/api/v1/yobidashi.json/api/v1/yobidashi/{id}.json/api/v1/news.json/api/v1/blog.json
Related docs:
docs/api/v1.en.mddocs/api/v1.mddocs/api/policy.en.mddocs/api/policy.mddocs/api/changelog.en.mddocs/api/changelog.mdDEVELOPMENT_en.mdDEVELOPMENT.md
Skill publishing:
SKILLS_en.mdSKILLS.mdpublic/.well-known/agent-skills/osumo-content/SKILL.mdpublic/.well-known/agent-skills/osumo-discovery/SKILL.mdskills/osumo-api/SKILL.md
- Direct navigation from the homepage to
Banzuke / Schedule / Results / Rikishi Directory / Gyoji Directory / Yobidashi Directory / My Rikishi / Compare Rikishi / Basho Analytics - Today's Highlights (Featured Matchups):
- Prominently featured right below the Hero on the homepage with 1-2 curated matchups for the day
- Displays East vs West bout cards, shikona, rank badges, deterministic avatars, Aikuchi (head-to-head record and two-tone win rate bar), insightful bout preview commentary, and one-tap direct links to the Rikishi Comparison page
- Generated automatically from the official musubi bout on days with published torikumi data; hand-written static matchups, fixed descriptions, and hand-curated head-to-head records have been removed
- On days without an official torikumi, renders only a neutral "Today's Highlights / Awaiting Official Bouts / Featured bouts, aikuchi, and comparison links will appear after the official torikumi is published." notice — no rikishi names, articles, aikuchi, comparison/bout links, or commentary box
- Aikuchi is sourced exclusively from
/api/v1/rikishi-matchups.json(pre-aggregated from official profile history) and is only shown when the JSON loads successfully. On failure the aikuchi block is hidden while comparison and bout links remain available
- Revamped Rikishi Comparison (
/compare/?ids={id1},{id2}):- Highlights Aikuchi (Head-to-Head Record / Compatibility) as the primary hero scoreboard with score, win rate, contextual lead badge, and two-tone ratio meter
- East vs West graphical faceoff card (rank, avatar, current basho win-loss record)
- Physical stats visual comparison bars (height, weight, career win rate) with advantage indicators
- Top winning techniques (Kimarite) side-by-side ranking dynamically aggregated from tournament archives
- Quick-pick matchup presets (Yokozuna, Ozeki, Sekiwake, Sanyaku rivalries) and one-tap selection for saved My Rikishi pairs
- Bilingual lists and profiles for 42 active gyoji and 45 active yobidashi, sourced from the official Japan Sumo Association website. Official numeric IDs are used in page and JSON API URLs, and no photographs are published
- Banzuke pages for makuuchi and juryo rankings and records, with MiniMax I2I Generation processed rikishi profile images and links to rikishi profiles
- My Rikishi (
/my-rikishi/) stores up to 10 wrestlers by shikona with navigation count badges, URL synchronisation, andlocalStoragepersistence across devices - Basho analytics (
/analytics/) shows makuuchi yusho, jun-yusho, kanto-sho, gin-o-sho, and juryo yusho for the current basho - The
ShareCurrentLinkbutton copies the current URL to the clipboard (with a manual fallback for non-secure contexts) - Monthly hub pages listing all 15 daily pages
- Daily pages for makuuchi and juryo torikumi, with profile links on wrestler names
ascending / descendingsorting on banzuke, hub, and daily pages- Unpublished days remain available as
pendingpages with empty-state messaging - The month key is derived dynamically from generated data in
app/lib/torikumi-data.ts - Homepage Latest News section split into two sub-sections: Japan Sumo Association announcements and Sumo World News (latest 5 from dmenu Sports)
- Homepage Kimarite card links to the
/kimariteindex page that lists all 82 winning techniques, grouped by category with bilingual Japanese/English commentary - News JSON is regenerated automatically by the
news-feed-updateGitHub Actions workflow via the Python scraper (/api/v1/news.json) - Stories are managed as Japanese Markdown posts in
blog/posts/*.md. Runnpm run blog:buildto generate the standaloneblog.osada.usoutput indist-blog/and refresh/api/v1/blog.jsonfor homepage integration - Four WebMCP tools (
search_rikishi,list_basho,get_banzuke_for_month,get_torikumi_for_day) are exposed for AI agents running in supported browsers (prefersdocument.modelContext.registerToolfrom the W3C Draft, falls back tonavigator.modelContext.registerTool)
- Frontend: React 19, TypeScript, React Router, Vite
- Testing: Vitest, Testing Library, jsdom
- Data generation: Python (
scripts/update_sumo_data.py,scripts/update_official_profiles.py) - Hosting: Cloudflare Pages
- Data sources: Japan Sumo Association Ajax endpoints and the official gyoji/yobidashi member list and profile pages
- Rikishi imagery: local PNG illustrations processed with MiniMax I2I Generation from Japan Sumo Association profile photos
- News sources: Japan Sumo Association announcements page and dmenu Sports (
https://sumo.sports.smt.docomo.ne.jp/news/)
Requirements:
- Node.js 20.19+ or 22.12+
- npm 9+
- Python 3.10+
Setup:
git clone https://github.com/dai/o-sumo.git
cd o-sumo
npm installNotes:
package-lock.jsonis committed- Use
npm installfor the initial setup - Use
npm cifor reproducible reinstalls and CI
Main commands:
# Dev server
npm run dev
# Type checking
npm run typecheck
# Tests
npm test
# Production build
npm run build
# Read-only current-data switch preflight
npm run preflight:current-data
# Local preview of the built app
npm run preview
# Generate the Stories site output
npm run blog:generateUseful local URLs:
http://localhost:3001/http://localhost:3001/archiveshttp://localhost:3001/rikishihttp://localhost:3001/rikishi/{id}http://localhost:3001/my-rikishi/http://localhost:3001/compare/?ids={id1},{id2}http://localhost:3001/gyoji/http://localhost:3001/gyoji/{id}/http://localhost:3001/yobidashi/http://localhost:3001/yobidashi/{id}/http://localhost:3001/{YYYYMM}-banzuke/http://localhost:3001/{YYYYMM}-torikumihttp://localhost:3001/{YYYYMM}-yoteihttp://localhost:3001/{YYYYMMDD}-torikumihttp://localhost:3001/{YYYYMMDD}-yoteihttp://localhost:3001/kimaritehttp://localhost:3001/analytics/http://localhost:3001/api/v1/rikishi-matchups.jsonhttp://localhost:3001/api/v1/news.json
Full refresh (banzuke + torikumi + rikishi profiles):
python scripts/update_sumo_data.pyRikishi profiles only:
python scripts/update_sumo_data.py --rikishi-onlyThis full-profile refresh also generates rikishi-matchups.json, resolving shikona before and after name changes to official rikishi IDs. The file is replaced only after all target profiles pass retrieval, ID resolution, and cross-profile consistency checks; partial fetches and parse failures preserve the last valid file. See docs/rikishi-profile-refresh-runbook.md for pre-publish checks.
Rikishi profiles only, limited to the first 10 profiles (for testing):
python scripts/update_sumo_data.py --rikishi-only --profile-limit 10Torikumi-only refresh:
python scripts/update_sumo_data.py --torikumi-onlyResults-only or schedule-only refresh:
python scripts/update_sumo_data.py --torikumi-scope result
python scripts/update_sumo_data.py --torikumi-scope scheduleTorikumi-only plus scoped refresh:
python scripts/update_sumo_data.py --torikumi-only --torikumi-scope result
python scripts/update_sumo_data.py --torikumi-only --torikumi-scope scheduleNews feed only (Japan Sumo Association announcements + dmenu Sports):
python scripts/update_news_feed.pyGyoji and yobidashi directories only, sourced from the official Japan Sumo Association website:
python scripts/update_official_profiles.py
python scripts/update_official_profiles_test.pySee docs/official-profile-refresh-runbook.md for generated-file and pre-publish integrity checks.
The July basho is final and retained as immutable snapshots in app/lib/july2026-data.ts and app/lib/july2026-banzuke-data.ts. The current /api/v1/banzuke.json and /api/v1/torikumi.json serve September; July remains available as an archive.
Run npm run preflight:current-data before switching current data. It fetches the official annual schedule and banzuke, then checks current banzuke/torikumi, archives, routes, sitemap, and workflow consistency without writing files. The September 2026 switch used --current-month 202607 --target-month 202609. It prints READY and exits 0 only when every gate is [OK]; official publication/fetch failures and contract mismatches print BLOCKED and exit 1. The generator is not run, and data, routes, sitemap, redirects, and workflow schedules are not changed.
To check a different basho month, pass arguments after -- so npm forwards them to the script: npm run preflight:current-data -- --current-month YYYYMM --target-month YYYYMM (npm 8+). You can also invoke the script directly: python scripts/preflight_current_basho.py --current-month YYYYMM --target-month YYYYMM. The npm script keeps the hard-coded defaults, so subsequent switches must specify the months explicitly.
For the next basho switch, preserve the current September data and the immutable July snapshots, and switch banzuke, torikumi, public JSON, monthly routes, and sitemap together only after validating the new official data.
Generated outputs:
app/lib/sumo-data.tsapp/lib/torikumi-data.tsapp/lib/news-data.tspublic/api/v1/banzuke.jsonpublic/api/v1/torikumi.jsonpublic/api/v1/rikishi.jsonpublic/api/v1/rikishi/{id}.json(one file per rikishi, includingname,yomi,currentRank,sourceUrl, andupdatedAt)public/api/v1/rikishi-matchups.json(unique official ID pairs and both career win totals derived from profile history)public/api/v1/news.jsonpublic/api/v1/gyoji.json/public/api/v1/gyoji/{id}.json(42 gyoji)public/api/v1/yobidashi.json/public/api/v1/yobidashi/{id}.json(45 yobidashi)public/images/rikishi/{id}.png(processed profile illustrations for all rikishi, used permanently on both profile and banzuke pages)
Key validations:
- 42 makuuchi rikishi
- 28 juryo rikishi
- 15-day archives for both results and schedules
- Published days are populated from source data when available
- Unpublished days remain as
pendingplaceholders - Gyoji and yobidashi list/detail JSON counts match and contain no image fields
GitHub Actions separates torikumi schedules, results, and news. Daily schedule updates open a PR and request auto-merge after checks; realtime result updates validate and push directly to main.
Shared helpers under scripts/ci/:
run_torikumi_generator.sh— runsupdate_sumo_data.pywith up to two retriesvalidate_torikumi.py— schema check forpublic/api/v1/torikumi.jsonvalidate_news.py— schema check forpublic/api/v1/news.jsonnotify_discord.sh— posts a Discord notification whenDISCORD_WEBHOOK_URLis configured
Workflows:
- Daily update:
.github/workflows/daily-data-update.yml- trigger: JST 13:00, 15:00, 17:00, and 19:00 plus
workflow_dispatch - updates torikumi schedules only (
--torikumi-only --torikumi-scope schedule)
- trigger: JST 13:00, 15:00, 17:00, and 19:00 plus
- Realtime results update:
.github/workflows/realtime-torikumi-direct-update.yml- trigger: every 10 minutes from JST 13:00 through 18:50 plus
workflow_dispatch - updates torikumi results only (
--torikumi-only --torikumi-scope result --skip-rikishi-fetch --strict-torikumi-fetch) - logs: GitHub Actions job summary aggregates step results, commit state, and the run URL
- trigger: every 10 minutes from JST 13:00 through 18:50 plus
- News update:
.github/workflows/news-feed-update.yml- schedule: every 2 hours from JST 09:05 through 19:05
- updates Japan Sumo Association and dmenu Sports news (
python scripts/update_news_feed.py) - skips rewriting
news.jsonwhen fetched items and source states are unchanged - One PR per day: the PR is opened on the JST-date-keyed branch
automation/news-updates-<YYYY-MM-DD>; multiple same-day runs accumulate commits on the same PR - Auto-merge: enabled only on the JST 19:xx run via
gh pr merge --auto --squash --delete-branch; the branch is removed after merge
Set DISCORD_WEBHOOK_URL as a repository secret to send failure notifications to a Discord channel from the three automated workflows. The workflows continue to work normally when the secret is absent.
Setup:
- In Discord open the target channel →
Edit channel→Integrations→Webhooks→New webhook, then copy the URL. - Open the GitHub repository →
Settings→Secrets and variables→Actions→New repository secret. - Name:
DISCORD_WEBHOOK_URL, Value: the webhook URL from step 1 →Add secret.
See scripts/ci/notify_discord.sh for the embedded payload structure.
- test runner: Vitest
- component testing: Testing Library
- workflow:
.github/workflows/test.yml
Current main coverage:
- routing helpers in
app/lib/torikumi-routes.ts - sorting helpers in
app/lib/sorting.ts - kimarite 82-entry master in
app/lib/kimarite-data.ts - homepage navigation
- homepage news section (Japan Sumo Association + Sumo World News split sections, empty states, see-all links)
- banzuke sorting
- 15-day hub rendering and sorting
- daily torikumi sorting and pending-state rendering
- gyoji/yobidashi lists and profiles, bilingual ranks, official numeric IDs, API paths, dynamic metadata, and sitemap entries
- My Rikishi toggle, list, IME input correction, and URL synchronisation
- Compare Rikishi (exactly two wrestlers) search, URL sync, seven metrics, and head-to-head results from both display perspectives
- Basho analytics (
/analytics/) makuuchi awards, special prizes, and juryo yusho WebMcpProviderfour-tool registration withdocument.modelContext.registerTool/navigator.modelContext.registerTool
GitHub Actions runs the following on pull requests and pushes to main, codex/**, and automation/data-updates:
npm cinpm run typechecknpm testnpm run build
- Production URL:
https://osada.us - SPA fallback file:
public/_redirects(app routes only;/api/v1/*serves static JSON as-is) - Direct access to date-based URLs falls back to
index.html
- The current APIs have switched to September; keep the July TypeScript snapshots immutable as archives.
- Scheduled triggers activate only after this workflow change reaches the default
mainbranch. Runs may be delayed or skipped, and the shared concurrency lock does not cover an outstanding Daily PR merge; there is no guaranteed ten-minute publication SLA. - After the official September 12 torikumi publication, run
gh workflow run daily-data-update.yml -R dai/o-sumo --ref main, watch the run, then inspect PR checks and merge, the JSON onmain, and production JSON. Empty data for both divisions before publication is a no-op, not successful publication. - After the September 27 final results and next-day verification, remove both cron schedules in a separate PR. Summaries are point-in-time snapshots, not merge or deployment proof.
preflight:current-datais a transition-only safety gate that requires paused workflows, not an in-tournament readiness check. The validator permits only the precise fusen exception and generation preserves old data on partial fetch failure.- The news workflow remains scheduled every two hours from JST 09:05 through 19:05.
- Manual runs can be started with
gh workflow run daily-data-update.yml -R dai/o-sumo --ref main,gh workflow run realtime-torikumi-direct-update.yml -R dai/o-sumo --ref main, orgh workflow run news-feed-update.yml -R dai/o-sumo --ref main. - The realtime workflow uses
--torikumi-only --torikumi-scope result --skip-rikishi-fetch, so it is limited to torikumi results. - If results still look stale, triage in this order: run history -> run logs (
event.schedule, JST time, updatedAt fields) -> upstreamjudgevalues. - Keep the cache policy in
public/_headersunchanged to control Cloudflare usage. - Keep the PWA Service Worker on
registerType: "autoUpdate"so updates are applied automatically.
app/main.tsx: route definitionsapp/page.tsx: homepageapp/archives/page.tsx: archives pageapp/banzuke/page.tsx: banzuke pageapp/kimarite/page.tsx: all 82 kimarite listing pageapp/officials/page.tsx: gyoji and yobidashi lists and profilesapp/torikumi/page.tsx: monthly hubs for results and schedulesapp/analytics/page.tsx: basho analytics pageapp/rikishi/MyRikishiPage.tsx: My Rikishi pageapp/rikishi/CompareRikishiPage.tsx: Compare Rikishi pageapp/components/TorikumiDayPage.tsx: daily result and schedule pagesapp/components/BanzukeTable.tsx: banzuke table componentapp/components/MyRikishiToggle.tsx: My Rikishi toggleapp/components/ShareCurrentLink.tsx: copy current URL to clipboardapp/components/NewsSection.tsx: homepage news section (Japan Sumo Association + Sumo World News split sections)app/components/GreetingSection.tsx: homepage "From the Editor" callout (h3, usesgetLatestBlogPost(), nested insideMonomosuSection)app/components/MonomosuSection.tsx: standalone "Have Your Say" section on homepage (visible h2 + zabuton reaction + textarea drawer + nestedGreetingSection)app/components/KimariteCard.tsx: homepage kimarite entry cardapp/components/WebMcpProvider.tsx: WebMCP tool registration (prefersdocument.modelContext.registerTool)app/components/PrimaryNavigation.tsx: primary navigationapp/lib/archives-data.ts: past basho datasetapp/lib/torikumi-routes.ts: month-key URL resolution and navigationapp/lib/sumo-data.ts: banzuke data (includes rikishi type definitions)app/lib/torikumi-data.ts: torikumi archive dataapp/lib/news-data.ts: static news feed dataapp/lib/kimarite-data.ts: master list of all 82 winning techniquesapp/lib/official-profile.ts: gyoji/yobidashi types, API fetching, and numeric-ID pathsapp/lib/my-rikishi.ts: My Rikishi registration state (localStorage sync)app/lib/basho-status.ts: basho status (live / upcoming / final) classificationapp/lib/webmcp.ts: WebMCP tool definitions (4 tools)app/lib/july2026-data.ts: immutable July 2026 (Nagoya) basho snapshotapp/lib/july2026-banzuke-data.ts: immutable July 2026 banzuke snapshotapp/lib/archive-basho-data.ts: aggregated past + current basho dataapp/lib/blog-build.ts: static HTML, RSS, and sitemap generation for Storiesapp/lib/agent-skills.ts: Agent Skills Index metadatascripts/update_sumo_data.py: data generation script for banzuke, torikumi, and rikishi profilesscripts/update_news_feed.py: news feed generation scriptscripts/update_official_profiles.py: gyoji and yobidashi data generatorscripts/build_blog.ts: Stories output andblog.jsongenerator
MIT