Implements two new API endpoints the bot calls after a game completes:
POST /api/v2/season-stats/update-game/{game_id}
Delegates to update_season_stats() service (WP-05). Returns
{"updated": N, "skipped": bool} with idempotency via ProcessedGame ledger.
POST /api/v2/evolution/evaluate-game/{game_id}
Finds all (player_id, team_id) pairs from the game's StratPlay rows,
calls evaluate_card() for each pair that has an EvolutionCardState,
and returns {"evaluated": N, "tier_ups": [...]} with full tier-up detail.
New files:
app/services/evolution_evaluator.py — evaluate_card() service (WP-08)
tests/test_postgame_evolution.py — 10 integration tests (all pass)
Modified files:
app/routers_v2/season_stats.py — rewritten to delegate to the service
app/routers_v2/evolution.py — evaluate-game endpoint added
app/main.py — season_stats router registered
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
62 lines
2.2 KiB
Python
62 lines
2.2 KiB
Python
"""Season stats API endpoints.
|
|
|
|
Covers WP-13 (Post-Game Callback Integration):
|
|
POST /api/v2/season-stats/update-game/{game_id}
|
|
|
|
Delegates to app.services.season_stats.update_season_stats() which
|
|
aggregates StratPlay and Decision rows for a completed game and
|
|
performs an additive upsert into player_season_stats.
|
|
|
|
Idempotency is enforced by the service layer: re-delivery of the same
|
|
game_id returns {"updated": 0, "skipped": true} without modifying stats.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
from ..dependencies import oauth2_scheme, valid_token
|
|
|
|
router = APIRouter(prefix="/api/v2/season-stats", tags=["season-stats"])
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@router.post("/update-game/{game_id}")
|
|
async def update_game_season_stats(game_id: int, token: str = Depends(oauth2_scheme)):
|
|
"""Increment season stats with batting and pitching deltas from a game.
|
|
|
|
Calls update_season_stats(game_id) from the service layer which:
|
|
- Aggregates all StratPlay rows by (player_id, team_id, season)
|
|
- Merges Decision rows into pitching groups
|
|
- Performs an additive ON CONFLICT upsert into player_season_stats
|
|
- Guards against double-counting via the last_game FK check
|
|
|
|
Response: {"updated": N, "skipped": false}
|
|
- N: total player_season_stats rows upserted (batters + pitchers)
|
|
- skipped: true when this game_id was already processed (idempotent re-delivery)
|
|
|
|
Errors from the service are logged but re-raised as 500 so the bot
|
|
knows to retry.
|
|
"""
|
|
if not valid_token(token):
|
|
logging.warning("Bad Token: [REDACTED]")
|
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
|
|
|
from ..services.season_stats import update_season_stats
|
|
|
|
try:
|
|
result = update_season_stats(game_id)
|
|
except Exception as exc:
|
|
logger.error("update-game/%d failed: %s", game_id, exc, exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Season stats update failed for game {game_id}: {exc}",
|
|
)
|
|
|
|
updated = result.get("batters_updated", 0) + result.get("pitchers_updated", 0)
|
|
return {
|
|
"updated": updated,
|
|
"skipped": result.get("skipped", False),
|
|
}
|