Backend
My leaderboard site updates itself while I sleep — pure-stdlib Python, GitHub Actions, zero servers
Justinnnnnnn045 Dev.to (EN Zone)
1 views
My side project, PD.Radar, ranks resources mentioned in big Ask HN threads by citations (distinct commenters naming the thing), not by upvotes. Upvotes measure drama; citations measure how many people independently bothered to name the same resource.
The site went live with one leaderboard: 540 comments from "What is the best money you have spent on professional development?", 24 resources ranked. Therapy won with 51 citations. Not a course, not a book.
Then came the problem every static site has: the data was frozen on the day I built it. The fix wasn't updating it by hand every morning — that's a hobby for people with more patience than me. The fix was making the site mine its own new content.
The engine: two jobs, one file, zero dependencies
auto_update.py is ~300 lines of pure stdlib Python. It runs daily on GitHub Actions and does exactly two things:
Re-mine the flagship thread so its numbers stay fresh (and only rewrites the JSON if something actually changed)
Discover new Ask HN question threads, mine them with the same method, and publish the ones that qualify as new leaderboards
The site reads a single data/threads.json index. New file in, new tab on the site. No manual commits, no code changes, no server.
MIN_POINTS = 80
MIN_COMMENTS = 80
MIN_RESOURCES = 5 # a new thread needs >=5 resources matched
MIN_CITES = 3 # ...each with >=3 citations, to publish
MAX_NEW_PER_RUN = 3 # gentle on the Algolia API; quality over volume
SKIP_DAYS = 45 # days to remember a rejected thread before re-checking
The hard part was the honesty gate, not the mining
Mining is easy — HN has the excellent Algolia API, and fetching every comment of a thread is a loop. The hard part: how does a script decide a thread is worth publishing without me looking at it?
My first fear was false positives. A rant like "Is all of FAANG like this?" has hundreds of comments mentioning Python, Go, books, courses — the lexicon would happily produce a garbage leaderboard out of pure noise. Publishing that would poison the whole premise, because the site's entire promise is verifiable rankings.
So the gate is two lists:
INTENT_KEYWORDS = ["best", "book", "books", "recommend", "course", "learn",
"resource", "resources", "worth", "advice", "skill",
"skills", "career", "study"]
RANT_PATTERNS = ["like this", "rant", "vent", "just me", "wtf", "nuke",
"stopped", "banned", "shut down", "died", "layoff"]
def title_has_intent(title):
t = title.lower()
if any(p in t for p in RANT_PATTERNS):
return False
return any(k in t for k in INTENT_KEYWORDS)
A thread is only mined if its title asks for recommendations, and a discovered thread only gets published if the lexicon genuinely matches it: at least 5 distinct resources, each with at least 3 separate citations. Weak matches never see the light of day. And everything that gets rejected is remembered for 45 days, so the same thread doesn't get re-evaluated every morning.
It also runs the same citation matcher that powers the flagship (extract_rank.py), on purpose:
# Only import the lexicon + matchers from the existing pipeline so the
# ranking method stays IDENTICAL across threads.
from extract_rank import LEXICON, matches, flatten_comments, clean, load_flat
One method, every thread. If I ever improve the matcher, every leaderboard improves with it — and stays comparable.
What actually happened on the first runs
Discovery queries like "best books", "worth paying for", "best investment in your career" pull candidate threads from Algolia search. Each pass examined dozens of candidates. The first real run:
Thread
Points
Comments
Resources ranked
Best money spent on professional development (flagship)
524
540
24
Best book / resources on leadership for tech teams?
882
209
14
Best books you read in the past decade?
873
417
13
Advice for finding an entry-level remote job?
740
199
8
Best computer science book you've read recently?
670
186
7
1,551 comments, 66 ranked resources — none of it hand-mined. The flagship is pinned as the site's default tab; the rest sort by points.
And the rejections are as satisfying as the additions. The run that found the leadership thread also looked at a FAANG rant, a Stripe outage story, and a YC blog post — and correctly declined all three. Watching a script make a defensible editorial call with nobody watching is genuinely the most fun I've had with a side project this year.
Why GitHub Actions and not a server
Because the whole thing is a static site, the engine's output is just files. So the deploy pipeline is embarrassingly simple:
Cron fires at 06:17 UTC on a free GitHub Actions runner
The script fetches from Algolia, mines, decides
If anything changed: commit + push (as a bot user)
GitHub Pages redeploys automatically
Total monthly cost: $0. Total infrastructure: a YAML file that's 20 lines long. The commit history doubles as a changelog of every autonomous editorial decision the engine has made.
Honest limits
The lexicon is hand-built. It knows ~90 resources across books, courses, tools. A great comment recommending something it doesn't know is invisible to the ranking. Growing the lexicon is ongoing, boring, necessary work.
Discovery depends on Algolia search. If a qualifying thread never surfaces in my queries, it never gets mined. The 7 discovery queries cast a decent net, but it's a net, not a census.
Citations ≠ quality. The ranking proves what a community mentioned, not what changed anyone's life. The #1 professional-development answer being "therapy" (51 citations) is probably the most honest thing on the whole site precisely because no marketing budget could have produced it.
Try it
The site: PD.Radar — ranked answers from real threads — five leaderboards live, each with the receipts (every resource links back to actual comments in its source thread).
The engine: auto_update.py in the repo — steal the honesty-gate pattern if you're building anything that publishes without supervision. The gate is the product.
If you've got an Ask HN thread you'd love to see ranked this way, drop the link in the comments — the engine takes requests now.
Read original: https://dev.to/justinnnnnnn045/my-leaderboard-site-updates-itself-while-i-sleep-pure-stdlib-python-github-actions-zero-servers-3gcd
← Previous
I am 18 should i continue
Next →
Neural Networks: Weights, Activation, and Backpropagation
Related
Somebody asked for help. Nobody came.
Backend
0
Dev.to (EN Zone)
Putting Authelia in front of my whole homelab: what worked, what didn't, and the £0 lesson that nearly undid all of it
Backend
0
DEV Community
AI Solutions for Managing Large Codebases: 2026 Guide
Backend
3
DEV Community
Legacy System Modernization Without the Rewrite
Backend
4
DEV Community
Comments0
No comments yet — be the first