from __future__ import annotations from typing import Any import streamlit.components.v1 as components def _fmt_odds(value: Any) -> str: if value is None: return "—" try: iv = int(value) return f"+{iv}" if iv > 0 else str(iv) except Exception: return str(value) def _fmt_edge(value: Any) -> str: if value is None: return "—" try: edge = float(value) except Exception: return str(value) pct = edge * 100 if pct >= 5: color = "#22c55e" elif pct >= 2: color = "#84cc16" elif pct >= 0: color = "#eab308" elif pct >= -3: color = "#f97316" else: color = "#ef4444" return f'{pct:.1f}%' def _fmt_confidence(value: Any) -> str: try: conf = float(value) except Exception: return "—" if conf >= 80: color = "#22c55e" elif conf >= 60: color = "#eab308" else: color = "#ef4444" return f'{conf:.0f}' def _fmt_tier(value: Any) -> str: tier = str(value or "").strip().lower() if tier == "bet": color = "#22c55e" bg = "rgba(34,197,94,0.12)" label = "BET" elif tier == "watch": color = "#eab308" bg = "rgba(234,179,8,0.12)" label = "WATCH" else: color = "#94a3b8" bg = "rgba(148,163,184,0.10)" label = "PASS" return ( f'{label}' ) def _fmt_badges(values: Any) -> str: badges = values or [] if not isinstance(badges, list): return "" parts = [] for badge in badges[:3]: text = str(badge or "").strip() if not text: continue if "BEST" in text.upper(): color = "#fbbf24" bg = "rgba(251,191,36,0.14)" elif text.upper() == "BET": color = "#22c55e" bg = "rgba(34,197,94,0.14)" elif text.upper() == "WATCH": color = "#eab308" bg = "rgba(234,179,8,0.14)" else: color = "#94a3b8" bg = "rgba(148,163,184,0.10)" parts.append( f'{text}' ) return "".join(parts) def render_recommendation_panels(rows: list[dict[str, Any]]) -> None: if not rows: return body_rows = [] for row in rows: ev90 = row.get("ev90") ev90_text = f" • EV90 {float(ev90):.1f}" if ev90 is not None else "" reason_tags = row.get("reason_tags", []) or [] reason_text = " • ".join(str(tag) for tag in reason_tags[:3]) badges_html = _fmt_badges(row.get("opportunity_badges", [])) _book_src = str(row.get("book_hr_odds_source") or "placeholder") _book_odds_raw = _fmt_odds(row.get("book_hr_odds")) if _book_src == "placeholder": _book_display = ( f'~{_book_odds_raw}' ) else: _book_display = _book_odds_raw body_rows.append( f"""
{row.get("slot", "")}: {row.get("batter_name", "")}{ev90_text}
{badges_html}
{reason_text}
{_fmt_odds(row.get("fair_hr_odds"))}
{_book_display}
{_fmt_edge(row.get("adjusted_edge", row.get("hr_edge")))}
{_fmt_confidence(row.get("confidence"))}
{_fmt_tier(row.get("recommendation_tier"))}
""" ) html = f"""
UPCOMING BATTER HR RECOMMENDATIONS • EV90 SIM MODEL V4.1
BATTER / REASONS
FAIR
BOOK
EDGE
CONF
TIER
{''.join(body_rows)}
""" height = 96 + (len(rows) * 70) components.html(html, height=height, scrolling=False)