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 files

Bug: 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>

Files changed (1) hide show
  1. 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
- status = (row.get("status") or "").lower()
 
 
 
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