Tighten provider normalization contract and fallback semantics
This commit is contained in:
@ -6,11 +6,28 @@ from typing import Any
|
||||
|
||||
from django.utils.text import slugify
|
||||
|
||||
from apps.providers.contracts import (
|
||||
CompetitionPayload,
|
||||
PlayerCareerPayload,
|
||||
PlayerPayload,
|
||||
PlayerStatsPayload,
|
||||
SeasonPayload,
|
||||
TeamPayload,
|
||||
)
|
||||
|
||||
def map_competitions() -> list[dict[str, Any]]:
|
||||
|
||||
NBA_COMPETITION_EXTERNAL_ID = "competition-nba"
|
||||
|
||||
|
||||
def map_competitions() -> list[CompetitionPayload]:
|
||||
"""
|
||||
balldontlie assumptions:
|
||||
- The API is NBA-focused, so competition is normalized as a single NBA league.
|
||||
- Competition country is set to US (league home country), not player/team nationality.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"external_id": "competition-nba",
|
||||
"external_id": NBA_COMPETITION_EXTERNAL_ID,
|
||||
"name": "NBA",
|
||||
"slug": "nba",
|
||||
"competition_type": "league",
|
||||
@ -22,8 +39,11 @@ def map_competitions() -> list[dict[str, Any]]:
|
||||
]
|
||||
|
||||
|
||||
def map_teams(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
mapped: list[dict[str, Any]] = []
|
||||
def map_teams(rows: list[dict[str, Any]]) -> list[TeamPayload]:
|
||||
"""
|
||||
Team country is unknown from balldontlie team payloads and stays null.
|
||||
"""
|
||||
mapped: list[TeamPayload] = []
|
||||
for row in rows:
|
||||
team_id = row.get("id")
|
||||
if not team_id:
|
||||
@ -36,7 +56,7 @@ def map_teams(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"name": full_name,
|
||||
"short_name": abbreviation,
|
||||
"slug": slugify(full_name) or f"team-{team_id}",
|
||||
"country": {"name": "United States", "iso2_code": "US", "iso3_code": "USA"},
|
||||
"country": None,
|
||||
"is_national_team": False,
|
||||
}
|
||||
)
|
||||
@ -75,8 +95,12 @@ def _map_role(position: str | None) -> dict[str, str] | None:
|
||||
return None
|
||||
|
||||
|
||||
def map_players(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
mapped: list[dict[str, Any]] = []
|
||||
def map_players(rows: list[dict[str, Any]]) -> list[PlayerPayload]:
|
||||
"""
|
||||
Player-level nationality/birth/physical details are not exposed by this provider's
|
||||
players endpoint in the current MVP integration, so they are left null.
|
||||
"""
|
||||
mapped: list[PlayerPayload] = []
|
||||
for row in rows:
|
||||
player_id = row.get("id")
|
||||
if not player_id:
|
||||
@ -86,7 +110,6 @@ def map_players(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
last_name = row.get("last_name", "")
|
||||
full_name = f"{first_name} {last_name}".strip() or f"Player {player_id}"
|
||||
position_value = row.get("position")
|
||||
team = row.get("team") or {}
|
||||
|
||||
mapped.append(
|
||||
{
|
||||
@ -95,7 +118,7 @@ def map_players(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"last_name": last_name,
|
||||
"full_name": full_name,
|
||||
"birth_date": None,
|
||||
"nationality": {"name": "Unknown", "iso2_code": "ZZ", "iso3_code": "ZZZ"},
|
||||
"nationality": None,
|
||||
"nominal_position": _map_position(position_value),
|
||||
"inferred_role": _map_role(position_value),
|
||||
"height_cm": None,
|
||||
@ -103,22 +126,27 @@ def map_players(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"dominant_hand": "unknown",
|
||||
"is_active": True,
|
||||
"aliases": [],
|
||||
"current_team_external_id": f"team-{team['id']}" if team.get("id") else None,
|
||||
}
|
||||
)
|
||||
return mapped
|
||||
|
||||
|
||||
def map_seasons(seasons: list[int]) -> list[dict[str, Any]]:
|
||||
mapped: list[dict[str, Any]] = []
|
||||
for season in seasons:
|
||||
def map_seasons(seasons: list[int]) -> list[SeasonPayload]:
|
||||
"""
|
||||
Current-season fallback:
|
||||
- if configured seasons are supplied, the maximum season year is treated as current.
|
||||
"""
|
||||
normalized_seasons = sorted(set(seasons))
|
||||
current = max(normalized_seasons) if normalized_seasons else None
|
||||
mapped: list[SeasonPayload] = []
|
||||
for season in normalized_seasons:
|
||||
mapped.append(
|
||||
{
|
||||
"external_id": f"season-{season}",
|
||||
"label": f"{season}-{season + 1}",
|
||||
"start_date": date(season, 10, 1).isoformat(),
|
||||
"end_date": date(season + 1, 6, 30).isoformat(),
|
||||
"is_current": False,
|
||||
"is_current": season == current,
|
||||
}
|
||||
)
|
||||
return mapped
|
||||
@ -159,7 +187,7 @@ def map_player_stats(
|
||||
rows: list[dict[str, Any]],
|
||||
*,
|
||||
allowed_seasons: list[int],
|
||||
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
||||
) -> tuple[list[PlayerStatsPayload], list[PlayerCareerPayload]]:
|
||||
aggregates: dict[tuple[int, int, int], dict[str, Any]] = defaultdict(
|
||||
lambda: {
|
||||
"games": 0,
|
||||
@ -213,8 +241,8 @@ def map_player_stats(
|
||||
agg["ft_pct_sum"] += _to_float(row.get("ft_pct"))
|
||||
agg["ft_pct_count"] += 1
|
||||
|
||||
player_stats: list[dict[str, Any]] = []
|
||||
player_careers: list[dict[str, Any]] = []
|
||||
player_stats: list[PlayerStatsPayload] = []
|
||||
player_careers: list[PlayerCareerPayload] = []
|
||||
|
||||
for (season, player_id, team_id), agg in aggregates.items():
|
||||
games = agg["games"] or 1
|
||||
@ -223,7 +251,7 @@ def map_player_stats(
|
||||
"external_id": f"ps-{season}-{player_id}-{team_id}",
|
||||
"player_external_id": f"player-{player_id}",
|
||||
"team_external_id": f"team-{team_id}",
|
||||
"competition_external_id": "competition-nba",
|
||||
"competition_external_id": NBA_COMPETITION_EXTERNAL_ID,
|
||||
"season_external_id": f"season-{season}",
|
||||
"games_played": agg["games"],
|
||||
"games_started": 0,
|
||||
@ -247,7 +275,7 @@ def map_player_stats(
|
||||
"external_id": f"career-{season}-{player_id}-{team_id}",
|
||||
"player_external_id": f"player-{player_id}",
|
||||
"team_external_id": f"team-{team_id}",
|
||||
"competition_external_id": "competition-nba",
|
||||
"competition_external_id": NBA_COMPETITION_EXTERNAL_ID,
|
||||
"season_external_id": f"season-{season}",
|
||||
"role_code": "",
|
||||
"shirt_number": None,
|
||||
|
||||
Reference in New Issue
Block a user