Michael Rabinovich Cursor commited on
Commit
c48c18f
·
1 Parent(s): 461547b

leaderboard: download-zip button in merge-path report + backfill tool

Browse files

Pass the submission-zip URL to generate_html in the merge path so reassembled
reports show the "Download submission ZIP" button, and add a one-off
tools/backfill_report_download_button.py that injects the button into
already-published reports/<id>.html in place (no re-eval). Bump CADGENBENCH_SHA
to 0c7690e.

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (3) hide show
  1. Dockerfile +1 -1
  2. submit.py +14 -0
  3. tools/backfill_report_download_button.py +182 -0
Dockerfile CHANGED
@@ -41,7 +41,7 @@ RUN pip install --no-cache-dir -r /tmp/requirements.txt \
41
  # image rebuild picks up the latest code (pre-v1: always-updated). Lock
42
  # to a specific commit SHA at the v1 release so published scores are
43
  # reproducible (see space-setup/post-gt-swap.md Stage F).
44
- ARG CADGENBENCH_SHA=25943a0
45
  # Cache-bust the install below whenever the tracked ref moves: the
46
  # GitHub commits endpoint's response changes with each new commit on
47
  # `main`, so BuildKit re-fetches and invalidates the cached pip layer.
 
41
  # image rebuild picks up the latest code (pre-v1: always-updated). Lock
42
  # to a specific commit SHA at the v1 release so published scores are
43
  # reproducible (see space-setup/post-gt-swap.md Stage F).
44
+ ARG CADGENBENCH_SHA=0c7690e
45
  # Cache-bust the install below whenever the tracked ref moves: the
46
  # GitHub commits endpoint's response changes with each new commit on
47
  # `main`, so BuildKit re-fetches and invalidates the cached pip layer.
submit.py CHANGED
@@ -741,6 +741,19 @@ def _slug(s: str) -> str:
741
  return cleaned[:SUBMISSION_ID_SLUG_MAX] or "unnamed"
742
 
743
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
  def _upload_submission_zip(submission_id: str, zip_path: Path) -> str:
745
  """Upload the submission zip to ``submissions/<id>.zip``.
746
 
@@ -1551,6 +1564,7 @@ def _merge_shards_and_publish(
1551
  render_base_url=render_submission_base_url(submission_id),
1552
  gt_base_url=GT_PROXY_BASE_URL,
1553
  input_base_url=INPUT_PROXY_BASE_URL,
 
1554
  )
1555
  html_path = tmp / f"{submission_id}.html"
1556
  html_path.write_text(html, encoding="utf-8")
 
741
  return cleaned[:SUBMISSION_ID_SLUG_MAX] or "unnamed"
742
 
743
 
744
+ def _submission_zip_url(submission_id: str) -> str:
745
+ """Hub resolve URL of ``submissions/<id>.zip`` (the report's download link).
746
+
747
+ Same canonical blob URL :func:`_upload_submission_zip` returns and the
748
+ gallery links, so the report's "Download submission ZIP" button points at
749
+ the identical artifact.
750
+ """
751
+ return (
752
+ f"{HF_ENDPOINT}/datasets/{HF_SUBMISSIONS_REPO}"
753
+ f"/resolve/main/{SUBMISSIONS_DIR}/{submission_id}.zip"
754
+ )
755
+
756
+
757
  def _upload_submission_zip(submission_id: str, zip_path: Path) -> str:
758
  """Upload the submission zip to ``submissions/<id>.zip``.
759
 
 
1564
  render_base_url=render_submission_base_url(submission_id),
1565
  gt_base_url=GT_PROXY_BASE_URL,
1566
  input_base_url=INPUT_PROXY_BASE_URL,
1567
+ download_url=_submission_zip_url(submission_id),
1568
  )
1569
  html_path = tmp / f"{submission_id}.html"
1570
  html_path.write_text(html, encoding="utf-8")
tools/backfill_report_download_button.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Backfill the "Download submission ZIP" button into existing HTML reports.
3
+
4
+ The report generator now renders a download button in the run header (see
5
+ ``cadgenbench`` ``eval/report/single_run.py``). Reports published before that
6
+ change don't have it. Rather than re-running ``evaluate`` (expensive) or even
7
+ re-rendering from artifacts, this one-off tool patches the **already-stored**
8
+ ``reports/<id>.html`` files in place: it injects the button's CSS + markup at a
9
+ stable anchor in the header and re-uploads. Pure HTML string edit, no eval, no
10
+ image re-render.
11
+
12
+ Idempotent: a report that already carries the button (``class="download-zip"``)
13
+ is left untouched, so re-running is safe.
14
+
15
+ The button links the submission's ``submissions/<id>.zip`` blob URL -- the same
16
+ artifact the gallery links and the freshly-generated reports point at -- so a
17
+ patched report is visually and behaviorally identical to a fresh one.
18
+
19
+ Usage (dry-run lists what would change; nothing is written)::
20
+
21
+ python tools/backfill_report_download_button.py
22
+
23
+ # actually patch + re-upload (needs a write-scoped HF_TOKEN):
24
+ python tools/backfill_report_download_button.py --apply
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import argparse
29
+ import html
30
+ import logging
31
+ import re
32
+ import sys
33
+ from pathlib import Path
34
+
35
+ # Import the Space's own read/identity helpers (this file lives in tools/, one
36
+ # level under the Space root).
37
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
38
+
39
+ from huggingface_hub import CommitOperationAdd, HfApi, hf_hub_download # noqa: E402
40
+
41
+ from leaderboard import ( # noqa: E402
42
+ HF_SUBMISSIONS_REPO,
43
+ _load_rows_from_hub,
44
+ _report_relative_url,
45
+ )
46
+ from submit import REPORTS_DIR, _submission_zip_url # noqa: E402
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+ # CSS injected before </style>. Mirrors the rules generate_html now emits so a
51
+ # patched report is byte-for-byte equivalent in the header to a fresh one.
52
+ _CSS_BLOCK = (
53
+ "\n.run-header-top { display: flex; align-items: center; "
54
+ "justify-content: space-between; gap: 16px; flex-wrap: wrap; }\n"
55
+ ".run-header-top h1 { border-bottom: none; padding-bottom: 0; margin: 0; }\n"
56
+ ".download-zip { background: #37474f; color: #fff; text-decoration: none; "
57
+ "padding: 8px 16px; border-radius: 6px; font-size: 0.9em; "
58
+ "font-weight: 600; white-space: nowrap; flex-shrink: 0; }\n"
59
+ ".download-zip:hover { background: #455a64; }\n"
60
+ )
61
+
62
+ # Matches the pre-button header: the run-header div immediately followed by the
63
+ # <h1> title. Capture the title so we can re-wrap it in the flex top row.
64
+ _HEADER_RE = re.compile(
65
+ r'(<div class="run-header">)\s*(<h1>.*?</h1>)',
66
+ re.DOTALL,
67
+ )
68
+
69
+
70
+ def patch_html(doc: str, zip_url: str) -> str | None:
71
+ """Return the patched HTML, or ``None`` if no change is needed/possible.
72
+
73
+ Injects the download-button CSS before ``</style>`` and wraps the header
74
+ title + button in a ``run-header-top`` flex row. Idempotent: returns
75
+ ``None`` when the button is already present or the header anchor is missing.
76
+ """
77
+ if 'class="download-zip"' in doc:
78
+ return None # already patched
79
+
80
+ href = html.escape(str(zip_url), quote=True)
81
+ button = (
82
+ f'<a class="download-zip" href="{href}" download rel="noopener">'
83
+ f"&#11015; Download submission ZIP</a>"
84
+ )
85
+
86
+ new_doc, n = _HEADER_RE.subn(
87
+ r'\1\n<div class="run-header-top">\n\2\n' + button + "\n</div>",
88
+ doc,
89
+ count=1,
90
+ )
91
+ if n == 0:
92
+ return None # header shape not recognized; skip rather than corrupt
93
+
94
+ if "</style>" in new_doc:
95
+ new_doc = new_doc.replace("</style>", _CSS_BLOCK + "</style>", 1)
96
+ return new_doc
97
+
98
+
99
+ def _zip_url_for(row: dict) -> str:
100
+ """Prefer the row's recorded blob URL; fall back to the canonical path."""
101
+ url = row.get("submission_blob_url")
102
+ if url:
103
+ return str(url)
104
+ return _submission_zip_url(row["submission_id"])
105
+
106
+
107
+ def main() -> int:
108
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
109
+ parser = argparse.ArgumentParser(description=__doc__)
110
+ parser.add_argument(
111
+ "--apply", action="store_true",
112
+ help="Re-upload patched reports (default is a dry run).",
113
+ )
114
+ parser.add_argument(
115
+ "--limit", type=int, default=None,
116
+ help="Patch at most N reports (for a cautious first pass).",
117
+ )
118
+ args = parser.parse_args()
119
+
120
+ rows = _load_rows_from_hub()
121
+ # Only completed rows from the modern pipeline have a reports/<id>.html.
122
+ targets = [
123
+ r for r in rows
124
+ if r.get("submission_id")
125
+ and _report_relative_url(
126
+ r.get("submission_id"), r.get("status"), r.get("submission_sha256"),
127
+ )
128
+ ]
129
+ logger.info("Found %d report(s) to consider.", len(targets))
130
+
131
+ ops: list[CommitOperationAdd] = []
132
+ skipped = 0
133
+ for row in targets:
134
+ sid = row["submission_id"]
135
+ try:
136
+ local = hf_hub_download(
137
+ repo_id=HF_SUBMISSIONS_REPO,
138
+ repo_type="dataset",
139
+ filename=f"{REPORTS_DIR}/{sid}.html",
140
+ )
141
+ except Exception as e: # noqa: BLE001
142
+ logger.warning(" skip %s: could not fetch report (%s)", sid, e)
143
+ skipped += 1
144
+ continue
145
+ doc = Path(local).read_text(encoding="utf-8")
146
+ patched = patch_html(doc, _zip_url_for(row))
147
+ if patched is None:
148
+ logger.info(" unchanged %s (already has button or no header)", sid)
149
+ skipped += 1
150
+ continue
151
+ logger.info(" patched %s", sid)
152
+ ops.append(
153
+ CommitOperationAdd(
154
+ path_in_repo=f"{REPORTS_DIR}/{sid}.html",
155
+ path_or_fileobj=patched.encode("utf-8"),
156
+ )
157
+ )
158
+ if args.limit is not None and len(ops) >= args.limit:
159
+ break
160
+
161
+ logger.info(
162
+ "%d to patch, %d unchanged/skipped.", len(ops), skipped,
163
+ )
164
+ if not ops:
165
+ logger.info("Nothing to do.")
166
+ return 0
167
+ if not args.apply:
168
+ logger.info("Dry run -- re-run with --apply to upload.")
169
+ return 0
170
+
171
+ HfApi().create_commit(
172
+ repo_id=HF_SUBMISSIONS_REPO,
173
+ repo_type="dataset",
174
+ operations=ops,
175
+ commit_message="reports: backfill download-submission-zip button",
176
+ )
177
+ logger.info("Uploaded %d patched report(s).", len(ops))
178
+ return 0
179
+
180
+
181
+ if __name__ == "__main__":
182
+ sys.exit(main())