Spaces:
Running on Zero
Running on Zero
Download render.py from ageraustine/HAWK-ONE: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/spaces/ageraustine/HAWK-ONE/resolve/main/render.py
- Command line
-
hf download hf://spaces/ageraustine/HAWK-ONE/render.py
-
curl -L -o render.py https://huggingface.co/spaces/ageraustine/HAWK-ONE/resolve/main/render.py
12.1 kB
| """HTML for the Film Room result panels (verdict, numbers, distribution, reasons, Path Lab).""" | |
| from __future__ import annotations | |
| import html | |
| CSS = """ | |
| <style> | |
| :root{--hk-navy:#071731;--hk-line:#1f3a6b;--hk-red:#d50a0a;--hk-cyan:#22d3ee} | |
| .hk{font-family:Inter,system-ui,sans-serif;color:#e8eefb;background:#071731;border:1px solid #1f3a6b;border-radius:10px;overflow:hidden} | |
| .hk *{box-sizing:border-box} | |
| .hk .vd{display:flex;align-items:center;gap:16px;padding:14px 18px;border-bottom:3px solid var(--hk-red);background:linear-gradient(90deg,#0b2347,#071731)} | |
| .hk .vd .tag{font:800 26px 'Barlow Condensed','Arial Narrow',Impact,sans-serif;letter-spacing:1.4px;text-transform:uppercase;padding:2px 14px;border-radius:4px} | |
| .hk .vd.better .tag{background:#0f7a4f}.hk .vd.worse .tag{background:#a31212}.hk .vd.same .tag{background:#3a4a6b} | |
| .hk .vd .big{font:800 34px 'Barlow Condensed','Arial Narrow',Impact,sans-serif;letter-spacing:.5px} | |
| .hk .vd .sm{color:#9db0cf;font-size:12.5px;line-height:1.4} | |
| .hk .grid{display:grid;grid-template-columns:1.05fr 1fr;gap:0} | |
| @media(max-width:900px){.hk .grid{grid-template-columns:1fr}} | |
| .hk h5{margin:0;padding:8px 18px;font:700 14px 'Barlow Condensed','Arial Narrow',Impact,sans-serif;letter-spacing:1.6px;text-transform:uppercase;color:#bcd0f3;background:#08172f;border-top:1px solid var(--hk-line);border-bottom:1px solid var(--hk-line)} | |
| .hk table.kp{width:100%;border-collapse:collapse;font-size:13px} | |
| .hk table.kp th{font:600 10px Inter,sans-serif;letter-spacing:1.2px;color:#9db0cf;text-transform:uppercase;text-align:right;padding:6px 14px} | |
| .hk table.kp th:first-child{text-align:left;padding-left:18px} | |
| .hk table.kp td{color:#e8eefb;padding:7px 14px;border-top:1px solid #12294f;text-align:right;font-variant-numeric:tabular-nums;font-weight:600} | |
| .hk table.kp td:first-child{text-align:left;padding-left:18px;font-weight:500;color:#cdd9ef} | |
| .hk .up{color:#4ade80}.hk .dn{color:#f87171}.hk .nu{color:#9db0cf} | |
| .hk .why{list-style:none;margin:0;padding:6px 18px 12px} | |
| .hk .why li{display:flex;gap:10px;padding:7px 0;border-top:1px solid #12294f;line-height:1.45;font-size:13px} | |
| .hk .why li:first-child{border-top:0} | |
| .hk .why .ic{flex:none;width:74px;font:700 11px Inter,sans-serif;letter-spacing:1px;text-transform:uppercase;color:#7f93b8;padding-top:2px} | |
| .hk .note{padding:10px 18px;color:#8195b8;font-size:11.5px;line-height:1.5;border-top:1px solid var(--hk-line)} | |
| .hk .legend span{display:inline-block;width:12px;height:12px;border-radius:2px;vertical-align:middle;margin:0 5px 0 12px} | |
| .hk .rank{display:flex;align-items:center;gap:14px;padding:11px 18px;border-top:1px solid #12294f} | |
| .hk .rank .n{font:800 30px 'Barlow Condensed','Arial Narrow',Impact,sans-serif;color:var(--hk-red);width:26px} | |
| .hk .rank .nm{font:700 17px 'Barlow Condensed','Arial Narrow',Impact,sans-serif;letter-spacing:.5px;text-transform:uppercase} | |
| .hk .rank .mt{color:#9db0cf;font-size:12px} | |
| .hk .rank .d{margin-left:auto;font:800 24px 'Barlow Condensed','Arial Narrow',Impact,sans-serif} | |
| .hk .pill{display:inline-block;border-radius:999px;padding:1px 9px;font-size:10.5px;font-weight:700;margin-left:6px} | |
| .hk .pill.ok{background:#0f5a3f;color:#9af0cc}.hk .pill.bad{background:#7a1414;color:#ffb4b4} | |
| #hawk_bridge{position:absolute!important;left:-9999px!important;width:1px!important;height:1px!important;overflow:hidden!important;opacity:0} | |
| </style> | |
| """ | |
| def _pct(v): | |
| return "-" if v is None else f"{v * 100:.0f}%" | |
| def _num(v, d=1): | |
| return "-" if v is None else f"{v:.{d}f}" | |
| def _row(label, a, b, fmt, higher_good=True, thr=0.0): | |
| if a is None or b is None: | |
| return f"<tr><td>{label}</td><td>-</td><td>-</td><td class='nu'>-</td></tr>" | |
| d = b - a | |
| cls = "nu" if abs(d) <= thr else ("up" if (d > 0) == higher_good else "dn") | |
| sign = "+" if d > 0 else "" | |
| dd = f"{sign}{d * 100:.0f} pts" if fmt == "pct" else f"{sign}{d:.1f}" | |
| f = _pct if fmt == "pct" else _num | |
| return f"<tr><td>{label}</td><td>{f(a)}</td><td>{f(b)}</td><td class='{cls}'>{dd}</td></tr>" | |
| def _hist(sb, sa): | |
| hb, ha = sb["hist"], sa["hist"] | |
| n = len(hb["p"]) | |
| W, H, pad = 420, 150, 26 | |
| m = max(max(hb["p"]), max(ha["p"]), 0.05) | |
| bw = (W - 2 * pad) / n | |
| out = [f"<svg viewBox='0 0 {W} {H + 22}' style='width:100%;display:block;background:#071a36'>"] | |
| for g in range(0, 5): | |
| y = 8 + (H - 20) * (1 - g / 4) | |
| out.append(f"<line x1='{pad}' x2='{W - 8}' y1='{y:.1f}' y2='{y:.1f}' stroke='#16315d'/>" | |
| f"<text x='{pad - 4}' y='{y + 3:.1f}' font-size='9' fill='#7f93b8' text-anchor='end'>{m * g / 4 * 100:.0f}%</text>") | |
| for i in range(n): | |
| x = pad + i * bw | |
| for k, (p, c) in enumerate(((hb["p"][i], "#8aa0c8"), (ha["p"][i], "#22d3ee"))): | |
| h = (H - 20) * p / m | |
| out.append(f"<rect x='{x + 2 + k * (bw - 4) / 2:.1f}' y='{H - 12 - h:.1f}' width='{(bw - 4) / 2:.1f}' " | |
| f"height='{h:.1f}' fill='{c}' opacity='.95'/>") | |
| lo = hb["lo"] + i * hb["step"] | |
| lab = f"{lo}" if 0 < i < n - 1 else (f"<{lo + hb['step']}" if i == 0 else f"{lo}+") | |
| out.append(f"<text x='{x + bw / 2:.1f}' y='{H + 3}' font-size='9' fill='#9db0cf' text-anchor='middle'>{lab}</text>") | |
| out.append(f"<text x='{W / 2}' y='{H + 17}' font-size='9.5' fill='#7f93b8' text-anchor='middle'>yards gained (per play)</text></svg>") | |
| return "".join(out) | |
| ICON = {"result": "Result", "target": "Target", "catch": "Catch", "space": "Space", "timing": "Timing", | |
| "pressure": "Pressure", "reaction": "Reaction", "consistency": "Steadiness", "turnover": "Turnovers", "flags": "Flags", "feasibility": "Body"} | |
| def outcome_html(c, need=10): | |
| sb, sa = c["base"], c["alt"] | |
| v = c["verdict"] | |
| word = {"better": "Better", "worse": "Worse", "same": "No clear difference"}[v] | |
| sign = "+" if c["d_mean"] >= 0 else "" | |
| sub = ("Your path beat the original beyond what luck explains." if v == "better" else | |
| "Your path cost yards beyond what luck explains." if v == "worse" else | |
| "The difference is within the noise of this many runs; try more runs or a bigger change.") | |
| caution = "" | |
| dc = sa["pchain"] - sb["pchain"] | |
| if v == "better" and dc <= -0.08: | |
| caution = f" But it moves the chains {abs(dc) * 100:.0f} pts less often: a boom-or-bust change." | |
| elif v == "worse" and dc >= 0.08: | |
| caution = f" It does move the chains {dc * 100:.0f} pts more often, so it is more consistent." | |
| sub += caution | |
| rows = "".join([ | |
| _row("Average gain (yds)", sb["avg"], sa["avg"], "num", thr=0.3), | |
| _row("Median gain (yds)", sb["med"], sa["med"], "num", thr=0.3), | |
| _row(f"Gains {need}+ yds (moves the chains)", sb["pchain"], sa["pchain"], "pct", thr=0.02), | |
| _row("Gains 5+ yds", sb["p5"], sa["p5"], "pct", thr=0.02), | |
| _row("Touchdown", sb["ptd"], sa["ptd"], "pct", thr=0.01), | |
| _row("Completion when thrown", sb["pcomp"], sa["pcomp"], "pct", thr=0.02), | |
| _row("Turnover", sb["pto"], sa["pto"], "pct", higher_good=False, thr=0.005), | |
| _row("Sack", sb["psack"], sa["psack"], "pct", higher_good=False, thr=0.01), | |
| _row("QB pressured", sb["ppress"], sa["ppress"], "pct", higher_good=False, thr=0.02), | |
| _row("Penalty flag", sb["pflag"], sa["pflag"], "pct", higher_good=False, thr=0.01), | |
| _row("Nearest defender at ball arrival (yds)", sb["sep_throw"], sa["sep_throw"], "num", thr=0.2), | |
| ]) | |
| why = "".join(f"<li><span class='ic'>{ICON.get(k, k)}</span><span>{html.escape(t)}</span></li>" for k, t in c["reasons"]) | |
| feas = "" | |
| for pid, f in c["feas"].items(): | |
| ok = not f["over"] | |
| feas += (f"<li><span class='ic'>{html.escape(pid)}</span><span>Path needs up to {f['worst']:.1f} yd/s; top speed " | |
| f"{f['vmax']:.1f} yd/s ({f['mph']} mph) <span class='pill {'ok' if ok else 'bad'}'>" | |
| f"{'within his speed' if ok else 'beyond his speed'}</span></span></li>") | |
| cr = "" | |
| if c.get("credit"): | |
| tot = sum(x["d"] for x in c["credit"]) | |
| inter = c["total_d"] - tot | |
| body = "".join( | |
| f"<tr><td>{html.escape(x['label'])}</td><td class='{'up' if x['d'] > 2 * x['se'] and x['d'] > .2 else 'dn' if x['d'] < -2 * x['se'] and x['d'] < -.2 else 'nu'}'>" | |
| f"{'+' if x['d'] >= 0 else ''}{x['d']:.1f} <span style='color:#7f93b8;font-weight:400'>±{2 * x['se']:.1f}</span></td></tr>" for x in c["credit"]) | |
| body += (f"<tr><td>Only in combination</td><td class='nu'>{'+' if inter >= 0 else ''}{inter:.1f}</td></tr>" | |
| f"<tr><td><b>All edits together</b></td><td><b>{'+' if c['total_d'] >= 0 else ''}{c['total_d']:.1f}</b></td></tr>") | |
| cr = (f"<h5>Who gets the credit · each edit on its own</h5>" | |
| f"<table class='kp'><tr><th>Edit</th><th>Yards per play</th></tr>{body}</table>") | |
| return (CSS + f"""<div class='hk'> | |
| <div class='vd {v}'><span class='tag'>{word}</span><span class='big'>{sign}{c['d_mean']:.1f} yds</span> | |
| <span class='sm'>per play, original vs your path<br>{sub}</span></div> | |
| <div class='grid'><div> | |
| <h5>The numbers · {c['n']} runs each</h5> | |
| <table class='kp'><tr><th></th><th>Original</th><th>Your edit</th><th>Change</th></tr>{rows}</table>{cr}</div> | |
| <div><h5>Where the plays ended up<span class='legend'><span style='background:#8aa0c8'></span>original<span style='background:#22d3ee'></span>your path</span></h5> | |
| {_hist(sb, sa)}<h5>Why</h5><ul class='why'>{why}</ul> | |
| <h5>Can he run it?</h5><ul class='why'>{feas or "<li><span class='ic'>-</span><span>No edited path.</span></li>"}</ul></div></div> | |
| <div class='note'><b>How to read this.</b> Both sides replay the same {c['n']} random seeds, so the gap comes from the path you changed, not luck. | |
| The original is the recorded path replayed through the same engine. Everyone else keeps playing by the rules: defenders react to the new route, | |
| blockers keep blocking, and the physics limits what the edited player's body can do. The model is plausible, not fitted to league data, | |
| so use the direction and the reasons more than the exact yardage.</div></div>""") | |
| def lab_html(pid, label, sg): | |
| if not sg["rows"]: | |
| return CSS + "<div class='hk'><div class='note'>No candidates.</div></div>" | |
| rows = "" | |
| never = sg.get("base_tgt", 1) == 0 and all(r["tgt"] == 0 for r in sg["rows"]) | |
| if never: | |
| rows += ("<div class='note' style='border-top:0;color:#ffd98a'>The QB never throws to this player on this play, so a new path " | |
| "cannot change the result unless it changes how the defense plays. Pick the target (or a blocker or defender whose " | |
| "path can change what happens).</div>") | |
| for i, r in enumerate(sg["rows"], 1): | |
| d = r["value"] - sg["base_value"] | |
| cls = "up" if d > 0.3 else "dn" if d < -0.3 else "nu" | |
| pc = "-" if r["pcomp"] is None else f"{r['pcomp'] * 100:.0f}% complete" | |
| rows += (f"<div class='rank'><div class='n'>{i}</div><div><div class='nm'>{html.escape(r['name'])}" | |
| f"<span class='pill {'ok' if r['feasible'] else 'bad'}'>{'he can run it' if r['feasible'] else 'beyond his speed'}</span></div>" | |
| f"<div class='mt'>{r['avg']:.1f} yds per play · {pc} · turnover {r['pto'] * 100:.1f}% " | |
| f"· targeted {r['tgt'] * 100:.0f}%</div></div>" | |
| f"<div class='d {cls}'>{'+' if d >= 0 else ''}{d:.1f}</div></div>") | |
| return (CSS + f"<div class='hk'><div class='vd same'><span class='tag'>Path Lab</span><span class='big'>{html.escape(label)}</span>" | |
| f"<span class='sm'>Candidate paths for this player, each run on the same seeds.<br>Score = yards minus the cost of turnovers and flags " | |
| f"(original path scores {sg['base_value']:.1f}).</span></div>{rows}" | |
| f"<div class='note'>Press an Apply button to load a path into the viewer and run the full comparison. " | |
| f"Candidates are simple reshapes of the recorded path (deeper, shorter, wider, tighter, mirrored); the coach decides what is realistic to coach.</div></div>") | |