Michael Rabinovich Cursor commited on
Commit ·
c48c18f
1
Parent(s): 461547b
leaderboard: download-zip button in merge-path report + backfill tool
Browse filesPass 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>
- Dockerfile +1 -1
- submit.py +14 -0
- 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=
|
| 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"⬇ 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())
|