diff --git a/app/routers_v2/refractor.py b/app/routers_v2/refractor.py index daaee0e..0b1c78d 100644 --- a/app/routers_v2/refractor.py +++ b/app/routers_v2/refractor.py @@ -1,3 +1,5 @@ +import os + from fastapi import APIRouter, Depends, HTTPException, Query import logging from typing import Optional @@ -307,8 +309,6 @@ async def evaluate_game(game_id: int, token: str = Depends(oauth2_scheme)): logging.warning("Bad Token: [REDACTED]") raise HTTPException(status_code=401, detail="Unauthorized") - import os - from ..db_engine import RefractorCardState, Player, StratPlay from ..services.refractor_boost import apply_tier_boost from ..services.refractor_evaluator import evaluate_card diff --git a/app/services/refractor_boost.py b/app/services/refractor_boost.py index 700a820..3eb1bd1 100644 --- a/app/services/refractor_boost.py +++ b/app/services/refractor_boost.py @@ -667,7 +667,11 @@ def apply_tier_boost( audit_data["battingcard"] = new_card.id else: audit_data["pitchingcard"] = new_card.id - _audit_model.create(**audit_data) + existing_audit = _audit_model.get_or_none( + (_audit_model.card_state == card_state.id) & (_audit_model.tier == new_tier) + ) + if existing_audit is None: + _audit_model.create(**audit_data) # 8b. Update RefractorCardState — this is the SOLE tier write on tier-up. card_state.current_tier = new_tier diff --git a/tests/test_refractor_boost_integration.py b/tests/test_refractor_boost_integration.py index 636f3ba..7635245 100644 --- a/tests/test_refractor_boost_integration.py +++ b/tests/test_refractor_boost_integration.py @@ -1115,7 +1115,19 @@ class TestAtomicity: """ class _FailingAuditModel: - """Stub that raises on .create() to simulate audit write failure.""" + """Stub that raises on .create() to simulate audit write failure. + + Provides card_state/tier attributes for the Peewee expression in the + idempotency guard, and get_or_none returns None so it proceeds to + create(), which then raises to simulate the failure. + """ + + card_state = RefractorBoostAudit.card_state + tier = RefractorBoostAudit.tier + + @staticmethod + def get_or_none(*args, **kwargs): + return None @staticmethod def create(**kwargs):