fix: guard T4 rarity upgrade from live-series pipeline collision (#57) #63
No reviewers
Labels
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cal/paper-dynasty-card-creation#63
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "issue/57-bug-t4-rarity-upgrade-collision-with-live-series-c"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #57
What was broken
post_player_updates()in bothbatters/creation.pyandpitchers/creation.pyunconditionally overwritesrarity_idwhen the OPS-derived rarity differs from the current rarity. Once Phase 2 applies T4 rarity upgrades (e.g. Bronze → Silver), the next live-series run silently reverts the earned upgrade back to Bronze based on raw OPS.Approach chosen
Strategy 1 from spec T4-3 — guard clause in
post_player_updates(). Before writing a rarity downgrade, the pipeline checks whether any affected players have a fully-evolved (T4) refractor card state. If a player is T4-protected, the downgrade is skipped and logged; upgrades and same-rarity writes are unaffected.Changes
rarity_thresholds.pyRARITY_LADDER = [5, 4, 3, 2, 1, 99]— ordered worst to best, used for ladder comparisonsrarity_is_downgrade(current, new)— returnsTrueonly whennewis a less prestigious tier thancurrentdb_calls.pyget_fully_evolved_players(player_ids)— callsGET /api/v2/refractor/fully-evolved?player_ids=...and returns the subset withfully_evolved=True. Falls back to empty set on any error, keeping the pipeline safe.batters/creation.pyandpitchers/creation.pyplayer_data.apply(get_player_updates, ...), a pre-flight check identifies players whose OPS rarity would be a downgrade, then fetches their T4 state in one API callget_player_updates, theelif rarity != new_rarity_idbranch is guarded: downgrade writes are skipped for T4-protected players; all other rarity changes proceed normallyDatabase API dependency
GET /api/v2/refractor/fully-evolved?player_ids=1,2,3must be added topaper-dynasty-database. It should return{"player_ids": [<int>, ...]}— the subset of the given player IDs where anyRefractorCardStaterow hasfully_evolved=True.Until that endpoint is added, the guard returns an empty set and is effectively a no-op. The guard activates automatically once the database side is wired up, with no further changes needed here.
Other observations
rarity_thresholds.RARITY_LADDERalso satisfies the prerequisite for issue #59 (rarity ladder utility) —next_rarity()can be built on top of this constant._RARITY_LADDERapproach was chosen over Strategy 2 (database-side rejection) and Strategy 3 (separate evolution_rarity field) to keep Phase 2 risk surface minimal. The guard is transparent and reversible.AI Code Review
Files Reviewed
rarity_thresholds.py(modified — addedRARITY_LADDERandrarity_is_downgrade)db_calls.py(modified — addedget_fully_evolved_players)batters/creation.py(modified — T4 guard inpost_player_updates)pitchers/creation.py(modified — T4 guard inpost_player_updates, mirroring batters)Findings
Correctness
No issues found.
RARITY_LADDER = [5, 4, 3, 2, 1, 99]ordering is correct (Common → … → HoF). Position comparisons inrarity_is_downgradefollow the ladder correctly:current_pos > new_posmeans new is at a lower (worse) index → True (is a downgrade). Spot-checked Bronze→Common (1>0=True ✓), Silver→Bronze (2>1=True ✓), Bronze→Silver (1>2=False ✓).Falsefromrarity_is_downgrade(ValueError caught), so unrecognized rarities never accidentally trigger the guard. ✓get_fully_evolved_playerscorrectly short-circuits on empty input, handlesNoneresponse and missing"player_ids"field, and returns an empty set on any error path. The 404 fallback keeps the pipeline safe while the endpoint doesn't exist yet. ✓t4_protected_idsin bothget_player_updatesclosures is valid — the variable is computed before the nested function is defined and called. ✓downgrade_candidatesis built via.tolist(), which converts numpy types to Python native ints.set(result["player_ids"])from JSON also produces Python ints.numpy.int64 in {int}works correctly via hash equality, so thedf_data.player_id in t4_protected_idsmembership check is safe. ✓rarity_is_downgrade(...) and player_id in t4_protected_ids. ✓Security
No issues found. No user-controlled input reaches queries or commands; no credentials introduced.
Style & Conventions
RARITY_LADDERconstant placement (after threshold factories, before utility functions) is clean.# T4 rarity guard: same protection as batters — see batters/creation.py.). No duplication concern at this scale.Suggestions
get_fully_evolved_playersdoes not guard againstresult["player_ids"]beingNone(i.e.,{"player_ids": null}).set(None)would raiseTypeError. This is a non-issue while the endpoint doesn't exist and very unlikely once it does, but a single extra check (if not isinstance(result.get("player_ids"), list): return set()) would make the fallback airtight.RARITY_LADDERalso satisfies the #59 prerequisite as noted — no action needed here, just confirmed the observation in the PR body is accurate.Verdict: COMMENT
LGTM. Logic is correct, the guard degrades safely while the database endpoint is pending, and the implementation matches the Strategy 1 spec from T4-3. The one suggestion (null-guard on
result["player_ids"]) is optional hardening, not a blocker.Automated review by Claude PR Reviewer
Approved via pd-pr