Syntrex commited on
Commit
01e59f0
·
verified ·
1 Parent(s): 726e2cd

Create recommendation_panel.py

Browse files
Files changed (1) hide show
  1. visualization/recommendation_panel.py +163 -0
visualization/recommendation_panel.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import streamlit.components.v1 as components
6
+
7
+
8
+ def _fmt_odds(value: Any) -> str:
9
+ if value is None:
10
+ return "—"
11
+ try:
12
+ iv = int(value)
13
+ return f"+{iv}" if iv > 0 else str(iv)
14
+ except Exception:
15
+ return str(value)
16
+
17
+
18
+ def _fmt_edge(value: Any) -> str:
19
+ if value is None:
20
+ return "—"
21
+
22
+ try:
23
+ edge = float(value)
24
+ except Exception:
25
+ return str(value)
26
+
27
+ pct = edge * 100
28
+
29
+ if pct >= 5:
30
+ color = "#22c55e"
31
+ elif pct >= 2:
32
+ color = "#84cc16"
33
+ elif pct >= 0:
34
+ color = "#eab308"
35
+ elif pct >= -3:
36
+ color = "#f97316"
37
+ else:
38
+ color = "#ef4444"
39
+
40
+ return f'<span style="color:{color};font-weight:800;">{pct:.1f}%</span>'
41
+
42
+
43
+ def _fmt_confidence(value: Any) -> str:
44
+ try:
45
+ conf = float(value)
46
+ except Exception:
47
+ return "—"
48
+
49
+ if conf >= 80:
50
+ color = "#22c55e"
51
+ elif conf >= 60:
52
+ color = "#eab308"
53
+ else:
54
+ color = "#ef4444"
55
+
56
+ return f'<span style="color:{color};font-weight:800;">{conf:.0f}</span>'
57
+
58
+
59
+ def _fmt_tier(value: Any) -> str:
60
+ tier = str(value or "").strip().lower()
61
+
62
+ if tier == "bet":
63
+ color = "#22c55e"
64
+ label = "BET"
65
+ elif tier == "watch":
66
+ color = "#eab308"
67
+ label = "WATCH"
68
+ else:
69
+ color = "#94a3b8"
70
+ label = "PASS"
71
+
72
+ return f'<span style="color:{color};font-weight:900;">{label}</span>'
73
+
74
+
75
+ def render_recommendation_panels(rows: list[dict[str, Any]]) -> None:
76
+ if not rows:
77
+ return
78
+
79
+ body_rows = []
80
+ for row in rows:
81
+ ev90 = row.get("ev90")
82
+ ev90_text = f" | EV90 {float(ev90):.1f}" if ev90 is not None else ""
83
+
84
+ body_rows.append(
85
+ f"""
86
+ <div class="grid-row">
87
+ <div>{row.get("slot", "")}: {row.get("batter_name", "")}{ev90_text}</div>
88
+ <div>{_fmt_odds(row.get("fair_hr_odds"))}</div>
89
+ <div>{_fmt_odds(row.get("book_hr_odds"))}</div>
90
+ <div>{_fmt_edge(row.get("hr_edge"))}</div>
91
+ <div>{_fmt_confidence(row.get("confidence"))}</div>
92
+ <div>{_fmt_tier(row.get("recommendation_tier"))}</div>
93
+ </div>
94
+ """
95
+ )
96
+
97
+ html = f"""
98
+ <!DOCTYPE html>
99
+ <html>
100
+ <head>
101
+ <meta charset="utf-8" />
102
+ <style>
103
+ body {{
104
+ margin: 0;
105
+ padding: 0;
106
+ background: transparent;
107
+ font-family: Arial, sans-serif;
108
+ }}
109
+ .panel {{
110
+ margin-top: -4px;
111
+ margin-bottom: 14px;
112
+ background: rgba(255,255,255,0.03);
113
+ border: 1px solid rgba(148,163,184,0.14);
114
+ border-radius: 16px;
115
+ padding: 12px;
116
+ box-sizing: border-box;
117
+ color: #e2e8f0;
118
+ }}
119
+ .title {{
120
+ color: #94a3b8;
121
+ font-size: 12px;
122
+ font-weight: 700;
123
+ margin-bottom: 8px;
124
+ }}
125
+ .grid-header, .grid-row {{
126
+ display: grid;
127
+ grid-template-columns: 1.7fr 0.6fr 0.6fr 0.6fr 0.6fr 0.6fr;
128
+ gap: 8px;
129
+ }}
130
+ .grid-header {{
131
+ color: #94a3b8;
132
+ font-size: 11px;
133
+ font-weight: 700;
134
+ margin-bottom: 6px;
135
+ }}
136
+ .grid-row {{
137
+ color: #e2e8f0;
138
+ font-size: 13px;
139
+ font-weight: 700;
140
+ margin-bottom: 4px;
141
+ }}
142
+ </style>
143
+ </head>
144
+ <body>
145
+ <div class="panel">
146
+ <div class="title">UPCOMING BATTER HR RECOMMENDATIONS • EV90 SIM MODEL V4</div>
147
+
148
+ <div class="grid-header">
149
+ <div>BATTER</div>
150
+ <div>FAIR</div>
151
+ <div>BOOK</div>
152
+ <div>EDGE</div>
153
+ <div>CONF</div>
154
+ <div>TIER</div>
155
+ </div>
156
+
157
+ {''.join(body_rows)}
158
+ </div>
159
+ </body>
160
+ </html>
161
+ """
162
+ height = 92 + (len(rows) * 30)
163
+ components.html(html, height=height, scrolling=False)