paper-dynasty-card-creation/docs/architecture/CARD_BUILDER_REDESIGN.md
Cal Corum 9a121d370f Add card builder architecture redesign documentation
Documents proposed architecture for moving card-building logic upstream
to Python, including:
- Executive summary with problem statement and migration path
- CardBuilder sketch with contract system for pluggable placement strategies
- Support for different card "personalities" (Standard, Clutch, Power Heavy, etc.)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 10:56:55 -06:00

6.3 KiB
Raw Permalink Blame History

Card Creation Architecture Redesign - Executive Summary

Date: 2026-01-22 Project: Paper Dynasty Card Generation System Author: Cal Corum + Claude (Jarvis)


Problem Statement

The current card creation pipeline has an architectural inconsistency:

  1. Python generates continuous chance values (e.g., 4.78 out of 108)
  2. Database fits to discrete card mechanics (2d6 × d20 combinations)
  3. Database saves fitted values back - what you send ≠ what gets stored

This results in subtle discrepancies (e.g., sending 4.75, card shows 4.65) because the card mechanics only support specific discrete probability values.

Root Cause

The card uses 2d6 (rows 2-12) combined with d20 splits. Valid chance values are constrained by:

  • Row frequencies: 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1 (totaling 36)
  • D20 subdivisions: 1-20 range creates fractional chances

Python generates values ignorant of these constraints; the database must "round" to fit.


Proposed Solution

Move the card-building logic upstream to Python, making it the single source of truth.

New Architecture

BEFORE:
Python: stats → continuous chances → POST raw values
Database: receives → fits to card → saves DIFFERENT values → renders

AFTER:
Python: stats → continuous chances → CardBuilder → discrete card structure → POST
Database: receives → stores → renders (no fitting needed)

Key Components

Component Responsibility
CardBuilder Fits continuous chances to discrete card structure
CardContract Defines placement strategy (which rows for which plays)
BuiltCard Structured output ready for storage/rendering
RawBattingChances / RawPitchingChances Input from existing stat calculations

Contract System

A major enhancement: pluggable placement strategies that give cards different "personalities" using the same raw stats.

Available Contracts

Contract Behavior Use Case
Standard On-base results on middle rows (6-8) Default behavior
Clutch On-base results on edge rows (2-4, 10-12) Role players, postseason heroes
Power Heavy HR/3B/2B on prime rows, singles on edges HR leaders, sluggers
Contact First Singles on prime rows, power on edges Leadoff hitters, high-AVG players
Groundball Pitcher Groundouts on middle rows Sinkerballers
Flyball Pitcher Strikeouts/flyouts on middle rows Power pitchers

Example: Same Stats, Different Cards

Power Heavy Contract:
  Row  7 (most likely): HOMERUN
  Row 12 (least likely): SINGLE

Contact First Contract:
  Row  7 (most likely): SINGLE
  Row 12 (least likely): HOMERUN

Contract Interface

class CardContract:
    def get_row_preference(self, category: PlayCategory) -> List[int]
    def get_play_priority(self) -> List[PlayCategory]
    def should_use_splits(self, category: PlayCategory) -> bool
    def max_splits_per_column(self) -> int

Benefits

Immediate

  1. Deterministic output - What Python calculates is exactly what appears on the card
  2. No step 7 - Database doesn't modify/re-save values
  3. Local preview - Validate cards without hitting the API
  4. Single source of truth - Fitting logic in one place

Strategic

  1. Card personalities - Same player can have different "feels" via contracts
  2. Testable - Unit test card building without database
  3. Extensible - Add new contracts without touching core logic
  4. Data-driven - Store contract name in DB, apply at generation time

Migration Path

Phase 1: Extract & Validate

  • Extract current database fitting logic to shared module
  • Run old and new in parallel, compare outputs
  • Fix discrepancies

Phase 2: Python Adoption

  • Modify Python card-creation to use CardBuilder
  • POST fitted card structures instead of raw chances
  • Keep raw chances in separate field for debugging

Phase 3: Database Simplification

  • Database receives pre-fitted card structures
  • Remove fitting logic from database
  • Eliminate step 7 (no re-saving)

Phase 4: Enhancements

  • Add preview endpoint (returns structure without saving)
  • Implement contract selection in card creation workflow
  • Build custom contract UI

Technical Details

Discrete Probability Space

Valid chance values are defined by EXACT_CHANCES:

[5.7, 5.4, 5.1, 4.8, 4.75, 4.5, 4.25, 4.2, 3.9, 3.8, 3.75, ...]

Plus whole numbers 1-6 (full row allocations).

Pitching Card Specifics

  • X-checks: Redirect to batter's card (e.g., GB (ss) X)
  • Batter Power: bp_homerun, bp_single redirect to batter
  • Secondary plays: D20 splits pair primary with complementary outcome
  • Bolding: Strikeouts bolded (good for pitcher), hits not bolded

Row Frequencies

Row:  2   3   4   5   6   7   8   9  10  11  12
Freq: 1   2   3   4   5   6   5   4   3   2   1

Row 7 is most valuable (6 chances = 16.67% of outcomes).


Files Produced

File Description
card_builder_sketch.py Main module: CardBuilder, contracts, data structures
contracts.py Standalone contracts reference (superseded by integration)
EXECUTIVE_SUMMARY.md This document

Open Questions

  1. Shared package or copy? Extract to pip-installable package both repos import, or maintain synced copies?

  2. Keep raw chances? Store both raw (for analysis) and fitted (for rendering), or just fitted?

  3. Contract storage - Where to store contract selection? Player level? Cardset level? Both?

  4. Custom contracts - Allow users to create custom contracts via UI?


Recommendation

Proceed with the shared card-building module approach.

The contract system adds significant value by enabling card personalities without changing the underlying statistics. This addresses the original consistency problem while opening new design possibilities.

Suggested next step: Create a proof-of-concept by implementing CardBuilder against a single real player's stats and comparing output to current database behavior.


Appendix: Code Location

All sketch files are in:

~/.claude/scratchpad/2026-01-22-card-builder-sketch/

To test:

cd ~/.claude/scratchpad/2026-01-22-card-builder-sketch
python card_builder_sketch.py