Spaces:
Running
Running
| """ | |
| analytics/props_view_model.py | |
| Page-ready HR props view models used by the Props page redesign. | |
| This module keeps grouping, sorting, and "featured props" selection out of the | |
| render layer so the upcoming UI can consume stable structures instead of raw | |
| rows. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| import pandas as pd | |
| def _coerce_int(value: Any, default: int = 0) -> int: | |
| try: | |
| if value is None or str(value).strip() in {"", "nan", "None"}: | |
| return default | |
| return int(float(value)) | |
| except Exception: | |
| return default | |
| def _coerce_bool(value: Any) -> bool: | |
| if isinstance(value, bool): | |
| return value | |
| text = str(value).strip().lower() | |
| return text in {"1", "true", "yes"} | |
| def _make_game_key(row: pd.Series) -> str: | |
| event_id = str(row.get("event_id") or "").strip() | |
| if event_id and event_id not in {"nan", "None"}: | |
| return event_id | |
| away = str(row.get("away_team") or "").strip() | |
| home = str(row.get("home_team") or "").strip() | |
| commence = str(row.get("commence_time") or "").strip() | |
| return f"{away}|{home}|{commence}" | |
| def _make_player_key(row: pd.Series) -> str: | |
| return f"{_make_game_key(row)}|{str(row.get('player_name') or '').strip().lower()}" | |
| def _market_family_series(df: pd.DataFrame) -> pd.Series: | |
| if "market_family" in df.columns: | |
| return df["market_family"].astype(str).str.lower() | |
| if "market" in df.columns: | |
| return df["market"].astype(str).str.lower() | |
| return pd.Series([""] * len(df), index=df.index, dtype="object") | |
| def _threshold_series(df: pd.DataFrame) -> pd.Series: | |
| if "threshold" in df.columns: | |
| source = df["threshold"] | |
| else: | |
| source = pd.Series([1] * len(df), index=df.index) | |
| return source.apply(lambda v: _coerce_int(v, default=1)) | |
| def _modeled_series(df: pd.DataFrame) -> pd.Series: | |
| if "is_modeled" in df.columns: | |
| source = df["is_modeled"] | |
| else: | |
| source = pd.Series([True] * len(df), index=df.index) | |
| return source.apply(_coerce_bool) | |
| def _has_model_probability_series(df: pd.DataFrame) -> pd.Series: | |
| if "has_model_probability" in df.columns: | |
| source = df["has_model_probability"] | |
| return source.apply(_coerce_bool) | |
| if "model_hr_prob" in df.columns: | |
| return df["model_hr_prob"].notna() | |
| return pd.Series([False] * len(df), index=df.index) | |
| def _modeled_hr_primary_series(df: pd.DataFrame) -> pd.Series: | |
| return ( | |
| (_market_family_series(df) == "hr") | |
| & (_threshold_series(df) == 1) | |
| & _modeled_series(df) | |
| ) | |
| def _modeled_hr_primary_with_probability_series(df: pd.DataFrame) -> pd.Series: | |
| return _modeled_hr_primary_series(df) & _has_model_probability_series(df) | |
| def _modeled_strikeout_with_probability_series(df: pd.DataFrame) -> pd.Series: | |
| market_series = _market_family_series(df) | |
| modeled_series = _modeled_series(df) | |
| if "has_model_probability" in df.columns: | |
| probability_series = df["has_model_probability"].apply(_coerce_bool) | |
| elif "fair_prob" in df.columns: | |
| probability_series = pd.to_numeric(df["fair_prob"], errors="coerce").notna() | |
| elif "model_k_prob" in df.columns: | |
| probability_series = pd.to_numeric(df["model_k_prob"], errors="coerce").notna() | |
| else: | |
| probability_series = pd.Series([False] * len(df), index=df.index) | |
| return (market_series == "k") & modeled_series & probability_series | |
| def _sort_props_df(df: pd.DataFrame) -> pd.DataFrame: | |
| if df.empty: | |
| return df | |
| out = df.copy() | |
| if "threshold" not in out.columns: | |
| out["threshold"] = 1 | |
| if "edge" not in out.columns: | |
| out["edge"] = None | |
| if "odds_american" not in out.columns: | |
| out["odds_american"] = None | |
| if "bet_ev" not in out.columns: | |
| out["bet_ev"] = None | |
| if "confidence_score" not in out.columns: | |
| out["confidence_score"] = None | |
| out["_threshold_sort"] = out["threshold"].apply(lambda v: _coerce_int(v, default=99)) | |
| out["_ev_sort"] = pd.to_numeric(out["bet_ev"], errors="coerce").fillna(-999.0) | |
| out["_edge_sort"] = pd.to_numeric(out["edge"], errors="coerce").fillna(-999.0) | |
| out["_conf_sort"] = pd.to_numeric(out["confidence_score"], errors="coerce").fillna(-999.0) | |
| out["_odds_sort"] = pd.to_numeric(out["odds_american"], errors="coerce").fillna(-99999.0) | |
| out = out.sort_values( | |
| ["_threshold_sort", "_ev_sort", "_edge_sort", "_conf_sort", "_odds_sort"], | |
| ascending=[True, False, False, False, False], | |
| na_position="last", | |
| ) | |
| return out.drop(columns=["_threshold_sort", "_ev_sort", "_edge_sort", "_conf_sort", "_odds_sort"]) | |
| def _compute_feature_score(df: pd.DataFrame) -> pd.DataFrame: | |
| if df.empty: | |
| return df | |
| out = df.copy() | |
| ev = pd.to_numeric(out.get("bet_ev"), errors="coerce").fillna(-9.0) | |
| edge = pd.to_numeric(out.get("edge"), errors="coerce").fillna(-9.0) | |
| confidence = pd.to_numeric(out.get("confidence_score"), errors="coerce").fillna(0.0) | |
| execution = pd.to_numeric(out.get("final_recommendation_score"), errors="coerce").fillna(0.0) | |
| out["featured_value_score"] = ( | |
| (ev * 0.50) | |
| + (edge * 0.35) | |
| + (((confidence - 50.0) / 50.0) * 0.10) | |
| + (execution * 0.05) | |
| ) | |
| return out | |
| def _best_line_key(row: pd.Series) -> str: | |
| explicit_key = str(row.get("player_event_market_key") or "").strip() | |
| if explicit_key and explicit_key not in {"", "nan", "None"}: | |
| return explicit_key | |
| return "|".join( | |
| [ | |
| str(row.get("event_id") or _make_game_key(row)).strip(), | |
| str(row.get("player_name") or "").strip().lower(), | |
| str(row.get("market_family") or row.get("market") or "").strip().lower(), | |
| str(_coerce_int(row.get("threshold"), default=1)), | |
| ] | |
| ) | |
| def select_best_lines_per_prop(mapped_df: pd.DataFrame) -> pd.DataFrame: | |
| """ | |
| Collapse sportsbook rows down to the bettor-best line for each prop identity. | |
| Grouping stays threshold-aware, so 1+ HR and 2+ HR are never mixed. | |
| """ | |
| if mapped_df is None or mapped_df.empty: | |
| return pd.DataFrame() | |
| working = mapped_df.copy() | |
| if "implied_prob" not in working.columns: | |
| working["implied_prob"] = None | |
| if "odds_american" not in working.columns: | |
| working["odds_american"] = None | |
| if "edge" not in working.columns: | |
| working["edge"] = None | |
| working["_best_line_key"] = working.apply(_best_line_key, axis=1) | |
| working["_implied_sort"] = pd.to_numeric(working["implied_prob"], errors="coerce").fillna(999.0) | |
| working["_edge_sort"] = pd.to_numeric(working["edge"], errors="coerce").fillna(-999.0) | |
| working["_odds_sort"] = pd.to_numeric(working["odds_american"], errors="coerce").fillna(-99999.0) | |
| working = working.sort_values( | |
| ["_best_line_key", "_implied_sort", "_edge_sort", "_odds_sort"], | |
| ascending=[True, True, False, False], | |
| na_position="last", | |
| ) | |
| best = working.drop_duplicates(subset=["_best_line_key"], keep="first").copy() | |
| return best.drop(columns=["_best_line_key", "_implied_sort", "_edge_sort", "_odds_sort"]).reset_index(drop=True) | |
| def build_featured_hr_props_df(mapped_df: pd.DataFrame, limit: int = 8) -> pd.DataFrame: | |
| if mapped_df is None or mapped_df.empty: | |
| return pd.DataFrame() | |
| featured = mapped_df.copy() | |
| featured = featured[_modeled_hr_primary_with_probability_series(featured)] | |
| if featured.empty: | |
| return pd.DataFrame() | |
| featured = select_best_lines_per_prop(featured) | |
| featured = _compute_feature_score(featured) | |
| sort_cols: list[str] = [] | |
| ascending: list[bool] = [] | |
| if "featured_value_score" in featured.columns: | |
| sort_cols.append("featured_value_score") | |
| ascending.append(False) | |
| if "bet_ev" in featured.columns: | |
| sort_cols.append("bet_ev") | |
| ascending.append(False) | |
| if "final_recommendation_score" in featured.columns: | |
| sort_cols.append("final_recommendation_score") | |
| ascending.append(False) | |
| if "edge" in featured.columns: | |
| sort_cols.append("edge") | |
| ascending.append(False) | |
| if "odds_american" in featured.columns: | |
| sort_cols.append("odds_american") | |
| ascending.append(False) | |
| if sort_cols: | |
| featured = featured.sort_values(sort_cols, ascending=ascending, na_position="last") | |
| return featured.head(max(1, int(limit))).reset_index(drop=True) | |
| def build_best_on_slate_df(mapped_df: pd.DataFrame, limit: int = 8) -> pd.DataFrame: | |
| if mapped_df is None or mapped_df.empty: | |
| return pd.DataFrame() | |
| best_on_slate_full_df = _build_best_on_slate_full_df(mapped_df) | |
| if best_on_slate_full_df.empty: | |
| return pd.DataFrame() | |
| return best_on_slate_full_df.head(max(1, int(limit))).reset_index(drop=True) | |
| def build_best_on_slate_summary(mapped_df: pd.DataFrame) -> dict[str, Any]: | |
| best_on_slate_full_df = _build_best_on_slate_full_df(mapped_df) | |
| if best_on_slate_full_df.empty: | |
| return { | |
| "modeled_props_count": 0, | |
| "sportsbooks_count": 0, | |
| "markets_count": 0, | |
| "best_ev": None, | |
| "best_edge": None, | |
| } | |
| return { | |
| "modeled_props_count": int(len(best_on_slate_full_df)), | |
| "sportsbooks_count": int(best_on_slate_full_df["sportsbook"].dropna().astype(str).nunique()) if "sportsbook" in best_on_slate_full_df.columns else 0, | |
| "markets_count": int(_market_family_series(best_on_slate_full_df).replace("", pd.NA).dropna().nunique()), | |
| "best_ev": pd.to_numeric(best_on_slate_full_df.get("bet_ev"), errors="coerce").dropna().max() if "bet_ev" in best_on_slate_full_df.columns else None, | |
| "best_edge": pd.to_numeric(best_on_slate_full_df.get("edge"), errors="coerce").dropna().max() if "edge" in best_on_slate_full_df.columns else None, | |
| } | |
| def _build_best_on_slate_full_df(mapped_df: pd.DataFrame) -> pd.DataFrame: | |
| if mapped_df is None or mapped_df.empty: | |
| return pd.DataFrame() | |
| working = mapped_df.copy() | |
| eligible = working[ | |
| _modeled_hr_primary_with_probability_series(working) | |
| | _modeled_strikeout_with_probability_series(working) | |
| ].copy() | |
| if eligible.empty: | |
| return pd.DataFrame() | |
| eligible = select_best_lines_per_prop(eligible) | |
| eligible = _compute_feature_score(eligible) | |
| sort_cols: list[str] = [] | |
| ascending: list[bool] = [] | |
| for col in ("bet_ev", "edge", "confidence_score", "final_recommendation_score", "featured_value_score"): | |
| if col in eligible.columns: | |
| sort_cols.append(col) | |
| ascending.append(False) | |
| if "odds_american" in eligible.columns: | |
| sort_cols.append("odds_american") | |
| ascending.append(False) | |
| if sort_cols: | |
| eligible = eligible.sort_values(sort_cols, ascending=ascending, na_position="last") | |
| return eligible.reset_index(drop=True) | |
| def build_games_summary_df(mapped_df: pd.DataFrame) -> pd.DataFrame: | |
| if mapped_df is None or mapped_df.empty: | |
| return pd.DataFrame() | |
| working = mapped_df.copy() | |
| working["_game_key"] = working.apply(_make_game_key, axis=1) | |
| summary_rows: list[dict[str, Any]] = [] | |
| for game_key, game_df in working.groupby("_game_key", dropna=False): | |
| primary_modeled = game_df[ | |
| _modeled_hr_primary_with_probability_series(game_df) | |
| ].copy() | |
| primary_modeled = _sort_props_df(select_best_lines_per_prop(primary_modeled)) | |
| if primary_modeled.empty: | |
| continue | |
| top_row = primary_modeled.iloc[0].to_dict() if not primary_modeled.empty else {} | |
| first_row = game_df.iloc[0] | |
| summary_rows.append( | |
| { | |
| "game_key": game_key, | |
| "event_id": first_row.get("event_id"), | |
| "away_team": first_row.get("away_team"), | |
| "home_team": first_row.get("home_team"), | |
| "commence_time": first_row.get("commence_time"), | |
| "modeled_props_count": int(len(primary_modeled)), | |
| "players_count": int(game_df["player_name"].nunique()), | |
| "best_edge": top_row.get("edge"), | |
| "best_bet_ev": top_row.get("bet_ev"), | |
| "top_confidence_score": top_row.get("confidence_score"), | |
| "top_player_name": top_row.get("player_name"), | |
| "top_display_label": top_row.get("display_label"), | |
| "top_book": top_row.get("sportsbook"), | |
| "top_odds_american": top_row.get("odds_american"), | |
| "top_verdict": top_row.get("verdict"), | |
| "top_market_family": top_row.get("market_family"), | |
| } | |
| ) | |
| summary_df = pd.DataFrame(summary_rows) | |
| if summary_df.empty: | |
| return summary_df | |
| if "best_edge" in summary_df.columns: | |
| summary_df = summary_df.sort_values("best_edge", ascending=False, na_position="last") | |
| return summary_df.reset_index(drop=True) | |
| def build_player_prop_detail_map(mapped_df: pd.DataFrame) -> dict[str, dict[str, Any]]: | |
| if mapped_df is None or mapped_df.empty: | |
| return {} | |
| working = _sort_props_df(mapped_df.copy()) | |
| detail_map: dict[str, dict[str, Any]] = {} | |
| working["_player_key"] = working.apply(_make_player_key, axis=1) | |
| for player_key, player_df in working.groupby("_player_key", dropna=False): | |
| first_row = player_df.iloc[0] | |
| threshold_values = _threshold_series(player_df) | |
| primary_rows = player_df[threshold_values == 1].copy() | |
| alt_rows = player_df[threshold_values > 1].copy() | |
| modeled_primary_rows = primary_rows[_modeled_series(primary_rows)].copy() | |
| modeled_primary_rows = modeled_primary_rows[_has_model_probability_series(modeled_primary_rows)].copy() | |
| best_primary_rows = select_best_lines_per_prop(modeled_primary_rows if not modeled_primary_rows.empty else primary_rows) | |
| best_primary = best_primary_rows.iloc[0].to_dict() if not best_primary_rows.empty else None | |
| best_edge = best_primary.get("edge") if best_primary else None | |
| best_ev = best_primary.get("bet_ev") if best_primary else None | |
| best_book = best_primary.get("sportsbook") if best_primary else None | |
| best_odds = best_primary.get("odds_american") if best_primary else None | |
| detail_map[player_key] = { | |
| "player_key": player_key, | |
| "game_key": _make_game_key(first_row), | |
| "event_id": first_row.get("event_id"), | |
| "away_team": first_row.get("away_team"), | |
| "home_team": first_row.get("home_team"), | |
| "commence_time": first_row.get("commence_time"), | |
| "player_name": first_row.get("player_name"), | |
| "player_name_raw": first_row.get("player_name_raw"), | |
| "has_modeled_row": not modeled_primary_rows.empty, | |
| "has_alt_ladders": not alt_rows.empty, | |
| "best_edge": best_edge, | |
| "best_bet_ev": best_ev, | |
| "best_book": best_book, | |
| "best_odds_american": best_odds, | |
| "best_primary": best_primary, | |
| "best_primary_row": best_primary, | |
| "best_verdict": best_primary.get("verdict") if best_primary else None, | |
| "model_voice": best_primary.get("model_voice") if best_primary else None, | |
| "model_voice_primary_reason": best_primary.get("model_voice_primary_reason") if best_primary else None, | |
| "model_voice_caveat": best_primary.get("model_voice_caveat") if best_primary else None, | |
| "model_voice_for": best_primary.get("model_voice_for") if best_primary else None, | |
| "model_voice_against": best_primary.get("model_voice_against") if best_primary else None, | |
| "primary_rows": primary_rows.to_dict("records"), | |
| "alt_rows": alt_rows.to_dict("records"), | |
| "all_rows": player_df.to_dict("records"), | |
| } | |
| return detail_map | |
| def _build_player_prop_detail_map_from_sorted(working: pd.DataFrame) -> dict[str, dict[str, Any]]: | |
| if working is None or working.empty: | |
| return {} | |
| detail_map: dict[str, dict[str, Any]] = {} | |
| if "_player_key" not in working.columns: | |
| working = working.copy() | |
| working["_player_key"] = working.apply(_make_player_key, axis=1) | |
| for player_key, player_df in working.groupby("_player_key", dropna=False): | |
| first_row = player_df.iloc[0] | |
| threshold_values = _threshold_series(player_df) | |
| primary_rows = player_df[threshold_values == 1].copy() | |
| alt_rows = player_df[threshold_values > 1].copy() | |
| modeled_primary_rows = primary_rows[_modeled_series(primary_rows)].copy() | |
| modeled_primary_rows = modeled_primary_rows[_has_model_probability_series(modeled_primary_rows)].copy() | |
| best_primary_rows = select_best_lines_per_prop(modeled_primary_rows if not modeled_primary_rows.empty else primary_rows) | |
| best_primary = best_primary_rows.iloc[0].to_dict() if not best_primary_rows.empty else None | |
| best_edge = best_primary.get("edge") if best_primary else None | |
| best_ev = best_primary.get("bet_ev") if best_primary else None | |
| best_book = best_primary.get("sportsbook") if best_primary else None | |
| best_odds = best_primary.get("odds_american") if best_primary else None | |
| detail_map[player_key] = { | |
| "player_key": player_key, | |
| "game_key": _make_game_key(first_row), | |
| "event_id": first_row.get("event_id"), | |
| "away_team": first_row.get("away_team"), | |
| "home_team": first_row.get("home_team"), | |
| "commence_time": first_row.get("commence_time"), | |
| "player_name": first_row.get("player_name"), | |
| "player_name_raw": first_row.get("player_name_raw"), | |
| "has_modeled_row": not modeled_primary_rows.empty, | |
| "has_alt_ladders": not alt_rows.empty, | |
| "best_edge": best_edge, | |
| "best_bet_ev": best_ev, | |
| "best_book": best_book, | |
| "best_odds_american": best_odds, | |
| "best_primary": best_primary, | |
| "best_primary_row": best_primary, | |
| "best_verdict": best_primary.get("verdict") if best_primary else None, | |
| "model_voice": best_primary.get("model_voice") if best_primary else None, | |
| "model_voice_primary_reason": best_primary.get("model_voice_primary_reason") if best_primary else None, | |
| "model_voice_caveat": best_primary.get("model_voice_caveat") if best_primary else None, | |
| "model_voice_for": best_primary.get("model_voice_for") if best_primary else None, | |
| "model_voice_against": best_primary.get("model_voice_against") if best_primary else None, | |
| "primary_rows": primary_rows.to_dict("records"), | |
| "alt_rows": alt_rows.to_dict("records"), | |
| "all_rows": player_df.to_dict("records"), | |
| } | |
| return detail_map | |
| def build_game_player_props_map(mapped_df: pd.DataFrame) -> dict[str, dict[str, Any]]: | |
| if mapped_df is None or mapped_df.empty: | |
| return {} | |
| working = _sort_props_df(mapped_df.copy()) | |
| working["_game_key"] = working.apply(_make_game_key, axis=1) | |
| detail_map = build_player_prop_detail_map(working) | |
| game_map: dict[str, dict[str, Any]] = {} | |
| for game_key, game_df in working.groupby("_game_key", dropna=False): | |
| first_row = game_df.iloc[0] | |
| player_entries: list[dict[str, Any]] = [] | |
| for _, player_seed_row in game_df.drop_duplicates(subset=["player_name"]).iterrows(): | |
| player_key = _make_player_key(player_seed_row) | |
| detail = detail_map.get(player_key) | |
| if detail is None: | |
| continue | |
| best_primary = detail.get("best_primary") or {} | |
| player_entries.append( | |
| { | |
| "player_key": player_key, | |
| "player_name": detail.get("player_name"), | |
| "player_name_raw": detail.get("player_name_raw"), | |
| "has_modeled_row": detail.get("has_modeled_row", False), | |
| "has_alt_ladders": detail.get("has_alt_ladders", False), | |
| "best_edge": best_primary.get("edge"), | |
| "best_book": best_primary.get("sportsbook"), | |
| "best_odds_american": best_primary.get("odds_american"), | |
| "best_model_hr_prob": best_primary.get("model_hr_prob") if pd.notna(best_primary.get("model_hr_prob")) else best_primary.get("fair_prob"), | |
| "best_display_label": best_primary.get("display_label"), | |
| "best_bet_ev": best_primary.get("bet_ev"), | |
| "best_confidence_score": best_primary.get("confidence_score"), | |
| "best_verdict": best_primary.get("verdict"), | |
| "model_voice": best_primary.get("model_voice"), | |
| "model_voice_primary_reason": best_primary.get("model_voice_primary_reason"), | |
| "model_voice_caveat": best_primary.get("model_voice_caveat"), | |
| "model_voice_for": best_primary.get("model_voice_for"), | |
| "model_voice_against": best_primary.get("model_voice_against"), | |
| "details": detail, | |
| } | |
| ) | |
| player_entries = sorted( | |
| player_entries, | |
| key=lambda item: ( | |
| item.get("best_edge") is None, | |
| -(float(item.get("best_edge") or -999.0)), | |
| str(item.get("player_name") or ""), | |
| ), | |
| ) | |
| primary_modeled = game_df[ | |
| _modeled_hr_primary_with_probability_series(game_df) | |
| ].copy() | |
| primary_modeled = _sort_props_df(select_best_lines_per_prop(primary_modeled)) | |
| if primary_modeled.empty: | |
| continue | |
| top_row = primary_modeled.iloc[0].to_dict() if not primary_modeled.empty else {} | |
| game_map[game_key] = { | |
| "game_key": game_key, | |
| "event_id": first_row.get("event_id"), | |
| "away_team": first_row.get("away_team"), | |
| "home_team": first_row.get("home_team"), | |
| "commence_time": first_row.get("commence_time"), | |
| "modeled_props_count": int(len(primary_modeled)), | |
| "players_count": int(game_df["player_name"].nunique()), | |
| "best_edge": top_row.get("edge"), | |
| "best_bet_ev": top_row.get("bet_ev"), | |
| "top_confidence_score": top_row.get("confidence_score"), | |
| "top_player_name": top_row.get("player_name"), | |
| "top_display_label": top_row.get("display_label"), | |
| "top_book": top_row.get("sportsbook"), | |
| "top_odds_american": top_row.get("odds_american"), | |
| "top_verdict": top_row.get("verdict"), | |
| "top_market_family": top_row.get("market_family"), | |
| "players": player_entries, | |
| } | |
| return game_map | |
| def build_hr_props_view_model(mapped_df: pd.DataFrame, featured_limit: int = 8) -> dict[str, Any]: | |
| if mapped_df is None or mapped_df.empty: | |
| return { | |
| "featured_props_df": pd.DataFrame(), | |
| "best_on_slate_df": pd.DataFrame(), | |
| "best_on_slate_summary": { | |
| "modeled_props_count": 0, | |
| "sportsbooks_count": 0, | |
| "markets_count": 0, | |
| "best_ev": None, | |
| "best_edge": None, | |
| }, | |
| "games_summary_df": pd.DataFrame(), | |
| "game_player_props_map": {}, | |
| "player_prop_detail_map": {}, | |
| } | |
| working = _sort_props_df(mapped_df.copy()) | |
| working["_game_key"] = working.apply(_make_game_key, axis=1) | |
| working["_player_key"] = working.apply(_make_player_key, axis=1) | |
| featured = working[_modeled_hr_primary_with_probability_series(working)].copy() | |
| if featured.empty: | |
| featured_props_df = pd.DataFrame() | |
| else: | |
| featured_props_df = select_best_lines_per_prop(featured) | |
| featured_props_df = _compute_feature_score(featured_props_df) | |
| sort_cols: list[str] = [] | |
| ascending: list[bool] = [] | |
| if "featured_value_score" in featured_props_df.columns: | |
| sort_cols.append("featured_value_score") | |
| ascending.append(False) | |
| if "bet_ev" in featured_props_df.columns: | |
| sort_cols.append("bet_ev") | |
| ascending.append(False) | |
| if "final_recommendation_score" in featured_props_df.columns: | |
| sort_cols.append("final_recommendation_score") | |
| ascending.append(False) | |
| if "edge" in featured_props_df.columns: | |
| sort_cols.append("edge") | |
| ascending.append(False) | |
| if "odds_american" in featured_props_df.columns: | |
| sort_cols.append("odds_american") | |
| ascending.append(False) | |
| if sort_cols: | |
| featured_props_df = featured_props_df.sort_values(sort_cols, ascending=ascending, na_position="last") | |
| featured_props_df = featured_props_df.head(max(1, int(featured_limit))).reset_index(drop=True) | |
| eligible = working[ | |
| _modeled_hr_primary_with_probability_series(working) | |
| | _modeled_strikeout_with_probability_series(working) | |
| ].copy() | |
| if eligible.empty: | |
| best_on_slate_full_df = pd.DataFrame() | |
| best_on_slate_df = pd.DataFrame() | |
| else: | |
| best_on_slate_full_df = select_best_lines_per_prop(eligible) | |
| best_on_slate_full_df = _compute_feature_score(best_on_slate_full_df) | |
| sort_cols = [] | |
| ascending = [] | |
| for col in ("bet_ev", "edge", "confidence_score", "final_recommendation_score", "featured_value_score"): | |
| if col in best_on_slate_full_df.columns: | |
| sort_cols.append(col) | |
| ascending.append(False) | |
| if "odds_american" in best_on_slate_full_df.columns: | |
| sort_cols.append("odds_american") | |
| ascending.append(False) | |
| if sort_cols: | |
| best_on_slate_full_df = best_on_slate_full_df.sort_values(sort_cols, ascending=ascending, na_position="last") | |
| best_on_slate_df = best_on_slate_full_df.head(max(1, int(featured_limit))).reset_index(drop=True) | |
| if best_on_slate_full_df.empty: | |
| best_on_slate_summary = { | |
| "modeled_props_count": 0, | |
| "sportsbooks_count": 0, | |
| "markets_count": 0, | |
| "best_ev": None, | |
| "best_edge": None, | |
| } | |
| else: | |
| best_on_slate_summary = { | |
| "modeled_props_count": int(len(best_on_slate_full_df)), | |
| "sportsbooks_count": int(best_on_slate_full_df["sportsbook"].dropna().astype(str).nunique()) if "sportsbook" in best_on_slate_full_df.columns else 0, | |
| "markets_count": int(_market_family_series(best_on_slate_full_df).replace("", pd.NA).dropna().nunique()), | |
| "best_ev": pd.to_numeric(best_on_slate_full_df.get("bet_ev"), errors="coerce").dropna().max() if "bet_ev" in best_on_slate_full_df.columns else None, | |
| "best_edge": pd.to_numeric(best_on_slate_full_df.get("edge"), errors="coerce").dropna().max() if "edge" in best_on_slate_full_df.columns else None, | |
| } | |
| player_prop_detail_map = _build_player_prop_detail_map_from_sorted(working) | |
| summary_rows: list[dict[str, Any]] = [] | |
| game_player_props_map: dict[str, dict[str, Any]] = {} | |
| for game_key, game_df in working.groupby("_game_key", dropna=False): | |
| primary_modeled = game_df[_modeled_hr_primary_with_probability_series(game_df)].copy() | |
| primary_modeled = _sort_props_df(select_best_lines_per_prop(primary_modeled)) | |
| if primary_modeled.empty: | |
| continue | |
| top_row = primary_modeled.iloc[0].to_dict() if not primary_modeled.empty else {} | |
| first_row = game_df.iloc[0] | |
| summary_rows.append( | |
| { | |
| "game_key": game_key, | |
| "event_id": first_row.get("event_id"), | |
| "away_team": first_row.get("away_team"), | |
| "home_team": first_row.get("home_team"), | |
| "commence_time": first_row.get("commence_time"), | |
| "modeled_props_count": int(len(primary_modeled)), | |
| "players_count": int(game_df["player_name"].nunique()), | |
| "best_edge": top_row.get("edge"), | |
| "best_bet_ev": top_row.get("bet_ev"), | |
| "top_confidence_score": top_row.get("confidence_score"), | |
| "top_player_name": top_row.get("player_name"), | |
| "top_display_label": top_row.get("display_label"), | |
| "top_book": top_row.get("sportsbook"), | |
| "top_odds_american": top_row.get("odds_american"), | |
| "top_verdict": top_row.get("verdict"), | |
| "top_market_family": top_row.get("market_family"), | |
| } | |
| ) | |
| player_entries: list[dict[str, Any]] = [] | |
| for _, player_seed_row in game_df.drop_duplicates(subset=["player_name"]).iterrows(): | |
| player_key = player_seed_row.get("_player_key") or _make_player_key(player_seed_row) | |
| detail = player_prop_detail_map.get(player_key) | |
| if detail is None: | |
| continue | |
| best_primary = detail.get("best_primary") or {} | |
| player_entries.append( | |
| { | |
| "player_key": player_key, | |
| "player_name": detail.get("player_name"), | |
| "player_name_raw": detail.get("player_name_raw"), | |
| "has_modeled_row": detail.get("has_modeled_row", False), | |
| "has_alt_ladders": detail.get("has_alt_ladders", False), | |
| "best_edge": best_primary.get("edge"), | |
| "best_book": best_primary.get("sportsbook"), | |
| "best_odds_american": best_primary.get("odds_american"), | |
| "best_model_hr_prob": best_primary.get("model_hr_prob") if pd.notna(best_primary.get("model_hr_prob")) else best_primary.get("fair_prob"), | |
| "best_display_label": best_primary.get("display_label"), | |
| "best_bet_ev": best_primary.get("bet_ev"), | |
| "best_confidence_score": best_primary.get("confidence_score"), | |
| "best_verdict": best_primary.get("verdict"), | |
| "model_voice": best_primary.get("model_voice"), | |
| "model_voice_primary_reason": best_primary.get("model_voice_primary_reason"), | |
| "model_voice_caveat": best_primary.get("model_voice_caveat"), | |
| "model_voice_for": best_primary.get("model_voice_for"), | |
| "model_voice_against": best_primary.get("model_voice_against"), | |
| "details": detail, | |
| } | |
| ) | |
| player_entries = sorted( | |
| player_entries, | |
| key=lambda item: ( | |
| item.get("best_edge") is None, | |
| -(float(item.get("best_edge") or -999.0)), | |
| str(item.get("player_name") or ""), | |
| ), | |
| ) | |
| game_player_props_map[game_key] = { | |
| "game_key": game_key, | |
| "event_id": first_row.get("event_id"), | |
| "away_team": first_row.get("away_team"), | |
| "home_team": first_row.get("home_team"), | |
| "commence_time": first_row.get("commence_time"), | |
| "modeled_props_count": int(len(primary_modeled)), | |
| "players_count": int(game_df["player_name"].nunique()), | |
| "best_edge": top_row.get("edge"), | |
| "best_bet_ev": top_row.get("bet_ev"), | |
| "top_confidence_score": top_row.get("confidence_score"), | |
| "top_player_name": top_row.get("player_name"), | |
| "top_display_label": top_row.get("display_label"), | |
| "top_book": top_row.get("sportsbook"), | |
| "top_odds_american": top_row.get("odds_american"), | |
| "top_verdict": top_row.get("verdict"), | |
| "top_market_family": top_row.get("market_family"), | |
| "players": player_entries, | |
| } | |
| games_summary_df = pd.DataFrame(summary_rows) | |
| if not games_summary_df.empty and "best_edge" in games_summary_df.columns: | |
| games_summary_df = games_summary_df.sort_values("best_edge", ascending=False, na_position="last").reset_index(drop=True) | |
| return { | |
| "featured_props_df": featured_props_df, | |
| "best_on_slate_df": best_on_slate_df, | |
| "best_on_slate_summary": best_on_slate_summary, | |
| "games_summary_df": games_summary_df, | |
| "game_player_props_map": game_player_props_map, | |
| "player_prop_detail_map": player_prop_detail_map, | |
| } | |