Spaces:
Sleeping
Sleeping
Update SQL
Browse files
SQL
CHANGED
|
@@ -1,50 +1,243 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Here is Claude's plan: βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Fix: HR Props 422 β Replace Upcoming Props Endpoint
|
| 2 |
+
|
| 3 |
+
Root Cause (Confirmed)
|
| 4 |
+
|
| 5 |
+
GET /v4/sports/baseball_mlb/odds?markets=batter_home_runs β HTTP 422.
|
| 6 |
+
The aggregate /odds endpoint only serves h2h, spreads, totals.
|
| 7 |
+
Player props require /v4/sports/baseball_mlb/events/{eventId}/odds.
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
Exact File/Function to Change
|
| 11 |
+
|
| 12 |
+
One function, one file.
|
| 13 |
+
|
| 14 |
+
- File: data/provider_theoddsapi.py
|
| 15 |
+
- Function: TheOddsAPIProvider.fetch_all_upcoming_hr_props (lines 193β305)
|
| 16 |
+
- What changes: The entire body after the two guard checks β everything from the
|
| 17 |
+
sport_key = "baseball_mlb" line onward is replaced.
|
| 18 |
+
- Signature: unchanged (self, sportsbooks: list[str] | None = None) β no call sites break.
|
| 19 |
+
|
| 20 |
+
Nothing else is modified. fetch_live_prop_odds is untouched.
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
Downstream Schema Contract
|
| 24 |
+
|
| 25 |
+
normalize_prop_odds in live_prop_odds.py:11-34 requires these columns
|
| 26 |
+
(missing ones are filled with None β order does not matter):
|
| 27 |
+
|
| 28 |
+
provider, event_id, commence_time, away_team, home_team,
|
| 29 |
+
sportsbook, market, player_name_raw, player_name, odds_american, line
|
| 30 |
+
|
| 31 |
+
The new code produces the identical row dict as the old code β same keys, same
|
| 32 |
+
derivation logic. The per-event /events/{id}/odds response uses the same
|
| 33 |
+
bookmakers β markets β outcomes JSON structure as the aggregate endpoint.
|
| 34 |
+
market_key and sportsbook_key are also produced (extra columns β safe, just ignored by
|
| 35 |
+
normalize_prop_odds, used downstream by the execution layer if present).
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
Live Props Path: Zero Risk
|
| 39 |
+
|
| 40 |
+
fetch_live_prop_odds (lines 101β191) is a separate method. It is not called, not
|
| 41 |
+
imported, and not refactored. The Dashboard debug panel that calls it will continue to
|
| 42 |
+
behave identically (including its own 422 bug, which is out of scope).
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
New Request Flow
|
| 46 |
+
|
| 47 |
+
Old (broken):
|
| 48 |
+
1 call β GET /v4/sports/baseball_mlb/odds?markets=batter_home_runs β 422
|
| 49 |
+
|
| 50 |
+
New:
|
| 51 |
+
Step 1 β GET /v4/sports/baseball_mlb/events
|
| 52 |
+
params: apiKey, dateFormat=iso,
|
| 53 |
+
commenceTimeFrom=<now UTC>,
|
| 54 |
+
commenceTimeTo=<now+7d UTC>
|
| 55 |
+
β list of {id, away_team, home_team, commence_time}
|
| 56 |
+
β 1 API credit
|
| 57 |
+
|
| 58 |
+
Step 2 β for each event_id (capped at _MAX_EVENTS=20):
|
| 59 |
+
GET /v4/sports/baseball_mlb/events/{event_id}/odds
|
| 60 |
+
params: apiKey, markets=batter_home_runs,
|
| 61 |
+
bookmakers=draftkings,fanduel,betmgm,
|
| 62 |
+
oddsFormat=american, dateFormat=iso
|
| 63 |
+
β {id, bookmakers:[{key, markets:[{key, outcomes:[{description,price,point}]}]}]}
|
| 64 |
+
β 1 API credit per event; failures logged and skipped
|
| 65 |
+
|
| 66 |
+
Step 2 response is parsed with the identical bookmakerβmarketβoutcome loop
|
| 67 |
+
already in the function β only the HTTP call changes.
|
| 68 |
+
|
| 69 |
+
Why commenceTimeFrom/To: limits events to upcoming games only; avoids fetching
|
| 70 |
+
a full past-season list; keeps credit cost predictable.
|
| 71 |
+
|
| 72 |
+
Note on regions: The per-event /events/{id}/odds endpoint accepts bookmakers
|
| 73 |
+
to filter by specific sportsbook keys. regions is not needed and not sent (it would
|
| 74 |
+
be ignored anyway when bookmakers is specified, per Odds API docs).
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
Credit Cost
|
| 78 |
+
|
| 79 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
|
| 80 |
+
β Scenario β Credits β
|
| 81 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
|
| 82 |
+
β Old code (every call) β 1 credit then 422 β wasted β
|
| 83 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
|
| 84 |
+
β New code, Opening Day (~15 games) β 1 (events) + 15 (per-event) = 16 β
|
| 85 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
|
| 86 |
+
β New code, mid-season full slate (~30 games, capped) β 1 + 20 = 21 (MAX_EVENTS cap) β
|
| 87 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
|
| 88 |
+
β Each additional Props page load (no cache) β same 16β21 credits β
|
| 89 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββ
|
| 90 |
+
|
| 91 |
+
No caching is added in this fix. The credit cost per load is real. If credit burn becomes
|
| 92 |
+
a concern, add a @st.cache_data(ttl=300) wrapper in app.py as a follow-up.
|
| 93 |
+
|
| 94 |
+
---
|
| 95 |
+
Exact Code Change
|
| 96 |
+
|
| 97 |
+
Add one constant after BOOK_KEY_MAP (line ~50):
|
| 98 |
+
_MAX_EVENTS = 20
|
| 99 |
+
|
| 100 |
+
Replace fetch_all_upcoming_hr_props body β keep the first two guard blocks
|
| 101 |
+
(if not ODDS_API_KEY and if not books), then replace the rest:
|
| 102 |
+
|
| 103 |
+
from datetime import datetime, timezone, timedelta
|
| 104 |
+
|
| 105 |
+
now = datetime.now(timezone.utc)
|
| 106 |
+
events_url = f"{ODDS_API_BASE}/baseball_mlb/events"
|
| 107 |
+
events_params = {
|
| 108 |
+
"apiKey": ODDS_API_KEY,
|
| 109 |
+
"dateFormat": "iso",
|
| 110 |
+
"commenceTimeFrom": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
| 111 |
+
"commenceTimeTo": (now + timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
_diag_log.info("[upcoming_hr_props] Step1 events window=now+7d")
|
| 115 |
+
try:
|
| 116 |
+
r1 = requests.get(events_url, params=events_params, timeout=30)
|
| 117 |
+
_diag_log.info(
|
| 118 |
+
"[upcoming_hr_props] events HTTP %s | remaining=%s",
|
| 119 |
+
r1.status_code, r1.headers.get("x-requests-remaining", "?"),
|
| 120 |
+
)
|
| 121 |
+
r1.raise_for_status()
|
| 122 |
+
except requests.HTTPError as exc:
|
| 123 |
+
body = (exc.response.text[:300] if exc.response is not None else "")
|
| 124 |
+
raise RuntimeError(
|
| 125 |
+
f"Odds API events list HTTP {exc.response.status_code}: {body}"
|
| 126 |
+
) from exc
|
| 127 |
+
except requests.RequestException as exc:
|
| 128 |
+
raise RuntimeError(f"Odds API events network error: {exc}") from exc
|
| 129 |
+
|
| 130 |
+
events = r1.json()
|
| 131 |
+
_diag_log.info(
|
| 132 |
+
"[upcoming_hr_props] events found=%d (cap=%d)", len(events), _MAX_EVENTS
|
| 133 |
+
)
|
| 134 |
+
events = events[:_MAX_EVENTS]
|
| 135 |
+
|
| 136 |
+
rows: list[dict[str, Any]] = []
|
| 137 |
+
for event in events:
|
| 138 |
+
event_id = str(event.get("id", "") or "")
|
| 139 |
+
if not event_id:
|
| 140 |
+
continue
|
| 141 |
+
away_team = str(event.get("away_team", "") or "")
|
| 142 |
+
home_team = str(event.get("home_team", "") or "")
|
| 143 |
+
commence_time = str(event.get("commence_time", "") or "")
|
| 144 |
+
|
| 145 |
+
odds_url = f"{ODDS_API_BASE}/baseball_mlb/events/{event_id}/odds"
|
| 146 |
+
odds_params = {
|
| 147 |
+
"apiKey": ODDS_API_KEY,
|
| 148 |
+
"markets": "batter_home_runs",
|
| 149 |
+
"bookmakers": ",".join(books),
|
| 150 |
+
"oddsFormat": "american",
|
| 151 |
+
"dateFormat": "iso",
|
| 152 |
+
}
|
| 153 |
+
try:
|
| 154 |
+
r2 = requests.get(odds_url, params=odds_params, timeout=30)
|
| 155 |
+
r2.raise_for_status()
|
| 156 |
+
except (requests.HTTPError, requests.RequestException) as exc:
|
| 157 |
+
_diag_log.warning(
|
| 158 |
+
"[upcoming_hr_props] event %s@%s odds failed: %s",
|
| 159 |
+
away_team, home_team, exc,
|
| 160 |
+
)
|
| 161 |
+
continue
|
| 162 |
+
|
| 163 |
+
event_data = r2.json()
|
| 164 |
+
bookmakers = (
|
| 165 |
+
event_data.get("bookmakers", []) if isinstance(event_data, dict) else []
|
| 166 |
+
)
|
| 167 |
+
_diag_log.info(
|
| 168 |
+
"[upcoming_hr_props] %s@%s books=%s",
|
| 169 |
+
away_team, home_team, [b.get("key") for b in bookmakers],
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
for bookmaker in bookmakers:
|
| 173 |
+
book_key = str(bookmaker.get("key", "") or "")
|
| 174 |
+
book_name = BOOK_KEY_MAP.get(book_key, book_key)
|
| 175 |
+
|
| 176 |
+
for market in bookmaker.get("markets", []) or []:
|
| 177 |
+
market_key = str(market.get("key", "") or "")
|
| 178 |
+
if market_key != "batter_home_runs":
|
| 179 |
+
continue
|
| 180 |
+
market_name = MARKET_NAME_MAP.get(market_key, market_key)
|
| 181 |
+
|
| 182 |
+
for outcome in market.get("outcomes", []) or []:
|
| 183 |
+
player_name_raw = str(
|
| 184 |
+
outcome.get("description", "") or outcome.get("name", "") or ""
|
| 185 |
+
).strip()
|
| 186 |
+
if not player_name_raw:
|
| 187 |
+
continue
|
| 188 |
+
|
| 189 |
+
price = outcome.get("price")
|
| 190 |
+
if price is None:
|
| 191 |
+
continue
|
| 192 |
+
|
| 193 |
+
rows.append(
|
| 194 |
+
{
|
| 195 |
+
"provider": self.provider_name,
|
| 196 |
+
"event_id": event_id,
|
| 197 |
+
"commence_time": commence_time,
|
| 198 |
+
"away_team": away_team,
|
| 199 |
+
"home_team": home_team,
|
| 200 |
+
"sportsbook": book_name,
|
| 201 |
+
"sportsbook_key": book_key,
|
| 202 |
+
"market_key": market_key,
|
| 203 |
+
"market": market_name,
|
| 204 |
+
"player_name_raw": player_name_raw,
|
| 205 |
+
"player_name": map_odds_name_to_model_name(player_name_raw),
|
| 206 |
+
"odds_american": int(price),
|
| 207 |
+
"line": _safe_float(outcome.get("point")),
|
| 208 |
+
}
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
_diag_log.info("[upcoming_hr_props] total rows=%d", len(rows))
|
| 212 |
+
return pd.DataFrame(rows)
|
| 213 |
+
|
| 214 |
+
The inner bookmaker/market/outcome loop is identical to the existing code β only
|
| 215 |
+
the outer HTTP layer changed. Downstream schema is preserved exactly.
|
| 216 |
+
|
| 217 |
+
---
|
| 218 |
+
Summary
|
| 219 |
+
|
| 220 |
+
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 221 |
+
β What β Answer β
|
| 222 |
+
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
| 223 |
+
β Files changed β 1: data/provider_theoddsapi.py β
|
| 224 |
+
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
| 225 |
+
β Functions changed β 1: fetch_all_upcoming_hr_props β
|
| 226 |
+
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
| 227 |
+
β Signature changed β No β
|
| 228 |
+
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
| 229 |
+
β Live props affected β No β fetch_live_prop_odds not touched β
|
| 230 |
+
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
| 231 |
+
β Downstream schema changed β No β identical row dicts β
|
| 232 |
+
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
| 233 |
+
β Why it fixes 422 β Per-event endpoint accepts batter_home_runs; aggregate does not β
|
| 234 |
+
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
| 235 |
+
β Credit tradeoff β ~16 credits/call on Opening Day vs 1 (which 422'd and was wasted) β
|
| 236 |
+
βββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 237 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 238 |
+
|
| 239 |
+
Claude has written up a plan and is ready to execute. Would you like to proceed?
|
| 240 |
+
|
| 241 |
+
β― 1. Yes, auto-accept edits
|
| 242 |
+
2. Yes, manually approve edits
|
| 243 |
+
3. Type here to tell Claude what to change
|