Commit Graph

5 Commits

Author SHA1 Message Date
Cal Corum
ab15228c44 fix(refractor): mask variant hash to 31 bits to fit Postgres INTEGER
compute_variant_hash took the first 8 hex chars of a SHA-256 digest and
cast to int, producing values up to 2^32 - 1. The variant columns on
Card, BattingCard, PitchingCard, and RefractorCardState are Peewee
IntegerField → Postgres INTEGER, which is signed 32-bit (max 2^31 - 1).
Roughly half of all players (~50%) would hash into the range [2^31,
2^32 - 1] and crash tier-up writes with:

  peewee.DataError: integer out of range

Surfaced via /dev refractor-test card_id:64460 (Charles Nagy,
player_id=10795), whose tier-1 hash was 2874960417. The outer
exception handler in refractor.evaluate_game caught the error and
logged a warning, so the tier-up was silently dropped — the test
harness reported "No tier-up detected (evaluated 2 cards)" while
apply_tier_boost was actually failing mid-write.

Fix: mask the hash with & 0x7FFFFFFF, dropping one bit of entropy.
~2.1B distinct values remain — still astronomically collision-safe.

Backwards-compatible: all 9 existing refractor_boost_audit rows and
9 persisted non-zero variants have hashes where the high bit was
already 0 (those tier-ups happened to land in the safe half). Masking
leaves those values unchanged.

Added regression test test_fits_postgres_int32 covering 10,000 player
IDs × 5 tiers = 50,000 combinations, all asserted ≤ 2,147,483,647.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 13:11:40 -05:00
Cal Corum
7f17c9b9f2 fix: address PR #177 review — move import os to top-level, add audit idempotency guard
- Move `import os` from inside evaluate_game() to module top-level imports
  (lazy imports are only for circular dependency avoidance)
- Add get_or_none idempotency guard before RefractorBoostAudit.create()
  inside db.atomic() to prevent IntegrityError on UNIQUE(card_state, tier)
  constraint in PostgreSQL when apply_tier_boost is called twice for the
  same tier
- Update atomicity test stub to provide card_state/tier attributes for
  the new Peewee expression in the idempotency guard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:16:27 -05:00
Cal Corum
6a176af7da feat: Refractor Phase 2 integration — wire boost into evaluate-game
When a card reaches a new Refractor tier during game evaluation, the
system now creates a boosted variant card with modified ratings. This
connects the Phase 2 Foundation pure functions (PR #176) to the live
evaluate-game endpoint.

Key changes:
- evaluate_card() gains dry_run parameter so apply_tier_boost() is the
  sole writer of current_tier, ensuring atomicity with variant creation
- apply_tier_boost() orchestrates the full boost flow: source card
  lookup, boost application, variant card + ratings creation, audit
  record, and atomic state mutations inside db.atomic()
- evaluate_game() calls evaluate_card(dry_run=True) then loops through
  intermediate tiers on tier-up, with error isolation per player
- Display stat helpers compute fresh avg/obp/slg for variant cards
- REFRACTOR_BOOST_ENABLED env var provides a kill switch
- 51 new tests: unit tests for display stats, integration tests for
  orchestration, HTTP endpoint tests for multi-tier jumps, pitcher
  path, kill switch, atomicity, idempotency, and cross-player isolation
- Clarified all "79-sum" references to note the 108-total card invariant

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:04:52 -05:00
Cal Corum
776f1a5302 fix: address PR review findings — rename evolution_tier to refractor_tier
- 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>
2026-03-30 11:06:38 -05:00
Cal Corum
4a1251a734 feat: add Refractor Phase 2 foundation — boost functions, schema, tests
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>
2026-03-29 13:39:03 -05:00