strat-gameplay-webapp/backend/app/config/common_x_check_tables.py
Cal Corum a4b99ee53e CLAUDE: Replace black and flake8 with ruff for formatting and linting
Migrated to ruff for faster, modern code formatting and linting:

Configuration changes:
- pyproject.toml: Added ruff 0.8.6, removed black/flake8
- Configured ruff with black-compatible formatting (88 chars)
- Enabled comprehensive linting rules (pycodestyle, pyflakes, isort,
  pyupgrade, bugbear, comprehensions, simplify, return)
- Updated CLAUDE.md: Changed code quality commands to use ruff

Code improvements (490 auto-fixes):
- Modernized type hints: List[T] → list[T], Dict[K,V] → dict[K,V],
  Optional[T] → T | None
- Sorted all imports (isort integration)
- Removed unused imports
- Fixed whitespace issues
- Reformatted 38 files for consistency

Bug fixes:
- app/core/play_resolver.py: Fixed type hint bug (any → Any)
- tests/unit/core/test_runner_advancement.py: Removed obsolete random mock

Testing:
- All 739 unit tests passing (100%)
- No regressions introduced

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 15:33:21 -06:00

487 lines
25 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Common X-Check resolution tables shared across SBA and PD leagues.
Tables include:
- Defense range tables (20x5) for each position type
- Error charts mapping 3d6 rolls to error types
- Holding runner responsibility chart
Author: Claude
Date: 2025-11-01
"""
# ============================================================================
# DEFENSE RANGE TABLES (1d20 × Defense Range 1-5)
# ============================================================================
# Row index = d20 roll - 1 (0-indexed)
# Column index = defense range - 1 (0-indexed)
# Values = base result code (G1, SI2, F2, etc.)
INFIELD_DEFENSE_TABLE: list[list[str]] = [
# Range: 1 2 3 4 5
# Best Good Avg Poor Worst
["G3#", "SI1", "SI2", "SI2", "SI2"], # d20 = 1
["G2#", "SI1", "SI2", "SI2", "SI2"], # d20 = 2
["G2#", "G3#", "SI1", "SI2", "SI2"], # d20 = 3
["G2#", "G3#", "SI1", "SI2", "SI2"], # d20 = 4
["G1", "G3#", "G3#", "SI1", "SI2"], # d20 = 5
["G1", "G2#", "G3#", "SI1", "SI2"], # d20 = 6
["G1", "G2", "G3#", "G3#", "SI1"], # d20 = 7
["G1", "G2", "G3#", "G3#", "SI1"], # d20 = 8
["G1", "G2", "G3", "G3#", "G3#"], # d20 = 9
["G1", "G1", "G2", "G3#", "G3#"], # d20 = 10
["G1", "G1", "G2", "G3", "G3#"], # d20 = 11
["G1", "G1", "G2", "G3", "G3#"], # d20 = 12
["G1", "G1", "G2", "G3", "G3"], # d20 = 13
["G1", "G1", "G2", "G2", "G3"], # d20 = 14
["G1", "G1", "G1", "G2", "G3"], # d20 = 15
["G1", "G1", "G1", "G2", "G3"], # d20 = 16
["G1", "G1", "G1", "G1", "G3"], # d20 = 17
["G1", "G1", "G1", "G1", "G2"], # d20 = 18
["G1", "G1", "G1", "G1", "G2"], # d20 = 19
["G1", "G1", "G1", "G1", "G1"], # d20 = 20
]
OUTFIELD_DEFENSE_TABLE: list[list[str]] = [
# Range: 1 2 3 4 5
# Best Good Avg Poor Worst
["TR3", "DO3", "DO3", "DO3", "DO3"], # d20 = 1
["DO3", "DO3", "DO3", "DO3", "DO3"], # d20 = 2
["DO2", "DO3", "DO3", "DO3", "DO3"], # d20 = 3
["DO2", "DO2", "DO3", "DO3", "DO3"], # d20 = 4
["SI2", "DO2", "DO2", "DO3", "DO3"], # d20 = 5
["SI2", "SI2", "DO2", "DO2", "DO3"], # d20 = 6
["F1", "SI2", "SI2", "DO2", "DO2"], # d20 = 7
["F1", "F1", "SI2", "SI2", "DO2"], # d20 = 8
["F1", "F1", "F1", "SI2", "SI2"], # d20 = 9
["F1", "F1", "F1", "SI2", "SI2"], # d20 = 10
["F1", "F1", "F1", "F1", "SI2"], # d20 = 11
["F1", "F1", "F1", "F1", "SI2"], # d20 = 12
["F1", "F1", "F1", "F1", "F1"], # d20 = 13
["F2", "F1", "F1", "F1", "F1"], # d20 = 14
["F2", "F2", "F1", "F1", "F1"], # d20 = 15
["F2", "F2", "F2", "F1", "F1"], # d20 = 16
["F2", "F2", "F2", "F2", "F1"], # d20 = 17
["F3", "F2", "F2", "F2", "F2"], # d20 = 18
["F3", "F3", "F2", "F2", "F2"], # d20 = 19
["F3", "F3", "F3", "F2", "F2"], # d20 = 20
]
CATCHER_DEFENSE_TABLE: list[list[str]] = [
# Range: 1 2 3 4 5
# Best Good Avg Poor Worst
["G3", "SI1", "SI1", "SI1", "SI1"], # d20 = 1
["G2", "G3", "SI1", "SI1", "SI1"], # d20 = 2
["G2", "G3", "SI1", "SI1", "SI1"], # d20 = 3
["G1", "G2", "G3", "SI1", "SI1"], # d20 = 4
["G1", "G2", "G3", "SI1", "SI1"], # d20 = 5
["G1", "G1", "G2", "G3", "SI1"], # d20 = 6
["G1", "G1", "G2", "G3", "SI1"], # d20 = 7
["G1", "G1", "G1", "G2", "G3"], # d20 = 8
["G1", "G1", "G1", "G2", "G3"], # d20 = 9
["SPD", "G1", "G1", "G1", "G2"], # d20 = 10
["SPD", "SPD", "G1", "G1", "G1"], # d20 = 11
["SPD", "SPD", "SPD", "G1", "G1"], # d20 = 12
["FO", "SPD", "SPD", "SPD", "G1"], # d20 = 13
["FO", "FO", "SPD", "SPD", "SPD"], # d20 = 14
["FO", "FO", "FO", "SPD", "SPD"], # d20 = 15
["PO", "FO", "FO", "FO", "SPD"], # d20 = 16
["PO", "PO", "FO", "FO", "FO"], # d20 = 17
["PO", "PO", "PO", "FO", "FO"], # d20 = 18
["PO", "PO", "PO", "PO", "FO"], # d20 = 19
["PO", "PO", "PO", "PO", "PO"], # d20 = 20
]
# ============================================================================
# ERROR CHARTS (3d6 totals by Error Rating and Position Type)
# ============================================================================
# Structure: {error_rating: {'RP': [rolls], 'E1': [rolls], 'E2': [rolls], 'E3': [rolls]}}
# If 3d6 sum is in the list for that error rating, apply that error type
# Otherwise, error_result = 'NO' (no error)
# Corner Outfield (LF, RF) Error Chart
LF_RF_ERROR_CHART: dict[int, dict[str, list[int]]] = {
0: {"RP": [5], "E1": [], "E2": [], "E3": []},
1: {"RP": [5], "E1": [3], "E2": [], "E3": [17]},
2: {"RP": [5], "E1": [3, 18], "E2": [], "E3": [16]},
3: {"RP": [5], "E1": [3, 18], "E2": [], "E3": [15]},
4: {"RP": [5], "E1": [4], "E2": [3, 15], "E3": [18]},
5: {"RP": [5], "E1": [4], "E2": [14], "E3": [18]},
6: {"RP": [5], "E1": [3, 4], "E2": [14, 17], "E3": [18]},
7: {"RP": [5], "E1": [16], "E2": [6, 15], "E3": [18]},
8: {"RP": [5], "E1": [16], "E2": [6, 15, 17], "E3": [3, 18]},
9: {"RP": [5], "E1": [16], "E2": [11], "E3": [3, 18]},
10: {"RP": [5], "E1": [4, 16], "E2": [14, 15, 17], "E3": [3, 18]},
11: {"RP": [5], "E1": [4, 16], "E2": [13, 15], "E3": [3, 18]},
12: {"RP": [5], "E1": [4, 16], "E2": [13, 14], "E3": [3, 18]},
13: {"RP": [5], "E1": [6], "E2": [4, 12, 15], "E3": [17]},
14: {"RP": [5], "E1": [3, 6], "E2": [12, 14], "E3": [17]},
15: {"RP": [5], "E1": [3, 6, 18], "E2": [4, 12, 14], "E3": [17]},
16: {"RP": [5], "E1": [4, 6], "E2": [12, 13], "E3": [17]},
17: {"RP": [5], "E1": [4, 6], "E2": [9, 12], "E3": [17]},
18: {"RP": [5], "E1": [3, 4, 6], "E2": [11, 12, 18], "E3": [17]},
19: {"RP": [5], "E1": [7], "E2": [3, 10, 11], "E3": [17, 18]},
20: {"RP": [5], "E1": [3, 7], "E2": [11, 13, 16], "E3": [17, 18]},
21: {"RP": [5], "E1": [3, 7], "E2": [11, 12, 15], "E3": [17, 18]},
22: {"RP": [5], "E1": [3, 6, 16], "E2": [9, 12, 14], "E3": [17, 18]},
23: {"RP": [5], "E1": [4, 7], "E2": [11, 12, 14], "E3": [17, 18]},
24: {"RP": [5], "E1": [4, 6, 16], "E2": [8, 11, 13], "E3": [3, 17, 18]},
25: {"RP": [5], "E1": [4, 6, 16], "E2": [11, 12, 13], "E3": [3, 17, 18]},
}
# Center Field Error Chart
CF_ERROR_CHART: dict[int, dict[str, list[int]]] = {
0: {"RP": [5], "E1": [], "E2": [], "E3": []},
1: {"RP": [5], "E1": [3], "E2": [], "E3": [17]},
2: {"RP": [5], "E1": [3, 18], "E2": [], "E3": [16]},
3: {"RP": [5], "E1": [3, 18], "E2": [], "E3": [15]},
4: {"RP": [5], "E1": [4], "E2": [3, 15], "E3": [18]},
5: {"RP": [5], "E1": [4], "E2": [14], "E3": [18]},
6: {"RP": [5], "E1": [3, 4], "E2": [14, 17], "E3": [18]},
7: {"RP": [5], "E1": [16], "E2": [6, 15], "E3": [18]},
8: {"RP": [5], "E1": [16], "E2": [6, 15, 17], "E3": [3, 18]},
9: {"RP": [5], "E1": [16], "E2": [11], "E3": [3, 18]},
10: {"RP": [5], "E1": [4, 16], "E2": [14, 15, 17], "E3": [3, 18]},
11: {"RP": [5], "E1": [4, 16], "E2": [13, 15], "E3": [3, 18]},
12: {"RP": [5], "E1": [4, 16], "E2": [13, 14], "E3": [3, 18]},
13: {"RP": [5], "E1": [6], "E2": [4, 12, 15], "E3": [17]},
14: {"RP": [5], "E1": [3, 6], "E2": [12, 14], "E3": [17]},
15: {"RP": [5], "E1": [3, 6, 18], "E2": [4, 12, 14], "E3": [17]},
16: {"RP": [5], "E1": [4, 6], "E2": [12, 13], "E3": [17]},
17: {"RP": [5], "E1": [4, 6], "E2": [9, 12], "E3": [17]},
18: {"RP": [5], "E1": [3, 4, 6], "E2": [11, 12, 18], "E3": [17]},
19: {"RP": [5], "E1": [7], "E2": [3, 10, 11], "E3": [17, 18]},
20: {"RP": [5], "E1": [3, 7], "E2": [11, 13, 16], "E3": [17, 18]},
21: {"RP": [5], "E1": [3, 7], "E2": [11, 12, 15], "E3": [17, 18]},
22: {"RP": [5], "E1": [3, 6, 16], "E2": [9, 12, 14], "E3": [17, 18]},
23: {"RP": [5], "E1": [4, 7], "E2": [11, 12, 14], "E3": [17, 18]},
24: {"RP": [5], "E1": [4, 6, 16], "E2": [8, 11, 13], "E3": [3, 17, 18]},
25: {"RP": [5], "E1": [4, 6, 16], "E2": [11, 12, 13], "E3": [3, 17, 18]},
}
# Infield Error Charts
# Note: Infield charts do not use E3 (unlike outfield charts)
# Structure: same as OF but E3 is always empty
# Catcher Error Chart
CATCHER_ERROR_CHART: dict[int, dict[str, list[int]]] = {
0: {"RP": [5], "E1": [], "E2": [], "E3": []},
1: {"RP": [5], "E1": [17], "E2": [], "E3": []},
2: {"RP": [5], "E1": [3, 17, 18], "E2": [], "E3": []},
3: {"RP": [5], "E1": [3, 16, 10], "E2": [], "E3": []},
4: {"RP": [5], "E1": [16, 17], "E2": [18], "E3": []},
5: {"RP": [5], "E1": [4, 16, 17], "E2": [18], "E3": []},
6: {"RP": [5], "E1": [14], "E2": [18], "E3": []},
7: {"RP": [5], "E1": [3, 15, 16], "E2": [18], "E3": []},
8: {"RP": [5], "E1": [6, 15], "E2": [18], "E3": []},
9: {"RP": [5], "E1": [3, 13], "E2": [18], "E3": []},
10: {"RP": [5], "E1": [12], "E2": [18], "E3": []},
11: {"RP": [5], "E1": [3, 11], "E2": [18], "E3": []},
12: {"RP": [5], "E1": [6, 15, 16, 17], "E2": [3, 18], "E3": []},
13: {"RP": [5], "E1": [4, 6, 15, 16, 17], "E2": [3, 18], "E3": []},
14: {"RP": [5], "E1": [12, 16, 17], "E2": [3, 18], "E3": []},
15: {"RP": [5], "E1": [11, 15], "E2": [3, 18], "E3": []},
16: {"RP": [5], "E1": [7, 14, 16, 17], "E2": [3, 18], "E3": []},
}
# First Base Error Chart
FIRST_BASE_ERROR_CHART: dict[int, dict[str, list[int]]] = {
0: {"RP": [5], "E1": [], "E2": [], "E3": []},
1: {"RP": [5], "E1": [17, 18], "E2": [], "E3": []},
2: {"RP": [5], "E1": [3, 16, 18], "E2": [], "E3": []},
3: {"RP": [5], "E1": [3, 15], "E2": [18], "E3": []},
4: {"RP": [5], "E1": [14], "E2": [18], "E3": []},
5: {"RP": [5], "E1": [14, 17], "E2": [18], "E3": []},
6: {"RP": [5], "E1": [3, 13], "E2": [18], "E3": []},
7: {"RP": [5], "E1": [3, 9], "E2": [18], "E3": []},
8: {"RP": [5], "E1": [6, 15, 16, 17], "E2": [3, 18], "E3": []},
9: {"RP": [5], "E1": [7, 14, 17], "E2": [3, 18], "E3": []},
10: {"RP": [5], "E1": [11, 15], "E2": [3, 18], "E3": []},
11: {"RP": [5], "E1": [6, 8, 15], "E2": [3, 18], "E3": []},
12: {"RP": [5], "E1": [6, 9, 15], "E2": [3, 18], "E3": []},
13: {"RP": [5], "E1": [11, 13], "E2": [17], "E3": []},
14: {"RP": [5], "E1": [3, 9, 12], "E2": [17], "E3": []},
15: {"RP": [5], "E1": [7, 12, 14], "E2": [17], "E3": []},
16: {"RP": [5], "E1": [3, 11, 12, 16], "E2": [17], "E3": []},
17: {"RP": [5], "E1": [3, 6, 11, 12], "E2": [17], "E3": []},
18: {"RP": [5], "E1": [11, 12, 14], "E2": [17], "E3": []},
19: {"RP": [5], "E1": [10, 11, 15, 16], "E2": [17, 18], "E3": []},
20: {"RP": [5], "E1": [6, 10, 11, 15], "E2": [17, 18], "E3": []},
21: {"RP": [5], "E1": [3, 9, 10, 12], "E2": [17, 18], "E3": []},
22: {"RP": [5], "E1": [7, 11, 12, 14], "E2": [17, 18], "E3": []},
23: {"RP": [5], "E1": [10, 11, 12, 16], "E2": [17, 18], "E3": []},
24: {"RP": [5], "E1": [11, 12, 13, 14], "E2": [3, 17, 18], "E3": []},
25: {"RP": [5], "E1": [9, 11, 12, 14], "E2": [3, 17, 18], "E3": []},
26: {"RP": [5], "E1": [9, 12, 13, 14, 15], "E2": [3, 17, 18], "E3": []},
27: {"RP": [5], "E1": [7, 8, 11, 13, 14], "E2": [3, 17, 18], "E3": []},
28: {"RP": [5], "E1": [7, 11, 12, 13, 14], "E2": [3, 17, 18], "E3": []},
29: {"RP": [5], "E1": [9, 10, 11, 12, 17], "E2": [16], "E3": []},
30: {"RP": [5], "E1": [10, 11, 12, 13, 15, 18], "E2": [16], "E3": []},
}
# Second Base Error Chart
SECOND_BASE_ERROR_CHART: dict[int, dict[str, list[int]]] = {
0: {"RP": [5], "E1": [], "E2": [], "E3": []},
1: {"RP": [5], "E1": [18], "E2": [], "E3": []},
2: {"RP": [5], "E1": [3, 18], "E2": [], "E3": []},
3: {"RP": [5], "E1": [3, 17], "E2": [], "E3": []},
4: {"RP": [5], "E1": [3, 17], "E2": [18], "E3": []},
5: {"RP": [5], "E1": [16], "E2": [18], "E3": []},
6: {"RP": [5], "E1": [3, 16], "E2": [18], "E3": []},
8: {"RP": [5], "E1": [16, 17], "E2": [18], "E3": []},
10: {"RP": [5], "E1": [4, 16, 17], "E2": [18], "E3": []},
11: {"RP": [5], "E1": [15, 17], "E2": [18], "E3": []},
12: {"RP": [5], "E1": [15, 17], "E2": [3, 18], "E3": []},
13: {"RP": [5], "E1": [14], "E2": [3, 18], "E3": []},
14: {"RP": [5], "E1": [15, 16], "E2": [3, 18], "E3": []},
15: {"RP": [5], "E1": [14, 17], "E2": [3, 18], "E3": []},
16: {"RP": [5], "E1": [15, 16, 17], "E2": [3, 18], "E3": []},
17: {"RP": [5], "E1": [6, 15], "E2": [3, 18], "E3": []},
18: {"RP": [5], "E1": [13], "E2": [3, 18], "E3": []},
19: {"RP": [5], "E1": [6, 15, 17], "E2": [3, 18], "E3": []},
20: {"RP": [5], "E1": [3, 13, 18], "E2": [17], "E3": []},
21: {"RP": [5], "E1": [4, 13], "E2": [17], "E3": []},
22: {"RP": [5], "E1": [12, 18], "E2": [17], "E3": []},
23: {"RP": [5], "E1": [11], "E2": [17], "E3": []},
24: {"RP": [5], "E1": [11, 18], "E2": [17], "E3": []},
25: {"RP": [5], "E1": [3, 11, 18], "E2": [17], "E3": []},
26: {"RP": [5], "E1": [13, 15], "E2": [17], "E3": []},
27: {"RP": [5], "E1": [13, 15, 18], "E2": [17], "E3": []},
28: {"RP": [5], "E1": [3, 13, 15], "E2": [17, 18], "E3": []},
29: {"RP": [5], "E1": [3, 11, 16], "E2": [17, 18], "E3": []},
30: {"RP": [5], "E1": [12, 15], "E2": [17, 18], "E3": []},
32: {"RP": [5], "E1": [11, 15], "E2": [17, 18], "E3": []},
34: {"RP": [5], "E1": [12, 14], "E2": [17, 18], "E3": []},
37: {"RP": [5], "E1": [11, 15, 18], "E2": [3, 17, 18], "E3": []},
39: {"RP": [5], "E1": [12, 13], "E2": [3, 17, 18], "E3": []},
41: {"RP": [5], "E1": [11, 13], "E2": [3, 17, 18], "E3": []},
44: {"RP": [5], "E1": [9, 12, 18], "E2": [16], "E3": []},
47: {"RP": [5], "E1": [7, 12, 14], "E2": [16], "E3": []},
50: {"RP": [5], "E1": [11, 13, 15, 18], "E2": [16], "E3": []},
53: {"RP": [5], "E1": [11, 12, 15], "E2": [16, 18], "E3": []},
56: {"RP": [5], "E1": [6, 12, 13, 15], "E2": [16, 18], "E3": []},
59: {"RP": [5], "E1": [6, 11, 13, 15], "E2": [3, 16, 18], "E3": []},
62: {"RP": [5], "E1": [6, 11, 12, 15], "E2": [3, 16, 18], "E3": []},
65: {"RP": [5], "E1": [7, 12, 13, 14], "E2": [3, 16, 18], "E3": []},
68: {"RP": [5], "E1": [10, 11, 12], "E2": [16, 17], "E3": []},
71: {"RP": [5], "E1": [11, 12, 13, 15], "E2": [16, 17], "E3": []},
}
# Third Base Error Chart
THIRD_BASE_ERROR_CHART: dict[int, dict[str, list[int]]] = {
0: {"RP": [5], "E1": [], "E2": [], "E3": []},
1: {"RP": [5], "E1": [3], "E2": [18], "E3": []},
2: {"RP": [5], "E1": [3, 4], "E2": [18], "E3": []},
3: {"RP": [5], "E1": [3, 4], "E2": [17], "E3": []},
4: {"RP": [5], "E1": [3, 16], "E2": [17], "E3": []},
5: {"RP": [5], "E1": [15], "E2": [17], "E3": []},
6: {"RP": [5], "E1": [4, 15], "E2": [17], "E3": []},
8: {"RP": [5], "E1": [3, 15, 16], "E2": [17, 18], "E3": []},
10: {"RP": [5], "E1": [13], "E2": [3, 17, 18], "E3": []},
11: {"RP": [5], "E1": [6, 15, 17], "E2": [16], "E3": []},
12: {"RP": [5], "E1": [12], "E2": [16], "E3": []},
13: {"RP": [5], "E1": [11], "E2": [16, 18], "E3": []},
14: {"RP": [5], "E1": [3, 4, 14, 15], "E2": [16, 18], "E3": []},
15: {"RP": [5], "E1": [13, 15], "E2": [3, 16, 18], "E3": []},
16: {"RP": [5], "E1": [4, 7, 14], "E2": [3, 16, 18], "E3": []},
17: {"RP": [5], "E1": [12, 15], "E2": [16, 17], "E3": []},
18: {"RP": [5], "E1": [3, 11, 15], "E2": [16, 17], "E3": []},
19: {"RP": [5], "E1": [7, 14, 16, 17], "E2": [15], "E3": []},
20: {"RP": [5], "E1": [11, 14], "E2": [15], "E3": []},
21: {"RP": [5], "E1": [6, 11, 16], "E2": [15, 18], "E3": []},
22: {"RP": [5], "E1": [12, 14, 16], "E2": [15, 18], "E3": []},
23: {"RP": [5], "E1": [11, 13], "E2": [3, 15, 18], "E3": []},
24: {"RP": [5], "E1": [9, 12], "E2": [3, 15, 18], "E3": []},
25: {"RP": [5], "E1": [6, 8, 13], "E2": [15, 17], "E3": []},
26: {"RP": [5], "E1": [10, 11], "E2": [15, 17], "E3": []},
27: {"RP": [5], "E1": [9, 12, 16], "E2": [15, 17, 18], "E3": []},
28: {"RP": [5], "E1": [11, 13, 15], "E2": [14], "E3": []},
29: {"RP": [5], "E1": [9, 12, 15], "E2": [14], "E3": []},
30: {"RP": [5], "E1": [6, 8, 13, 15], "E2": [14, 18], "E3": []},
31: {"RP": [5], "E1": [10, 11, 15], "E2": [14, 18], "E3": []},
32: {"RP": [5], "E1": [11, 13, 14, 17], "E2": [15, 16, 18], "E3": []},
33: {"RP": [5], "E1": [8, 11, 13], "E2": [15, 16, 18], "E3": []},
34: {"RP": [5], "E1": [6, 9, 12, 15], "E2": [14, 17], "E3": []},
35: {"RP": [5], "E1": [11, 12, 13], "E2": [14, 17], "E3": []},
37: {"RP": [5], "E1": [9, 11, 12], "E2": [15, 16, 17], "E3": []},
39: {"RP": [5], "E1": [7, 9, 12, 14, 18], "E2": [6, 15], "E3": []},
41: {"RP": [5], "E1": [10, 11, 12, 16], "E2": [13], "E3": []},
44: {"RP": [5], "E1": [4, 11, 12, 13, 14], "E2": [6, 15, 17], "E3": []},
47: {"RP": [5], "E1": [8, 9, 11, 12], "E2": [13, 17], "E3": []},
50: {"RP": [5], "E1": [9, 10, 11, 12], "E2": [14, 15, 18], "E3": []},
53: {"RP": [5], "E1": [6, 8, 9, 10, 11], "E2": [13, 16], "E3": []},
56: {"RP": [5], "E1": [8, 9, 10, 15, 14, 17], "E2": [3, 11, 18], "E3": []},
59: {"RP": [5], "E1": [4, 7, 9, 10, 11, 12], "E2": [13, 15], "E3": []},
62: {"RP": [5], "E1": [7, 8, 9, 10, 11, 14], "E2": [12, 16, 18], "E3": []},
65: {"RP": [5], "E1": [7, 8, 9, 10, 12, 13], "E2": [11, 16, 18], "E3": []},
}
# Shortstop Error Chart
SHORTSTOP_ERROR_CHART: dict[int, dict[str, list[int]]] = {
0: {"RP": [5], "E1": [], "E2": [], "E3": []},
1: {"RP": [5], "E1": [18], "E2": [], "E3": []},
2: {"RP": [5], "E1": [3, 18], "E2": [], "E3": []},
3: {"RP": [5], "E1": [17], "E2": [], "E3": []},
4: {"RP": [5], "E1": [17], "E2": [18], "E3": []},
5: {"RP": [5], "E1": [3, 17], "E2": [18], "E3": []},
6: {"RP": [5], "E1": [16], "E2": [18], "E3": []},
7: {"RP": [5], "E1": [3, 16], "E2": [18], "E3": []},
8: {"RP": [5], "E1": [16, 17], "E2": [18], "E3": []},
10: {"RP": [5], "E1": [6, 17], "E2": [3, 18], "E3": []},
12: {"RP": [5], "E1": [15], "E2": [3, 18], "E3": []},
14: {"RP": [5], "E1": [4, 15], "E2": [17], "E3": []},
16: {"RP": [5], "E1": [14], "E2": [17], "E3": []},
17: {"RP": [5], "E1": [15, 16], "E2": [17], "E3": []},
18: {"RP": [5], "E1": [15, 16, 18], "E2": [17], "E3": []},
19: {"RP": [5], "E1": [4, 14], "E2": [17], "E3": []},
20: {"RP": [5], "E1": [4, 15, 16], "E2": [17], "E3": []},
21: {"RP": [5], "E1": [6, 15], "E2": [17], "E3": []},
22: {"RP": [5], "E1": [6, 15], "E2": [17, 18], "E3": []},
23: {"RP": [5], "E1": [3, 13], "E2": [17, 18], "E3": []},
24: {"RP": [5], "E1": [4, 6, 15], "E2": [17, 18], "E3": []},
25: {"RP": [5], "E1": [4, 13], "E2": [17, 18], "E3": []},
26: {"RP": [5], "E1": [12], "E2": [17, 18], "E3": []},
27: {"RP": [5], "E1": [3, 12], "E2": [17, 18], "E3": []},
28: {"RP": [5], "E1": [6, 15, 16], "E2": [3, 17, 18], "E3": []},
29: {"RP": [5], "E1": [11], "E2": [3, 17, 18], "E3": []},
30: {"RP": [5], "E1": [4, 12], "E2": [3, 17, 18], "E3": []},
31: {"RP": [5], "E1": [4, 6, 15, 16], "E2": [3, 17, 18], "E3": []},
32: {"RP": [5], "E1": [13, 15], "E2": [3, 17, 18], "E3": []},
33: {"RP": [5], "E1": [13, 15], "E2": [16], "E3": []},
34: {"RP": [5], "E1": [13, 15, 18], "E2": [16], "E3": []},
36: {"RP": [5], "E1": [13, 15, 17], "E2": [16], "E3": []},
38: {"RP": [5], "E1": [13, 14], "E2": [16], "E3": []},
40: {"RP": [5], "E1": [11, 15], "E2": [16, 18], "E3": []},
42: {"RP": [5], "E1": [12, 14], "E2": [16, 18], "E3": []},
44: {"RP": [5], "E1": [8, 13], "E2": [16, 18], "E3": []},
48: {"RP": [5], "E1": [6, 12, 15], "E2": [3, 18, 18], "E3": []},
52: {"RP": [5], "E1": [11, 13, 18], "E2": [16, 17], "E3": []},
56: {"RP": [5], "E1": [11, 12, 18], "E2": [16, 17], "E3": []},
60: {"RP": [5], "E1": [7, 11, 14], "E2": [15], "E3": []},
64: {"RP": [5], "E1": [6, 9, 12], "E2": [15, 18], "E3": []},
68: {"RP": [5], "E1": [9, 12, 14], "E2": [15, 18], "E3": []},
72: {"RP": [5], "E1": [6, 11, 13, 15], "E2": [4, 16, 17], "E3": []},
76: {"RP": [5], "E1": [9, 12, 13], "E2": [15, 17], "E3": []},
80: {"RP": [5], "E1": [4, 11, 12, 13], "E2": [15, 17], "E3": []},
84: {"RP": [5], "E1": [10, 11, 12], "E2": [3, 15, 17], "E3": []},
88: {"RP": [5], "E1": [9, 11, 12, 16], "E2": [14], "E3": []},
}
# Pitcher Error Chart
PITCHER_ERROR_CHART: dict[int, dict[str, list[int]]] = {
0: {"RP": [5], "E1": [], "E2": [], "E3": []},
4: {"RP": [5], "E1": [14], "E2": [18], "E3": []},
6: {"RP": [5], "E1": [3, 13], "E2": [18], "E3": []},
7: {"RP": [5], "E1": [3, 12], "E2": [18], "E3": []},
8: {"RP": [5], "E1": [6, 15, 16, 17], "E2": [3, 18], "E3": []},
10: {"RP": [5], "E1": [11, 15], "E2": [3, 18], "E3": []},
11: {"RP": [5], "E1": [12, 15, 16], "E2": [3, 18], "E3": []},
12: {"RP": [5], "E1": [6, 12, 15], "E2": [3, 18], "E3": []},
13: {"RP": [5], "E1": [11, 13], "E2": [17], "E3": []},
14: {"RP": [5], "E1": [7, 13, 14], "E2": [17], "E3": []},
15: {"RP": [5], "E1": [4, 11, 12], "E2": [17], "E3": []},
16: {"RP": [5], "E1": [4, 9, 12, 16], "E2": [17], "E3": []},
17: {"RP": [5], "E1": [3, 6, 11, 12], "E2": [17], "E3": []},
18: {"RP": [5], "E1": [11, 12, 14], "E2": [17], "E3": []},
19: {"RP": [5], "E1": [6, 9, 12, 15], "E2": [17, 18], "E3": []},
20: {"RP": [5], "E1": [6, 10, 11, 15], "E2": [17, 18], "E3": []},
21: {"RP": [5], "E1": [7, 11, 13, 14], "E2": [17, 18], "E3": []},
22: {"RP": [5], "E1": [8, 12, 13, 14], "E2": [17, 18], "E3": []},
23: {"RP": [5], "E1": [10, 11, 12, 16], "E2": [17, 18], "E3": []},
24: {"RP": [5], "E1": [10, 11, 12, 15], "E2": [17, 18], "E3": []},
26: {"RP": [5], "E1": [9, 12, 13, 14, 15], "E2": [3, 17, 18], "E3": []},
27: {"RP": [5], "E1": [10, 11, 12, 13], "E2": [3, 17, 18], "E3": []},
28: {"RP": [5], "E1": [9, 10, 11, 12], "E2": [3, 17, 18], "E3": []},
30: {"RP": [5], "E1": [3, 10, 11, 12, 13, 15], "E2": [16], "E3": []},
31: {"RP": [5], "E1": [10, 11, 12, 13, 14], "E2": [16], "E3": []},
33: {"RP": [5], "E1": [3, 8, 10, 11, 12, 13], "E2": [16], "E3": []},
34: {"RP": [5], "E1": [9, 10, 11, 12, 13], "E2": [16, 18], "E3": []},
35: {"RP": [5], "E1": [9, 10, 11, 12, 14, 15], "E2": [16, 18], "E3": []},
36: {"RP": [5], "E1": [3, 4, 6, 7, 9, 10, 11, 12], "E2": [16, 18], "E3": []},
38: {"RP": [5], "E1": [6, 8, 10, 11, 12, 13, 15], "E2": [16, 18], "E3": []},
39: {"RP": [5], "E1": [6, 7, 8, 9, 10, 12, 13], "E2": [3, 16, 18], "E3": []},
40: {"RP": [5], "E1": [4, 6, 9, 10, 11, 12, 13, 15], "E2": [3, 16, 18], "E3": []},
42: {"RP": [5], "E1": [7, 9, 10, 11, 12, 13, 14], "E2": [3, 16, 18], "E3": []},
43: {"RP": [5], "E1": [6, 7, 8, 9, 10, 12, 13, 14], "E2": [3, 16, 18], "E3": []},
44: {"RP": [5], "E1": [3, 7, 8, 9, 10, 11, 12, 13], "E2": [16, 17], "E3": []},
46: {"RP": [5], "E1": [6, 8, 9, 10, 11, 12, 13, 15], "E2": [16, 17, 18], "E3": []},
47: {"RP": [5], "E1": [7, 8, 9, 10, 11, 12, 13, 15], "E2": [16, 17, 18], "E3": []},
48: {"RP": [5], "E1": [7, 8, 9, 10, 11, 12, 13, 14], "E2": [16, 17, 18], "E3": []},
50: {"RP": [5], "E1": [7, 8, 9, 10, 11, 12, 13, 14], "E2": [15, 17], "E3": []},
51: {"RP": [5], "E1": [7, 8, 9, 10, 11, 12, 13, 14], "E2": [15, 16], "E3": []},
}
# ============================================================================
# HOLDING RUNNER RESPONSIBILITY CHART
# ============================================================================
def get_fielders_holding_runners(
runner_bases: list[int], batter_handedness: str
) -> list[str]:
"""
Determine which fielders are responsible for holding runners.
Used to determine if G2#/G3# results should convert to SI2.
Args:
runner_bases: List of bases with runners (e.g., [1, 3] for R1 and R3)
batter_handedness: 'L' or 'R'
Returns:
List of position codes responsible for holds (e.g., ['1B', 'SS'])
"""
if not runner_bases:
return []
holding_positions = []
mif_vs_batter = "2B" if batter_handedness.lower() == "r" else "SS"
if 1 in runner_bases:
holding_positions.append("1B")
holding_positions.append(mif_vs_batter)
if 2 in runner_bases and mif_vs_batter not in holding_positions:
holding_positions.append(mif_vs_batter)
if 3 in runner_bases:
holding_positions.append("3B")
return holding_positions
# ============================================================================
# ERROR CHART LOOKUP HELPER
# ============================================================================
def get_error_chart_for_position(position: str) -> dict[int, dict[str, list[int]]]:
"""
Get error chart for a specific position.
Args:
position: Position code (P, C, 1B, 2B, 3B, SS, LF, CF, RF)
Returns:
Error chart dict
Raises:
ValueError: If position not recognized
"""
charts = {
"P": PITCHER_ERROR_CHART,
"C": CATCHER_ERROR_CHART,
"1B": FIRST_BASE_ERROR_CHART,
"2B": SECOND_BASE_ERROR_CHART,
"3B": THIRD_BASE_ERROR_CHART,
"SS": SHORTSTOP_ERROR_CHART,
"LF": LF_RF_ERROR_CHART,
"RF": LF_RF_ERROR_CHART,
"CF": CF_ERROR_CHART,
}
if position not in charts:
raise ValueError(f"Unknown position: {position}")
return charts[position]