Spaces:
Running
Running
Zhu Jiajun (jz28583) Claude Opus 4.7 (1M context) commited on
Commit ·
f819f00
1
Parent(s): 1a157a1
Fix kaggle status parse + add /admin/repoll for stuck rows
Browse filesBug: kaggle CLI prints status as "SubmissionStatus.COMPLETE" (an enum repr),
not "complete". The poller's `.lower() == 'complete'` check never matched,
so every kaggle submission timed out at 1800s with "polled without complete"
even when Kaggle had already scored it.
Fix: split on the last `.` and lowercase the suffix.
/admin/repoll/<run_id>: re-trigger the poll for a row whose poller bailed
out, without re-submitting (and burning Kaggle quota).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- server/api.py +37 -1
server/api.py
CHANGED
|
@@ -191,7 +191,10 @@ def _kaggle_poll_loop(competition: str, description: str, run_id: str,
|
|
| 191 |
for row in csv.DictReader(io.StringIO(ls.stdout)):
|
| 192 |
if row.get("description") != description:
|
| 193 |
continue
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
| 195 |
if status == "complete":
|
| 196 |
pub = row.get("publicScore") or ""
|
| 197 |
priv = row.get("privateScore") or ""
|
|
@@ -472,6 +475,39 @@ def admin_delete():
|
|
| 472 |
})
|
| 473 |
|
| 474 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
@app.get("/run/<run_id>")
|
| 476 |
def run_status(run_id: str):
|
| 477 |
"""Look up a submission by run_id. Useful for kaggle-backend submissions
|
|
|
|
| 191 |
for row in csv.DictReader(io.StringIO(ls.stdout)):
|
| 192 |
if row.get("description") != description:
|
| 193 |
continue
|
| 194 |
+
# Kaggle prints status as "SubmissionStatus.COMPLETE" (enum repr),
|
| 195 |
+
# not just "complete" — match the suffix after the last dot.
|
| 196 |
+
status_raw = (row.get("status") or "")
|
| 197 |
+
status = status_raw.rsplit(".", 1)[-1].lower()
|
| 198 |
if status == "complete":
|
| 199 |
pub = row.get("publicScore") or ""
|
| 200 |
priv = row.get("privateScore") or ""
|
|
|
|
| 475 |
})
|
| 476 |
|
| 477 |
|
| 478 |
+
@app.post("/admin/repoll/<run_id>")
|
| 479 |
+
def admin_repoll(run_id: str):
|
| 480 |
+
"""Re-trigger the Kaggle poll loop for a stuck/failed pending row, without
|
| 481 |
+
re-submitting to Kaggle. Useful after fixing a poller bug — the existing
|
| 482 |
+
Kaggle submission still has its score, we just need to read it.
|
| 483 |
+
"""
|
| 484 |
+
sent_key = request.headers.get("X-Bypass-Key", "").strip()
|
| 485 |
+
if not (BYPASS_KEY and sent_key
|
| 486 |
+
and __import__("hmac").compare_digest(sent_key, BYPASS_KEY)):
|
| 487 |
+
return jsonify({"error": "bypass key required"}), 403
|
| 488 |
+
conn = _db()
|
| 489 |
+
row = conn.execute(
|
| 490 |
+
"SELECT task FROM submissions WHERE run_id = ?", (run_id,)
|
| 491 |
+
).fetchone()
|
| 492 |
+
conn.close()
|
| 493 |
+
if not row:
|
| 494 |
+
return jsonify({"error": f"no run '{run_id}'"}), 404
|
| 495 |
+
task = row[0]
|
| 496 |
+
cfg = _manifest().get(task, {})
|
| 497 |
+
comp = cfg.get("backend_config", {}).get("competition")
|
| 498 |
+
if not comp:
|
| 499 |
+
return jsonify({"error": f"task '{task}' is not a kaggle backend"}), 400
|
| 500 |
+
description = f"graphtestbed-{run_id}"
|
| 501 |
+
import threading
|
| 502 |
+
threading.Thread(
|
| 503 |
+
target=_kaggle_poll_loop,
|
| 504 |
+
args=(comp, description, run_id),
|
| 505 |
+
daemon=True,
|
| 506 |
+
).start()
|
| 507 |
+
return jsonify({"run_id": run_id, "task": task, "competition": comp,
|
| 508 |
+
"status": "repolling"})
|
| 509 |
+
|
| 510 |
+
|
| 511 |
@app.get("/run/<run_id>")
|
| 512 |
def run_status(run_id: str):
|
| 513 |
"""Look up a submission by run_id. Useful for kaggle-backend submissions
|