From 51398dfe34b94aa1f3651c2973211cdedd3fa622 Mon Sep 17 00:00:00 2001 From: dipakchaudhari12717 Date: Wed, 22 Jul 2026 17:58:44 +0530 Subject: [PATCH] leaderboard: show each model's rank number and rank shift on the reorder plot The bump chart drew connecting lines and per-node rates but not the explicit rank position, so a reader had to infer ranks from vertical order. Render each model's integer rank at both the hijack and leakage endpoints (white numeral on the node), and mark movers with an up/down glyph plus the rank delta (reusing the same moved test as the crossing count). A null (Kendall tau = 1) shows no delta. Closes #8 --- src/leakgauge/leaderboard.py | 22 ++++++++++++++++++++-- tests/test_leaderboard.py | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/leakgauge/leaderboard.py b/src/leakgauge/leaderboard.py index 621e71a..d1d78fc 100644 --- a/src/leakgauge/leaderboard.py +++ b/src/leakgauge/leaderboard.py @@ -100,8 +100,26 @@ def y(rank: int) -> int: f'' ) - parts.append(f'') - parts.append(f'') + parts.append(f'') + parts.append(f'') + # Explicit rank position at each endpoint, so the reader doesn't have to + # infer it from vertical order. + parts.append( + f'{h_rank}' + ) + parts.append( + f'{l_rank}' + ) + # A mover gets its rank delta with an up/down glyph; a null (tau = 1) + # shows none. Rank 1 is best, so a smaller leakage rank is a move up. + if moved: + arrow = "▲" if l_rank < h_rank else "▼" + parts.append( + f'{arrow}{abs(l_rank - h_rank)}' + ) name = html.escape(m) parts.append( f'' diff --git a/tests/test_leaderboard.py b/tests/test_leaderboard.py index 68b57fe..6f2ee6e 100644 --- a/tests/test_leaderboard.py +++ b/tests/test_leaderboard.py @@ -87,3 +87,29 @@ def test_write_site_emits_index_html(tmp_path: Path) -> None: path = write_site(summaries, rank_reorder(summaries), tmp_path) assert path == tmp_path / "index.html" assert path.read_text(encoding="utf-8").startswith("") + + +def test_rank_numerals_and_delta_render_for_a_reorder() -> None: + # a hijacks more (hijack rank 1) but b leaks more (leakage rank 1): both move. + summaries = [_summary("prov:a", 0.9, 0.3), _summary("prov:b", 0.6, 0.7)] + reorder = rank_reorder(summaries) + assert reorder is not None + + out = render_html(summaries, reorder) + # Explicit rank integers at the endpoints (white numerals on the nodes). + assert 'fill="#ffffff">1' in out + assert 'fill="#ffffff">2' in out + # Both models change rank, so an up and a down glyph with the delta appear. + assert "▲1" in out # ▲1 (moved up) + assert "▼1" in out # ▼1 (moved down) + + +def test_no_rank_delta_glyph_when_ranks_agree() -> None: + # a hijacks AND leaks more -> ranks agree (tau = 1), nobody moves. + summaries = [_summary("prov:a", 0.9, 0.8), _summary("prov:b", 0.4, 0.3)] + reorder = rank_reorder(summaries) + assert reorder is not None + + out = render_html(summaries, reorder) + assert 'fill="#ffffff">1' in out # ranks still numbered + assert "▲" not in out and "▼" not in out # but no movement glyph