Someone asked, after the NE Open rating-changes writeup: how did you check where players were with respect to other players in the country? Short answer: the AGA publishes the whole national rating list, so it is a sort. This page is the long answer, with the code.
The TDList is the AGA's list of every rated player - the same file OpenGotha
downloads with its "Update AGA rating list" button. About 17,000 rows, ~1 MB, free and public.
Three variants: A (tab-separated, chapter short code), B (chapter full
name), N (fixed width). Everything here uses A.
The AGA's stable handle is https://www.usgo.org/TDListA. Two things will trip up a script:
curl -L follows nothing and hands back a
21 KB HTML page. The real endpoint is in a JavaScript redirect inside that page
(window.location.href='https://aga-membership-functions-…/api/GenerateTDListA').
Resolve it at run time - that Azure hostname carries a random suffix and has already rotated once,
which silently froze a lot of downstream copies for six months in 2026.urllib/curl User-Agent gets a 403. Send a
browser one.The list is a snapshot: it changes whenever the AGA runs ratings. So take one the night before your tournament and another after it is rated, keep both, and every "what changed" question answers itself later. The rating-changes page is exactly that diff, joined on AGA ID.
Nine tab-separated columns, no header row:
| # | Column | Example |
|---|---|---|
| 1 | name (Last, First) | Chiu, Jeremy |
| 2 | AGA id | 17531 |
| 3 | membership type | Adult Full - Lifetime |
| 4 | rating | 7.98574 |
| 5 | membership expires | 5/14/2067 |
| 6 | chapter code | PENG |
| 7 | state | CA |
| 8 | sigma (rating uncertainty) | 0.16805 |
| 9 | date of last rated game | 9/5/2026 |
Columns 4 and 9 are the two that matter here: the rating answers "where", and the last-game date
answers "compared to whom". Column 9 carries the start date of the last event the player was
rated for - every NE Open player reads 9/5/2026, the date on the results file.
A blank rating means the player joined but has never played a rated game - 1,736 of the 17,159 rows in the September 7 list. Those are not players in this sense; drop them and you are left with 15,423 rated players.
The AGA scale is continuous, and it skips the open interval (-1, 1) - there is no zero. Positive is dan, negative is kyu, and the rank truncates toward zero:
| Rating | Rank | Note |
|---|---|---|
| 7.98574 | 7d | not 8d - truncation is the rule |
| 4.98811 | 4d | 4.99 is emphatically a 4d |
| -3.99209 | 3k | a 3k by eight thousandths |
One consequence worth knowing if you subtract two ratings: a 1k → 1d promotion is a raw change of about +2 for what is really a bit of movement across one boundary. To compare differences, shift dan ratings down by 2 so ranks index continuously (2k = -2, 1k = -1, 1d = 0, 2d = 1). For the position question on this page it does not matter - sorting inside one band never crosses the gap.
Filter the list to one rank, sort by rating, find the player. That is the entire method - the only real decision is who is in the pool (next section). Two results from the NE Open, against everyone who has played a rated game in the last five years:
$ python3 aga-rank.py tdlist.tsv "Steinberg, Micah" Steinberg, Micah (30588) -3.99209 3k [pool: played in the last 5 years, 3449 players] #2 of 149 3k players from the bottom (#148 from the top) stronger than 1944 of 3449 (56.4%) $ python3 aga-rank.py tdlist.tsv 17531 Chiu, Jeremy (17531) 7.98574 7d [pool: played in the last 5 years, 3449 players] #52 of 52 7d players from the bottom (#1 from the top) stronger than 3416 of 3449 (99.0%)
Micah Steinberg was promoted by 0.06 of a point and is the second-weakest active 3k in the country - which is what a promotion looks like the day it happens. Jeremy Chiu crossed the same kind of boundary going the other way, on a change of -0.046, and came out the strongest active 7d in the country.
"The country" sounds like a fixed number and is not. The list holds 15,423 rated players - everyone the AGA has ever rated. 6,967 of them last played before 2010, and 2,399 before 2000. Ranking someone against all of them compares them to a mostly dormant population, and it inflates every denominator.
Column 9 settles it without much guesswork: played a rated game in the last five years. That is 3,449 players, and it is the default here.
| Player | Band | Played <5 yrs (3,449) |
Ever rated (15,423) |
|---|---|---|---|
| Micah Steinberg | 3k | #2 weakest of 149 | #3 weakest of 562 |
| Jeremy Chiu | 7d | strongest of 52 | #3 of 260 |
Both are true; they answer different questions, and the shape of the answer survives either. Say which pool you used - that is the part that matters. The five-year pool is the one used in the rating-changes writeup, because "where does this player stand" means among people you could actually meet at a tournament.
Membership expiry (column 5) would work as a proxy - current or lapsed within five years is 4,515 players - but there is no reason to reach for it. It is strictly looser: every one of the 3,449 who played recently is also a recent member, and the extra 1,067 are people who pay dues without playing. The activity date is right there in column 9; use it.
aga-rank.py - Python 3.9+, standard library only, nothing to install. It downloads the list and answers the question:
python3 aga-rank.py tdlist.tsv --fetch # download today's list python3 aga-rank.py tdlist.tsv "Steinberg" # by name (substring; prints every match) python3 aga-rank.py tdlist.tsv 17531 # by AGA id python3 aga-rank.py tdlist.tsv 17531 --years 10 # widen the activity window python3 aga-rank.py tdlist.tsv 17531 --pool all # everyone the AGA has ever rated
The script, and the rest of the tournament toolkit it came from, now live in gotrevor/baduk-tools - publishers that turn a desktop OpenGotha into the live pairings and cross-tab pages this tournament ran on, plus the AGA rating-list tools. Apache 2.0; patches from other TDs welcome.
git clone https://github.com/gotrevor/baduk-tools python3 baduk-tools/bin/aga-rank.py tdlist.tsv --fetch