helen9975 commited on
Commit
9b74583
Β·
verified Β·
1 Parent(s): f8a81ba

Deploy portal from RoboEval corl2026-challenge

Browse files
Files changed (5) hide show
  1. Dockerfile +1 -1
  2. app.py +5 -3
  3. portal_core.py +20 -8
  4. test_portal.py +14 -1
  5. test_scoring.py +42 -0
Dockerfile CHANGED
@@ -28,7 +28,7 @@ WORKDIR /home/user
28
  # otherwise reuse a stale clone) and fails the build if the tag has moved.
29
  ARG ROBOEVAL_REPO=https://github.com/Robo-Eval/RoboEval.git
30
  ARG ROBOEVAL_REF=corl2026-challenge
31
- ARG ROBOEVAL_SHA=f0705aa1676c0d97ac29bc7d981e707fa233ba17
32
  RUN git clone --branch "$ROBOEVAL_REF" "$ROBOEVAL_REPO" RoboEval \
33
  && if [ -n "$ROBOEVAL_SHA" ]; then test "$(git -C RoboEval rev-parse HEAD)" = "$ROBOEVAL_SHA"; fi \
34
  && git -C RoboEval submodule update --init \
 
28
  # otherwise reuse a stale clone) and fails the build if the tag has moved.
29
  ARG ROBOEVAL_REPO=https://github.com/Robo-Eval/RoboEval.git
30
  ARG ROBOEVAL_REF=corl2026-challenge
31
+ ARG ROBOEVAL_SHA=c8b79598c9fb9021652128020517d1847abe4c95
32
  RUN git clone --branch "$ROBOEVAL_REF" "$ROBOEVAL_REPO" RoboEval \
33
  && if [ -n "$ROBOEVAL_SHA" ]; then test "$(git -C RoboEval rev-parse HEAD)" = "$ROBOEVAL_SHA"; fi \
34
  && git -C RoboEval submodule update --init \
app.py CHANGED
@@ -159,9 +159,11 @@ with gr.Blocks(title="RoboEval Challenge Β· CoRL 2026") as demo:
159
  "[Submission guidelines](https://robo-eval.github.io/challenge-submission.html)"
160
  )
161
  with gr.Tab("Leaderboard"):
162
- gr.Markdown("Each team's most recent accepted submission, ranked by Success, then Task Progression, "
163
- "then submission time, until the composite score is published. ↑ higher is better, "
164
- "↓ lower is better. Every entry runs at 20 Hz; **Action** is the team's declared action mode.")
 
 
165
  with gr.Row():
166
  task = gr.Dropdown(TASK_CHOICES, value="overall", label="Task")
167
  family = gr.Radio(FAMILY_CHOICES, value="All metrics", label="Metrics")
 
159
  "[Submission guidelines](https://robo-eval.github.io/challenge-submission.html)"
160
  )
161
  with gr.Tab("Leaderboard"):
162
+ gr.Markdown("Each team's most recent accepted submission, ranked by the RoboEval Diagnostic Score (**SCORE**: "
163
+ "40% outcome, 30% safety & stability, 20% efficiency, 10% coordination; "
164
+ "[how it is computed](https://robo-eval.github.io/challenge-rules.html#ranking)), then Success, "
165
+ "Task Progression, and submission time. ↑ higher is better, ↓ lower is better. Every entry runs "
166
+ "at 20 Hz; **Action** is the team's declared action mode.")
167
  with gr.Row():
168
  task = gr.Dropdown(TASK_CHOICES, value="overall", label="Task")
169
  family = gr.Radio(FAMILY_CHOICES, value="All metrics", label="Metrics")
portal_core.py CHANGED
@@ -23,7 +23,7 @@ from typing import Any, Dict, List, Optional, Tuple
23
  import requests
24
  import yaml
25
 
26
- from roboeval.challenge import spec
27
  from roboeval.challenge.package import MANIFEST
28
  from roboeval.challenge.validate import validate
29
 
@@ -457,6 +457,16 @@ def action_label(record: Dict[str, Any]) -> str:
457
  return f"{'EE' if mode.get('ee') else 'Joint'} {'absolute' if mode.get('absolute') else 'delta'}"
458
 
459
 
 
 
 
 
 
 
 
 
 
 
460
  def task_metrics(record: Dict[str, Any], task: Optional[str]) -> Dict[str, float]:
461
  """Suite means (task=None) or one task family's means for a submission."""
462
  summary = record["summary"]
@@ -464,18 +474,18 @@ def task_metrics(record: Dict[str, Any], task: Optional[str]) -> Dict[str, float
464
 
465
 
466
  def leaderboard(records: List[Dict[str, Any]], task: Optional[str] = None) -> List[Dict[str, Any]]:
467
- """Most recent accepted submission per team, ranked by the tie-break order.
468
 
469
- Until the normalized RoboEval Diagnostic Score is implemented, rows are
470
- ordered by Success, then Task Progression, then earlier submission, over
471
- the whole suite or over one task family.
472
  """
473
  latest: Dict[str, Dict[str, Any]] = {}
474
  for r in sorted(records, key=lambda r: r["created_utc"]):
475
  if r["status"] in RANKED_STATUSES:
476
  latest[r["team_name"].casefold()] = r
477
  rows = [r for r in latest.values() if task is None or task in r["summary"].get("per_family", {})]
478
- rows.sort(key=lambda r: (-task_metrics(r, task)["success"], -task_metrics(r, task)["tp"], r["created_utc"]))
 
479
  return rows
480
 
481
 
@@ -490,9 +500,11 @@ def leaderboard_table(records: List[Dict[str, Any]], observation_mode: Optional[
490
  continue
491
  if action_mode and action_label(r) != action_mode:
492
  continue
493
- m = task_metrics(r, task)
494
  row = {"Rank": len(out) + 1, "Team": r["team_name"], "Method": r["method_name"],
495
- "Obs.": r["observation_mode"], "Action": action_label(r)}
 
 
496
  row.update({f"{k.upper()} {'↑' if k in HIGHER_IS_BETTER else '↓'}": round(m[k], 3) for k in keys})
497
  row["Open source"] = "yes" if r["open_source"] else ""
498
  row["Note"] = "organizer (not prize-eligible)" if r["organizer"] else ""
 
23
  import requests
24
  import yaml
25
 
26
+ from roboeval.challenge import scoring, spec
27
  from roboeval.challenge.package import MANIFEST
28
  from roboeval.challenge.validate import validate
29
 
 
457
  return f"{'EE' if mode.get('ee') else 'Joint'} {'absolute' if mode.get('absolute') else 'delta'}"
458
 
459
 
460
+ FAMILY_SCORE_KEYS = {"Outcome": "outcome", "Efficiency": "efficiency", "Coordination": "coordination",
461
+ "Safety & Stability": "safety"}
462
+
463
+
464
+ def record_scores(record: Dict[str, Any], task: Optional[str]) -> Dict[str, float]:
465
+ """RDS and family scores for a submission, over the suite or one task."""
466
+ scores = scoring.suite_scores(record["summary"]["per_family"])
467
+ return scores["overall"] if task is None else scores["tasks"][task]
468
+
469
+
470
  def task_metrics(record: Dict[str, Any], task: Optional[str]) -> Dict[str, float]:
471
  """Suite means (task=None) or one task family's means for a submission."""
472
  summary = record["summary"]
 
474
 
475
 
476
  def leaderboard(records: List[Dict[str, Any]], task: Optional[str] = None) -> List[Dict[str, Any]]:
477
+ """Most recent accepted submission per team, over the suite or one task.
478
 
479
+ Ranked by the RoboEval Diagnostic Score, then Success, Task Progression,
480
+ and earlier submission, as the rules' tie-break order specifies.
 
481
  """
482
  latest: Dict[str, Dict[str, Any]] = {}
483
  for r in sorted(records, key=lambda r: r["created_utc"]):
484
  if r["status"] in RANKED_STATUSES:
485
  latest[r["team_name"].casefold()] = r
486
  rows = [r for r in latest.values() if task is None or task in r["summary"].get("per_family", {})]
487
+ rows.sort(key=lambda r: (-record_scores(r, task)["score"], -task_metrics(r, task)["success"],
488
+ -task_metrics(r, task)["tp"], -record_scores(r, task)["safety"], r["created_utc"]))
489
  return rows
490
 
491
 
 
500
  continue
501
  if action_mode and action_label(r) != action_mode:
502
  continue
503
+ m, sc = task_metrics(r, task), record_scores(r, task)
504
  row = {"Rank": len(out) + 1, "Team": r["team_name"], "Method": r["method_name"],
505
+ "Obs.": r["observation_mode"], "Action": action_label(r), "SCORE ↑": round(sc["score"], 3)}
506
+ if family:
507
+ row[f"{family.upper()} SCORE ↑"] = round(sc[FAMILY_SCORE_KEYS[family]], 3)
508
  row.update({f"{k.upper()} {'↑' if k in HIGHER_IS_BETTER else '↓'}": round(m[k], 3) for k in keys})
509
  row["Open source"] = "yes" if r["open_source"] else ""
510
  row["Note"] = "organizer (not prize-eligible)" if r["organizer"] else ""
test_portal.py CHANGED
@@ -94,6 +94,19 @@ def test_edited_file_breaks_manifest(cfg, store, tmp_path):
94
  core.process_submission(cfg, store, "alice", str(bad), sha(bad))
95
 
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  def test_edited_results_with_regenerated_manifest_is_rejected(cfg, store, tmp_path):
98
  def inflate(b):
99
  r = json.loads(b)
@@ -206,5 +219,5 @@ def test_leaderboard_views(cfg, store):
206
  per_task = core.leaderboard_table(records, task="LiftPot")
207
  assert per_task[0]["TL ↓"] == round(records[0]["summary"]["per_family"]["LiftPot"]["tl"], 3)
208
  coordination = core.leaderboard_table(records, family="Coordination")
209
- assert {k for k in coordination[0] if "↑" in k or "↓" in k} == {"BAVD ↓", "BGVD ↓"}
210
  assert core.leaderboard_table(records, action_mode="Joint delta") == []
 
94
  core.process_submission(cfg, store, "alice", str(bad), sha(bad))
95
 
96
 
97
+ def test_edited_summary_is_rejected(cfg, store, tmp_path):
98
+ def inflate(b):
99
+ r = json.loads(b)
100
+ r["summary"]["suite"]["success"] = 0.99
101
+ return json.dumps(r).encode()
102
+
103
+ bad = repack(Path(BUNDLE), tmp_path / "bad.tar.gz", edit={"submission/results.json": inflate},
104
+ rewrite_manifest=True)
105
+ out = core.process_submission(cfg, store, "alice", str(bad), sha(bad))
106
+ assert out.record["status"] == core.REJECTED
107
+ assert any(i["check"] == "consistency" and "summary" in i["message"] for i in out.report)
108
+
109
+
110
  def test_edited_results_with_regenerated_manifest_is_rejected(cfg, store, tmp_path):
111
  def inflate(b):
112
  r = json.loads(b)
 
219
  per_task = core.leaderboard_table(records, task="LiftPot")
220
  assert per_task[0]["TL ↓"] == round(records[0]["summary"]["per_family"]["LiftPot"]["tl"], 3)
221
  coordination = core.leaderboard_table(records, family="Coordination")
222
+ assert {k for k in coordination[0] if "↑" in k or "↓" in k} == {"SCORE ↑", "COORDINATION SCORE ↑", "BAVD ↓", "BGVD ↓"}
223
  assert core.leaderboard_table(records, action_mode="Joint delta") == []
test_scoring.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the RoboEval Diagnostic Score (no simulator or bundle needed)."""
2
+ import pytest
3
+
4
+ from roboeval.challenge import scoring, spec
5
+
6
+ REF = {"success": 1.0, "tp": 1.0, "cpl": 2.0, "jpl": 4.0, "opl": 3.0, "ct": 8.0, "tl": 160.0,
7
+ "bavd": 0.2, "bgvd": 0.05, "scc": 0.0, "ecc": 1.0, "sc": 0.0, "mjj": 20.0, "mcj": 5.0}
8
+
9
+
10
+ @pytest.fixture(autouse=True)
11
+ def reference(monkeypatch):
12
+ monkeypatch.setattr(scoring, "human_reference", lambda: {f.key: dict(REF) for f in spec.TASK_FAMILIES})
13
+
14
+
15
+ def test_doing_nothing_scores_zero():
16
+ idle = {k: 0.0 for k in REF} # no motion, no contacts, no success
17
+ assert scoring.task_scores("LiftPot", idle)["score"] == 0.0
18
+
19
+
20
+ def test_human_level_success_scores_one():
21
+ assert scoring.task_scores("LiftPot", REF)["score"] == pytest.approx(1.0)
22
+
23
+
24
+ def test_worse_behavior_lowers_score_smoothly():
25
+ m = dict(REF, cpl=4.0) # twice the human path length
26
+ assert scoring.metric_score("cpl", 4.0, 2.0) == pytest.approx(2.2 / 4.2)
27
+ assert scoring.task_scores("LiftPot", m)["efficiency"] < 1.0
28
+ assert scoring.task_scores("LiftPot", m)["score"] < 1.0
29
+
30
+
31
+ def test_count_metric_with_zero_reference():
32
+ assert scoring.metric_score("scc", 1.0, 0.0) == pytest.approx(0.5)
33
+ assert scoring.metric_score("scc", 3.0, 0.0) == pytest.approx(0.25)
34
+
35
+
36
+ def test_outcome_weighting_and_suite_mean():
37
+ half = dict(REF, success=0.5, tp=0.5)
38
+ t = scoring.task_scores("LiftPot", half)
39
+ assert t["outcome"] == pytest.approx(0.5)
40
+ assert t["score"] == pytest.approx(0.4 * 0.5 + 0.5 * 0.6)
41
+ s = scoring.suite_scores({"LiftPot": REF, "PackBox": half})
42
+ assert s["overall"]["score"] == pytest.approx((1.0 + t["score"]) / 2)