"""HAWK ONE - watch a play run, redraw a player's path, see what would have happened. Run: python app.py""" import json import math import os import random import tempfile import gradio as gr import tracking import whatif from coach import DefCoach, OffCoach, fourth_down, snap_spot from film import build_film_html from playbook import DEFENSE_CALLS, OFFENSE_PLAYS from render import CSS as FILM_CSS, lab_html, outcome_html from rules import PROFILES import spaces # This dummy function satisfies the Hugging Face ZeroGPU startup validator @spaces.GPU(duration=1) def dummy_gpu_initializer(): return "GPU Initialized" LEVELS = [("NFL", "NFL"), ("College (NCAA)", "NCAA")] SPOTS = ["Left hash", "Middle", "Right hash"] COACH = "Coach decides" HEADER = """
Choose a tracking CSV first.
", *keep) try: path = file if isinstance(file, str) else getattr(file, "name", file) text = open(path, encoding="utf-8-sig").read() pid = tracking.plays_in(text)[0] rep = tracking.load_csv(text, pid) except Exception as e: return (st, gr.update(), f"Could not read that file: {e}
", *keep) yl = int(round(rep["bx"] - 10)) setup = dict(level=level, off_play="Recorded play", def_call=f"play {pid}", bx=rep["bx"], by=rep["by"], o_rat=0, d_rat=0, team_seed=0) st = dict(level=level, setup=setup, paths=whatif.extract_paths(rep), rep=rep, play_no=0, sit=dict(yl=yl, by=rep["by"], down=1, dist=10), why=("recorded", "recorded"), res=None, lab=None, args=(78, 75, 7), source="recorded") chs = [(f"{m['pos']} #{m['num']} ({m['id']})", m["id"]) for m in rep["meta"]] first = rep["marks"].get("target") or rep["meta"][0]["id"] info = (f"Run a play first.
" if st["level"] != level: return st, _viewer(st, st["rep"]), "The level changed after this play ran. Press Run play again.
" if st.get("source") == "recorded": return st, _viewer(st, st["rep"]), ("What-if needs a simulated play: the engine must know the play design and the coverage to let " "the other players react. Recorded plays can be inspected and annotated here; running them through the " "engine is the next step once real data is available.
") edits, sel, react = _msg(bridge) edits = whatif.changed(edits, st["paths"]) react = {k: v for k, v in react.items() if abs(v) > 1e-6} if not edits and not react: return st, _viewer(st, st["rep"], sel=sel), "Nothing has been changed yet. Select a player, drag a point on his path or change his reaction time, then run.
" return (st, *_compare_and_show(st, edits, sel, n, react)) def _compare_and_show(st, edits, sel, n, react=None): react = react or {} pool = list(edits) + list(react) focus = sel if sel in pool else pool[0] c = whatif.compare(st["setup"], edits, st["paths"], n=int(n), seed0=st["play_no"], focus=focus, need=st["sit"]["dist"], react=react) st["cmp"] = c return _viewer(st, c["rep_base"], c["rep_alt"], c["scripts"], focus, "overlay", c["react"]), outcome_html(c, st["sit"]["dist"]) def film_lab(st, bridge, focus, n): none = [gr.update(visible=False)] * 3 if not st: return st, "Run a play first.
", *none _, sel, _r = _msg(bridge) pid = sel or focus m = next((m for m in st["rep"]["meta"] if m["id"] == pid), None) if m is None: return st, "Pick a player.
", *none sg = whatif.suggest(st["setup"], pid, st["paths"], n=max(40, int(n) // 4), seed0=st["play_no"], need=st["sit"]["dist"]) st["lab"] = dict(pid=pid, rows=sg["rows"]) btns = [gr.update(visible=i < len(sg["rows"]), value=f"Apply #{i + 1}: {sg['rows'][i]['name']}" if i < len(sg["rows"]) else "") for i in range(3)] return st, lab_html(pid, f"{m['pos']} #{m['num']}", sg), *btns def film_apply(k): def go(st, n): if not st or not st.get("lab") or k >= len(st["lab"]["rows"]): return st, EMPTY, "" lab = st["lab"] return (st, *_compare_and_show(st, {lab["pid"]: lab["rows"][k]["path"]}, lab["pid"], n)) return go # ---------------------------------------------------------------- UI with gr.Blocks(title="HAWK ONE") as demo: gr.HTML(HEADER + FILM_CSS) with gr.Row(equal_height=True): level = gr.Radio(LEVELS, value="NFL", label="Level of play", scale=2) f_off = gr.Dropdown([COACH] + list(OFFENSE_PLAYS), value=COACH, label="Offense", scale=2) f_def = gr.Dropdown([COACH] + list(DEFENSE_CALLS), value=COACH, label="Defense", scale=3) b_run = gr.Button("Run play", variant="primary", scale=1) b_next = gr.Button("Next play", scale=1) with gr.Accordion("Situation and teams", open=False): with gr.Row(): f_down = gr.Radio([1, 2, 3, 4], value=1, label="Down") f_dist = gr.Slider(1, 20, value=10, step=1, label="Yards to go") f_yl = gr.Slider(1, 99, value=30, step=1, label="Ball on (from own goal)") f_spot = gr.Radio(SPOTS, value="Middle", label="Ball spot") with gr.Row(): f_play = gr.Number(3, precision=0, label="Play # (scenario)") f_orat = gr.Slider(55, 95, value=78, step=1, label="Offense rating") f_drat = gr.Slider(55, 95, value=75, step=1, label="Defense rating") f_tseed = gr.Number(7, precision=0, label="Roster seed") f_info = gr.HTML() f_view = gr.HTML(EMPTY) with gr.Row(): f_n = gr.Slider(60, 600, value=200, step=20, label="Runs per side", scale=3) f_run = gr.Button("Run what-if", variant="primary", elem_id="hawk_run", scale=1) f_clear = gr.Button("Replay original", scale=1) f_out = gr.HTML() with gr.Accordion("Path Lab - let the engine try variations of one player's path", open=False): with gr.Row(): f_focus = gr.Dropdown([], label="Player (or the one selected in the viewer)", scale=3) f_labbtn = gr.Button("Find better paths", scale=1) f_lab = gr.HTML() with gr.Row(): f_a1 = gr.Button("Apply #1", visible=False) f_a2 = gr.Button("Apply #2", visible=False) f_a3 = gr.Button("Apply #3", visible=False) with gr.Accordion("Recorded plays (tracking data)", open=False): gr.Markdown("Load a play from a tracking CSV (columns: `play_id, t, entity, side, pos, num, x, y, z`; format notes in `tracking.py`). " "Use **Save this play as CSV** to get a sample file in exactly that format.") with gr.Row(): f_file = gr.File(label="Tracking CSV", file_types=[".csv"], type="filepath", scale=3) f_open = gr.Button("Open recorded play", scale=1) f_save = gr.Button("Save this play as CSV", scale=1) f_csv = gr.File(label="Download", scale=2) f_bridge = gr.Textbox(elem_id="hawk_bridge", label="bridge", lines=1) film_state = gr.State(None) demo.load(None, None, None, js=BRIDGE_JS) sit_in = [level, f_off, f_def, f_orat, f_drat, f_tseed, f_yl, f_spot, f_play, f_down, f_dist] outs = [film_state, f_view, f_out, f_lab, f_focus, f_bridge, f_a1, f_a2, f_a3, f_info] b_run.click(run_play, sit_in, outs) level.change(run_play, sit_in, outs) b_next.click(next_play, [film_state] + sit_in, outs) f_open.click(open_recorded, [film_state, level, f_file], outs) f_save.click(export_csv, film_state, f_csv) f_run.click(film_run, [film_state, f_bridge, f_n, level], [film_state, f_view, f_out]) f_clear.click(restart, film_state, [film_state, f_view, f_out]) f_labbtn.click(film_lab, [film_state, f_bridge, f_focus, f_n], [film_state, f_lab, f_a1, f_a2, f_a3]) for k, b in enumerate((f_a1, f_a2, f_a3)): b.click(film_apply(k), [film_state, f_n], [film_state, f_view, f_out]) demo.load(run_play, sit_in, outs) # the first play runs as soon as the page opens # if __name__ == "__main__": demo.launch()