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>
Adds four Tier 3 (medium-priority) test cases to the existing refractor test
suite. All tests use SQLite in-memory databases and run without a PostgreSQL
connection.
T3-1 (test_refractor_track_api.py): Two tests verifying that
GET /api/v2/refractor/tracks?card_type= returns 200 with count=0 for both
an unrecognised card_type value ('foo') and an empty string, rather than
a 4xx/5xx. A full SQLite-backed TestClient is added to the track API test
module for these cases.
T3-6 (test_refractor_state_api.py): Verifies that
GET /api/v2/refractor/cards/{card_id} returns last_evaluated_at: null (not
a crash or missing key) when the RefractorCardState was initialised but
never evaluated. Adds the SQLite test infrastructure (models, fixtures,
helper factories, TestClient) to the state API test module.
T3-7 (test_refractor_evaluator.py): Two tests covering fully_evolved/tier
mismatch correction. When the database has fully_evolved=True but
current_tier=3 (corruption), evaluate_card must re-derive fully_evolved
from the freshly-computed tier (False for tier 3, True for tier 4).
T3-8 (test_refractor_evaluator.py): Two tests confirming per-team stat
isolation. A player with BattingSeasonStats on two different teams must
have each team's RefractorCardState reflect only that team's stats — not
a combined total. Covers both same-season and multi-season scenarios.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements all gap tests identified in the PO review for the refractor
card progression system (Phase 1 foundation).
TIER 1 (critical):
- T1-1: Negative singles guard in compute_batter_value — documents that
hits=1, doubles=1, triples=1 produces singles=-1 and flows through
unclamped (value=8.0, not 10.0)
- T1-2: SP tier boundary precision with floats — outs=29 (IP=9.666) stays
T0, outs=30 (IP=10.0) promotes to T1; also covers T2 float boundary
- T1-3: evaluate-game with non-existent game_id returns 200 with empty results
- T1-4: Seed threshold ordering + positivity invariant (t1<t2<t3<t4, all >0)
TIER 2 (high):
- T2-1: fully_evolved=True persists when stats are zeroed or drop below
previous tier — no-regression applies to both tier and fully_evolved flag
- T2-2: Parametrized edge cases for _determine_card_type: DH, C, 2B, empty
string, None, and compound "SP/RP" (resolves to "sp", SP checked first)
- T2-3: evaluate-game with zero StratPlay rows returns empty batch result
- T2-4: GET /teams/{id}/refractors with valid team and zero states is empty
- T2-5: GET /teams/99999/refractors documents 200+empty (no team existence check)
- T2-6: POST /cards/{id}/evaluate with zero season stats stays at T0 value=0.0
- T2-9: Per-player error isolation — patches source module so router's local
from-import picks up the patched version; one failure, one success = evaluated=1
- T2-10: Each card_type has exactly one RefractorTrack after seeding
All 101 tests pass (15 PostgreSQL-only tests skip without POSTGRES_HOST).
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>