canonical mapping

CANONICAL_SCHEMA.md in ~/Projects/Stats APIs is the wire contract. LeetCode implements it in models/canonical/ with PLATFORM = "leetcode" and CATEGORY = "dsa".

Mapper comment in canonical_mapper.py: legacy routes call the pure *_from converters on the object they already fetched, so they do not GraphQL twice. build_* fetchers are for clients that only want one section.

Converter Input Empty behavior
profile_from profile response Profile(username=...), countryFlag=None, verified=False
stats_from stats + topics zeros, topicAnalysis may still have bars
contests_from contest ranking empty Contests(), history attended only, rating 0 becomes None
rating_from same contests points with a non-null rating
heatmap_from heatmap response empty Heatmap()
badges_from badges empty list, level always None
summary_from a full Card rollup totals

byDifficulty on LeetCode is easy / medium / hard. Schema keys school, basic, fundamental stay 0 so GFG can fill them without a second shape.

build_card fetches contests once, then profile, stats plus skillStats, rating from those contests, heatmap, badges. The summary route first calls get_user_stats as an existence check, then build_card, so stats GraphQL runs twice there. Wasteful, leftover, still the path.

Envelope:

json
{
  "status": "success",
  "message": "retrieved",
  "platform": "leetcode",
  "username": "khan-tashif",
  "cached": false,
  "data": {}
}

cached on the JSON is set by the mapper, usually false. Redis hits are visible as X-Cache: HIT on the HTTP response, not as that field flipping. Do not trust data.cached as a cache probe.