Spaces:
Running
Running
Commit ·
8c8c92e
1
Parent(s): ea1199a
Add leaderboard auto-sync design spec
Browse filesDaily Claude Code scheduled run: pull EOG + EVA upstream sources,
re-derive data/leaderboard.json, diff vs committed, push to staging
on change. Human-gated promotion to prod.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs/superpowers/specs/2026-06-02-leaderboard-sync-design.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Leaderboard Auto-Sync — Design
|
| 2 |
+
|
| 3 |
+
**Date:** 2026-06-02
|
| 4 |
+
**Status:** Approved (design)
|
| 5 |
+
**Author:** brad + Claude
|
| 6 |
+
|
| 7 |
+
## Problem
|
| 8 |
+
|
| 9 |
+
The NowAI-Bench landing page (`index.html`) hardcodes three leaderboards — EnterpriseOps-Gym (EOG), EVA-Accuracy, EVA-Experience. Upstream results change over time. We need them to track two canonical upstream sources automatically, with a human gate before anything reaches production.
|
| 10 |
+
|
| 11 |
+
Upstream sources:
|
| 12 |
+
- **EOG:** `https://raw.githubusercontent.com/EnterpriseOps-Gym/EnterpriseOps-Gym.github.io/main/script.js` — a JS array `LEADERBOARD_DATA`.
|
| 13 |
+
- **EVA:** `https://raw.githubusercontent.com/ServiceNow/eva/main/website/src/data/leaderboardStats.json` — JSON `{ "systems": [...] }`.
|
| 14 |
+
|
| 15 |
+
## Decisions (locked)
|
| 16 |
+
|
| 17 |
+
| Decision | Choice |
|
| 18 |
+
|----------|--------|
|
| 19 |
+
| Run environment | Claude Code scheduled run (skill + `CronCreate`) |
|
| 20 |
+
| Cadence | Daily |
|
| 21 |
+
| Data flow into page | Extract to `data/leaderboard.json`; page renders from it via inline JS |
|
| 22 |
+
| Change detection | Compare derived output (re-derive our JSON, diff vs committed) |
|
| 23 |
+
| On change | Commit + push to **staging only** (`origin` = ServiceNow-AI/NowAI-Bench-Staging), then notify |
|
| 24 |
+
| Prod | Never touched automatically — human promotes after review |
|
| 25 |
+
|
| 26 |
+
## Architecture
|
| 27 |
+
|
| 28 |
+
Two parts.
|
| 29 |
+
|
| 30 |
+
### Part A — One-time page refactor (no visible behavior change)
|
| 31 |
+
|
| 32 |
+
1. Move the three leaderboards out of `index.html` into `data/leaderboard.json`.
|
| 33 |
+
2. Add a small inline `<script>` that `fetch`es `data/leaderboard.json` on load and renders the three cards from a template function, using the **same Tailwind classes/markup as today**. Styling, tooltips, hover animation, accessibility all unchanged.
|
| 34 |
+
3. Fallback: if the fetch fails, render the last-built static markup (kept inline) so the page never renders empty. Works on HF Space and the local IntelliJ server (both http).
|
| 35 |
+
|
| 36 |
+
### Part B — Sync skill + schedule
|
| 37 |
+
|
| 38 |
+
- New skill `sync-nowai-leaderboard` (`~/.claude/skills/sync-nowai-leaderboard/`).
|
| 39 |
+
- `sync.mjs` Node script: fetch both raw upstream URLs → parse → transform to our schema → diff against committed `data/leaderboard.json`.
|
| 40 |
+
- On change: rewrite `data/leaderboard.json`, commit to `main`, push to `origin` (staging) only, notify.
|
| 41 |
+
- On no change: exit quietly.
|
| 42 |
+
- `CronCreate` fires the skill daily.
|
| 43 |
+
|
| 44 |
+
**Change detection — compare derived output:** every run re-derives `leaderboard.json` from upstream and diffs against the committed file. Updates fire only when something *we display* changes (ignores upstream churn in fields we don't render). Self-correcting. Upstream raw-file SHA-256 hashes are stored as provenance metadata, not as the trigger.
|
| 45 |
+
|
| 46 |
+
## Data schema — `data/leaderboard.json`
|
| 47 |
+
|
| 48 |
+
```json
|
| 49 |
+
{
|
| 50 |
+
"updated": "2026-06-02T17:30:00Z",
|
| 51 |
+
"sources": {
|
| 52 |
+
"eog": { "url": "...script.js", "hash": "sha256:...", "fetchedAt": "..." },
|
| 53 |
+
"eva": { "url": "...leaderboardStats.json", "hash": "sha256:...", "fetchedAt": "..." }
|
| 54 |
+
},
|
| 55 |
+
"eog": {
|
| 56 |
+
"metricLabel": "Task Success Rate · Oracle mode",
|
| 57 |
+
"rows": [
|
| 58 |
+
{ "rank": 1, "model": "Gemini 3.5 Flash (High)", "type": "closed", "score": 46.0, "bar": 100 }
|
| 59 |
+
]
|
| 60 |
+
},
|
| 61 |
+
"evaAccuracy": {
|
| 62 |
+
"metricLabel": "Pass@1",
|
| 63 |
+
"rows": [
|
| 64 |
+
{ "rank": 1, "name": "...", "subtitle": "Mixed Models · Cascade",
|
| 65 |
+
"score": 0.57, "ciLower": 0.54, "ciUpper": 0.59, "bar": 100 }
|
| 66 |
+
]
|
| 67 |
+
},
|
| 68 |
+
"evaExperience": { "metricLabel": "Pass@1", "rows": [ "...same shape as evaAccuracy" ] }
|
| 69 |
+
}
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
Each board carries 5 rows (rank 1–5). Renderer tolerates fewer.
|
| 73 |
+
|
| 74 |
+
## Transform rules
|
| 75 |
+
|
| 76 |
+
### EOG (from `LEADERBOARD_DATA` JS array)
|
| 77 |
+
|
| 78 |
+
- Isolate the `LEADERBOARD_DATA = [ ... ]` block from `script.js` and parse it (normalize to JSON, or eval in a sandboxed `Function` — no network/fs access).
|
| 79 |
+
- Each entry: `{ model, type: "closed"|"open", teams, csm, email, itsm, calendar, hr, drive, hybrid, avg }`.
|
| 80 |
+
- Sort by `avg` descending → take top 5.
|
| 81 |
+
- `score = avg` (1 decimal). `bar = round(avg / topAvg × 100)`. Carry `type`.
|
| 82 |
+
|
| 83 |
+
### EVA (from `systems[]`)
|
| 84 |
+
|
| 85 |
+
- Each system: `{ id, name, type: "cascade"|"s2s", stt, llm, tts, clean: { EVA-A_mean.pooled.{point,ci_lower,ci_upper}, EVA-X_mean.pooled.{...} } }`.
|
| 86 |
+
- **Accuracy board:** sort by `clean.EVA-A_mean.pooled.point` desc → top 5.
|
| 87 |
+
- **Experience board:** sort by `clean.EVA-X_mean.pooled.point` desc → top 5.
|
| 88 |
+
- `score = point` (2 decimals). `ciLower`/`ciUpper` from pooled. `bar = round(point / topPoint × 100)`.
|
| 89 |
+
- `subtitle`:
|
| 90 |
+
- `type === "cascade"` → `"Mixed Models · Cascade"`.
|
| 91 |
+
- `type === "s2s"` → `"<vendor> · Speech-to-Speech"`, vendor from a small name→vendor map (Gemini→Google, GPT→OpenAI, …), fallback to first token of `name`.
|
| 92 |
+
|
| 93 |
+
### Assumptions to verify during implementation (flagged, non-blocking)
|
| 94 |
+
|
| 95 |
+
1. **EOG "Oracle mode."** Upstream exposes one `LEADERBOARD_DATA` array; we treat `avg` as the displayed score under the existing "Task Success Rate · Oracle mode" label. If the EOG site actually has an Oracle-vs-realistic toggle backed by a second dataset, revisit the mapping then.
|
| 96 |
+
2. **s2s vendor map.** Small hardcoded lookup; extend as new vendors appear.
|
| 97 |
+
|
| 98 |
+
## Skill layout
|
| 99 |
+
|
| 100 |
+
`~/.claude/skills/sync-nowai-leaderboard/`
|
| 101 |
+
- `SKILL.md` — when/how to run, decision rules, staging-only push guardrail.
|
| 102 |
+
- `sync.mjs` — fetch → parse → transform → diff. Flags:
|
| 103 |
+
- `--check` — dry-run: print diff, no writes.
|
| 104 |
+
- `--apply` — write + commit + push.
|
| 105 |
+
- Run flow: Claude invokes `--check`, inspects the printed diff, then `--apply` only if the diff passes the sanity gates.
|
| 106 |
+
|
| 107 |
+
## Sanity gates (abort + notify if any fail, before any commit)
|
| 108 |
+
|
| 109 |
+
- Both upstream fetches return HTTP 200 and parse cleanly.
|
| 110 |
+
- Each board yields ≥ 3 rows (≥ 5 preferred; renderer tolerates fewer).
|
| 111 |
+
- No score outside expected range (EOG 0–100, EVA 0–1).
|
| 112 |
+
- Δ guard: top model's score swings > 25 absolute pts (EOG) or > 0.25 (EVA) vs current → suspicious, abort + notify for manual review (catches garbled parses).
|
| 113 |
+
|
| 114 |
+
## Error handling
|
| 115 |
+
|
| 116 |
+
- **Fetch fail / non-200** → log, no repo change, retry next daily run.
|
| 117 |
+
- **Parse fail (schema drift)** → abort, notify with the exception, no commit. (Primary reason for Claude-in-the-loop.)
|
| 118 |
+
- **Git push fail** → report; local commit remains for manual push.
|
| 119 |
+
|
| 120 |
+
## Git + notify
|
| 121 |
+
|
| 122 |
+
- Commit straight to `main`; push `origin` (staging) only. **Never `prod`.**
|
| 123 |
+
- Commit message: `Sync leaderboard: EOG <date>, EVA <date>` plus a one-line summary of rank/score changes.
|
| 124 |
+
- Notify via `PushNotification` (fallback: scheduled-run summary): "Leaderboard synced to staging, N changes. Review + promote to prod."
|
| 125 |
+
|
| 126 |
+
## Testing
|
| 127 |
+
|
| 128 |
+
- **Transform unit test:** committed fixtures (snapshot of today's `script.js` + `leaderboardStats.json`) → asserts expected `leaderboard.json`.
|
| 129 |
+
- **Render test (Chrome DevTools MCP):** load page, assert exactly 5 rows per card, bars present, no console errors.
|
| 130 |
+
- **`--check` dry-run** doubles as the manual smoke test.
|
| 131 |
+
|
| 132 |
+
## Out of scope
|
| 133 |
+
|
| 134 |
+
- Auto-promotion to prod.
|
| 135 |
+
- Per-domain breakdowns / radar view (planned v1.1).
|
| 136 |
+
- Historical trend tracking.
|