Complete rename of the card progression system from "Evolution" to "Refractor" across all code, routes, models, services, seeds, and tests. - Route prefix: /api/v2/evolution → /api/v2/refractor - Model classes: EvolutionTrack → RefractorTrack, etc. - 12 files renamed, 8 files content-edited - New migration to rename DB tables - 117 tests pass, no logic changes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
139 lines
4.4 KiB
Python
139 lines
4.4 KiB
Python
"""
|
|
WP-10: Pack opening hook — refractor_card_state initialization.
|
|
|
|
Public API
|
|
----------
|
|
initialize_card_evolution(player_id, team_id, card_type)
|
|
Get-or-create a RefractorCardState for the (player_id, team_id) pair.
|
|
Returns the state instance on success, or None if initialization fails
|
|
(missing track, integrity error, etc.). Never raises.
|
|
|
|
_determine_card_type(player)
|
|
Pure function: inspect player.pos_1 and return 'sp', 'rp', or 'batter'.
|
|
Exported so the cards router and tests can call it directly.
|
|
|
|
Design notes
|
|
------------
|
|
- The function is intentionally fire-and-forget from the caller's perspective.
|
|
All exceptions are caught and logged; pack opening is never blocked.
|
|
- No RefractorProgress rows are created here. Progress accumulation is a
|
|
separate concern handled by the stats-update pipeline (WP-07/WP-08).
|
|
- AI teams and Gauntlet teams skip Paperdex insertion (cards.py pattern);
|
|
we do NOT replicate that exclusion here — all teams get a refractor state
|
|
so that future rule changes don't require back-filling.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
from app.db_engine import DoesNotExist, RefractorCardState, RefractorTrack
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _determine_card_type(player) -> str:
|
|
"""Map a player's primary position to a refractor card_type string.
|
|
|
|
Rules (from WP-10 spec):
|
|
- pos_1 contains 'SP' -> 'sp'
|
|
- pos_1 contains 'RP' or 'CP' -> 'rp'
|
|
- anything else -> 'batter'
|
|
|
|
Args:
|
|
player: Any object with a ``pos_1`` attribute (Player model or stub).
|
|
|
|
Returns:
|
|
One of the strings 'batter', 'sp', 'rp'.
|
|
"""
|
|
pos = (player.pos_1 or "").upper()
|
|
if "SP" in pos:
|
|
return "sp"
|
|
if "RP" in pos or "CP" in pos:
|
|
return "rp"
|
|
return "batter"
|
|
|
|
|
|
def initialize_card_evolution(
|
|
player_id: int,
|
|
team_id: int,
|
|
card_type: str,
|
|
) -> Optional[RefractorCardState]:
|
|
"""Get-or-create a RefractorCardState for a newly acquired card.
|
|
|
|
Called by the cards POST endpoint after each card is inserted. The
|
|
function is idempotent: if a state row already exists for the
|
|
(player_id, team_id) pair it is returned unchanged — existing
|
|
refractor progress is never reset.
|
|
|
|
Args:
|
|
player_id: Primary key of the Player row (Player.player_id).
|
|
team_id: Primary key of the Team row (Team.id).
|
|
card_type: One of 'batter', 'sp', 'rp'. Determines which
|
|
RefractorTrack is assigned to the new state.
|
|
|
|
Returns:
|
|
The existing or newly created RefractorCardState instance, or
|
|
None if initialization could not complete (missing track seed
|
|
data, unexpected DB error, etc.).
|
|
"""
|
|
try:
|
|
track = RefractorTrack.get(RefractorTrack.card_type == card_type)
|
|
except DoesNotExist:
|
|
logger.warning(
|
|
"refractor_init: no RefractorTrack found for card_type=%r "
|
|
"(player_id=%s, team_id=%s) — skipping state creation",
|
|
card_type,
|
|
player_id,
|
|
team_id,
|
|
)
|
|
return None
|
|
except Exception:
|
|
logger.exception(
|
|
"refractor_init: unexpected error fetching track "
|
|
"(card_type=%r, player_id=%s, team_id=%s)",
|
|
card_type,
|
|
player_id,
|
|
team_id,
|
|
)
|
|
return None
|
|
|
|
try:
|
|
state, created = RefractorCardState.get_or_create(
|
|
player_id=player_id,
|
|
team_id=team_id,
|
|
defaults={
|
|
"track": track,
|
|
"current_tier": 0,
|
|
"current_value": 0.0,
|
|
"fully_evolved": False,
|
|
},
|
|
)
|
|
if created:
|
|
logger.debug(
|
|
"refractor_init: created RefractorCardState id=%s "
|
|
"(player_id=%s, team_id=%s, card_type=%r)",
|
|
state.id,
|
|
player_id,
|
|
team_id,
|
|
card_type,
|
|
)
|
|
else:
|
|
logger.debug(
|
|
"refractor_init: state already exists id=%s "
|
|
"(player_id=%s, team_id=%s) — no-op",
|
|
state.id,
|
|
player_id,
|
|
team_id,
|
|
)
|
|
return state
|
|
|
|
except Exception:
|
|
logger.exception(
|
|
"refractor_init: failed to get_or_create state "
|
|
"(player_id=%s, team_id=%s, card_type=%r)",
|
|
player_id,
|
|
team_id,
|
|
card_type,
|
|
)
|
|
return None
|