amarorn / ingest /sofascore /fept_mapper.py
beAnalytic's picture
feat: sync main with feature/superbet-live-inplay
16c19b8 verified
Raw
History Blame Contribute Delete
7.11 kB
from __future__ import annotations
from typing import Any
from schemas.wc_kxl_dynamic import FeptEscalacao, FeptTitularesEstruturados
POSITION_LINE = {
"G": "goleiro",
"D": "defesa",
"M": "meio",
"F": "ataque",
}
TOURNAMENT_PRIORITY = (
"world-championship",
"european-championship",
"copa-america",
"international-friendly",
"world-championship-qual",
"uefa-nations-league",
"conmebol-nations-league",
"laliga",
"premier-league",
"bundesliga",
"serie-a",
"ligue-1",
"brasileirao",
"eredivisie",
"primeira-liga",
)
def _line_from_position(position: str | None) -> str:
key = (position or "M").strip().upper()[:1]
return POSITION_LINE.get(key, "meio")
def _lineup_rating(entry: dict[str, Any]) -> float | None:
for key in ("rating", "averageRating"):
value = entry.get(key)
if value is not None:
return float(value)
stats = entry.get("statistics") or {}
value = stats.get("rating")
if value is not None:
return float(value)
return None
def _season_priority(season_row: dict[str, Any]) -> tuple[int, int, float]:
tournament = season_row.get("uniqueTournament") or {}
slug = str(tournament.get("slug") or "").lower()
priority = len(TOURNAMENT_PRIORITY)
for idx, token in enumerate(TOURNAMENT_PRIORITY):
if token in slug:
priority = idx
break
stats = season_row.get("statistics") or {}
count_rating = int(stats.get("countRating") or 0)
rating = float(stats.get("rating") or 0.0)
return priority, -count_rating, -rating
def pick_player_rating_from_statistics(seasons: list[dict[str, Any]]) -> float | None:
candidates: list[tuple[tuple[int, int, float], float]] = []
for row in seasons:
stats = row.get("statistics") or {}
rating = stats.get("rating")
count_rating = stats.get("countRating") or 0
if rating is None or int(count_rating) < 3:
continue
candidates.append((_season_priority(row), float(rating)))
if not candidates:
return None
candidates.sort(key=lambda item: item[0])
return candidates[0][1]
def resolve_player_rating(
entry: dict[str, Any],
*,
rating_cache: dict[int, float | None],
fetch_statistics,
) -> float | None:
rating = _lineup_rating(entry)
if rating is not None:
return rating
player = entry.get("player") or {}
player_id = player.get("id")
if player_id is None:
return None
pid = int(player_id)
if pid in rating_cache:
return rating_cache[pid]
seasons = fetch_statistics(pid)
rating_cache[pid] = pick_player_rating_from_statistics(seasons)
return rating_cache[pid]
def _player_payload(entry: dict[str, Any], rating: float | None) -> dict[str, Any]:
player = entry.get("player") or {}
position = entry.get("position") or player.get("position")
payload = {
"nome": player.get("shortName") or player.get("name") or "Desconhecido",
"posicao": position,
}
if rating is not None:
payload["nota_sofascore"] = round(rating, 2)
return payload
def map_side_to_structured(
side_block: dict[str, Any],
*,
rating_cache: dict[int, float | None],
fetch_statistics,
) -> FeptTitularesEstruturados:
players = [
entry
for entry in (side_block.get("players") or [])
if entry.get("substitute") is False
]
goleiro = None
defensores: list[dict] = []
meio_campistas: list[dict] = []
atacantes: list[dict] = []
for entry in players:
player = entry.get("player") or {}
position = entry.get("position") or player.get("position")
line = _line_from_position(position)
rating = resolve_player_rating(
entry,
rating_cache=rating_cache,
fetch_statistics=fetch_statistics,
)
payload = _player_payload(entry, rating)
if line == "goleiro" and goleiro is None:
goleiro = payload
elif line == "defesa":
defensores.append(payload)
elif line == "meio":
meio_campistas.append(payload)
else:
atacantes.append(payload)
return FeptTitularesEstruturados(
goleiro=goleiro,
defensores=defensores,
meio_campistas=meio_campistas,
atacantes=atacantes,
)
def map_side_bench_players(
side_block: dict[str, Any],
*,
rating_cache: dict[int, float | None],
fetch_statistics,
) -> list[dict[str, Any]]:
"""Reservas do jogo (substitute=true) com nota quando disponível."""
bench: list[dict[str, Any]] = []
for entry in side_block.get("players") or []:
if entry.get("substitute") is not True:
continue
player = entry.get("player") or {}
position = entry.get("position") or player.get("position")
rating = resolve_player_rating(
entry,
rating_cache=rating_cache,
fetch_statistics=fetch_statistics,
)
payload = _player_payload(entry, rating)
payload["linha"] = _line_from_position(position)
bench.append(payload)
return bench
def map_lineups_bench(
lineups: dict[str, Any],
*,
home_is_event_home: bool,
fetch_statistics,
rating_cache: dict[int, float | None] | None = None,
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
cache = rating_cache if rating_cache is not None else {}
home_block = lineups.get("home") or {}
away_block = lineups.get("away") or {}
if home_is_event_home:
mandante_block, visitante_block = home_block, away_block
else:
mandante_block, visitante_block = away_block, home_block
return (
map_side_bench_players(
mandante_block,
rating_cache=cache,
fetch_statistics=fetch_statistics,
),
map_side_bench_players(
visitante_block,
rating_cache=cache,
fetch_statistics=fetch_statistics,
),
)
def map_lineups_to_fept(
lineups: dict[str, Any],
*,
home_is_event_home: bool,
fetch_statistics,
rating_cache: dict[int, float | None] | None = None,
) -> FeptEscalacao:
cache = rating_cache if rating_cache is not None else {}
home_block = lineups.get("home") or {}
away_block = lineups.get("away") or {}
if home_is_event_home:
mandante_block, visitante_block = home_block, away_block
else:
mandante_block, visitante_block = away_block, home_block
return FeptEscalacao(
esquema_mandante=mandante_block.get("formation"),
esquema_visitante=visitante_block.get("formation"),
mandante_titulares_notas=map_side_to_structured(
mandante_block,
rating_cache=cache,
fetch_statistics=fetch_statistics,
),
visitante_titulares_notas=map_side_to_structured(
visitante_block,
rating_cache=cache,
fetch_statistics=fetch_statistics,
),
)