""" WP-10: Pack opening hook — refractor_card_state initialization. Public API ---------- initialize_card_refractor(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_refractor( 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