bradsansnow Claude Opus 4.8 commited on
Commit
8c8c92e
·
1 Parent(s): ea1199a

Add leaderboard auto-sync design spec

Browse files

Daily 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.