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>
210 lines
6.3 KiB
Markdown
210 lines
6.3 KiB
Markdown
# 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
|
||
|
||
```python
|
||
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`:
|
||
```python
|
||
[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:
|
||
```bash
|
||
cd ~/.claude/scratchpad/2026-01-22-card-builder-sketch
|
||
python card_builder_sketch.py
|
||
```
|