Update app.py
Browse files
app.py
CHANGED
|
@@ -92,25 +92,69 @@ def _extract_pairs_from_texts(texts):
|
|
| 92 |
pairs.append({"home": home, "away": away})
|
| 93 |
return pairs
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
def scrape_nba_preseason_pairs_for_dates(dates):
|
| 96 |
found = []
|
| 97 |
seen = set()
|
| 98 |
headers = {"User-Agent": "Mozilla/5.0"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
for d in dates:
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
key = (p["home"].lower(), p["away"].lower())
|
| 109 |
-
if key not in seen:
|
| 110 |
-
seen.add(key)
|
| 111 |
-
found.append(p)
|
| 112 |
-
except Exception:
|
| 113 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
return found
|
| 115 |
|
| 116 |
# --- Verdict helpers ---
|
|
@@ -242,26 +286,25 @@ def aggregate_outcomes(prob_matrix, thresholds=(0.5, 1.5, 2.5, 3.5)):
|
|
| 242 |
@app.get("/soccer-predictions")
|
| 243 |
def soccer_predictions():
|
| 244 |
from datetime import timedelta
|
| 245 |
-
|
| 246 |
-
|
| 247 |
today = date.today()
|
| 248 |
date_from = (today - timedelta(days=5)).isoformat()
|
| 249 |
date_to = (today + timedelta(days=5)).isoformat()
|
| 250 |
-
|
| 251 |
predictions = []
|
| 252 |
debug_info = []
|
| 253 |
|
| 254 |
for league, code in SOCCER_LEAGUES.items():
|
| 255 |
headers = {"X-Auth-Token": FOOTBALL_API_KEY}
|
| 256 |
try:
|
| 257 |
-
|
| 258 |
f"{FOOTBALL_ENDPOINT}?competitions={code}&dateFrom={date_from}&dateTo={date_to}",
|
| 259 |
-
|
| 260 |
-
|
| 261 |
resp.raise_for_status()
|
| 262 |
data = resp.json()
|
| 263 |
matches = data.get("matches", [])
|
| 264 |
-
|
| 265 |
debug_info.append({
|
| 266 |
"league": league,
|
| 267 |
"code": code,
|
|
@@ -269,7 +312,7 @@ def soccer_predictions():
|
|
| 269 |
"api_response_status": resp.status_code,
|
| 270 |
"date_range": f"{date_from} to {date_to}"
|
| 271 |
})
|
| 272 |
-
|
| 273 |
except requests.exceptions.RequestException as e:
|
| 274 |
debug_info.append({
|
| 275 |
"league": league,
|
|
@@ -331,7 +374,7 @@ def soccer_predictions():
|
|
| 331 |
"""
|
| 332 |
reasoning = reasoning_model(
|
| 333 |
f"Provide a concise betting-style verdict (1X2, O/U, double chance) with rationale given the context. Context: {context}",
|
| 334 |
-
|
| 335 |
)[0]['generated_text']
|
| 336 |
|
| 337 |
predictions.append({
|
|
@@ -439,6 +482,7 @@ def nba_predictions():
|
|
| 439 |
predictions = []
|
| 440 |
debug_info = []
|
| 441 |
|
|
|
|
| 442 |
try:
|
| 443 |
# Test NBA API connectivity first
|
| 444 |
games_response = nba_api.nba.games.list(dates=dates)
|
|
@@ -530,7 +574,7 @@ def nba_predictions():
|
|
| 530 |
"""
|
| 531 |
reasoning = reasoning_model(
|
| 532 |
f"Provide a concise NBA verdict: likely winner and Over/Under guidance. Context: {context}",
|
| 533 |
-
|
| 534 |
)[0]['generated_text']
|
| 535 |
|
| 536 |
predictions.append({
|
|
|
|
| 92 |
pairs.append({"home": home, "away": away})
|
| 93 |
return pairs
|
| 94 |
|
| 95 |
+
def fetch_espn_pairs_for_date(d: str):
|
| 96 |
+
# d: YYYY-MM-DD -> ESPN expects YYYYMMDD
|
| 97 |
+
try:
|
| 98 |
+
y, m, day = d.split("-")
|
| 99 |
+
ymd = f"{y}{m}{day}"
|
| 100 |
+
url = f"https://site.web.api.espn.com/apis/v2/sports/basketball/nba/scoreboard?dates={ymd}"
|
| 101 |
+
r = requests.get(url, timeout=20)
|
| 102 |
+
r.raise_for_status()
|
| 103 |
+
data = r.json()
|
| 104 |
+
events = data.get("events", [])
|
| 105 |
+
pairs = []
|
| 106 |
+
for ev in events:
|
| 107 |
+
comps = (ev.get("competitions") or [{}])[0].get("competitors") or []
|
| 108 |
+
home = next((c for c in comps if c.get("homeAway") == "home"), None)
|
| 109 |
+
away = next((c for c in comps if c.get("homeAway") == "away"), None)
|
| 110 |
+
if home and away:
|
| 111 |
+
pairs.append({
|
| 112 |
+
"home": home.get("team", {}).get("displayName") or home.get("team", {}).get("name"),
|
| 113 |
+
"away": away.get("team", {}).get("displayName") or away.get("team", {}).get("name")
|
| 114 |
+
})
|
| 115 |
+
return pairs
|
| 116 |
+
except Exception:
|
| 117 |
+
return []
|
| 118 |
+
|
| 119 |
def scrape_nba_preseason_pairs_for_dates(dates):
|
| 120 |
found = []
|
| 121 |
seen = set()
|
| 122 |
headers = {"User-Agent": "Mozilla/5.0"}
|
| 123 |
+
queries = [
|
| 124 |
+
"NBA preseason {d}",
|
| 125 |
+
"NBA schedule {d}",
|
| 126 |
+
"NBA games {d}",
|
| 127 |
+
"site:espn.com NBA {d}",
|
| 128 |
+
"site:yahoo.com/sports NBA {d}"
|
| 129 |
+
]
|
| 130 |
for d in dates:
|
| 131 |
+
# First try ESPN JSON scoreboard (works for preseason too)
|
| 132 |
+
espn_pairs = fetch_espn_pairs_for_date(d)
|
| 133 |
+
for p in espn_pairs:
|
| 134 |
+
key = (p["home"].lower(), p["away"].lower())
|
| 135 |
+
if key not in seen:
|
| 136 |
+
seen.add(key)
|
| 137 |
+
found.append(p)
|
| 138 |
+
if espn_pairs:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
continue
|
| 140 |
+
# Fallback: lightweight Google scrape with multiple queries
|
| 141 |
+
for qtpl in queries:
|
| 142 |
+
q = qtpl.format(d=d)
|
| 143 |
+
url = f"https://www.google.com/search?q={q}"
|
| 144 |
+
try:
|
| 145 |
+
r = requests.get(url, headers=headers, timeout=20)
|
| 146 |
+
soup = BeautifulSoup(r.text, "html.parser")
|
| 147 |
+
h3s = [h.text for h in soup.select("h3")[:30]]
|
| 148 |
+
pairs = _extract_pairs_from_texts(h3s)
|
| 149 |
+
for p in pairs:
|
| 150 |
+
key = (p["home"].lower(), p["away"].lower())
|
| 151 |
+
if key not in seen:
|
| 152 |
+
seen.add(key)
|
| 153 |
+
found.append(p)
|
| 154 |
+
if found:
|
| 155 |
+
break
|
| 156 |
+
except Exception:
|
| 157 |
+
continue
|
| 158 |
return found
|
| 159 |
|
| 160 |
# --- Verdict helpers ---
|
|
|
|
| 286 |
@app.get("/soccer-predictions")
|
| 287 |
def soccer_predictions():
|
| 288 |
from datetime import timedelta
|
| 289 |
+
|
|
|
|
| 290 |
today = date.today()
|
| 291 |
date_from = (today - timedelta(days=5)).isoformat()
|
| 292 |
date_to = (today + timedelta(days=5)).isoformat()
|
| 293 |
+
|
| 294 |
predictions = []
|
| 295 |
debug_info = []
|
| 296 |
|
| 297 |
for league, code in SOCCER_LEAGUES.items():
|
| 298 |
headers = {"X-Auth-Token": FOOTBALL_API_KEY}
|
| 299 |
try:
|
| 300 |
+
resp = requests.get(
|
| 301 |
f"{FOOTBALL_ENDPOINT}?competitions={code}&dateFrom={date_from}&dateTo={date_to}",
|
| 302 |
+
headers=headers
|
| 303 |
+
)
|
| 304 |
resp.raise_for_status()
|
| 305 |
data = resp.json()
|
| 306 |
matches = data.get("matches", [])
|
| 307 |
+
|
| 308 |
debug_info.append({
|
| 309 |
"league": league,
|
| 310 |
"code": code,
|
|
|
|
| 312 |
"api_response_status": resp.status_code,
|
| 313 |
"date_range": f"{date_from} to {date_to}"
|
| 314 |
})
|
| 315 |
+
|
| 316 |
except requests.exceptions.RequestException as e:
|
| 317 |
debug_info.append({
|
| 318 |
"league": league,
|
|
|
|
| 374 |
"""
|
| 375 |
reasoning = reasoning_model(
|
| 376 |
f"Provide a concise betting-style verdict (1X2, O/U, double chance) with rationale given the context. Context: {context}",
|
| 377 |
+
max_new_tokens=256
|
| 378 |
)[0]['generated_text']
|
| 379 |
|
| 380 |
predictions.append({
|
|
|
|
| 482 |
predictions = []
|
| 483 |
debug_info = []
|
| 484 |
|
| 485 |
+
games = []
|
| 486 |
try:
|
| 487 |
# Test NBA API connectivity first
|
| 488 |
games_response = nba_api.nba.games.list(dates=dates)
|
|
|
|
| 574 |
"""
|
| 575 |
reasoning = reasoning_model(
|
| 576 |
f"Provide a concise NBA verdict: likely winner and Over/Under guidance. Context: {context}",
|
| 577 |
+
max_new_tokens=256
|
| 578 |
)[0]['generated_text']
|
| 579 |
|
| 580 |
predictions.append({
|