Spaces:
Sleeping
Sleeping
Add forensic diagnostic logging to fetch_all_upcoming_hr_props
Browse filesInstruments the per-event odds path with full visibility into every step:
- Step 1: log exact events request params (apiKey masked)
- Step 2: log exact per-event odds params + HTTP status before raise_for_status
- Response: log type, top-level keys, books returned, markets per book, outcome counts
- Row-build loop: counters for bookmakers/markets/outcomes/rows plus per-reason skip counts
- Sample first 3 outcomes verbatim (keys, name, description, price, point)
No fixes applied. Diagnostic only — break point to be identified from log output.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- data/provider_theoddsapi.py +73 -3
data/provider_theoddsapi.py
CHANGED
|
@@ -221,7 +221,11 @@ class TheOddsAPIProvider(MarketProviderBase):
|
|
| 221 |
"commenceTimeTo": (now + timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
| 222 |
}
|
| 223 |
|
| 224 |
-
_diag_log.info(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
try:
|
| 226 |
r1 = requests.get(events_url, params=events_params, timeout=30)
|
| 227 |
_diag_log.info(
|
|
@@ -262,8 +266,24 @@ class TheOddsAPIProvider(MarketProviderBase):
|
|
| 262 |
"oddsFormat": "american",
|
| 263 |
"dateFormat": "iso",
|
| 264 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
try:
|
| 266 |
r2 = requests.get(odds_url, params=odds_params, timeout=30)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
r2.raise_for_status()
|
| 268 |
except (requests.HTTPError, requests.RequestException) as exc:
|
| 269 |
_diag_log.warning(
|
|
@@ -276,30 +296,71 @@ class TheOddsAPIProvider(MarketProviderBase):
|
|
| 276 |
bookmakers = (
|
| 277 |
event_data.get("bookmakers", []) if isinstance(event_data, dict) else []
|
| 278 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
_diag_log.info(
|
| 280 |
-
"[upcoming_hr_props] %s@%s books=%s",
|
| 281 |
-
away_team, home_team,
|
|
|
|
| 282 |
)
|
| 283 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
for bookmaker in bookmakers:
|
|
|
|
| 285 |
book_key = str(bookmaker.get("key", "") or "")
|
| 286 |
book_name = BOOK_KEY_MAP.get(book_key, book_key)
|
| 287 |
|
| 288 |
for market in bookmaker.get("markets", []) or []:
|
|
|
|
| 289 |
market_key = str(market.get("key", "") or "")
|
| 290 |
if market_key != "batter_home_runs":
|
|
|
|
| 291 |
continue
|
| 292 |
market_name = MARKET_NAME_MAP.get(market_key, market_key)
|
| 293 |
|
| 294 |
for outcome in market.get("outcomes", []) or []:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
player_name_raw = str(
|
| 296 |
outcome.get("description", "") or outcome.get("name", "") or ""
|
| 297 |
).strip()
|
| 298 |
if not player_name_raw:
|
|
|
|
| 299 |
continue
|
| 300 |
|
| 301 |
price = outcome.get("price")
|
| 302 |
if price is None:
|
|
|
|
| 303 |
continue
|
| 304 |
|
| 305 |
rows.append(
|
|
@@ -319,6 +380,15 @@ class TheOddsAPIProvider(MarketProviderBase):
|
|
| 319 |
"line": _safe_float(outcome.get("point")),
|
| 320 |
}
|
| 321 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
|
| 323 |
_diag_log.info("[upcoming_hr_props] total rows=%d", len(rows))
|
| 324 |
return pd.DataFrame(rows)
|
|
|
|
| 221 |
"commenceTimeTo": (now + timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
| 222 |
}
|
| 223 |
|
| 224 |
+
_diag_log.info(
|
| 225 |
+
"[upcoming_hr_props] Step1 GET %s params=%s",
|
| 226 |
+
events_url,
|
| 227 |
+
{k: (v if k != "apiKey" else v[:6] + "...") for k, v in events_params.items()},
|
| 228 |
+
)
|
| 229 |
try:
|
| 230 |
r1 = requests.get(events_url, params=events_params, timeout=30)
|
| 231 |
_diag_log.info(
|
|
|
|
| 266 |
"oddsFormat": "american",
|
| 267 |
"dateFormat": "iso",
|
| 268 |
}
|
| 269 |
+
_diag_log.info(
|
| 270 |
+
"[upcoming_hr_props] Step2 event_id=%s %s@%s url=%s params=%s",
|
| 271 |
+
event_id,
|
| 272 |
+
away_team,
|
| 273 |
+
home_team,
|
| 274 |
+
odds_url,
|
| 275 |
+
{k: (v if k != "apiKey" else v[:6] + "...") for k, v in odds_params.items()},
|
| 276 |
+
)
|
| 277 |
try:
|
| 278 |
r2 = requests.get(odds_url, params=odds_params, timeout=30)
|
| 279 |
+
_diag_log.info(
|
| 280 |
+
"[upcoming_hr_props] Step2 HTTP %s | remaining=%s | event_id=%s %s@%s",
|
| 281 |
+
r2.status_code,
|
| 282 |
+
r2.headers.get("x-requests-remaining", "?"),
|
| 283 |
+
event_id,
|
| 284 |
+
away_team,
|
| 285 |
+
home_team,
|
| 286 |
+
)
|
| 287 |
r2.raise_for_status()
|
| 288 |
except (requests.HTTPError, requests.RequestException) as exc:
|
| 289 |
_diag_log.warning(
|
|
|
|
| 296 |
bookmakers = (
|
| 297 |
event_data.get("bookmakers", []) if isinstance(event_data, dict) else []
|
| 298 |
)
|
| 299 |
+
_data_type = type(event_data).__name__
|
| 300 |
+
_top_keys = list(event_data.keys()) if isinstance(event_data, dict) else f"LIST len={len(event_data)}"
|
| 301 |
+
_bk_keys = [b.get("key") for b in bookmakers]
|
| 302 |
+
_markets_by_book = {
|
| 303 |
+
b.get("key"): [m.get("key") for m in b.get("markets", [])]
|
| 304 |
+
for b in bookmakers
|
| 305 |
+
}
|
| 306 |
+
_outcome_counts = {
|
| 307 |
+
f"{b.get('key')}:{m.get('key')}": len(m.get("outcomes", []))
|
| 308 |
+
for b in bookmakers
|
| 309 |
+
for m in b.get("markets", [])
|
| 310 |
+
}
|
| 311 |
_diag_log.info(
|
| 312 |
+
"[upcoming_hr_props] %s@%s type=%s top_keys=%s books=%s markets=%s outcome_counts=%s",
|
| 313 |
+
away_team, home_team, _data_type, _top_keys, _bk_keys,
|
| 314 |
+
_markets_by_book, _outcome_counts,
|
| 315 |
)
|
| 316 |
|
| 317 |
+
_bookmakers_seen = 0
|
| 318 |
+
_markets_seen = 0
|
| 319 |
+
_outcomes_seen = 0
|
| 320 |
+
_rows_appended = 0
|
| 321 |
+
_skip_market_mismatch = 0
|
| 322 |
+
_skip_empty_name = 0
|
| 323 |
+
_skip_missing_price = 0
|
| 324 |
+
_sample_logged = False
|
| 325 |
+
|
| 326 |
for bookmaker in bookmakers:
|
| 327 |
+
_bookmakers_seen += 1
|
| 328 |
book_key = str(bookmaker.get("key", "") or "")
|
| 329 |
book_name = BOOK_KEY_MAP.get(book_key, book_key)
|
| 330 |
|
| 331 |
for market in bookmaker.get("markets", []) or []:
|
| 332 |
+
_markets_seen += 1
|
| 333 |
market_key = str(market.get("key", "") or "")
|
| 334 |
if market_key != "batter_home_runs":
|
| 335 |
+
_skip_market_mismatch += 1
|
| 336 |
continue
|
| 337 |
market_name = MARKET_NAME_MAP.get(market_key, market_key)
|
| 338 |
|
| 339 |
for outcome in market.get("outcomes", []) or []:
|
| 340 |
+
_outcomes_seen += 1
|
| 341 |
+
|
| 342 |
+
if not _sample_logged and _outcomes_seen <= 3:
|
| 343 |
+
_diag_log.info(
|
| 344 |
+
"[upcoming_hr_props] sample outcome keys=%s name=%r description=%r price=%r point=%r",
|
| 345 |
+
list(outcome.keys()),
|
| 346 |
+
outcome.get("name"),
|
| 347 |
+
outcome.get("description"),
|
| 348 |
+
outcome.get("price"),
|
| 349 |
+
outcome.get("point"),
|
| 350 |
+
)
|
| 351 |
+
if _outcomes_seen >= 3:
|
| 352 |
+
_sample_logged = True
|
| 353 |
+
|
| 354 |
player_name_raw = str(
|
| 355 |
outcome.get("description", "") or outcome.get("name", "") or ""
|
| 356 |
).strip()
|
| 357 |
if not player_name_raw:
|
| 358 |
+
_skip_empty_name += 1
|
| 359 |
continue
|
| 360 |
|
| 361 |
price = outcome.get("price")
|
| 362 |
if price is None:
|
| 363 |
+
_skip_missing_price += 1
|
| 364 |
continue
|
| 365 |
|
| 366 |
rows.append(
|
|
|
|
| 380 |
"line": _safe_float(outcome.get("point")),
|
| 381 |
}
|
| 382 |
)
|
| 383 |
+
_rows_appended += 1
|
| 384 |
+
|
| 385 |
+
_diag_log.info(
|
| 386 |
+
"[upcoming_hr_props] %s@%s counters: bookmakers=%d markets=%d outcomes=%d rows=%d "
|
| 387 |
+
"skip_market=%d skip_name=%d skip_price=%d",
|
| 388 |
+
away_team, home_team,
|
| 389 |
+
_bookmakers_seen, _markets_seen, _outcomes_seen, _rows_appended,
|
| 390 |
+
_skip_market_mismatch, _skip_empty_name, _skip_missing_price,
|
| 391 |
+
)
|
| 392 |
|
| 393 |
_diag_log.info("[upcoming_hr_props] total rows=%d", len(rows))
|
| 394 |
return pd.DataFrame(rows)
|