Skip to content
KLR.gg (in-house product) · Gaming · Esports analytics

KLR.gg: automated CS2 match analytics for private lobbies

A platform that pulls server logs, match stats and demo recordings from Counter-Strike 2 community servers automatically, scores every duel and round, and turns them into player ratings, trends and balanced teams — no uploads, no spreadsheets.

Duration
4 months to launch, ongoing
Published
26 August 2026
Stack
PythonpandasFastAPIPostgreSQLNext.jsTypeScriptNode.jsMinIO (S3)DockerSteam OpenIDStripe

100%

of match days ingested with no manual upload

4

rating models: 2.0, 3.0, Round Swing, Glicko-2

~2 s

pipeline time per map from server log

4

balanced team splits per lobby

What was the starting point?

The starting point was an argument every group of friends who play Counter-Strike 2 on their own server has every week: who is actually good, and how to split the teams fairly. Public stats sites cover official matchmaking, not private lobbies, so someone keeps a spreadsheet until they stop. The audience is not small: Counter-Strike 2 is consistently among the most played titles on Steam, with concurrent player counts in the hundreds of thousands to over a million (Steam, 2024), and Statista (2024) puts the esports market in the low billions of dollars a year.

KLR is our own product built to settle those arguments with numbers. It started as one Python script for a closed lobby of about 25 players and grew into a multi-tenant web platform. It is also our reference project for event-data engineering: automatic collection, an idempotent parsing pipeline, and ratings people trust enough to argue about.

What problem had to be solved?

The problem was turning raw server output into ratings people trust, without anyone uploading anything. The data is there: a CS2 server with the MatchZy plugin (2024 docs) writes a log line for every kill, a CSV of per-player stats for every map, and a GOTV demo of every match. But raw numbers are not a rating — kills without round impact, missing round counts, players who change nicknames, several Steam accounts per person. A leaderboard that players do not trust is worse than no leaderboard.

The leaderboard

One lobby, one season: HLTV-style Rating 2.0 and 3.0, Round Swing — how much each player's actions moved the round win probability — and a Glicko-2 skill rating that treats every kill as a duel between two players.

KLR season leaderboard with maps, rounds, K/D, ADR, KAST, Rating 2.0, Rating 3.0, Swing, Skill, trend and a 30-day sparkline per player
Season leaderboard: per-player rounds, KAST, Rating 2.0 / 3.0, Round Swing, Glicko-2 Skill and trend.

How was it built?

It was built as a collection worker, a recompute-from-scratch rating pipeline and a multi-tenant web product. The worker pulls logs, stats and demos from each community server; the pipeline parses logs into duels and computes four ratings — HLTV-style Rating 2.0 and 3.0 (HLTV published the 2.0 formula’s components in 2017), Round Swing, and a Glicko-2 skill rating (Glickman’s system with rating deviation and volatility, 2012). Every run recomputes the whole season, so there is no double counting and re-running is safe.

  • Automatic collection. A Python worker connects over FTP/FTPS, detects new files by modification time, pulls them into S3-compatible storage and queues a recompute. Credentials are stored encrypted. Logs (~0.5 MB per map) are the primary source and are kept; demos are the fallback and are deleted after parsing.
  • Rating pipeline. pandas modules parse logs into duels, compute round-by-round KAST, Round Swing with an economy weighting for each kill, Rating 2.0 and 3.0, per-map ratings and Glicko-2. About two seconds per map.
  • Team balancer. For a chosen set of players it proposes four splits — by Rating 2.0, Rating 3.0, Skill and a mix — with the expected gap between teams. The logic is mirrored in TypeScript so the web does it instantly; shared lineups get a public link with an Open Graph preview.
  • Product. Multi-tenant lobbies with roles, Steam OpenID sign-in, statistics snapshots with one-click rollback, notifications, credits paid via Stripe for the render studio (2D round replays), GDPR clickwrap consent with audit, data export and account deletion.
  • Steam GC bot. A Node.js bot fetches official matchmaking demos by share code, so ranked games count in the same rating as private ones.

Spreadsheet vs automated pipeline: what changes for a private lobby?

The table compares the spreadsheet most lobbies start with and the automated pipeline KLR replaced it with. A spreadsheet is free and needs no server access, but it depends on one person, cannot compute round-level metrics from a stats CSV, and silently double-counts when a map is entered twice. The pipeline needs FTP credentials and a worker, and then nobody touches it.

Manual spreadsheet vs KLR pipeline
CriterionSpreadsheetKLR pipeline
Data entryBy hand after each match dayAutomatic from server logs
Round-level metrics (KAST, Round Swing)Not availableComputed from every kill line
Re-entry of the same mapDouble countedIdempotent full-season recompute
Nickname and multi-account changesManual fixesSteam ID mapping
Team balancingCaptains pickFour rating-based splits, 0.6% expected gap
Time per mapMinutesAbout 2 seconds
Manual spreadsheet vs KLR pipeline

How the data flows

From a game server to a leaderboard without a human in the loop: worker, pipeline, API and web are separate services in Docker; the only artifact the web ever sees is one JSON per lobby, served through the API with access checks.

KLR data pipeline diagram: CS2 community server, Python worker, rating pipeline, FastAPI, Next.js web, plus the artifacts that flow through
Data flow: DatHost server → FTP worker → logs, stats, demos → rating pipeline → players.json → API → web.

Results

Ingestion is fully automatic and the pipeline keeps up with match nights: a six-map evening is parsed and rated within a minute of the last round. The balancer closes the gap between teams to under one percent of predicted round-win share.

Maps rated per match day, first season
Day 1Day 1: 44Day 2Day 2: 55Day 3Day 3: 44Day 4Day 4: 66Day 5Day 5: 44Day 6Day 6: 33Day 7Day 7: 55Day 8Day 8: 55
Data table
Maps
Day 14
Day 25
Day 34
Day 46
Day 54
Day 63
Day 75
Day 85
Expected score gap between balanced teams
Captains pickingCaptains picking: 8.4%8.4%RandomRandom: 11.2%11.2%KLR balancer, best splitKLR balancer, best split: 0.6%0.6%

Average over the season; gap is the difference in predicted round-win share between the two teams.

Data table
Gap
Captains picking8.4%
Random11.2%
KLR balancer, best split0.6%

Trends and lineups

Rating over time per match day, switchable between four metrics, next to the team balancer: the two screens that actually get opened before a match night.

KLR trends page with cumulative rating lines per player over eight match days and a team balancer panel with three candidate splits
Trends: cumulative rating per match day; balancer: three of the four candidate splits with the expected gap.

Stack

Python 3 rating pipeline (pandas, matplotlib, awpy for demo parsing); worker on ftplib with zstd and MinIO; FastAPI with SQLAlchemy and Alembic on PostgreSQL 17; Next.js 16 with React 19 and Tailwind 4; Node.js Steam GC bot; Steam OpenID, JWT sessions, Stripe. Everything runs in Docker Compose locally and under systemd behind nginx on a VPS in the EU.

Who is this approach for?

This approach is for any team that needs an event-data platform: automatic collection from many remote sources, a parsing pipeline that must be idempotent and fast, scoring models people can trust, and a multi-tenant web product with roles, billing and compliance on top. Strip away the game and the same architecture serves telematics, IoT fleets, trading logs and clickstream analytics. Compliance is part of the design rather than a bolt-on: because player profiles are personal data, account deletion and export were built in from the first release, as the GDPR right to erasure (Article 17, 2016) requires.

Frequently asked questions