refactor resolvers
This commit is contained in:
@@ -5,6 +5,60 @@ screenshotter:
|
||||
|
||||
resolver:
|
||||
api_key: 733f6882605be2de8980bbd074091ee4
|
||||
league_map:
|
||||
# European cups
|
||||
liga mistrů: 2
|
||||
champions league: 2
|
||||
evropská liga: 3
|
||||
europa league: 3
|
||||
konferenční liga: 848
|
||||
conference league: 848
|
||||
# Top flights
|
||||
1. anglie: 39
|
||||
1. belgie: 144
|
||||
1. česko: 345
|
||||
1. dánsko: 119
|
||||
1. francie: 61
|
||||
1. itálie: 135
|
||||
1. itálie - ženy: 794
|
||||
1. německo: 78
|
||||
1. nizozemsko: 88
|
||||
1. polsko: 106
|
||||
1. portugalsko: 94
|
||||
1. rakousko: 218
|
||||
1. rumunsko: 283
|
||||
1. skotsko: 179
|
||||
1. slovensko: 332
|
||||
1. španělsko: 140
|
||||
1. wales: 771
|
||||
# Second divisions
|
||||
2. anglie: 40
|
||||
2. česko: 346
|
||||
2. francie: 62
|
||||
2. itálie: 136
|
||||
2. německo: 79
|
||||
2. nizozemsko: 89
|
||||
2. rakousko: 219
|
||||
2. slovensko: 333
|
||||
2. španělsko: 141
|
||||
# Third divisions
|
||||
3. francie: 63
|
||||
3. česko msfl: 349
|
||||
3. česko čfl: 348
|
||||
# Fourth divisions
|
||||
4. česko - sk. a: 350
|
||||
4. česko - sk. b: 351
|
||||
4. česko - sk. c: 352
|
||||
4. česko - sk. d: 353
|
||||
4. česko - sk. e: 354
|
||||
4. česko - sk. f: 686
|
||||
# Women
|
||||
1. česko - ženy: 669
|
||||
fortuna=liga ženy: 669
|
||||
# Domestic cups
|
||||
anglie - fa cup: 45
|
||||
anglie - efl cup: 48
|
||||
česko - pohár: 347
|
||||
|
||||
img_classifier:
|
||||
target_path: data/screenshots/
|
||||
|
||||
@@ -3,18 +3,24 @@ import argparse
|
||||
import yaml
|
||||
from pydantic import ValidationError
|
||||
|
||||
from beaky import _ansi
|
||||
from beaky.config import Config
|
||||
from beaky.link_classifier.classifier import LinkClassifier
|
||||
from beaky.resolvers.resolver import _B, _GRAY, _GREEN, _R, _RED, _YELLOW, TicketResolver, TicketVerdict
|
||||
from beaky.resolvers.resolver import TicketResolver, TicketVerdict
|
||||
from beaky.scanner.scanner import Links
|
||||
from beaky.screenshotter.screenshotter import Screenshotter
|
||||
|
||||
_VERDICT_COLOR = {
|
||||
TicketVerdict.TRUTHFUL: _GREEN,
|
||||
TicketVerdict.NOT_TRUTHFUL: _RED,
|
||||
TicketVerdict.POSSIBLY_TRUTHFUL: _YELLOW,
|
||||
TicketVerdict.UNKNOWN: _GRAY,
|
||||
}
|
||||
|
||||
def _verdict_str(verdict: TicketVerdict) -> str:
|
||||
text = f"VERDICT: {verdict.value.upper()}"
|
||||
if verdict == TicketVerdict.TRUTHFUL:
|
||||
return _ansi.green(text)
|
||||
if verdict == TicketVerdict.NOT_TRUTHFUL:
|
||||
return _ansi.red(text)
|
||||
if verdict == TicketVerdict.POSSIBLY_TRUTHFUL:
|
||||
return _ansi.yellow(text)
|
||||
return _ansi.gray(text)
|
||||
|
||||
|
||||
def load_config(path: str) -> Config | None:
|
||||
with open(path) as f:
|
||||
@@ -81,8 +87,7 @@ def main() -> None:
|
||||
|
||||
print(f"\n--- Resolving ticket {link.id} ---")
|
||||
resolved = resolver.resolve(ticket)
|
||||
color = _VERDICT_COLOR.get(resolved.verdict, "")
|
||||
print(f"\n {color}{_B}VERDICT: {resolved.verdict.value.upper()}{_R}")
|
||||
print(f"\n {_ansi.bold(_verdict_str(resolved.verdict))}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -15,7 +15,23 @@ class BetType(str, Enum):
|
||||
GOAL_AMOUNT = "goal_amount"
|
||||
GOAL_HANDICAP = "goal_handicap"
|
||||
UNKNOWN = "unknown"
|
||||
...
|
||||
|
||||
|
||||
class BetOutcome(str, Enum):
|
||||
WIN = "win"
|
||||
LOSE = "lose"
|
||||
VOID = "void" # stake returned (e.g. WinLose on draw, integer goal line hit)
|
||||
UNKNOWN = "unknown" # fixture not found or unclassified bet
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatchInfo:
|
||||
goals_home: int
|
||||
goals_away: int
|
||||
half_time_home: int | None = None
|
||||
half_time_away: int | None = None
|
||||
corners_home: int | None = None
|
||||
corners_away: int | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -25,67 +41,102 @@ class Bet(ABC):
|
||||
team2Name: str
|
||||
date: datetime
|
||||
league: str
|
||||
|
||||
@abstractmethod
|
||||
def resolve(self) -> None: pass
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class WinDrawLose(Bet):
|
||||
"""Výsledek zápasu 1X2"""
|
||||
betType: Literal["X", "0", "1", "2"] = "0"
|
||||
def resolve(self) -> None:
|
||||
...
|
||||
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome:
|
||||
home, away = match.goals_home, match.goals_away
|
||||
bet_draw = self.betType in ("X", "0")
|
||||
if bet_draw:
|
||||
return BetOutcome.WIN if home == away else BetOutcome.LOSE
|
||||
actual = "1" if home > away else ("0" if home == away else "2")
|
||||
return BetOutcome.WIN if actual == self.betType else BetOutcome.LOSE
|
||||
|
||||
|
||||
@dataclass
|
||||
class Advance(Bet):
|
||||
"""What team advances to next round"""
|
||||
def resolve(self) -> None:
|
||||
raise NotImplementedError("Vyser si voko vine")
|
||||
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome:
|
||||
raise NotImplementedError("Advance bet resolution is not implemented")
|
||||
|
||||
|
||||
@dataclass
|
||||
class WinDrawLoseDouble(Bet):
|
||||
"""Výsledek zápasu - double"""
|
||||
betType: Literal["01", "12", "02"] = "01"
|
||||
def resolve(self) -> None:
|
||||
...
|
||||
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome:
|
||||
home, away = match.goals_home, match.goals_away
|
||||
actual = "1" if home > away else ("0" if home == away else "2")
|
||||
return BetOutcome.WIN if actual in self.betType else BetOutcome.LOSE
|
||||
|
||||
|
||||
@dataclass
|
||||
class WinLose(Bet):
|
||||
"""Výsledek zápasu bez remízy"""
|
||||
betType: Literal["1", "2"] = "1"
|
||||
def resolve(self) -> None:
|
||||
...
|
||||
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome:
|
||||
home, away = match.goals_home, match.goals_away
|
||||
if home == away:
|
||||
return BetOutcome.VOID
|
||||
actual = "1" if home > away else "2"
|
||||
return BetOutcome.WIN if actual == self.betType else BetOutcome.LOSE
|
||||
|
||||
|
||||
@dataclass
|
||||
class BothTeamScored(Bet):
|
||||
def resolve(self) -> None:
|
||||
...
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome:
|
||||
return BetOutcome.WIN if match.goals_home > 0 and match.goals_away > 0 else BetOutcome.LOSE
|
||||
|
||||
|
||||
@dataclass
|
||||
class GoalAmount(Bet):
|
||||
"""Počet gólů v zápasu — over/under total goals"""
|
||||
line: float = 0.0 # goal line, e.g. 2.5
|
||||
over: bool = True # True = more than line, False = less than line
|
||||
def resolve(self) -> None:
|
||||
...
|
||||
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome:
|
||||
total = match.goals_home + match.goals_away
|
||||
if total == self.line:
|
||||
return BetOutcome.VOID
|
||||
won = total > self.line if self.over else total < self.line
|
||||
return BetOutcome.WIN if won else BetOutcome.LOSE
|
||||
|
||||
|
||||
@dataclass
|
||||
class GoalHandicap(Bet):
|
||||
"""Goal handicap for a specific team — add handicap_amount to team's score, team wins = you win"""
|
||||
team_bet: Literal["1", "2"] = "1" # which team the handicap is applied to
|
||||
handicap_amount: float = 0.0 # e.g. +1.5 or -0.5
|
||||
def resolve(self) -> None:
|
||||
...
|
||||
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome:
|
||||
home = match.goals_home + (self.handicap_amount if self.team_bet == "1" else 0.0)
|
||||
away = match.goals_away + (self.handicap_amount if self.team_bet == "2" else 0.0)
|
||||
if home == away:
|
||||
return BetOutcome.VOID
|
||||
actual_winner = "1" if home > away else "2"
|
||||
return BetOutcome.WIN if actual_winner == self.team_bet else BetOutcome.LOSE
|
||||
|
||||
|
||||
@dataclass
|
||||
class UnknownTicket(Bet):
|
||||
"""Bet type that could not be classified"""
|
||||
raw_text: str = ""
|
||||
def resolve(self) -> None:
|
||||
...
|
||||
|
||||
def resolve(self, match: MatchInfo) -> BetOutcome:
|
||||
return BetOutcome.UNKNOWN
|
||||
|
||||
|
||||
@dataclass
|
||||
class Ticket:
|
||||
id: int
|
||||
bets: list[Bet]
|
||||
bets: list[Bet]
|
||||
|
||||
@@ -4,3 +4,4 @@ from pydantic.dataclasses import dataclass
|
||||
@dataclass
|
||||
class ResolverConfig:
|
||||
api_key: str
|
||||
league_map: dict[str, int]
|
||||
|
||||
@@ -7,97 +7,20 @@ from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
from beaky import _ansi
|
||||
from beaky.datamodels.ticket import (
|
||||
Bet,
|
||||
BothTeamScored,
|
||||
GoalAmount,
|
||||
GoalHandicap,
|
||||
BetOutcome,
|
||||
MatchInfo,
|
||||
Ticket,
|
||||
UnknownTicket,
|
||||
WinDrawLose,
|
||||
WinDrawLoseDouble,
|
||||
WinLose,
|
||||
)
|
||||
from beaky.resolvers.config import ResolverConfig
|
||||
|
||||
_API_BASE = "https://v3.football.api-sports.io"
|
||||
|
||||
# Fortuna league strings (lowercased substring match) -> api-football league ID
|
||||
_LEAGUE_MAP: dict[str, int] = {
|
||||
# European cups
|
||||
"liga mistrů": 2,
|
||||
"champions league": 2,
|
||||
"evropská liga": 3,
|
||||
"europa league": 3,
|
||||
"konferenční liga": 848,
|
||||
"conference league": 848,
|
||||
# Top flights
|
||||
"1. anglie": 39,
|
||||
"1. belgie": 144,
|
||||
"1. česko": 345,
|
||||
"1. dánsko": 119,
|
||||
"1. francie": 61,
|
||||
"1. itálie": 135,
|
||||
"1. itálie - ženy": 794,
|
||||
"1. německo": 78,
|
||||
"1. nizozemsko": 88,
|
||||
"1. polsko": 106,
|
||||
"1. portugalsko": 94,
|
||||
"1. rakousko": 218,
|
||||
"1. rumunsko": 283,
|
||||
"1. skotsko": 179,
|
||||
"1. slovensko": 332,
|
||||
"1. španělsko": 140,
|
||||
"1. wales": 771,
|
||||
# Second divisions
|
||||
"2. anglie": 40,
|
||||
"2. česko": 346,
|
||||
"2. francie": 62,
|
||||
"2. itálie": 136,
|
||||
"2. německo": 79,
|
||||
"2. nizozemsko": 89,
|
||||
"2. rakousko": 219,
|
||||
"2. slovensko": 333,
|
||||
"2. španělsko": 141,
|
||||
# Third divisions
|
||||
"3. francie": 63,
|
||||
"3. česko msfl": 349,
|
||||
"3. česko čfl": 348,
|
||||
# Fourth divisions
|
||||
"4. česko - sk. a": 350,
|
||||
"4. česko - sk. b": 351,
|
||||
"4. česko - sk. c": 352,
|
||||
"4. česko - sk. d": 353,
|
||||
"4. česko - sk. e": 354,
|
||||
"4. česko - sk. f": 686,
|
||||
# Women
|
||||
"1. česko - ženy": 669,
|
||||
"fortuna=liga ženy": 669,
|
||||
# Domestic cups
|
||||
"anglie - fa cup": 45,
|
||||
"anglie - efl cup": 48,
|
||||
"česko - pohár": 347,
|
||||
}
|
||||
|
||||
_DATE_WINDOW = 3 # days either side of the bet date to search
|
||||
|
||||
# ANSI color helpers
|
||||
_R = "\033[0m"
|
||||
_B = "\033[1m"
|
||||
_DIM= "\033[2m"
|
||||
_GREEN = "\033[32m"
|
||||
_RED = "\033[31m"
|
||||
_YELLOW = "\033[33m"
|
||||
_CYAN = "\033[36m"
|
||||
_GRAY = "\033[90m"
|
||||
|
||||
_OUTCOME_COLOR = {
|
||||
"win": _GREEN,
|
||||
"lose": _RED,
|
||||
"void": _YELLOW,
|
||||
"unknown": _GRAY,
|
||||
}
|
||||
|
||||
|
||||
class TicketVerdict(str, Enum):
|
||||
TRUTHFUL = "truthful"
|
||||
@@ -106,13 +29,6 @@ class TicketVerdict(str, Enum):
|
||||
UNKNOWN = "unknown — could not resolve enough bets to decide"
|
||||
|
||||
|
||||
class BetOutcome(str, Enum):
|
||||
WIN = "win"
|
||||
LOSE = "lose"
|
||||
VOID = "void" # stake returned (e.g. WinLose on draw, integer goal line hit)
|
||||
UNKNOWN = "unknown" # fixture not found or unclassified bet
|
||||
|
||||
|
||||
@dataclass
|
||||
class ResolvedBet:
|
||||
bet: Bet
|
||||
@@ -166,6 +82,7 @@ def _get(url: str, headers: dict[str, str], params: dict[str, str | int], retrie
|
||||
class TicketResolver:
|
||||
def __init__(self, config: ResolverConfig):
|
||||
self._headers = {"x-apisports-key": config.api_key}
|
||||
self._league_map = config.league_map
|
||||
# Cache maps (center_date_str, league_id | None) -> list of fixture dicts
|
||||
self._fixture_cache: dict[tuple[str, int | None], list[dict[str, Any]]] = {}
|
||||
# Cache maps league name -> (league_id, confidence)
|
||||
@@ -179,31 +96,40 @@ class TicketResolver:
|
||||
|
||||
def _resolve_bet(self, bet: Bet) -> ResolvedBet:
|
||||
bet_type = type(bet).__name__
|
||||
print(f"\n {_B}{_CYAN}┌─ [{bet_type}]{_R} {_B}{bet.team1Name} vs {bet.team2Name}{_R}"
|
||||
f" {_DIM}{bet.date.strftime('%Y-%m-%d')} | {bet.league}{_R}")
|
||||
print(f"\n {_ansi.bold(_ansi.cyan(f'┌─ [{bet_type}]'))} {_ansi.bold(f'{bet.team1Name} vs {bet.team2Name}')}"
|
||||
f" {_ansi.dim(f'{bet.date.strftime('%Y-%m-%d')} | {bet.league}')}")
|
||||
|
||||
if isinstance(bet, UnknownTicket):
|
||||
print(f" {_GRAY}│ skipping — not implemented: {bet.raw_text!r}{_R}")
|
||||
print(f" {_GRAY}└─ UNKNOWN{_R}")
|
||||
print(_ansi.gray(f" │ skipping — not implemented: {bet.raw_text!r}"))
|
||||
print(_ansi.gray(" └─ UNKNOWN"))
|
||||
return ResolvedBet(bet=bet, outcome=BetOutcome.UNKNOWN)
|
||||
|
||||
fixture, name_match, date_prox, league_conf = self._find_fixture(bet)
|
||||
if fixture is None:
|
||||
print(f" {_GRAY}└─ UNKNOWN — no fixture found{_R}")
|
||||
print(_ansi.gray(" └─ UNKNOWN — no fixture found"))
|
||||
return ResolvedBet(bet=bet, outcome=BetOutcome.UNKNOWN, league_found=league_conf)
|
||||
|
||||
home_name = fixture["teams"]["home"]["name"]
|
||||
away_name = fixture["teams"]["away"]["name"]
|
||||
finished = _is_finished(fixture)
|
||||
confidence = round((name_match + date_prox + league_conf + finished) / 4, 3)
|
||||
outcome = _evaluate_bet(bet, fixture) if finished == 1.0 else BetOutcome.UNKNOWN
|
||||
|
||||
if finished == 1.0:
|
||||
match_info = _fixture_to_match_info(fixture)
|
||||
outcome = bet.resolve(match_info)
|
||||
else:
|
||||
outcome = BetOutcome.UNKNOWN
|
||||
|
||||
goals = fixture["goals"]
|
||||
color = _OUTCOME_COLOR.get(outcome.value, _GRAY)
|
||||
print(f" {_DIM}│ matched #{fixture['fixture']['id']}: {home_name} vs {away_name}"
|
||||
f" | {goals['home']}:{goals['away']} | {fixture['fixture']['status']['short']}"
|
||||
f" | confidence {confidence} (name={name_match:.2f} date={date_prox:.2f} league={league_conf} finished={finished}){_R}")
|
||||
print(f" {color}{_B}└─ {outcome.value.upper()}{_R}")
|
||||
print(_ansi.dim(
|
||||
f" │ matched #{fixture['fixture']['id']}: {home_name} vs {away_name}"
|
||||
f" | {goals['home']}:{goals['away']} | {fixture['fixture']['status']['short']}"
|
||||
f" | confidence {confidence} (name={name_match:.2f} date={date_prox:.2f} league={league_conf} finished={finished})"
|
||||
))
|
||||
print(_ansi.bold(_ansi.green(f" └─ {outcome.value.upper()}") if outcome == BetOutcome.WIN
|
||||
else _ansi.red(f" └─ {outcome.value.upper()}") if outcome == BetOutcome.LOSE
|
||||
else _ansi.yellow(f" └─ {outcome.value.upper()}") if outcome == BetOutcome.VOID
|
||||
else _ansi.gray(f" └─ {outcome.value.upper()}")))
|
||||
|
||||
return ResolvedBet(
|
||||
bet=bet,
|
||||
@@ -230,13 +156,13 @@ class TicketResolver:
|
||||
if league_id is not None:
|
||||
params["league"] = league_id
|
||||
params["season"] = center.year if center.month >= 7 else center.year - 1
|
||||
print(f" {_GRAY}│ GET /fixtures {params}{_R}")
|
||||
print(_ansi.gray(f" │ GET /fixtures {params}"))
|
||||
resp = _get(f"{_API_BASE}/fixtures", headers=self._headers, params=params)
|
||||
resp.raise_for_status()
|
||||
self._fixture_cache[cache_key] = resp.json().get("response", [])
|
||||
print(f" {_GRAY}│ {len(self._fixture_cache[cache_key])} fixtures returned (cached){_R}")
|
||||
print(_ansi.gray(f" │ {len(self._fixture_cache[cache_key])} fixtures returned (cached)"))
|
||||
else:
|
||||
print(f" {_GRAY}│ /fixtures (±{_DATE_WINDOW}d of {date_str}, league={league_id}) served from cache{_R}")
|
||||
print(_ansi.gray(f" │ /fixtures (±{_DATE_WINDOW}d of {date_str}, league={league_id}) served from cache"))
|
||||
|
||||
fixture, name_match, date_prox = _best_fixture_match(
|
||||
self._fixture_cache[cache_key], bet.team1Name, bet.team2Name, center
|
||||
@@ -248,28 +174,57 @@ class TicketResolver:
|
||||
if key in self._league_cache:
|
||||
return self._league_cache[key]
|
||||
|
||||
for pattern, league_id in _LEAGUE_MAP.items():
|
||||
for pattern, league_id in self._league_map.items():
|
||||
if pattern in key:
|
||||
print(f" {_GRAY}│ league {league_name!r} -> id={league_id} (static map){_R}")
|
||||
print(_ansi.gray(f" │ league {league_name!r} -> id={league_id} (static map)"))
|
||||
self._league_cache[key] = (league_id, 1.0)
|
||||
return league_id, 1.0
|
||||
|
||||
# Fall back to API search — lower confidence since first result is taken unverified
|
||||
print(f" {_GRAY}│ GET /leagues search={league_name!r}{_R}")
|
||||
print(_ansi.gray(f" │ GET /leagues search={league_name!r}"))
|
||||
resp = _get(f"{_API_BASE}/leagues", headers=self._headers, params={"search": league_name[:20]})
|
||||
results = resp.json().get("response", [])
|
||||
if results:
|
||||
league_id = results[0]["league"]["id"]
|
||||
league_found_name = results[0]["league"]["name"]
|
||||
print(f" {_GRAY}│ matched {league_found_name!r} id={league_id} (API fallback, confidence=0.7){_R}")
|
||||
print(_ansi.gray(f" │ matched {league_found_name!r} id={league_id} (API fallback, confidence=0.7)"))
|
||||
self._league_cache[key] = (league_id, 0.7)
|
||||
return league_id, 0.7
|
||||
|
||||
print(f" {_GRAY}│ no league found, searching fixtures by date only (confidence=0.3){_R}")
|
||||
print(_ansi.gray(" │ no league found, searching fixtures by date only (confidence=0.3)"))
|
||||
self._league_cache[key] = (None, 0.3)
|
||||
return None, 0.3
|
||||
|
||||
|
||||
def _fixture_to_match_info(fixture: dict[str, Any]) -> MatchInfo:
|
||||
goals = fixture.get("goals", {})
|
||||
score = fixture.get("score", {})
|
||||
halftime = score.get("halftime", {})
|
||||
|
||||
corners_home: int | None = None
|
||||
corners_away: int | None = None
|
||||
for stat_entry in fixture.get("statistics", []):
|
||||
for stat in stat_entry.get("statistics", []):
|
||||
if stat.get("type") == "Corner Kicks":
|
||||
team_id = stat_entry.get("team", {}).get("id")
|
||||
home_team_id = fixture.get("teams", {}).get("home", {}).get("id")
|
||||
value = stat.get("value")
|
||||
if isinstance(value, int):
|
||||
if team_id == home_team_id:
|
||||
corners_home = value
|
||||
else:
|
||||
corners_away = value
|
||||
|
||||
return MatchInfo(
|
||||
goals_home=goals.get("home", 0),
|
||||
goals_away=goals.get("away", 0),
|
||||
half_time_home=halftime.get("home"),
|
||||
half_time_away=halftime.get("away"),
|
||||
corners_home=corners_home,
|
||||
corners_away=corners_away,
|
||||
)
|
||||
|
||||
|
||||
def _similarity(a: str, b: str) -> float:
|
||||
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
|
||||
|
||||
@@ -306,49 +261,3 @@ def _best_fixture_match(fixtures: list[dict[str, Any]], team1: str, team2: str,
|
||||
def _is_finished(fixture: dict[str, Any]) -> float:
|
||||
status = fixture.get("fixture", {}).get("status", {}).get("short", "")
|
||||
return 1.0 if status in ("FT", "AET", "PEN", "AWD", "WO") else 0.0
|
||||
|
||||
|
||||
def _evaluate_bet(bet: Bet, fixture: dict[str, Any]) -> BetOutcome:
|
||||
goals = fixture.get("goals", {})
|
||||
home = goals.get("home")
|
||||
away = goals.get("away")
|
||||
|
||||
if home is None or away is None:
|
||||
return BetOutcome.UNKNOWN
|
||||
|
||||
if isinstance(bet, WinDrawLose):
|
||||
bet_draw = bet.betType in ("X", "0")
|
||||
if bet_draw:
|
||||
return BetOutcome.WIN if home == away else BetOutcome.LOSE
|
||||
actual = "1" if home > away else ("0" if home == away else "2")
|
||||
return BetOutcome.WIN if actual == bet.betType else BetOutcome.LOSE
|
||||
|
||||
if isinstance(bet, WinDrawLoseDouble):
|
||||
actual = "1" if home > away else ("0" if home == away else "2")
|
||||
return BetOutcome.WIN if actual in bet.betType else BetOutcome.LOSE
|
||||
|
||||
if isinstance(bet, WinLose):
|
||||
if home == away:
|
||||
return BetOutcome.VOID
|
||||
actual = "1" if home > away else "2"
|
||||
return BetOutcome.WIN if actual == bet.betType else BetOutcome.LOSE
|
||||
|
||||
if isinstance(bet, BothTeamScored):
|
||||
return BetOutcome.WIN if home > 0 and away > 0 else BetOutcome.LOSE
|
||||
|
||||
if isinstance(bet, GoalAmount):
|
||||
total = home + away
|
||||
if total == bet.line:
|
||||
return BetOutcome.VOID
|
||||
won = total > bet.line if bet.over else total < bet.line
|
||||
return BetOutcome.WIN if won else BetOutcome.LOSE
|
||||
|
||||
if isinstance(bet, GoalHandicap):
|
||||
h_home = home + (bet.handicap_amount if bet.team_bet == "1" else 0.0)
|
||||
h_away = away + (bet.handicap_amount if bet.team_bet == "2" else 0.0)
|
||||
if h_home == h_away:
|
||||
return BetOutcome.VOID
|
||||
actual_winner = "1" if h_home > h_away else "2"
|
||||
return BetOutcome.WIN if actual_winner == bet.team_bet else BetOutcome.LOSE
|
||||
|
||||
return BetOutcome.UNKNOWN
|
||||
|
||||
Reference in New Issue
Block a user