Update major-domo skill CLI refactor and plugin/config updates
- Refactor major-domo skill: api_client.py, cli.py, and CLI modules (admin, common, injuries, results, schedule, transactions) with significant simplification (-275 lines net) - Update CLI_REFERENCE.md and SKILL.md docs for major-domo - Update create-scheduled-task SKILL.md - Update plugins blocklist.json and known_marketplaces.json - Add patterns/ directory to repo - Update CLAUDE.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
655dc64033
commit
43d32e9b9d
@ -32,8 +32,7 @@ Automatic loads are NOT enough — Read loads required CLAUDE.md context along t
|
||||
> **Fallback:** If MCP is unavailable, use `tea` CLI. Always pass `--repo owner/name`.
|
||||
|
||||
## Tech Preferences
|
||||
- Python with uv for package/environment management
|
||||
- Utilize dependency injection pattern whenever possible
|
||||
- Python → see [`~/.claude/patterns/python.md`](patterns/python.md) (uv, DI, FastAPI hexagonal architecture)
|
||||
- Never add lazy imports to middle of file
|
||||
|
||||
## SSH
|
||||
|
||||
38
patterns/python.md
Normal file
38
patterns/python.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Python Patterns & Preferences
|
||||
|
||||
## Package Management
|
||||
- Always use `uv` for package and environment management
|
||||
- Never use pip directly; prefer `uv pip`, `uv sync`, `uv run`
|
||||
|
||||
## Linting & Formatting
|
||||
- Use **Ruff** for linting and **Black** for formatting
|
||||
- Include both as dev dependencies in new projects (`uv add --dev ruff black`)
|
||||
|
||||
## Code Style
|
||||
- Utilize dependency injection pattern whenever possible
|
||||
- Never add lazy imports to the middle of a file — all imports at the top
|
||||
|
||||
## FastAPI / Backend Services
|
||||
Use **Ports & Adapters (Hexagonal Architecture)** to cleanly separate concerns:
|
||||
|
||||
```
|
||||
project/
|
||||
domain/ # Pure business logic, no framework imports
|
||||
models.py # Domain entities / value objects
|
||||
services.py # Use cases / business rules
|
||||
ports.py # Abstract interfaces (ABC) for external dependencies
|
||||
adapters/
|
||||
inbound/ # FastAPI routers, CLI handlers — drive the domain
|
||||
outbound/ # Database repos, API clients — implement domain ports
|
||||
config/ # App wiring, dependency injection, settings
|
||||
main.py # FastAPI app creation, adapter registration
|
||||
```
|
||||
|
||||
### Key rules
|
||||
- **Domain layer has zero framework imports** — no FastAPI, SQLAlchemy, httpx, etc.
|
||||
- **Ports** are abstract base classes in the domain that define what the domain *needs* (e.g., `UserRepository(ABC)`)
|
||||
- **Outbound adapters** implement ports (e.g., `PostgresUserRepository(UserRepository)`)
|
||||
- **Inbound adapters** (routers) depend on domain services, never on outbound adapters directly
|
||||
- **Dependency injection** wires adapters to ports at startup in `config/` or `main.py`
|
||||
- **Tests** can swap outbound adapters for in-memory fakes by implementing the same port
|
||||
- Models passed across boundaries should be domain models or DTOs — never ORM models in router responses
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"fetchedAt": "2026-03-07T20:00:33.830Z",
|
||||
"fetchedAt": "2026-03-09T07:00:31.477Z",
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "code-review@claude-plugins-official",
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"url": "https://github.com/anthropics/claude-plugins-official.git"
|
||||
},
|
||||
"installLocation": "/home/cal/.claude/plugins/marketplaces/claude-plugins-official",
|
||||
"lastUpdated": "2026-03-05T17:30:45.691Z"
|
||||
"lastUpdated": "2026-03-08T20:02:31.612Z"
|
||||
},
|
||||
"claude-code-plugins": {
|
||||
"source": {
|
||||
@ -13,6 +13,6 @@
|
||||
"repo": "anthropics/claude-code"
|
||||
},
|
||||
"installLocation": "/home/cal/.claude/plugins/marketplaces/claude-code-plugins",
|
||||
"lastUpdated": "2026-03-07T20:45:27.810Z"
|
||||
"lastUpdated": "2026-03-09T07:00:34.612Z"
|
||||
}
|
||||
}
|
||||
@ -225,6 +225,7 @@ ls -lt ~/.local/share/claude-scheduled/logs/<task-name>/
|
||||
| Task | Schedule | Budget | Description |
|
||||
|------|----------|--------|-------------|
|
||||
| `backlog-triage` | Daily 09:00 | $0.75 | Scan Gitea issues across repos, prioritize, suggest focus |
|
||||
| `sync-config` | Daily 02:00 | $0.25 | Sync ~/.claude and ~/dotfiles to Gitea (bash pre-check, Claude only on changes) |
|
||||
|
||||
## How the Runner Works
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ cli.py player move --batch "N1:T1,N2:T2" # Batch moves
|
||||
```bash
|
||||
cli.py team list [--active] # List teams
|
||||
cli.py team get CAR # Team details
|
||||
cli.py team roster CAR # Roster breakdown (Active/Short IL/Long IL)
|
||||
cli.py team roster CAR # Roster breakdown (Active/Injured List/Minor League)
|
||||
```
|
||||
|
||||
### Standings
|
||||
@ -92,8 +92,7 @@ cli.py admin refresh-batting --season 11 # Specific season
|
||||
## Key Notes
|
||||
|
||||
- `team get` shows salary_cap in formatted output; use `--json` for all fields
|
||||
- `team roster` shows Active/Short IL/Long IL with WARA values for Active only
|
||||
- "Long IL" = MiL (minor leagues)
|
||||
- `team roster` shows Active/Injured List/Minor League with WARA values for Active only
|
||||
- For individual player lookups, use `player get "Name"` — bulk queries can timeout
|
||||
- `transactions simulate` validates compliance without making changes
|
||||
- `stats` commands support standard baseball stats sorting (woba, obp, slg, era, whip, fip, etc.)
|
||||
|
||||
@ -38,7 +38,7 @@ Comprehensive system for managing the **Strat-o-Matic Baseball Association (SBA)
|
||||
```bash
|
||||
python3 ~/.claude/skills/major-domo/cli.py <command>
|
||||
```
|
||||
See `CLI_REFERENCE.md` for full command list and flag ordering rules.
|
||||
**IMPORTANT**: Before running ANY CLI command, read `~/.claude/skills/major-domo/CLI_REFERENCE.md` for the full command list, flag ordering rules, and available options. Do not guess at CLI syntax — the reference is authoritative.
|
||||
|
||||
### Before Every Commit
|
||||
- Run `git remote -v` to verify repository
|
||||
|
||||
@ -6,7 +6,7 @@ Shared API client for all Major Domo (SBA) operations.
|
||||
Provides methods for interacting with teams, players, standings, stats, transactions, and more.
|
||||
|
||||
Environment Variables:
|
||||
API_TOKEN: Bearer token for API authentication (required)
|
||||
API_TOKEN: Bearer token for API authentication (required for write operations)
|
||||
DATABASE: 'prod' or 'dev' (default: prod)
|
||||
"""
|
||||
|
||||
@ -15,6 +15,11 @@ import sys
|
||||
from typing import Optional, Dict, List, Any, Literal
|
||||
import requests
|
||||
|
||||
_BASE_URLS = {
|
||||
"prod": "https://api.sba.manticorum.com/v3",
|
||||
"dev": "http://10.10.0.42:8000/api/v3",
|
||||
}
|
||||
|
||||
|
||||
class MajorDomoAPI:
|
||||
"""
|
||||
@ -36,7 +41,12 @@ class MajorDomoAPI:
|
||||
standings = api.get_standings(season=12, division_abbrev='ALE')
|
||||
"""
|
||||
|
||||
def __init__(self, environment: str = 'prod', token: Optional[str] = None, verbose: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
environment: str = "prod",
|
||||
token: Optional[str] = None,
|
||||
verbose: bool = False,
|
||||
):
|
||||
"""
|
||||
Initialize API client
|
||||
|
||||
@ -46,38 +56,28 @@ class MajorDomoAPI:
|
||||
verbose: Print request/response details
|
||||
"""
|
||||
self.env = environment.lower()
|
||||
self.base_url = _BASE_URLS.get(self.env, _BASE_URLS["prod"])
|
||||
|
||||
# Set base URL based on environment
|
||||
if 'prod' in self.env:
|
||||
self.base_url = 'https://api.sba.manticorum.com/v3'
|
||||
else:
|
||||
self.base_url = 'http://10.10.0.42:8000/api/v3' # Docker dev container
|
||||
|
||||
self.token = token or os.getenv('API_TOKEN')
|
||||
self.token = token or os.getenv("API_TOKEN")
|
||||
self.verbose = verbose
|
||||
|
||||
if not self.token:
|
||||
raise ValueError(
|
||||
"API_TOKEN environment variable required. "
|
||||
"Set it with: export API_TOKEN='your-token-here'"
|
||||
)
|
||||
|
||||
self.headers = {
|
||||
'Authorization': f'Bearer {self.token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
self.headers = {"Content-Type": "application/json"}
|
||||
if self.token:
|
||||
self.headers["Authorization"] = f"Bearer {self.token}"
|
||||
|
||||
def _log(self, message: str):
|
||||
"""Print message if verbose mode enabled"""
|
||||
if self.verbose:
|
||||
print(f"[API] {message}")
|
||||
|
||||
def _build_url(self, endpoint: str, object_id: Optional[int] = None, **params) -> str:
|
||||
def _build_url(
|
||||
self, endpoint: str, object_id: Optional[int] = None, **params
|
||||
) -> str:
|
||||
"""Build API URL with query parameters"""
|
||||
url = f'{self.base_url}/{endpoint}'
|
||||
url = f"{self.base_url}/{endpoint}"
|
||||
|
||||
if object_id is not None:
|
||||
url += f'/{object_id}'
|
||||
url += f"/{object_id}"
|
||||
|
||||
# Add query parameters
|
||||
if params:
|
||||
@ -85,15 +85,15 @@ class MajorDomoAPI:
|
||||
for key, value in params.items():
|
||||
if value is not None:
|
||||
if isinstance(value, bool):
|
||||
param_parts.append(f'{key}={str(value).lower()}')
|
||||
param_parts.append(f"{key}={str(value).lower()}")
|
||||
elif isinstance(value, list):
|
||||
for item in value:
|
||||
param_parts.append(f'{key}={item}')
|
||||
param_parts.append(f"{key}={item}")
|
||||
else:
|
||||
param_parts.append(f'{key}={value}')
|
||||
param_parts.append(f"{key}={value}")
|
||||
|
||||
if param_parts:
|
||||
url += '?' + '&'.join(param_parts)
|
||||
url += "?" + "&".join(param_parts)
|
||||
|
||||
return url
|
||||
|
||||
@ -101,7 +101,13 @@ class MajorDomoAPI:
|
||||
# Low-level HTTP methods
|
||||
# ====================
|
||||
|
||||
def get(self, endpoint: str, object_id: Optional[int] = None, timeout: int = 10, **params) -> Any:
|
||||
def get(
|
||||
self,
|
||||
endpoint: str,
|
||||
object_id: Optional[int] = None,
|
||||
timeout: int = 10,
|
||||
**params,
|
||||
) -> Any:
|
||||
"""GET request to API"""
|
||||
url = self._build_url(endpoint, object_id=object_id, **params)
|
||||
self._log(f"GET {url}")
|
||||
@ -109,24 +115,47 @@ class MajorDomoAPI:
|
||||
response.raise_for_status()
|
||||
return response.json() if response.text else None
|
||||
|
||||
def post(self, endpoint: str, payload: Optional[Dict] = None, timeout: int = 10, **params) -> Any:
|
||||
def _require_token(self, method: str):
|
||||
"""Raise if no API token is set (required for write operations)."""
|
||||
if not self.token:
|
||||
raise ValueError(
|
||||
f"{method} requires API_TOKEN. Set it with: export API_TOKEN='your-token-here'"
|
||||
)
|
||||
|
||||
def post(
|
||||
self, endpoint: str, payload: Optional[Dict] = None, timeout: int = 10, **params
|
||||
) -> Any:
|
||||
"""POST request to API"""
|
||||
self._require_token("POST")
|
||||
url = self._build_url(endpoint, **params)
|
||||
self._log(f"POST {url}")
|
||||
response = requests.post(url, headers=self.headers, json=payload, timeout=timeout)
|
||||
response = requests.post(
|
||||
url, headers=self.headers, json=payload, timeout=timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json() if response.text else {}
|
||||
|
||||
def patch(self, endpoint: str, object_id: Optional[int] = None, payload: Optional[Dict] = None, timeout: int = 10, **params) -> Any:
|
||||
def patch(
|
||||
self,
|
||||
endpoint: str,
|
||||
object_id: Optional[int] = None,
|
||||
payload: Optional[Dict] = None,
|
||||
timeout: int = 10,
|
||||
**params,
|
||||
) -> Any:
|
||||
"""PATCH request to API"""
|
||||
self._require_token("PATCH")
|
||||
url = self._build_url(endpoint, object_id=object_id, **params)
|
||||
self._log(f"PATCH {url}")
|
||||
response = requests.patch(url, headers=self.headers, json=payload, timeout=timeout)
|
||||
response = requests.patch(
|
||||
url, headers=self.headers, json=payload, timeout=timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json() if response.text else {}
|
||||
|
||||
def delete(self, endpoint: str, object_id: int, timeout: int = 10) -> str:
|
||||
"""DELETE request to API"""
|
||||
self._require_token("DELETE")
|
||||
url = self._build_url(endpoint, object_id=object_id)
|
||||
self._log(f"DELETE {url}")
|
||||
response = requests.delete(url, headers=self.headers, timeout=timeout)
|
||||
@ -147,7 +176,7 @@ class MajorDomoAPI:
|
||||
Returns:
|
||||
Current status dict with season, week, trade_deadline, etc.
|
||||
"""
|
||||
return self.get('current', season=season)
|
||||
return self.get("current", season=season)
|
||||
|
||||
def update_current(self, current_id: int, **updates) -> Dict:
|
||||
"""
|
||||
@ -160,13 +189,18 @@ class MajorDomoAPI:
|
||||
Returns:
|
||||
Updated current status dict
|
||||
"""
|
||||
return self.patch('current', object_id=current_id, **updates)
|
||||
return self.patch("current", object_id=current_id, **updates)
|
||||
|
||||
# ====================
|
||||
# Team Operations
|
||||
# ====================
|
||||
|
||||
def get_team(self, team_id: Optional[int] = None, abbrev: Optional[str] = None, season: Optional[int] = None) -> Dict:
|
||||
def get_team(
|
||||
self,
|
||||
team_id: Optional[int] = None,
|
||||
abbrev: Optional[str] = None,
|
||||
season: Optional[int] = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
Get a team by ID or abbreviation
|
||||
|
||||
@ -179,12 +213,15 @@ class MajorDomoAPI:
|
||||
Team dict
|
||||
"""
|
||||
if team_id:
|
||||
return self.get('teams', object_id=team_id)
|
||||
return self.get("teams", object_id=team_id)
|
||||
elif abbrev:
|
||||
result = self.get('teams', team_abbrev=[abbrev.upper()], season=season)
|
||||
teams = result.get('teams', [])
|
||||
result = self.get("teams", team_abbrev=[abbrev.upper()], season=season)
|
||||
teams = result.get("teams", [])
|
||||
if not teams:
|
||||
raise ValueError(f"Team '{abbrev}' not found" + (f" in season {season}" if season else ""))
|
||||
raise ValueError(
|
||||
f"Team '{abbrev}' not found"
|
||||
+ (f" in season {season}" if season else "")
|
||||
)
|
||||
return teams[0]
|
||||
else:
|
||||
raise ValueError("Must provide team_id or abbrev")
|
||||
@ -196,7 +233,7 @@ class MajorDomoAPI:
|
||||
manager_id: Optional[List[int]] = None,
|
||||
team_abbrev: Optional[List[str]] = None,
|
||||
active_only: bool = False,
|
||||
short_output: bool = False
|
||||
short_output: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
List teams
|
||||
@ -213,21 +250,21 @@ class MajorDomoAPI:
|
||||
List of team dicts
|
||||
"""
|
||||
result = self.get(
|
||||
'teams',
|
||||
"teams",
|
||||
season=season,
|
||||
owner_id=owner_id,
|
||||
manager_id=manager_id,
|
||||
team_abbrev=team_abbrev,
|
||||
active_only=active_only,
|
||||
short_output=short_output
|
||||
short_output=short_output,
|
||||
)
|
||||
return result.get('teams', [])
|
||||
return result.get("teams", [])
|
||||
|
||||
def get_team_roster(
|
||||
self,
|
||||
team_id: int,
|
||||
which: Literal['current', 'next'] = 'current',
|
||||
sort: Optional[str] = None
|
||||
which: Literal["current", "next"] = "current",
|
||||
sort: Optional[str] = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
Get team roster breakdown
|
||||
@ -238,9 +275,9 @@ class MajorDomoAPI:
|
||||
sort: Sort method (e.g., 'wara-desc')
|
||||
|
||||
Returns:
|
||||
Roster dict with active/shortil/longil player lists
|
||||
Roster dict with active (Active), shortil (Injured List), longil (Minor League) player lists
|
||||
"""
|
||||
return self.get(f'teams/{team_id}/roster/{which}', sort=sort)
|
||||
return self.get(f"teams/{team_id}/roster/{which}", sort=sort)
|
||||
|
||||
def update_team(self, team_id: int, **updates) -> Dict:
|
||||
"""
|
||||
@ -253,7 +290,7 @@ class MajorDomoAPI:
|
||||
Returns:
|
||||
Updated team dict
|
||||
"""
|
||||
return self.patch('teams', object_id=team_id, **updates)
|
||||
return self.patch("teams", object_id=team_id, **updates)
|
||||
|
||||
# ====================
|
||||
# Player Operations
|
||||
@ -264,7 +301,7 @@ class MajorDomoAPI:
|
||||
player_id: Optional[int] = None,
|
||||
name: Optional[str] = None,
|
||||
season: Optional[int] = None,
|
||||
short_output: bool = False
|
||||
short_output: bool = False,
|
||||
) -> Optional[Dict]:
|
||||
"""
|
||||
Get a player by ID or name
|
||||
@ -279,10 +316,12 @@ class MajorDomoAPI:
|
||||
Player dict or None if not found
|
||||
"""
|
||||
if player_id:
|
||||
return self.get('players', object_id=player_id, short_output=short_output)
|
||||
return self.get("players", object_id=player_id, short_output=short_output)
|
||||
elif name and season:
|
||||
result = self.get('players', season=season, name=name, short_output=short_output)
|
||||
players = result.get('players', [])
|
||||
result = self.get(
|
||||
"players", season=season, name=name, short_output=short_output
|
||||
)
|
||||
players = result.get("players", [])
|
||||
return players[0] if players else None
|
||||
else:
|
||||
raise ValueError("Must provide player_id or (name and season)")
|
||||
@ -295,7 +334,7 @@ class MajorDomoAPI:
|
||||
strat_code: Optional[List[str]] = None,
|
||||
is_injured: Optional[bool] = None,
|
||||
sort: Optional[str] = None,
|
||||
short_output: bool = False
|
||||
short_output: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
List players with filters
|
||||
@ -313,23 +352,23 @@ class MajorDomoAPI:
|
||||
List of player dicts
|
||||
"""
|
||||
result = self.get(
|
||||
'players',
|
||||
"players",
|
||||
season=season,
|
||||
team_id=team_id,
|
||||
pos=pos,
|
||||
strat_code=strat_code,
|
||||
is_injured=is_injured,
|
||||
sort=sort,
|
||||
short_output=short_output
|
||||
short_output=short_output,
|
||||
)
|
||||
return result.get('players', [])
|
||||
return result.get("players", [])
|
||||
|
||||
def search_players(
|
||||
self,
|
||||
query: str,
|
||||
season: Optional[int] = None,
|
||||
limit: int = 10,
|
||||
short_output: bool = False
|
||||
short_output: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Fuzzy search players by name
|
||||
@ -344,13 +383,13 @@ class MajorDomoAPI:
|
||||
List of player dicts
|
||||
"""
|
||||
result = self.get(
|
||||
'players/search',
|
||||
"players/search",
|
||||
q=query,
|
||||
season=season,
|
||||
limit=limit,
|
||||
short_output=short_output
|
||||
short_output=short_output,
|
||||
)
|
||||
return result.get('players', [])
|
||||
return result.get("players", [])
|
||||
|
||||
def update_player(self, player_id: int, **updates) -> Dict:
|
||||
"""
|
||||
@ -363,7 +402,7 @@ class MajorDomoAPI:
|
||||
Returns:
|
||||
Updated player dict
|
||||
"""
|
||||
return self.patch('players', object_id=player_id, **updates)
|
||||
return self.patch("players", object_id=player_id, **updates)
|
||||
|
||||
# ====================
|
||||
# Standings Operations
|
||||
@ -375,7 +414,7 @@ class MajorDomoAPI:
|
||||
team_id: Optional[List[int]] = None,
|
||||
league_abbrev: Optional[str] = None,
|
||||
division_abbrev: Optional[str] = None,
|
||||
short_output: bool = False
|
||||
short_output: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get league standings
|
||||
@ -391,14 +430,14 @@ class MajorDomoAPI:
|
||||
List of standings dicts (sorted by win percentage)
|
||||
"""
|
||||
result = self.get(
|
||||
'standings',
|
||||
"standings",
|
||||
season=season,
|
||||
team_id=team_id,
|
||||
league_abbrev=league_abbrev,
|
||||
division_abbrev=division_abbrev,
|
||||
short_output=short_output
|
||||
short_output=short_output,
|
||||
)
|
||||
return result.get('standings', [])
|
||||
return result.get("standings", [])
|
||||
|
||||
def get_team_standings(self, team_id: int) -> Dict:
|
||||
"""
|
||||
@ -410,7 +449,7 @@ class MajorDomoAPI:
|
||||
Returns:
|
||||
Standings dict
|
||||
"""
|
||||
return self.get(f'standings/team/{team_id}')
|
||||
return self.get(f"standings/team/{team_id}")
|
||||
|
||||
def recalculate_standings(self, season: int) -> str:
|
||||
"""
|
||||
@ -422,7 +461,7 @@ class MajorDomoAPI:
|
||||
Returns:
|
||||
Success message
|
||||
"""
|
||||
return self.post(f'standings/s{season}/recalculate')
|
||||
return self.post(f"standings/s{season}/recalculate")
|
||||
|
||||
# ====================
|
||||
# Transaction Operations
|
||||
@ -439,7 +478,7 @@ class MajorDomoAPI:
|
||||
player_name: Optional[List[str]] = None,
|
||||
player_id: Optional[List[int]] = None,
|
||||
move_id: Optional[str] = None,
|
||||
short_output: bool = False
|
||||
short_output: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get transactions with filters
|
||||
@ -460,7 +499,7 @@ class MajorDomoAPI:
|
||||
List of transaction dicts
|
||||
"""
|
||||
result = self.get(
|
||||
'transactions',
|
||||
"transactions",
|
||||
season=season,
|
||||
team_abbrev=team_abbrev,
|
||||
week_start=week_start,
|
||||
@ -470,123 +509,90 @@ class MajorDomoAPI:
|
||||
player_name=player_name,
|
||||
player_id=player_id,
|
||||
move_id=move_id,
|
||||
short_output=short_output
|
||||
short_output=short_output,
|
||||
)
|
||||
return result.get('transactions', [])
|
||||
return result.get("transactions", [])
|
||||
|
||||
# ====================
|
||||
# Statistics Operations (Advanced Views)
|
||||
# ====================
|
||||
|
||||
def get_season_batting_stats(
|
||||
def _get_season_stats(
|
||||
self,
|
||||
stat_type: Literal["batting", "pitching"],
|
||||
season: Optional[int] = None,
|
||||
team_id: Optional[int] = None,
|
||||
player_id: Optional[int] = None,
|
||||
sbaplayer_id: Optional[int] = None,
|
||||
min_pa: Optional[int] = None,
|
||||
sort_by: str = 'woba',
|
||||
sort_order: Literal['asc', 'desc'] = 'desc',
|
||||
min_threshold: Optional[int] = None,
|
||||
sort_by: Optional[str] = None,
|
||||
sort_order: Optional[Literal["asc", "desc"]] = None,
|
||||
limit: int = 200,
|
||||
offset: int = 0
|
||||
offset: int = 0,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get season batting statistics
|
||||
|
||||
Args:
|
||||
season: Season number (defaults to current)
|
||||
team_id: Filter by team
|
||||
player_id: Filter by player
|
||||
sbaplayer_id: Filter by SBA player reference
|
||||
min_pa: Minimum plate appearances
|
||||
sort_by: Sort field (woba, avg, obp, slg, ops, hr, rbi, etc.)
|
||||
sort_order: 'asc' or 'desc'
|
||||
limit: Maximum results
|
||||
offset: Offset for pagination
|
||||
|
||||
Returns:
|
||||
List of batting stat dicts
|
||||
"""
|
||||
"""Get season statistics for batting or pitching."""
|
||||
threshold_key = "min_pa" if stat_type == "batting" else "min_outs"
|
||||
result = self.get(
|
||||
'views/season-stats/batting',
|
||||
f"views/season-stats/{stat_type}",
|
||||
season=season,
|
||||
team_id=team_id,
|
||||
player_id=player_id,
|
||||
sbaplayer_id=sbaplayer_id,
|
||||
min_pa=min_pa,
|
||||
sort_by=sort_by,
|
||||
sort_order=sort_order,
|
||||
limit=limit,
|
||||
offset=offset
|
||||
offset=offset,
|
||||
**({threshold_key: min_threshold} if min_threshold is not None else {}),
|
||||
)
|
||||
return result.get("stats", [])
|
||||
|
||||
def get_season_batting_stats(
|
||||
self,
|
||||
*,
|
||||
min_pa: Optional[int] = None,
|
||||
sort_by: str = "woba",
|
||||
sort_order: Literal["asc", "desc"] = "desc",
|
||||
**kwargs,
|
||||
) -> List[Dict]:
|
||||
"""Get season batting statistics. See _get_season_stats for full kwargs."""
|
||||
return self._get_season_stats(
|
||||
"batting",
|
||||
min_threshold=min_pa,
|
||||
sort_by=sort_by,
|
||||
sort_order=sort_order,
|
||||
**kwargs,
|
||||
)
|
||||
return result.get('stats', [])
|
||||
|
||||
def get_season_pitching_stats(
|
||||
self,
|
||||
season: Optional[int] = None,
|
||||
team_id: Optional[int] = None,
|
||||
player_id: Optional[int] = None,
|
||||
sbaplayer_id: Optional[int] = None,
|
||||
*,
|
||||
min_outs: Optional[int] = None,
|
||||
sort_by: str = 'era',
|
||||
sort_order: Literal['asc', 'desc'] = 'asc',
|
||||
limit: int = 200,
|
||||
offset: int = 0
|
||||
sort_by: str = "era",
|
||||
sort_order: Literal["asc", "desc"] = "asc",
|
||||
**kwargs,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get season pitching statistics
|
||||
|
||||
Args:
|
||||
season: Season number (defaults to current)
|
||||
team_id: Filter by team
|
||||
player_id: Filter by player
|
||||
sbaplayer_id: Filter by SBA player reference
|
||||
min_outs: Minimum outs pitched
|
||||
sort_by: Sort field (era, whip, k, bb, w, l, sv, etc.)
|
||||
sort_order: 'asc' or 'desc'
|
||||
limit: Maximum results
|
||||
offset: Offset for pagination
|
||||
|
||||
Returns:
|
||||
List of pitching stat dicts
|
||||
"""
|
||||
result = self.get(
|
||||
'views/season-stats/pitching',
|
||||
season=season,
|
||||
team_id=team_id,
|
||||
player_id=player_id,
|
||||
sbaplayer_id=sbaplayer_id,
|
||||
min_outs=min_outs,
|
||||
"""Get season pitching statistics. See _get_season_stats for full kwargs."""
|
||||
return self._get_season_stats(
|
||||
"pitching",
|
||||
min_threshold=min_outs,
|
||||
sort_by=sort_by,
|
||||
sort_order=sort_order,
|
||||
limit=limit,
|
||||
offset=offset
|
||||
**kwargs,
|
||||
)
|
||||
return result.get('stats', [])
|
||||
|
||||
def refresh_stats(
|
||||
self, stat_type: Literal["batting", "pitching"], season: int
|
||||
) -> Dict:
|
||||
"""Refresh batting or pitching statistics for a season (private endpoint)."""
|
||||
return self.post(f"views/season-stats/{stat_type}/refresh", season=season)
|
||||
|
||||
def refresh_batting_stats(self, season: int) -> Dict:
|
||||
"""
|
||||
Refresh batting statistics for a season (private endpoint)
|
||||
|
||||
Args:
|
||||
season: Season number
|
||||
|
||||
Returns:
|
||||
Result dict with players_updated count
|
||||
"""
|
||||
return self.post(f'views/season-stats/batting/refresh', season=season)
|
||||
"""Refresh batting statistics for a season."""
|
||||
return self.refresh_stats("batting", season)
|
||||
|
||||
def refresh_pitching_stats(self, season: int) -> Dict:
|
||||
"""
|
||||
Refresh pitching statistics for a season (private endpoint)
|
||||
|
||||
Args:
|
||||
season: Season number
|
||||
|
||||
Returns:
|
||||
Result dict with players_updated count
|
||||
"""
|
||||
return self.post(f'views/season-stats/pitching/refresh', season=season)
|
||||
"""Refresh pitching statistics for a season."""
|
||||
return self.refresh_stats("pitching", season)
|
||||
|
||||
# ====================
|
||||
# Schedule & Results Operations
|
||||
@ -597,7 +603,7 @@ class MajorDomoAPI:
|
||||
season: int,
|
||||
team_id: Optional[List[int]] = None,
|
||||
week: Optional[int] = None,
|
||||
short_output: bool = False
|
||||
short_output: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get game schedules
|
||||
@ -612,20 +618,20 @@ class MajorDomoAPI:
|
||||
List of schedule dicts
|
||||
"""
|
||||
result = self.get(
|
||||
'schedules',
|
||||
"schedules",
|
||||
season=season,
|
||||
team_id=team_id,
|
||||
week=week,
|
||||
short_output=short_output
|
||||
short_output=short_output,
|
||||
)
|
||||
return result.get('schedules', [])
|
||||
return result.get("schedules", [])
|
||||
|
||||
def get_results(
|
||||
self,
|
||||
season: int,
|
||||
team_id: Optional[List[int]] = None,
|
||||
week: Optional[int] = None,
|
||||
short_output: bool = False
|
||||
short_output: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get game results
|
||||
@ -640,13 +646,13 @@ class MajorDomoAPI:
|
||||
List of result dicts
|
||||
"""
|
||||
result = self.get(
|
||||
'results',
|
||||
"results",
|
||||
season=season,
|
||||
team_id=team_id,
|
||||
week=week,
|
||||
short_output=short_output
|
||||
short_output=short_output,
|
||||
)
|
||||
return result.get('results', [])
|
||||
return result.get("results", [])
|
||||
|
||||
# ====================
|
||||
# Utility Methods
|
||||
@ -675,9 +681,11 @@ def main():
|
||||
"""CLI interface for testing"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Major Domo API Client')
|
||||
parser.add_argument('--env', choices=['prod', 'dev'], default='prod', help='Environment')
|
||||
parser.add_argument('--verbose', action='store_true', help='Verbose output')
|
||||
parser = argparse.ArgumentParser(description="Major Domo API Client")
|
||||
parser.add_argument(
|
||||
"--env", choices=["prod", "dev"], default="prod", help="Environment"
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Verbose output")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
@ -695,7 +703,7 @@ def main():
|
||||
print(f" Trade Deadline: Week {current['trade_deadline']}")
|
||||
|
||||
# List teams
|
||||
teams = api.list_teams(season=current['season'], active_only=True)
|
||||
teams = api.list_teams(season=current["season"], active_only=True)
|
||||
print(f"\nActive Teams in Season {current['season']}: {len(teams)}")
|
||||
for team in teams[:5]:
|
||||
print(f" {team['abbrev']}: {team['lname']}")
|
||||
@ -709,5 +717,5 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -19,7 +19,7 @@ Usage:
|
||||
majordomo stats batting --sort woba --min-pa 100
|
||||
|
||||
Environment:
|
||||
API_TOKEN: Required. Bearer token for API authentication.
|
||||
API_TOKEN: Bearer token for API authentication (required for write operations only).
|
||||
"""
|
||||
|
||||
import os
|
||||
@ -40,6 +40,7 @@ from cli_common import (
|
||||
output_table,
|
||||
handle_error,
|
||||
get_season,
|
||||
safe_nested,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
@ -92,14 +93,6 @@ def main(
|
||||
try:
|
||||
state.api = MajorDomoAPI(environment=env, verbose=verbose)
|
||||
state.json_output = json_output
|
||||
# Cache current season
|
||||
current = state.api.get_current()
|
||||
state.current_season = current["season"]
|
||||
except ValueError as e:
|
||||
console.print(f"[red]Configuration Error:[/red] {e}")
|
||||
console.print("\nSet API_TOKEN environment variable:")
|
||||
console.print(" export API_TOKEN='your-token-here'")
|
||||
raise typer.Exit(1)
|
||||
except Exception as e:
|
||||
handle_error(e)
|
||||
|
||||
@ -176,8 +169,7 @@ def player_get(
|
||||
return
|
||||
|
||||
# Extract nested team info
|
||||
team = player.get("team", {})
|
||||
team_abbrev = team.get("abbrev", "N/A") if isinstance(team, dict) else "N/A"
|
||||
team_abbrev = safe_nested(player, "team", "abbrev")
|
||||
|
||||
# Collect positions
|
||||
positions = [
|
||||
@ -227,8 +219,7 @@ def player_search(
|
||||
|
||||
rows = []
|
||||
for p in players:
|
||||
team = p.get("team", {})
|
||||
team_abbrev = team.get("abbrev", "N/A") if isinstance(team, dict) else "N/A"
|
||||
team_abbrev = safe_nested(p, "team", "abbrev")
|
||||
rows.append(
|
||||
[
|
||||
p["id"],
|
||||
@ -318,12 +309,7 @@ def player_move(
|
||||
continue
|
||||
|
||||
# Get current team
|
||||
current_team = player.get("team", {})
|
||||
current_abbrev = (
|
||||
current_team.get("abbrev", "N/A")
|
||||
if isinstance(current_team, dict)
|
||||
else "N/A"
|
||||
)
|
||||
current_abbrev = safe_nested(player, "team", "abbrev")
|
||||
|
||||
# Find target team
|
||||
try:
|
||||
@ -414,14 +400,8 @@ def team_list(
|
||||
|
||||
rows = []
|
||||
for t in teams:
|
||||
manager = t.get("manager1", {})
|
||||
manager_name = manager.get("name", "") if isinstance(manager, dict) else ""
|
||||
division = t.get("division", {})
|
||||
div_abbrev = (
|
||||
division.get("division_abbrev", "")
|
||||
if isinstance(division, dict)
|
||||
else ""
|
||||
)
|
||||
manager_name = safe_nested(t, "manager1", "name", default="")
|
||||
div_abbrev = safe_nested(t, "division", "division_abbrev", default="")
|
||||
rows.append(
|
||||
[
|
||||
t["abbrev"],
|
||||
@ -456,16 +436,8 @@ def team_get(
|
||||
output_json(team)
|
||||
return
|
||||
|
||||
manager = team.get("manager1", {})
|
||||
manager_name = (
|
||||
manager.get("name", "N/A") if isinstance(manager, dict) else "N/A"
|
||||
)
|
||||
division = team.get("division", {})
|
||||
div_name = (
|
||||
division.get("division_name", "N/A")
|
||||
if isinstance(division, dict)
|
||||
else "N/A"
|
||||
)
|
||||
manager_name = safe_nested(team, "manager1", "name")
|
||||
div_name = safe_nested(team, "division", "division_name")
|
||||
salary_cap = team.get("salary_cap")
|
||||
cap_str = f"{salary_cap:.1f}" if salary_cap is not None else "N/A"
|
||||
|
||||
@ -511,42 +483,38 @@ def team_roster(
|
||||
f"\n[bold cyan]{team.get('lname', abbrev)} Roster ({which.title()})[/bold cyan]\n"
|
||||
)
|
||||
|
||||
# Active roster
|
||||
active = roster.get("active", {}).get("players", [])
|
||||
if active:
|
||||
active_rows = [
|
||||
[p["name"], p.get("pos_1", ""), f"{p.get('wara', 0):.2f}"]
|
||||
for p in active
|
||||
]
|
||||
output_table(
|
||||
f"Active ({len(active)})", ["Name", "Pos", "WARA"], active_rows
|
||||
)
|
||||
# Roster sections: (API key, display label, columns, row builder)
|
||||
sections = [
|
||||
(
|
||||
"active",
|
||||
"Active",
|
||||
["Name", "Pos", "WARA"],
|
||||
lambda p: [p["name"], p.get("pos_1", ""), f"{p.get('wara', 0):.2f}"],
|
||||
),
|
||||
(
|
||||
"shortil",
|
||||
"Injured List",
|
||||
["Name", "Pos", "Return"],
|
||||
lambda p: [p["name"], p.get("pos_1", ""), p.get("il_return", "")],
|
||||
),
|
||||
(
|
||||
"longil",
|
||||
"Minor League",
|
||||
["Name", "Pos", "Return"],
|
||||
lambda p: [p["name"], p.get("pos_1", ""), p.get("il_return", "")],
|
||||
),
|
||||
]
|
||||
|
||||
# Short IL
|
||||
short_il = roster.get("shortil", {}).get("players", [])
|
||||
if short_il:
|
||||
console.print()
|
||||
il_rows = [
|
||||
[p["name"], p.get("pos_1", ""), p.get("il_return", "")]
|
||||
for p in short_il
|
||||
]
|
||||
output_table(
|
||||
f"Short IL ({len(short_il)})", ["Name", "Pos", "Return"], il_rows
|
||||
)
|
||||
|
||||
# Long IL
|
||||
long_il = roster.get("longil", {}).get("players", [])
|
||||
if long_il:
|
||||
console.print()
|
||||
lil_rows = [
|
||||
[p["name"], p.get("pos_1", ""), p.get("il_return", "")] for p in long_il
|
||||
]
|
||||
output_table(
|
||||
f"Long IL ({len(long_il)})", ["Name", "Pos", "Return"], lil_rows
|
||||
)
|
||||
|
||||
# Summary
|
||||
total = len(active) + len(short_il) + len(long_il)
|
||||
total = 0
|
||||
for i, (key, label, columns, row_fn) in enumerate(sections):
|
||||
players = roster.get(key, {}).get("players", [])
|
||||
if players:
|
||||
if i > 0:
|
||||
console.print()
|
||||
output_table(
|
||||
f"{label} ({len(players)})", columns, [row_fn(p) for p in players]
|
||||
)
|
||||
total += len(players)
|
||||
console.print(f"\n[dim]Total: {total} players[/dim]")
|
||||
|
||||
except Exception as e:
|
||||
@ -592,9 +560,8 @@ def standings(
|
||||
|
||||
rows = []
|
||||
for s in standings_data:
|
||||
team = s.get("team", {})
|
||||
team_abbrev = team.get("abbrev", "N/A") if isinstance(team, dict) else "N/A"
|
||||
team_name = team.get("lname", "N/A") if isinstance(team, dict) else "N/A"
|
||||
team_abbrev = safe_nested(s, "team", "abbrev")
|
||||
team_name = safe_nested(s, "team", "lname")
|
||||
wins = s.get("wins", 0)
|
||||
losses = s.get("losses", 0)
|
||||
total = wins + losses
|
||||
|
||||
@ -17,6 +17,7 @@ from cli_common import (
|
||||
output_table,
|
||||
handle_error,
|
||||
get_season,
|
||||
safe_nested,
|
||||
)
|
||||
from cli_stats import _format_rate_stat, _outs_to_ip
|
||||
|
||||
@ -32,9 +33,8 @@ def _show_standings(season: int):
|
||||
|
||||
rows = []
|
||||
for s in standings_data:
|
||||
team = s.get("team", {})
|
||||
abbrev = team.get("abbrev", "N/A") if isinstance(team, dict) else "N/A"
|
||||
name = team.get("lname", "N/A") if isinstance(team, dict) else "N/A"
|
||||
abbrev = safe_nested(s, "team", "abbrev")
|
||||
name = safe_nested(s, "team", "lname")
|
||||
wins = s.get("wins", 0)
|
||||
losses = s.get("losses", 0)
|
||||
total = wins + losses
|
||||
|
||||
@ -7,7 +7,7 @@ All CLI sub-modules import from here for consistent output and state management.
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
@ -67,6 +67,20 @@ def handle_error(e: Exception, context: str = ""):
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
def get_season(season: Optional[int]) -> int:
|
||||
"""Get season, defaulting to current if not specified"""
|
||||
return season if season is not None else state.current_season
|
||||
def safe_nested(obj: Any, *keys: str, default: str = "N/A") -> str:
|
||||
"""Safely extract a value from nested dicts. Returns default if any key is missing or obj isn't a dict."""
|
||||
for key in keys:
|
||||
if not isinstance(obj, dict):
|
||||
return default
|
||||
obj = obj.get(key)
|
||||
return obj if obj is not None else default
|
||||
|
||||
|
||||
def get_season(season: Optional[int] = None) -> int:
|
||||
"""Get season, defaulting to current if not specified. Lazy-fetches current season on first call."""
|
||||
if season is not None:
|
||||
return season
|
||||
if state.current_season is None:
|
||||
current = state.api.get_current()
|
||||
state.current_season = current["season"]
|
||||
return state.current_season
|
||||
|
||||
@ -16,6 +16,7 @@ from cli_common import (
|
||||
output_table,
|
||||
handle_error,
|
||||
get_season,
|
||||
safe_nested,
|
||||
)
|
||||
|
||||
injuries_app = typer.Typer(help="Injury operations")
|
||||
@ -90,12 +91,7 @@ def injuries_list(
|
||||
for injury in injuries:
|
||||
player = injury.get("player", {})
|
||||
player_name = player.get("name", "N/A")
|
||||
player_team = player.get("team", {})
|
||||
team_abbrev_display = (
|
||||
player_team.get("abbrev", "N/A")
|
||||
if isinstance(player_team, dict)
|
||||
else "N/A"
|
||||
)
|
||||
team_abbrev_display = safe_nested(player, "team", "abbrev")
|
||||
|
||||
total_games = injury.get("total_games", 0)
|
||||
start_week = injury.get("start_week", 0)
|
||||
|
||||
@ -15,6 +15,7 @@ from cli_common import (
|
||||
output_table,
|
||||
handle_error,
|
||||
get_season,
|
||||
safe_nested,
|
||||
)
|
||||
|
||||
results_app = typer.Typer(
|
||||
@ -96,18 +97,11 @@ def results_callback(
|
||||
# Build table rows
|
||||
rows = []
|
||||
for r in results_list:
|
||||
away_team = r.get("away_team", {})
|
||||
home_team = r.get("home_team", {})
|
||||
away_abbrev = safe_nested(r, "away_team", "abbrev")
|
||||
home_abbrev = safe_nested(r, "home_team", "abbrev")
|
||||
away_score = r.get("away_score", 0)
|
||||
home_score = r.get("home_score", 0)
|
||||
|
||||
away_abbrev = (
|
||||
away_team.get("abbrev", "N/A") if isinstance(away_team, dict) else "N/A"
|
||||
)
|
||||
home_abbrev = (
|
||||
home_team.get("abbrev", "N/A") if isinstance(home_team, dict) else "N/A"
|
||||
)
|
||||
|
||||
# Format score as "away_score-home_score"
|
||||
score_str = f"{away_score}-{home_score}"
|
||||
|
||||
@ -200,18 +194,11 @@ def results_list(
|
||||
# Build table rows
|
||||
rows = []
|
||||
for r in results_list:
|
||||
away_team = r.get("away_team", {})
|
||||
home_team = r.get("home_team", {})
|
||||
away_abbrev = safe_nested(r, "away_team", "abbrev")
|
||||
home_abbrev = safe_nested(r, "home_team", "abbrev")
|
||||
away_score = r.get("away_score", 0)
|
||||
home_score = r.get("home_score", 0)
|
||||
|
||||
away_abbrev = (
|
||||
away_team.get("abbrev", "N/A") if isinstance(away_team, dict) else "N/A"
|
||||
)
|
||||
home_abbrev = (
|
||||
home_team.get("abbrev", "N/A") if isinstance(home_team, dict) else "N/A"
|
||||
)
|
||||
|
||||
# Format score as "away_score-home_score"
|
||||
score_str = f"{away_score}-{home_score}"
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ from cli_common import (
|
||||
output_table,
|
||||
handle_error,
|
||||
get_season,
|
||||
safe_nested,
|
||||
)
|
||||
|
||||
schedule_app = typer.Typer(
|
||||
@ -96,15 +97,8 @@ def schedule_callback(
|
||||
# Build table rows
|
||||
rows = []
|
||||
for s in schedules:
|
||||
away_team = s.get("away_team", {})
|
||||
home_team = s.get("home_team", {})
|
||||
|
||||
away_abbrev = (
|
||||
away_team.get("abbrev", "N/A") if isinstance(away_team, dict) else "N/A"
|
||||
)
|
||||
home_abbrev = (
|
||||
home_team.get("abbrev", "N/A") if isinstance(home_team, dict) else "N/A"
|
||||
)
|
||||
away_abbrev = safe_nested(s, "away_team", "abbrev")
|
||||
home_abbrev = safe_nested(s, "home_team", "abbrev")
|
||||
|
||||
rows.append(
|
||||
[
|
||||
@ -194,15 +188,8 @@ def schedule_list(
|
||||
# Build table rows
|
||||
rows = []
|
||||
for s in schedules:
|
||||
away_team = s.get("away_team", {})
|
||||
home_team = s.get("home_team", {})
|
||||
|
||||
away_abbrev = (
|
||||
away_team.get("abbrev", "N/A") if isinstance(away_team, dict) else "N/A"
|
||||
)
|
||||
home_abbrev = (
|
||||
home_team.get("abbrev", "N/A") if isinstance(home_team, dict) else "N/A"
|
||||
)
|
||||
away_abbrev = safe_nested(s, "away_team", "abbrev")
|
||||
home_abbrev = safe_nested(s, "home_team", "abbrev")
|
||||
|
||||
rows.append(
|
||||
[
|
||||
|
||||
@ -16,6 +16,7 @@ from cli_common import (
|
||||
output_table,
|
||||
handle_error,
|
||||
get_season,
|
||||
safe_nested,
|
||||
)
|
||||
|
||||
transactions_app = typer.Typer(
|
||||
@ -77,8 +78,12 @@ def transactions_callback(
|
||||
week_start = week
|
||||
week_end = week
|
||||
|
||||
# Build filter parameters
|
||||
team_list = [team] if team else None
|
||||
# Build filter parameters — include affiliate rosters (IL, MiL)
|
||||
if team:
|
||||
base = team.upper().removesuffix("MIL").removesuffix("IL")
|
||||
team_list = [base, f"{base}IL", f"{base}MiL"]
|
||||
else:
|
||||
team_list = None
|
||||
player_list = [player] if player else None
|
||||
|
||||
# Get transactions from API
|
||||
@ -111,25 +116,9 @@ def transactions_callback(
|
||||
# Build table rows
|
||||
rows = []
|
||||
for t in transactions:
|
||||
player_dict = t.get("player", {})
|
||||
oldteam_dict = t.get("oldteam", {})
|
||||
newteam_dict = t.get("newteam", {})
|
||||
|
||||
player_name = (
|
||||
player_dict.get("name", "N/A")
|
||||
if isinstance(player_dict, dict)
|
||||
else "N/A"
|
||||
)
|
||||
old_abbrev = (
|
||||
oldteam_dict.get("abbrev", "N/A")
|
||||
if isinstance(oldteam_dict, dict)
|
||||
else "N/A"
|
||||
)
|
||||
new_abbrev = (
|
||||
newteam_dict.get("abbrev", "N/A")
|
||||
if isinstance(newteam_dict, dict)
|
||||
else "N/A"
|
||||
)
|
||||
player_name = safe_nested(t, "player", "name")
|
||||
old_abbrev = safe_nested(t, "oldteam", "abbrev")
|
||||
new_abbrev = safe_nested(t, "newteam", "abbrev")
|
||||
|
||||
rows.append(
|
||||
[
|
||||
@ -200,8 +189,12 @@ def transactions_list(
|
||||
week_start = week
|
||||
week_end = week
|
||||
|
||||
# Build filter parameters
|
||||
team_list = [team] if team else None
|
||||
# Build filter parameters — include affiliate rosters (IL, MiL)
|
||||
if team:
|
||||
base = team.upper().removesuffix("MIL").removesuffix("IL")
|
||||
team_list = [base, f"{base}IL", f"{base}MiL"]
|
||||
else:
|
||||
team_list = None
|
||||
player_list = [player] if player else None
|
||||
|
||||
# Get transactions from API
|
||||
@ -234,25 +227,9 @@ def transactions_list(
|
||||
# Build table rows
|
||||
rows = []
|
||||
for t in transactions:
|
||||
player_dict = t.get("player", {})
|
||||
oldteam_dict = t.get("oldteam", {})
|
||||
newteam_dict = t.get("newteam", {})
|
||||
|
||||
player_name = (
|
||||
player_dict.get("name", "N/A")
|
||||
if isinstance(player_dict, dict)
|
||||
else "N/A"
|
||||
)
|
||||
old_abbrev = (
|
||||
oldteam_dict.get("abbrev", "N/A")
|
||||
if isinstance(oldteam_dict, dict)
|
||||
else "N/A"
|
||||
)
|
||||
new_abbrev = (
|
||||
newteam_dict.get("abbrev", "N/A")
|
||||
if isinstance(newteam_dict, dict)
|
||||
else "N/A"
|
||||
)
|
||||
player_name = safe_nested(t, "player", "name")
|
||||
old_abbrev = safe_nested(t, "oldteam", "abbrev")
|
||||
new_abbrev = safe_nested(t, "newteam", "abbrev")
|
||||
|
||||
rows.append(
|
||||
[
|
||||
@ -363,11 +340,7 @@ def transactions_simulate(
|
||||
|
||||
p_name = player["name"]
|
||||
p_wara = float(player.get("wara", 0) or 0)
|
||||
p_team = (
|
||||
player.get("team", {}).get("abbrev", "?")
|
||||
if isinstance(player.get("team"), dict)
|
||||
else "?"
|
||||
)
|
||||
p_team = safe_nested(player, "team", "abbrev", default="?")
|
||||
|
||||
# Determine if this move adds to or removes from ML
|
||||
target_is_ml = target == team_upper
|
||||
|
||||
Loading…
Reference in New Issue
Block a user