Spaces:
Running
Running
| 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'<span style="color:{color};font-weight:800;">{pct:.1f}%</span>' | |
| def render_upcoming_edge_strip(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 "" | |
| body_rows.append( | |
| f""" | |
| <div class="grid-row"> | |
| <div>{row.get("slot", "")}: {row.get("batter_name", "")}{ev90_text}</div> | |
| <div>{_fmt_odds(row.get("fair_hr_odds"))}</div> | |
| <div>{_fmt_odds(row.get("book_hr_odds"))}</div> | |
| <div>{_fmt_edge(row.get("hr_edge"))}</div> | |
| </div> | |
| """ | |
| ) | |
| html = f""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <style> | |
| body {{ | |
| margin: 0; | |
| padding: 0; | |
| background: transparent; | |
| font-family: Arial, sans-serif; | |
| }} | |
| .edge-strip {{ | |
| margin-top: -4px; | |
| margin-bottom: 14px; | |
| background: rgba(255,255,255,0.03); | |
| border: 1px solid rgba(148,163,184,0.14); | |
| border-radius: 16px; | |
| padding: 12px; | |
| box-sizing: border-box; | |
| color: #e2e8f0; | |
| }} | |
| .title {{ | |
| color: #94a3b8; | |
| font-size: 12px; | |
| font-weight: 700; | |
| margin-bottom: 8px; | |
| }} | |
| .grid-header, .grid-row {{ | |
| display: grid; | |
| grid-template-columns: 1.7fr 0.6fr 0.6fr 0.6fr; | |
| gap: 8px; | |
| }} | |
| .grid-header {{ | |
| color: #94a3b8; | |
| font-size: 11px; | |
| font-weight: 700; | |
| margin-bottom: 6px; | |
| }} | |
| .grid-row {{ | |
| color: #e2e8f0; | |
| font-size: 13px; | |
| font-weight: 700; | |
| margin-bottom: 4px; | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="edge-strip"> | |
| <div class="title">UPCOMING BATTER HR EDGES • EV90 SIM MODEL V3</div> | |
| <div class="grid-header"> | |
| <div>BATTER</div> | |
| <div>FAIR</div> | |
| <div>BOOK</div> | |
| <div>EDGE</div> | |
| </div> | |
| {''.join(body_rows)} | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| height = 88 + (len(rows) * 30) | |
| components.html(html, height=height, scrolling=False) |