Update app.py
Browse files
app.py
CHANGED
|
@@ -63,7 +63,7 @@ SOCCER_LEAGUES = {
|
|
| 63 |
|
| 64 |
def get_team_news(team: str, sport: str = "football"):
|
| 65 |
url = f"https://news.google.com/search?q={team}+{sport}&hl=en"
|
| 66 |
-
r = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
|
| 67 |
soup = BeautifulSoup(r.text, "html.parser")
|
| 68 |
headlines = [h.text for h in soup.select("h3")[:5]]
|
| 69 |
return " ".join(headlines)
|
|
@@ -142,7 +142,7 @@ def scrape_nba_preseason_pairs_for_dates(dates):
|
|
| 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=
|
| 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)
|
|
@@ -168,6 +168,48 @@ def build_soccer_verdict(home, away, agg):
|
|
| 168 |
conf_ou = max(ou_25.get("over", 0.0), ou_25.get("under", 0.0))
|
| 169 |
return f"{home} vs {away}: {pick_1x2} ({conf_1x2:.2f}), {pick_ou} ({conf_ou:.2f})"
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
# --- Helpers: Football-Data last-5 and simple Poisson model ---
|
| 172 |
def football_headers():
|
| 173 |
return {"X-Auth-Token": FOOTBALL_API_KEY}
|
|
@@ -323,6 +365,10 @@ def soccer_predictions():
|
|
| 323 |
continue
|
| 324 |
|
| 325 |
for match in matches:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
home, away = match["homeTeam"]["name"], match["awayTeam"]["name"]
|
| 327 |
home_id = match["homeTeam"].get("id")
|
| 328 |
away_id = match["awayTeam"].get("id")
|
|
@@ -567,20 +613,26 @@ def nba_predictions():
|
|
| 567 |
home_sent = sentiment_model(default_text, labels)
|
| 568 |
away_sent = sentiment_model(default_text, labels)
|
| 569 |
|
|
|
|
|
|
|
|
|
|
| 570 |
context = f"""
|
| 571 |
Match: {home} vs {away}.
|
| 572 |
Home news: {home_news}.
|
| 573 |
Away news: {away_news}.
|
|
|
|
| 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({
|
| 581 |
"match": f"{home} vs {away}",
|
| 582 |
"reason": reasoning,
|
| 583 |
-
"verdict":
|
| 584 |
"news_summary": {
|
| 585 |
home: home_sent,
|
| 586 |
away: away_sent
|
|
|
|
| 63 |
|
| 64 |
def get_team_news(team: str, sport: str = "football"):
|
| 65 |
url = f"https://news.google.com/search?q={team}+{sport}&hl=en"
|
| 66 |
+
r = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
|
| 67 |
soup = BeautifulSoup(r.text, "html.parser")
|
| 68 |
headlines = [h.text for h in soup.select("h3")[:5]]
|
| 69 |
return " ".join(headlines)
|
|
|
|
| 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=10)
|
| 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)
|
|
|
|
| 168 |
conf_ou = max(ou_25.get("over", 0.0), ou_25.get("under", 0.0))
|
| 169 |
return f"{home} vs {away}: {pick_1x2} ({conf_1x2:.2f}), {pick_ou} ({conf_ou:.2f})"
|
| 170 |
|
| 171 |
+
def _score_zero_shot(sent_result):
|
| 172 |
+
# Expecting HF zero-shot output: {labels: [...], scores: [...]} possibly wrapped
|
| 173 |
+
try:
|
| 174 |
+
labels = sent_result.get('labels') or []
|
| 175 |
+
scores = sent_result.get('scores') or []
|
| 176 |
+
table = {lbl: scores[i] for i, lbl in enumerate(labels)}
|
| 177 |
+
positive = float(table.get('positive', 0.0))
|
| 178 |
+
negative = float(table.get('negative', 0.0))
|
| 179 |
+
injury = float(table.get('injury', 0.0))
|
| 180 |
+
motivation = float(table.get('motivation', 0.0))
|
| 181 |
+
transfer = float(table.get('transfer', 0.0))
|
| 182 |
+
return {
|
| 183 |
+
'positive': positive,
|
| 184 |
+
'negative': negative,
|
| 185 |
+
'injury': injury,
|
| 186 |
+
'motivation': motivation,
|
| 187 |
+
'transfer': transfer,
|
| 188 |
+
'net': positive - negative - 0.5 * injury + 0.25 * motivation
|
| 189 |
+
}
|
| 190 |
+
except Exception:
|
| 191 |
+
return {'positive': 0.0, 'negative': 0.0, 'injury': 0.0, 'motivation': 0.0, 'transfer': 0.0, 'net': 0.0}
|
| 192 |
+
|
| 193 |
+
def build_nba_verdict(home, away, home_sig, away_sig):
|
| 194 |
+
# Convert net signals into a simple probability with softmax-like scaling
|
| 195 |
+
h = home_sig.get('net', 0.0)
|
| 196 |
+
a = away_sig.get('net', 0.0)
|
| 197 |
+
margin = h - a
|
| 198 |
+
# logistic transform to [0,1]
|
| 199 |
+
import math
|
| 200 |
+
p_home = 1.0 / (1.0 + math.exp(-3.0 * margin))
|
| 201 |
+
p_away = 1.0 - p_home
|
| 202 |
+
# Over/Under lean from total sentiment energy
|
| 203 |
+
energy = max(0.0, home_sig.get('positive', 0.0) + away_sig.get('positive', 0.0) + home_sig.get('motivation', 0.0) + away_sig.get('motivation', 0.0))
|
| 204 |
+
injury_load = home_sig.get('injury', 0.0) + away_sig.get('injury', 0.0)
|
| 205 |
+
ou_pick = 'Over' if energy >= (0.6 + 0.3 * injury_load) else 'Under'
|
| 206 |
+
return {
|
| 207 |
+
'winner_probs': { home: p_home, away: p_away },
|
| 208 |
+
'winner_pick': home if p_home >= p_away else away,
|
| 209 |
+
'winner_confidence': max(p_home, p_away),
|
| 210 |
+
'ou_pick': ou_pick
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
# --- Helpers: Football-Data last-5 and simple Poisson model ---
|
| 214 |
def football_headers():
|
| 215 |
return {"X-Auth-Token": FOOTBALL_API_KEY}
|
|
|
|
| 365 |
continue
|
| 366 |
|
| 367 |
for match in matches:
|
| 368 |
+
# Skip matches that are already played or not upcoming
|
| 369 |
+
m_status = (match.get("status") or "").upper()
|
| 370 |
+
if m_status and m_status not in ("SCHEDULED", "TIMED", "POSTPONED"): # e.g., FINISHED, IN_PLAY, PAUSED
|
| 371 |
+
continue
|
| 372 |
home, away = match["homeTeam"]["name"], match["awayTeam"]["name"]
|
| 373 |
home_id = match["homeTeam"].get("id")
|
| 374 |
away_id = match["awayTeam"].get("id")
|
|
|
|
| 613 |
home_sent = sentiment_model(default_text, labels)
|
| 614 |
away_sent = sentiment_model(default_text, labels)
|
| 615 |
|
| 616 |
+
sig_home = _score_zero_shot(home_sent if isinstance(home_sent, dict) else (home_sent[0] if isinstance(home_sent, list) else {}))
|
| 617 |
+
sig_away = _score_zero_shot(away_sent if isinstance(away_sent, dict) else (away_sent[0] if isinstance(away_sent, list) else {}))
|
| 618 |
+
|
| 619 |
context = f"""
|
| 620 |
Match: {home} vs {away}.
|
| 621 |
Home news: {home_news}.
|
| 622 |
Away news: {away_news}.
|
| 623 |
+
Signals: home={sig_home}, away={sig_away}.
|
| 624 |
"""
|
| 625 |
reasoning = reasoning_model(
|
| 626 |
f"Provide a concise NBA verdict: likely winner and Over/Under guidance. Context: {context}",
|
| 627 |
max_new_tokens=256
|
| 628 |
)[0]['generated_text']
|
| 629 |
|
| 630 |
+
verdict = build_nba_verdict(home, away, sig_home, sig_away)
|
| 631 |
+
|
| 632 |
predictions.append({
|
| 633 |
"match": f"{home} vs {away}",
|
| 634 |
"reason": reasoning,
|
| 635 |
+
"verdict": verdict,
|
| 636 |
"news_summary": {
|
| 637 |
home: home_sent,
|
| 638 |
away: away_sent
|