- Rename `evolution_tier` parameter to `refractor_tier` in compute_variant_hash()
to match the refractor naming convention established in PR #131
- Update hash input dict key accordingly (safe: function is new, no stored hashes)
- Update test docstrings referencing the old parameter name
- Remove redundant parentheses on boost_delta_json TextField declaration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pure functions for computing boosted card ratings when a player
reaches a new Refractor tier. Batter boost applies fixed +0.5 to
four offensive columns per tier; pitcher boost uses a 1.5 TB-budget
priority algorithm. Both preserve the 108-sum invariant.
- Create refractor_boost.py with apply_batter_boost, apply_pitcher_boost,
and compute_variant_hash (Decimal arithmetic, zero-floor truncation)
- Add RefractorBoostAudit model, Card.variant, BattingCard/PitchingCard
image_url, RefractorCardState.variant fields to db_engine.py
- Add migration SQL for refractor_card_state.variant column and
refractor_boost_audit table (JSONB, UNIQUE constraint, transactional)
- 26 unit tests covering 108-sum invariant, deltas, truncation, TB
accounting, determinism, x-check protection, and variant hash behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes#174
Adds `evaluated_only: bool = Query(default=True)` to `list_card_states()`.
When True (the default), cards with `last_evaluated_at IS NULL` are excluded —
these are placeholder rows created at pack-open time but never run through the
evaluator. At team scale this eliminates ~2739 zero-value rows from the
default response, making the Discord /refractor status command efficient
without any bot-side changes.
Set `evaluated_only=false` to include all rows (admin/pipeline use case).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes#172
- New GET /api/v2/refractor/cards endpoint in refractor router with
team_id (required), card_type, tier, season, progress, limit, offset filters
- season filter uses EXISTS subquery against batting/pitching_season_stats
- progress=close filter uses CASE expression to compare current_value
against next tier threshold (>= 80%)
- LEFT JOIN on Player so deleted players return player_name: null
- Sorting: current_tier DESC, current_value DESC
- count reflects total matching rows before pagination
- Extended _build_card_state_response() with progress_pct (computed) and
optional player_name; single-card endpoint gains progress_pct automatically
- Added non-unique team_id index on refractor_card_state in db_engine.py
- Migration: 2026-03-25_add_refractor_card_state_team_index.sql
- Removed pre-existing unused RefractorTrack import in evaluate_game (ruff)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Guard total_count with `if not csv` ternary to avoid unnecessary
COUNT query on CSV export paths (10 files)
- Consolidate rewards.py from 3 COUNT queries to 1 (used for both
empty-check and response)
- Clean up scout_claims.py double `if limit is not None` block
- Normalize scout_opportunities.py from max(1,...) to max(0,...)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ensures the `count` field in JSON responses reflects total matching
records rather than the page size, consistent with the notifications
endpoint pattern from PR #150.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses reviewer feedback: max(0,...) admitted limit=0 which would
silently return no results even when matching records exist.
Changed to max(1,...) consistent with feedback on PRs #149 and #152.