rivalsdata-api 1.2.1__tar.gz → 1.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rivalsdata-api
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: Python client and MCP server for public Marvel Rivals stats from RivalsData
5
5
  Project-URL: Homepage, https://github.com/GS-Rionnag/rivalsdata-api
6
6
  Project-URL: Repository, https://github.com/GS-Rionnag/rivalsdata-api
@@ -64,6 +64,7 @@ print(hero_id("Loki")) # 1016
64
64
  with RivalsDataClient() as rd:
65
65
  player = rd.get_player("GS-") # numeric UID works too
66
66
  print(player.name, player.level, player.rank_game_season)
67
+ print(player.win_rate) # Current-season competitive win rate
67
68
 
68
69
  # Player profile sections are lazy resource managers.
69
70
  hero_season = player.heroes.fetch(season=20)
@@ -85,6 +86,11 @@ with RivalsDataClient() as rd:
85
86
  print(hero_season[0].win_rate) # integer percent when wins/losses are present
86
87
  ```
87
88
 
89
+ The player overview's overall win rate (`player.win_rate`) is the **current-season
90
+ competitive win rate**. It uses the latest available competitive season with
91
+ usable counts when a direct source rate is absent; it does not combine seasons.
92
+ Hero and class stats use the season selector supplied to their own methods.
93
+
88
94
  Calculated player class statistics are available through
89
95
  `player.stats.classes(season=20)` and the MCP `get_player_stats` tool with
90
96
  `category="classes"`. Pass a numeric season ID for that season, or
@@ -110,8 +116,12 @@ the same class response structure.
110
116
 
111
117
  Win rates are `total wins / (total wins + total losses)`, rounded to an integer
112
118
  percent. They are weighted by hero records, rather than averaging hero win
113
- rates. Switching heroes can make one match contribute to multiple records;
114
- these totals describe hero participation, not distinct matches. Empty modes
119
+ rates. The response's `metadata` identifies the source, formula, and requested
120
+ season scope. Upstream hero-switch attribution is unknown; hero records may
121
+ overlap within a match, so these totals cannot establish distinct match counts
122
+ or the player's overall match win rate. All-seasons coverage is limited to
123
+ records returned by the source; complete lifetime coverage is unverified.
124
+ Excluded rows also produce a metadata warning. Empty modes
115
125
  have a `None` win rate. Role mappings were observed on RivalsData on
116
126
  2026-09-30, including Deadpool's separate role IDs; generic Deadpool and unknown
117
127
  IDs are excluded rather than assigned a guessed class.
@@ -28,6 +28,7 @@ print(hero_id("Loki")) # 1016
28
28
  with RivalsDataClient() as rd:
29
29
  player = rd.get_player("GS-") # numeric UID works too
30
30
  print(player.name, player.level, player.rank_game_season)
31
+ print(player.win_rate) # Current-season competitive win rate
31
32
 
32
33
  # Player profile sections are lazy resource managers.
33
34
  hero_season = player.heroes.fetch(season=20)
@@ -49,6 +50,11 @@ with RivalsDataClient() as rd:
49
50
  print(hero_season[0].win_rate) # integer percent when wins/losses are present
50
51
  ```
51
52
 
53
+ The player overview's overall win rate (`player.win_rate`) is the **current-season
54
+ competitive win rate**. It uses the latest available competitive season with
55
+ usable counts when a direct source rate is absent; it does not combine seasons.
56
+ Hero and class stats use the season selector supplied to their own methods.
57
+
52
58
  Calculated player class statistics are available through
53
59
  `player.stats.classes(season=20)` and the MCP `get_player_stats` tool with
54
60
  `category="classes"`. Pass a numeric season ID for that season, or
@@ -74,8 +80,12 @@ the same class response structure.
74
80
 
75
81
  Win rates are `total wins / (total wins + total losses)`, rounded to an integer
76
82
  percent. They are weighted by hero records, rather than averaging hero win
77
- rates. Switching heroes can make one match contribute to multiple records;
78
- these totals describe hero participation, not distinct matches. Empty modes
83
+ rates. The response's `metadata` identifies the source, formula, and requested
84
+ season scope. Upstream hero-switch attribution is unknown; hero records may
85
+ overlap within a match, so these totals cannot establish distinct match counts
86
+ or the player's overall match win rate. All-seasons coverage is limited to
87
+ records returned by the source; complete lifetime coverage is unverified.
88
+ Excluded rows also produce a metadata warning. Empty modes
79
89
  have a `None` win rate. Role mappings were observed on RivalsData on
80
90
  2026-09-30, including Deadpool's separate role IDs; generic Deadpool and unknown
81
91
  IDs are excluded rather than assigned a guessed class.
@@ -48,6 +48,12 @@ integer percentage from a direct win-rate field, `wins`/`losses`, or the
48
48
  competitive profile row's `win_count`/`battle_count`. If the source provides
49
49
  none of these, it returns `None`.
50
50
 
51
+ The player overview's overall win rate (`Player.win_rate`) refers to the current
52
+ competitive season, using the latest available competitive season with usable
53
+ counts when a direct source rate is absent. It does not sum historical seasons.
54
+ MCP overview/dashboard descriptions identify this scope, and the dashboard labels
55
+ the value as a season win rate. Hero and class rates follow their season selectors.
56
+
51
57
  ## Observed UI and routes
52
58
 
53
59
  `player.stats.classes(season=...)` derives tank (Vanguard), support (Strategist),
@@ -59,8 +65,14 @@ omitting the season keeps the endpoint default. `player.stats.heroes` and MCP
59
65
  `get_player_stats` support the same selector. The method also sums
60
66
  MVP/SVP counts when every included row supplies them. Win rate is calculated
61
67
  from summed wins and losses. Unknown classes and incomplete win/loss rows appear
62
- in `excluded`. Hero switching can count a single match in multiple hero records,
63
- so class totals are participation counts rather than distinct matches.
68
+ in `excluded`. The response includes `metadata` with `source`, `counts_basis`,
69
+ `win_rate_formula`, `unique_matches_verified`, `hero_switch_attribution`,
70
+ `season`, `season_scope`, and `warnings`. Numeric seasons have scope `season`,
71
+ `"all"` and `-1` normalize to season/scope `"all"`, and an omitted season has
72
+ scope `endpoint_default` with season `None`. Hero-switch attribution is unknown,
73
+ so summed hero records cannot establish unique match counts or a player's
74
+ overall match win rate. Complete lifetime coverage for all-seasons data is
75
+ unverified. Warnings flag those limitations and any excluded records.
64
76
 
65
77
  The roster at `/stats` on 2026-09-30 identifies Deadpool variants as 10571
66
78
  (tank), 10572 (DPS), and 10573 (support), Daredevil as 1055, and Angela as 1056.
@@ -15,7 +15,7 @@ as public, stable methods. Keep requests respectful and conservative.
15
15
  ## Current package
16
16
 
17
17
  - Distribution: `rivalsdata-api`; import: `rivalsdata`.
18
- - Version: `1.2.1`.
18
+ - Version: `1.2.2`.
19
19
  - Python `>=3.10`, Hatchling build, `src/` layout.
20
20
  - Runtime HTTP dependency: `curl-cffi`; optional browser fallback: Camoufox.
21
21
  - Public entry point: `RivalsDataClient`.
@@ -54,6 +54,12 @@ Favorites requires numeric UIDs and returns player summary rows.
54
54
  Rows offer `.win_rate` and `.winrate` integer-percent access when data supports
55
55
  it; all original data remains in mapping access.
56
56
 
57
+ The player overview's overall win rate (`player.win_rate`) is for the current
58
+ competitive season (latest available season with usable counts when there is no
59
+ direct source rate), not an all-seasons aggregate. Keep this scope explicit in
60
+ docs, MCP descriptions, and dashboard labels. Hero/class stats have separate
61
+ season selectors.
62
+
57
63
  `player.stats.classes(season=...)` groups observed hero IDs into tank, support,
58
64
  and DPS. Supply a numeric season ID for one season or `season="all"` for
59
65
  combined all-seasons stats; the latter sends `season=-1` to the upstream API.
@@ -63,8 +69,12 @@ It sums games, wins, losses, and available MVP/SVP counts separately
63
69
  for competitive and quickplay, with win rates calculated from summed wins and
64
70
  losses. The response has typed `classes` rows and an `excluded` list for unknown
65
71
  roles or incomplete win/loss data. MCP exposes it through `get_player_stats`
66
- with `category="classes"`. Hero switching means totals count participation,
67
- not unique matches. Role IDs and the corrected Angela/Daredevil IDs were
72
+ with `category="classes"`. Response `metadata` describes the source, formula,
73
+ season scope, and limitations. Upstream hero-switch attribution and distinct
74
+ match counts are unverified; do not derive player overall win rate from class
75
+ totals. All-seasons coverage is limited to returned records, with complete
76
+ lifetime coverage unverified. Excluded rows produce a metadata warning.
77
+ Role IDs and the corrected Angela/Daredevil IDs were
68
78
  verified against RivalsData's roster on 2026-09-30; Deadpool has separate role
69
79
  variants (10571/10572/10573), while generic 1057 remains unclassified.
70
80
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rivalsdata-api"
7
- version = "1.2.1"
7
+ version = "1.2.3"
8
8
  description = "Python client and MCP server for public Marvel Rivals stats from RivalsData"
9
9
  keywords = ["marvel-rivals", "rivalsdata", "game-stats", "api-client", "mcp"]
10
10
  readme = "README.md"
@@ -153,4 +153,4 @@ __all__ = [
153
153
  "hero_id",
154
154
  "hero_name",
155
155
  ]
156
- __version__ = "1.2.1"
156
+ __version__ = "1.2.2"
@@ -108,7 +108,11 @@ def search_players(name: str) -> dict[str, Any]:
108
108
 
109
109
  @mcp.tool()
110
110
  def get_player(uid_or_name: str) -> dict[str, Any]:
111
- """Get a public player overview by numeric UID or in-game name."""
111
+ """Get a public player overview by numeric UID or in-game name.
112
+
113
+ The overview's overall win rate refers to the current competitive season
114
+ (latest available season with usable counts), not all seasons combined.
115
+ """
112
116
  return _call(lambda client, value: client.get_player(value).raw, uid_or_name)
113
117
 
114
118
 
@@ -141,6 +145,7 @@ def show_player_dashboard(
141
145
  combining live-match, hero, and match-history pulls. MCP Apps hosts can
142
146
  render the returned dashboard resource; all values are escaped before HTML
143
147
  output.
148
+ The competitive record and win rate are for the latest available season.
144
149
  """
145
150
  with RivalsDataClient() as client:
146
151
  player = client.get_player(uid_or_name)
@@ -168,7 +173,7 @@ def show_player_dashboard(
168
173
  competitive_summary = (
169
174
  f'<div class="competitive-line">'
170
175
  f'<span>{battles:,} competitive games</span>'
171
- + (f'<span>{rate}% win rate</span>' if rate is not None else "")
176
+ + (f'<span>{rate}% season win rate</span>' if rate is not None else "")
172
177
  + (f'<span>{escape(str(score))} RP</span>' if score is not None else "")
173
178
  + '</div>'
174
179
  )
@@ -414,7 +419,9 @@ def get_player_stats(
414
419
  """Get player stats: heroes, maps, bans, or calculated classes.
415
420
 
416
421
  Classes sum hero wins/losses by tank/support/dps and game mode; these are
417
- hero participation totals, which can count a match more than once.
422
+ summed hero records, which may overlap within a match. The response metadata
423
+ explains the calculation and scope; class totals cannot establish a player's
424
+ overall match win rate. Upstream hero-switch attribution is unknown.
418
425
  Supply a season ID or "all" for combined all-seasons totals. Omitting the
419
426
  season uses the endpoint default.
420
427
  """
@@ -428,10 +428,11 @@ class ClassStatsRecord(DataModel):
428
428
 
429
429
 
430
430
  class ClassStatsResponse(DataModel):
431
- """Derived class totals and hero/mode rows excluded from calculation."""
431
+ """Derived class totals, excluded rows, and calculation metadata."""
432
432
 
433
433
  classes: list[ClassStatsRecord]
434
434
  excluded: list[DataModel]
435
+ metadata: DataModel
435
436
 
436
437
  def __init__(self, data: Mapping[str, Any] | None = None, **values: Any) -> None:
437
438
  super().__init__(data, **values)
@@ -983,7 +984,11 @@ class Player(DataModel):
983
984
 
984
985
  @property
985
986
  def win_rate(self) -> int | None:
986
- """Overall competitive win rate when the profile includes wins/losses."""
987
+ """Current-season competitive win rate when profile counts are available.
988
+
989
+ Uses a direct source rate or the latest available competitive season
990
+ with usable counts. This property does not aggregate across seasons.
991
+ """
987
992
  direct = StatRecord(self._data).win_rate
988
993
  if direct is not None:
989
994
  return direct
@@ -225,8 +225,10 @@ class PlayerStats(PlayerResource):
225
225
  Supply a season ID or ``season="all"`` for combined all-seasons totals.
226
226
  Omitting ``season`` preserves the endpoint default.
227
227
  Win rate uses total wins / (wins + losses), not the average of hero
228
- percentages. Counts describe hero participation: switching heroes can
229
- cause one match to contribute to multiple hero or class records.
228
+ percentages. Counts are summed upstream hero records. The upstream
229
+ attribution rule for hero switches is unknown, so distinct match
230
+ counts cannot be established from these rows. ``metadata`` describes
231
+ the calculation and requested season scope.
230
232
  Unknown roles and incomplete win/loss rows are listed in ``excluded``.
231
233
  """
232
234
  groups = {
@@ -271,7 +273,34 @@ class PlayerStats(PlayerResource):
271
273
  total = totals["wins"] + totals["losses"]
272
274
  totals["win_rate"] = round(totals["wins"] * 100 / total) if total else None
273
275
  group[mode] = totals
274
- return ClassStatsResponse({"classes": list(groups.values()), "excluded": excluded})
276
+ all_seasons = season in ("all", -1)
277
+ warnings = [
278
+ "Hero records may overlap within a match. Class totals must not "
279
+ "be used to calculate the player's overall match win rate."
280
+ ]
281
+ if all_seasons:
282
+ warnings.append(
283
+ "All-seasons results cover the records returned by the source; "
284
+ "complete lifetime coverage is not verified."
285
+ )
286
+ if excluded:
287
+ warnings.append(
288
+ "Some hero or mode records were excluded; see excluded for details."
289
+ )
290
+ metadata = {
291
+ "source": "/player/stats/heroes",
292
+ "counts_basis": "summed_hero_records",
293
+ "win_rate_formula": "wins / (wins + losses) * 100",
294
+ "unique_matches_verified": False,
295
+ "hero_switch_attribution": "unknown",
296
+ "season": "all" if all_seasons else season,
297
+ "season_scope": "all" if all_seasons else (
298
+ "endpoint_default" if season is None else "season"
299
+ ),
300
+ "warnings": warnings,
301
+ }
302
+ return ClassStatsResponse({"classes": list(groups.values()), "excluded": excluded,
303
+ "metadata": metadata})
275
304
 
276
305
  def maps(self, *, season: int | None = None) -> list[MapRecord]:
277
306
  payload = {"season": season} if season is not None else {}
File without changes