# Config Module - League Configuration & Play Outcomes

## Purpose

League-specific configuration system and play outcome definitions. Provides immutable config objects for game rules, API endpoints, and result chart behaviors.

## Structure

```
app/config/
├── __init__.py              # Public exports
├── base_config.py           # Abstract BaseConfig
├── league_configs.py        # SbaConfig, PdConfig implementations
├── result_charts.py         # PlayOutcome enum
└── common_x_check_tables.py # X-Check defense/error tables
```

## League Configuration

### Usage
```python
from app.config import get_league_config

config = get_league_config("sba")  # or "pd"
api_url = config.get_api_base_url()
supports_ratings = config.supports_position_ratings()
```

### BaseConfig (Abstract)
Common interface for league configs:
- `league_id`, `innings`, `dh_rule`, `mercy_rule`
- `get_api_base_url()`, `get_result_chart_name()`
- `supports_position_ratings()`, `supports_auto_mode()`

### SbaConfig
- Manual outcome selection only
- No position ratings support
- Simple player data

### PdConfig
- Manual or auto resolution
- Position ratings from API
- Cardset validation
- Detailed scouting data

### Immutability
Configs use Pydantic `frozen=True`:
```python
config.innings = 7  # Raises ValidationError
```

## PlayOutcome Enum

Universal enum for all play outcomes (both leagues).

### Categories
```python
from app.config import PlayOutcome

# Standard outcomes
PlayOutcome.STRIKEOUT
PlayOutcome.SINGLE
PlayOutcome.HOMERUN

# Uncapped (requires advancement decision)
PlayOutcome.SINGLE_UNCAPPED
PlayOutcome.DOUBLE_UNCAPPED

# Interrupts (logged with pa=0)
PlayOutcome.WILD_PITCH
PlayOutcome.STOLEN_BASE
```

### Helper Methods
```python
outcome.is_hit()           # True for hits
outcome.is_out()           # True for outs
outcome.is_uncapped()      # Requires decision tree
outcome.get_bases_advanced()  # Base advancement
```

## X-Check Tables

Defense range tables and error charts for defensive play resolution.

### Defense Tables
- `INFIELD_DEFENSE_TABLE` - 20×5 (d20 × range)
- `OUTFIELD_DEFENSE_TABLE` - 20×5
- `CATCHER_DEFENSE_TABLE` - 20×5

### Error Charts
- 3d6 (3-18) by error rating (0-25)
- Results: RP (rare play), E1-E3, NO

### Helpers
```python
from app.config import get_fielders_holding_runners, get_error_chart_for_position

holders = get_fielders_holding_runners([1, 2], 'R')  # Runners on 1st & 2nd, RHB
chart = get_error_chart_for_position('SS')
```

## Common Tasks

### Get League-Specific Behavior
```python
config = get_league_config(state.league_id)
if config.supports_position_ratings():
    ratings = await fetch_ratings(player_id)
```

### Validate Outcome
```python
outcome = PlayOutcome.GROUNDBALL_C
if outcome.requires_location():
    assert hit_location is not None
```

## References

- **League Differences**: See root `CLAUDE.md`
- **Play Resolution**: See `../core/play_resolver.py`

---

**Tests**: `tests/unit/config/` | **Updated**: 2025-01-19
