dota-data
Data engineering guidance for Dota AI Coach — sourcing match data from OpenDota/STRATZ, identifiers (Steam IDs, match IDs, patch IDs, rank tiers), hero/item reference data, match timelines and replays
SKILL.md.claude/skills/dota-data/SKILL.md
GitHubDota AI Coach Data Engineering
This skill governs how historical Dota 2 match data gets from external APIs into a trustworthy training dataset. The core risk this guards against isn't "the code doesn't run" — it's silently wrong data that still produces a plausible-looking, trained model: Turbo games diluting a ranked-meta model, a leaked test match inflating an eval score, duplicate matches skewing item/hero frequencies. These bugs don't crash anything; they just make the model wrong in ways that are hard to notice later.
Ground truth warning
Neither OpenDota nor STRATZ publish a fully stable, exhaustively-documented
contract, and their exact field semantics (rate limits, rank_tier
encoding, STRATZ-specific fields) shift and are inconsistently documented.
Treat field names and numeric codes in references/apis-and-identifiers.md
the same way dota-gsi treats GSI fields: verify against a live API
response or odota/dotaconstants before depending on an exact value in
code, especially rate limits and anything STRATZ-specific.
Identifiers
Getting identifiers wrong silently corrupts joins across data sources.
See references/apis-and-identifiers.md for full detail; short version:
- Steam IDs — OpenDota's
account_idis Steam32, not Steam64. Convert consistently (steam64 = steam32 + 76561197960265728) and pick one canonical form to store; don't let both forms coexist unlabeled in the same column. - Match IDs — globally unique per match across OpenDota/STRATZ/Steam; the natural join key across all data sources for a given match.
- Patch IDs — a match's patch is either present as a direct field or
must be derived from
start_timeagainst a patch-date lookup table. Every stored match-level record must carry patch, not just start_time, even when patch could theoretically be re-derived later — see the provenance rule below. - Rank tiers — encode medal + star (and a separate leaderboard rank
for top Immortals); confirm exact digit encoding against current
dotaconstantsbefore hardcoding a mapping.
Data sources covered
- OpenDota — primary match/player data source; free tier with
optional API key, higher limits with a key. Parsed vs. unparsed matches
expose different data (see
references/apis-and-identifiers.md) — timelines/positions require an explicit parse request. - STRATZ — GraphQL API, generally used for richer parsed/historical data where OpenDota is insufficient. Treat STRATZ-specific claims as lower-confidence until verified against current STRATZ docs.
- Replays — raw
.demfiles are the ultimate source of truth but expensive to fetch/parse at scale; most pipeline work should run off already-parsed API data, falling back to replay parsing only when a needed field genuinely isn't available otherwise. - Heroes/items reference data — static, patch-versioned reference
data (hero/item mechanics) — this is what
dota-domain's data model calls "static reference data"; keep it patch-tagged and don't silently reuse a stale copy across patches that changed hero/item balance.
API caching
Never call OpenDota/STRATZ live from a training run or from
dota-architecture's Data Platform being invoked repeatedly for the same
match. Cache raw API responses (see the RAW stage below) keyed by match
ID/account ID/endpoint, and treat the cache — not the live API — as the
input to everything downstream. This protects rate limits, makes pipeline
runs reproducible (the API's live state can change; a cached RAW snapshot
doesn't), and keeps dota-architecture's "UI must not call OpenDota
directly" rule easy to honor, since only the caching layer talks to the
network at all.
The pipeline: RAW → BRONZE → NORMALIZED → FEATURES → DATASET
Full detail in references/pipeline-stages.md. Summary:
- RAW — unmodified API responses / replay data, exactly as received, cached with source/timestamp metadata. Never mutated after capture.
- BRONZE — RAW parsed into typed records (still close to source shape), with obvious garbage rejected (malformed responses, private- profile placeholders) but no business rules applied yet.
- NORMALIZED — BRONZE reshaped into this project's canonical schema:
consistent IDs, consistent units, patch/timestamp attached to every
record, duplicates resolved. This is the layer
dota-domain's data model concepts (Hero, Item, Position, DamageType) should align with. Deduplication and the "attach patch and timestamp" rule apply here — seereferences/data-quality-rules.md. - FEATURES — NORMALIZED data transformed into model-ready features
(equivalent in spirit to
dota-architecture's Feature Engine, but for offline/training data rather than liveMatchState). Turbo/Ranked separation and abandoned-game exclusion are applied here or earlier — never later, since a downstream consumer shouldn't have to re-derive "was this game valid to use." - DATASET — the final train/validation/test split, with the temporal
split and leakage rules from
references/data-quality-rules.mdenforced and the split itself version-tracked as part of provenance.
Storage: DuckDB and Parquet
- Parquet for BRONZE/NORMALIZED/FEATURES at-rest storage — columnar, compressed, easy to version by writing new files rather than mutating existing ones (append-only partitions, e.g. by date or patch, rather than in-place updates).
- DuckDB as the query/transform engine across pipeline stages —
reads Parquet directly, good for the kind of analytical joins/
aggregations this pipeline needs (joining match/player/hero data,
computing per-patch aggregates) without standing up a server database,
consistent with this being a local-first project (see
dota-architecture). - Keep RAW as whatever format the source naturally provides (JSON from
APIs,
.demfor replays) rather than forcing it into Parquet — RAW's job is fidelity to the source, not query performance.
Data quality rules
The eight rules from the requesting brief, with reasoning, live in
references/data-quality-rules.md — read it before building or modifying
any FEATURES/DATASET-stage logic. Summary list:
- Never mix Turbo with normal Ranked training data by default.
- Exclude abandoned/incomplete games when inappropriate for the task.
- Always attach patch and timestamp to every stored record.
- Avoid duplicate matches across sources/re-fetches.
- Track data provenance (source, fetch time, pipeline stage/version).
- Handle private/missing profiles explicitly, not silently.
- Prefer temporal dataset splits over random splits.
- Never cause train/test leakage.
Data quality checklists
references/data-quality-checklists.md has pre-ingestion, pre-training,
and pre-release checklists — run the relevant one before trusting a new
data source, before kicking off a training run, or before shipping a
dataset/model built from this pipeline.