Concepts
Iodid domain glossary — puuid, riotId, shard, filter, MMR, staleness, ApiResult states.
Operational glossary for reading APIs, models and caches without ambiguity.
Identity
puuid
Riot global identifier, stable and unique per account. Primary key of Summoner (src/main/java/com/safjnest/lol/model/summoner/Summoner.java:21 @JsonProperty("puuid")). Used in all profile routes: GET /api/lol/{shard}/profile/{puuid} (src/main/java/com/safjnest/spring/controller/LolController.java:63). It contains no region information; region is always an explicit shard parameter.
riotId — String, not a record
riotId is a String in the format gameName#tagLine (src/main/java/com/safjnest/lol/model/summoner/Summoner.java:23 private String riotId). No dedicated RiotId type exists by architectural choice (AGENTS.md: "riotId remains a String"). Split via name()/tag() which do split("#",2) (src/main/java/com/safjnest/lol/model/summoner/Summoner.java:171).
Routes that accept Riot ID: GET /profile-by-name/{gameName}/{tagLine} (src/main/java/com/safjnest/spring/controller/LolController.java:226), GET /livegame-by-name/{gameName}/{tagLine} (:253).
Shard vs RegionShard
-
LeagueShard (
no.stelar7.api.r4j.basic.constants.api.regions.LeagueShard) — LoL platform shard:EUW1,NA1,KR, etc. It is the{shard}parameter of every controller (LolController.java:36). Active list inLeagueShardUtils.getActives(src/main/java/com/safjnest/lol/utils/LeagueShardUtils.java:64). -
RegionShard (
RegionShard) — geographic grouping for Account/Match V5 APIs:EUROPE,AMERICAS,ASIA,SEA. Derived viaLeagueShardUtils.getAccountRegion(src/main/java/com/safjnest/lol/utils/LeagueShardUtils.java:45) which mapsVN2, OC1, SG2, PH2, TH2, TW2 -> ASIA, otherwiseshard.toRegionShard().
Cache region is LeagueShardUtils.cacheRegion (src/main/java/com/safjnest/lol/utils/LeagueShardUtils.java:52) which returns shard.toRegionShard().name() — used as the first segment of Redis keys (ProfileService.java:341).
Filter — base64 key
Filter (src/main/java/com/safjnest/lol/model/Filter.java:13) encapsulates 10 dimensions:
champion, lane, queue, rank, rankBehavior, patch, region, opponent, duo, timeStart, timeEnd
canonical()(src/main/java/com/safjnest/lol/model/Filter.java:44) — current-season filter, zeroes everything except the periodSeasonRange(SeasonUtils.getCurrentSeasonRangesrc/main/java/com/safjnest/lol/utils/SeasonUtils.java:41).summoner(tStart, tEnd)(src/main/java/com/safjnest/lol/model/Filter.java:31) — filter for profile activity, explicit period.
Serialization:
toSummonerKey()(src/main/java/com/safjnest/lol/model/Filter.java:283) —|join +FilterCodec.encodeUrl(URL-safe Base64 without padding,src/main/java/com/safjnest/lol/utils/FilterCodec.java:15). 11-part format:champion|lane|queue|rank|rankBehavior|patch|region|opponent|duo|timeStart|timeEnd. Reverse decodefromSummonerKey(Filter.java:105) with validationparts.length == 11.toKey()/pageKey()/toStateKey()/genericKey()— variants with standardBase64(FilterCodec.encode:10) or URL, used for champion stats and pagination.
Filter keys are Redis keys for SUMMONER_STATISTICS, SUMMONER_ACTIVITY, SUMMONER_MATCHUPS (ProfileService.java:341).
Queue canonicalization
GameQueueTypeUtils.canonicalQueue (src/main/java/com/safjnest/lol/utils/GameQueueTypeUtils.java:38):
queue == TEAM_BUILDER_RANKED_SOLO ? RANKED_SOLO_5X5 : queue
TEAM_BUILDER_RANKED_SOLO is Riot's historic ID for Solo/Duo; it is always normalized to RANKED_SOLO_5X5 before persistence, indexing and queries. isRankedSolo (:48), defaultQueue (:44) and rankHistoryQueue (LolApiParameters.java:61) apply the same rule. Queues with leaderboards are only RANKED_SOLO_5X5 and RANKED_FLEX_SR (:25).
Patch — patchMajor
PatchUtils.getPatch() (src/main/java/com/safjnest/lol/utils/PatchUtils.java:20) returns the most recent patch from https://ddragon.leagueoflegends.com/api/versions.json (:84). patchMajor(patch) (:53) extracts major.minor (truncates the third bugfix segment). Patch is persisted both as the full string and as patchMajor for indexed queries (src/main/java/com/safjnest/lol/model/match/Match.java:74).
Filter.patch is validated as \d+\.\d+ in LolApiParameters.patch (src/main/java/com/safjnest/spring/controller/LolApiParameters.java:103). Champion statistical matrices cover the last 3 patches (ComputeScheduler.startChampionStatistics src/main/java/com/safjnest/lol/queue/scheduler/ComputeScheduler.java:132).
Season
SeasonUtils (src/main/java/com/safjnest/lol/utils/SeasonUtils.java:13) reads rsc/testing/lol_testing/split.json (:15). Structure: seasons[].splits[] with start_date/end_date (yyyy-MM-dd HH:mm:ss).
getCurrentSplitRange()(:22) — current split (now between start/end).getCurrentSeasonRange()(:41) — aggregated current season (min start, max end of splits).getSeasonRange(year)(:66) — lookup by year (year = 2010 + seasonNumber).getSeasonRanges()(:143) — complete list.
Filter.canonical() uses getCurrentSeasonRange for timeStart/timeEnd. RankHistoryQuery validates season and patch against SeasonUtils (LolApiParameters.java:93).
MMR vs LP
- LP — League Points of a single tier/division, value 0..100 (typically) provided by Riot.
- MMR — continuous score computed by
TierDivisionUtils.getMmr(src/main/java/com/safjnest/lol/utils/TierDivisionUtils.java:78): fixed base forTierDivisionType(IRON_IV=0, +100 per division, MASTER_I=10000, GRANDMASTER=20000, CHALLENGER=30000) +lp.UNRANKEDreturns-1.
getMmrRange(TierType) (src/main/java/com/safjnest/lol/utils/TierDivisionUtils.java:119) defines half-open intervals: IRON 0..400, BRONZE 400..800, ..., CHALLENGER 30000..∞. getTierFromMmr (:136) inverts the mapping. getAverageRank (:49) averages TierDivisionType ordinals.
MMR is used for leaderboard ordering and distribution; LP remains visible only as a Rank detail.
Staleness — 30 days + jitter
ProfileService.isStale (src/main/java/com/safjnest/lol/service/ProfileService.java:356):
STALE_BASE_MILLIS = 30 * DAY
STALE_JITTER_DAYS = 14
STALE_LAST_SEEN_MILLIS = 60 * DAY
jitter = floorMod(puuid.hashCode(), 15) * DAY // 0..14 days
stale = now - lastUpdate >= 30d + jitter
Additional conditions:
- If
lastSeenAtmissing ornow - lastSeenAt > 60d→ not stale (dormant profile, avoids useless refreshes). - If
lastUpdate <=0→ stale. - If
lastSeenAtnull/blank → stale.
getStatistics(summoner, shard, filter) (ProfileService.java:101) starts ComputeScheduler.startProfileStatistics if missing, startStaleProfileStatistics (priority BACKGROUND) if stale. Same logic for getActivity (:146) and getMatchups (:168). Jitter spreads refresh load over time and avoids thundering herd at exactly 30 days.
Pending vs Ready — ApiResult
ApiResult<T> (src/main/java/com/safjnest/lol/model/ApiResult.java:3) — uniform wrapper for async responses:
enum Status { READY, PARTIAL, PENDING, NOT_FOUND }
| Status | Meaning | HTTP | Typical use |
|---|---|---|---|
READY | complete and fresh payload | 200 | profile with current statistics, leaderboard |
PARTIAL | payload present but stale/incomplete | 200 | ProfileService.get returns ApiResult.partial when isStale (ProfileService.java:58) — client shows data but knows a refresh is in progress |
PENDING | no payload, work queued | 202 + LolApiError | profile_pending, match_pending, profile_activity_pending — scheduled via ComputeScheduler/SyncScheduler |
NOT_FOUND | entity does not exist | 404 | unknown puuid, invalid match id |
LolApiResponses.from (src/main/java/com/safjnest/spring/controller/LolApiResponses.java:14) does the switch. PARTIAL and READY share the same 200 body but the client can distinguish via ResponseMetadata.refresh (ProfileService.java:58): ApiResult.partial(page, metadata) with refresh=true vs ApiResult.ready(page, metadata).
ResponseMetadata (src/main/java/com/safjnest/lol/model/ResponseMetadata.java:3) carries lastUpdate, refresh, pagination, filter — always present when data comes from cached/Mongo computation, null on NOT_FOUND/PENDING without context.
