Skip to content

Commit 88f854d

Browse files
ihhclaude
andcommitted
Add time-at-top tracking to leaderboard
Tracks leadership changes in top_history.json and computes the fraction of competition time each team has held the #1 position. Displayed as a percentage with a bar in the leaderboard table. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 26990c4 commit 88f854d

11 files changed

Lines changed: 97 additions & 13 deletions

File tree

.github/workflows/validate.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ jobs:
8989
run: |
9090
mkdir -p docs/scores
9191
cp leaderboard.json docs/ 2>/dev/null || true
92+
cp top_history.json docs/ 2>/dev/null || true
9293
cp scores/*.json docs/scores/ 2>/dev/null || true
9394
# Remove stale docs scores
9495
for f in docs/scores/*.json; do
@@ -109,5 +110,7 @@ jobs:
109110
add-paths: |
110111
scores/
111112
leaderboard.json
113+
top_history.json
112114
docs/leaderboard.json
115+
docs/top_history.json
113116
docs/scores/

ci/rebuild_leaderboard.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def main():
8383
docs_scores = docs_dir / "scores"
8484
docs_scores.mkdir(parents=True, exist_ok=True)
8585
shutil.copy2(leaderboard_path, docs_dir / "leaderboard.json")
86+
history_path = root / "top_history.json"
87+
if history_path.exists():
88+
shutil.copy2(history_path, docs_dir / "top_history.json")
8689
for f in scores_dir.glob("*.json"):
8790
shutil.copy2(f, docs_scores / f.name)
8891
# Remove stale docs scores

ci/validate.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,48 @@ def _build_report(
167167
}
168168

169169

170+
def _update_top_history(history_path: Path, leader: str, score: float) -> list[dict]:
171+
"""Append to top_history.json if the leader changed. Returns the history."""
172+
history: list[dict] = []
173+
if history_path.exists():
174+
try:
175+
history = json.loads(history_path.read_text())
176+
except (json.JSONDecodeError, ValueError):
177+
history = []
178+
179+
now = datetime.now(timezone.utc).isoformat()
180+
181+
# Append if leader changed (or first entry)
182+
if not history or history[-1].get("team") != leader:
183+
history.append({"team": leader, "score": score, "timestamp": now})
184+
history_path.write_text(json.dumps(history, indent=2) + "\n")
185+
186+
return history
187+
188+
189+
def _compute_time_at_top(history: list[dict]) -> dict[str, float]:
190+
"""Compute fraction of time each team has been the leader."""
191+
if not history:
192+
return {}
193+
194+
now = datetime.now(timezone.utc)
195+
durations: dict[str, float] = {}
196+
197+
for i, entry in enumerate(history):
198+
start = datetime.fromisoformat(entry["timestamp"])
199+
if i + 1 < len(history):
200+
end = datetime.fromisoformat(history[i + 1]["timestamp"])
201+
else:
202+
end = now
203+
secs = max(0, (end - start).total_seconds())
204+
durations[entry["team"]] = durations.get(entry["team"], 0) + secs
205+
206+
total = sum(durations.values())
207+
if total == 0:
208+
return {t: 1.0 for t in durations} # single snapshot
209+
return {t: d / total for t, d in durations.items()}
210+
211+
170212
def update_leaderboard(scores_dir: Path, output_path: Path) -> None:
171213
"""Read all score files and produce a ranked leaderboard.json."""
172214
entries = []
@@ -194,6 +236,18 @@ def update_leaderboard(scores_dir: Path, output_path: Path) -> None:
194236
reverse=True,
195237
)
196238

239+
# Track time at top
240+
history_path = output_path.parent / "top_history.json"
241+
if entries:
242+
history = _update_top_history(
243+
history_path, entries[0]["team"], entries[0]["score"]
244+
)
245+
time_at_top = _compute_time_at_top(history)
246+
for entry in entries:
247+
entry["time_at_top"] = round(time_at_top.get(entry["team"], 0), 4)
248+
else:
249+
time_at_top = {}
250+
197251
leaderboard = {
198252
"updated": datetime.now(timezone.utc).isoformat(),
199253
"entries": entries,

docs/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ <h1>ITm Seed Dataset Leaderboard</h1>
2525
<th class="hide-mobile">Size</th>
2626
<th>Seqs</th>
2727
<th>Fam</th>
28+
<th class="hide-mobile">Top</th>
2829
<th>Commit</th>
2930
</tr>
3031
</thead>

docs/leaderboard.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@
145145

146146
// ── Detail panel builder ────────────────────────────────────────────
147147

148+
function formatTimeAtTop(frac) {
149+
if (frac == null || frac === 0) return "";
150+
const pct = (frac * 100).toFixed(0);
151+
return `${scoreBar(frac * 100, 100)}${pct}%`;
152+
}
153+
148154
function buildRow(entry, rank) {
149155
const tr = document.createElement("tr");
150156
tr.innerHTML = `
@@ -157,6 +163,7 @@
157163
<td class="score-sub hide-mobile">${(entry.scores?.size ?? "").toString().slice(0, 4)}</td>
158164
<td class="count">${entry.n_sequences}</td>
159165
<td class="count">${entry.n_families}</td>
166+
<td class="score-sub hide-mobile">${formatTimeAtTop(entry.time_at_top)}</td>
160167
<td class="score-sub"><code>${(entry.commit || "").slice(0, 7)}</code></td>
161168
`;
162169
return tr;
@@ -166,7 +173,7 @@
166173
const tr = document.createElement("tr");
167174
tr.className = "detail-row";
168175
const td = document.createElement("td");
169-
td.colSpan = 10;
176+
td.colSpan = 11;
170177

171178
let checksHTML = "";
172179
if (teamData.checks) {
@@ -241,7 +248,7 @@
241248
data = await fetchJSON("leaderboard.json");
242249
} catch (e) {
243250
tbody.innerHTML =
244-
'<tr><td colspan="10" class="empty-state">No leaderboard data yet. Submit an entry to get started.</td></tr>';
251+
'<tr><td colspan="11" class="empty-state">No leaderboard data yet. Submit an entry to get started.</td></tr>';
245252
return;
246253
}
247254

@@ -251,7 +258,7 @@
251258

252259
if (!data.entries || data.entries.length === 0) {
253260
tbody.innerHTML =
254-
'<tr><td colspan="10" class="empty-state">No entries yet.</td></tr>';
261+
'<tr><td colspan="11" class="empty-state">No entries yet.</td></tr>';
255262
return;
256263
}
257264

docs/leaderboard.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
{
2-
"updated": "2026-03-09T00:59:35.694353+00:00",
2+
"updated": "2026-03-09T01:02:45.670618+00:00",
33
"entries": [
44
{
55
"team": "test-entry",
66
"score": 31.5,
77
"n_sequences": 3,
88
"n_families": 1,
9-
"commit": "dffe4a1",
9+
"commit": "26990c4",
1010
"scores": {
1111
"diversity": 0.0554,
1212
"annotation": 1.0,
1313
"functionality": 0.0167,
1414
"size": 0.2642,
1515
"total": 31.5
16-
}
16+
},
17+
"time_at_top": 1.0
1718
}
1819
]
1920
}

docs/scores/test-entry.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"team": "test-entry",
3-
"timestamp": "2026-03-09T00:46:07.291930+00:00",
4-
"commit": "dffe4a1",
3+
"timestamp": "2026-03-09T01:02:45.668555+00:00",
4+
"commit": "26990c4",
55
"n_sequences": 3,
66
"n_families": 1,
77
"checks": {

docs/top_history.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"team": "test-entry",
4+
"score": 31.5,
5+
"timestamp": "2026-03-09T01:02:45.670343+00:00"
6+
}
7+
]

leaderboard.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
{
2-
"updated": "2026-03-09T00:59:35.694353+00:00",
2+
"updated": "2026-03-09T01:02:45.670618+00:00",
33
"entries": [
44
{
55
"team": "test-entry",
66
"score": 31.5,
77
"n_sequences": 3,
88
"n_families": 1,
9-
"commit": "dffe4a1",
9+
"commit": "26990c4",
1010
"scores": {
1111
"diversity": 0.0554,
1212
"annotation": 1.0,
1313
"functionality": 0.0167,
1414
"size": 0.2642,
1515
"total": 31.5
16-
}
16+
},
17+
"time_at_top": 1.0
1718
}
1819
]
1920
}

scores/test-entry.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"team": "test-entry",
3-
"timestamp": "2026-03-09T00:46:07.291930+00:00",
4-
"commit": "dffe4a1",
3+
"timestamp": "2026-03-09T01:02:45.668555+00:00",
4+
"commit": "26990c4",
55
"n_sequences": 3,
66
"n_families": 1,
77
"checks": {

0 commit comments

Comments
 (0)