Search any collegiate ballroom dancer by name or school and see how they've placed and how many callbacks they've earned across competitions run on DSCO.
Live site: https://akumgill.github.io/callback-tracker/
For each dancer it shows:
- placement over time for each style, with one line per level (Bronze, Silver, Gold, ...)
- a grid of every dance at every competition: placement, and the share of judges' marks received in each round
- a table per competition with partner, place, marks per round and per dance, and a link to the DSCO results page
- YCN proficiency points by style and level, and the lowest level the dancer can still enter in each dance
- a rating per style (prototype), with rank among active dancers at the same level, the partnership's score, and each event's expected place and performance
Each school also has a team page: results by competition, the team's placement trend by level, and its roster.
dsco_crawl.py reads each DSCO competition's public results (the same data DSCO's results pages show: events, judges' marks and saved results) and writes a compact index to site/data/. It uses only the Python standard library.
A GitHub Action runs every Monday morning, after weekend competitions; run it any time from the repo's Actions tab ("Run workflow") or with gh workflow run update.yml. A competition is re-checked for 30 days after it starts and then left alone, and upcoming competitions only have their dates checked. Ratings are rebuilt, check_data.py stops the run if the new data looks broken (GitHub emails the repo owner), and otherwise new results are committed and the site is redeployed to GitHub Pages.
Placements match DSCO's results pages: finals use DSCO's stored skating-system results, and couples eliminated earlier are ranked by recalls in the round where they went out. The field size is the number of couples in the first round.
Placement score is the share of the other couples a dancer finished ahead of: (couples − place) ÷ (couples − 1). 1st place is 100% and last is 0%.
ratings.py runs after each crawl (it needs numpy).
- Performance per event. Each couple gets the rating at which the Elo formula would expect them to beat exactly the share of the field they beat, given the field's ratings going in. Beating a strong field is a higher performance than beating a weak one.
- Rating = fading average of performances. A dancer's rating in a style averages their performances, each counting half as much after 90 days. Recent results matter most, and partners who dance together get the same performances, so a long-term partnership converges on one rating once older separate results fade. A partnership's score is the average of its two dancers.
- Levels start apart. Couples who dance two adjacent levels at one competition place about 20–35 placement-score points lower in the higher one, so a dancer's first rating in a style is their level's base: Newcomer 1,100, Bronze 1,500, Silver 1,900, Gold 2,300 (worth 0.1 of an event, so it fades quickly).
- Ranks count dancers active in the past year with at least 3 events in the style, grouped by the level of their latest event.
Settings were chosen on 2025 events and tested on 2026 (python ratings.py --backtest; python benchmark.py compares it with other schemes): the rating predicts finishing order with a rank correlation of 0.608, against 0.513 for each dancer's average placement score so far, and picks 74.4% of finalists (67.5% for the baseline). A typical established dancer's rating moves about 70 points per competition. It only sees DSCO competitions, and it measures how judges ranked couples against each other, not absolute skill.
python dsco_crawl.py # new competitions, plus re-checks of the last 30 days
python dsco_crawl.py --all # re-fetch everything
python -m http.server -d site 8000 # then open http://localhost:8000- This project isn't affiliated with DSCO. Competitions scored elsewhere (O2CM, for example) aren't included.
- Spellings that differ only by accents, capitalization or apostrophes count as one dancer (Faria and Faría, for example). A dancer stays one person across schools, since transfers are common and DSCO records one school per couple. For anything else,
names.txtmerges other spellings or splits two dancers who share a name. - To fix a result, merge or split a dancer, or be left out, use Request a correction or removal on any dancer or team page (it opens a GitHub issue form).
opt_out.txtandnames.txthold the fixes.
Callback Tracker reads only DSCO's public results data (the same data its results pages show), once a day, and re-checks a competition for 30 days after it starts. Every request identifies itself as callback-tracker/1.0 (+https://github.com/akumgill/callback-tracker). It follows robots.txt rules for User-agent: callback-tracker on every host it reads, never signs in, and never reads registration records. Each competition links back to its original results.
To stop it, change how your results are used, or have something removed, open a request.