Skip to content

Treat systems as their own class of entrant - #464

Merged
LennartPurucker merged 42 commits into
mainfrom
systems-as-entrants
Aug 8, 2026
Merged

Treat systems as their own class of entrant#464
LennartPurucker merged 42 commits into
mainfrom
systems-as-entrants

Conversation

@LennartPurucker

@LennartPurucker LennartPurucker commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

TabArena benchmarks models and carries AutoGluon along as a "Reference Pipeline". This makes systems their own class of entrant: a system is a whole pipeline that picks, tunes and ensembles models inside its own budget, whether that is an AutoML framework, an agent, a hosted API, or a model run through a heavier self-managing interface.

Two problems motivated it.

The reference-pipeline handling was already inconsistent. AutoGluon rows sit in website_leaderboard.csv and therefore in the Elo pool, so every model's Elo was computed against them. But the Pareto figure, the cross-subset overview and the win-rate matrix all excluded or hid them. The site showed a "models only" view built on numbers that were not computed models-only.

A participant filter cannot be client-side. Elo is pairwise over the participants, improvability is 1 - best_error_in_pool / error, and the ranks are positions in the field. Change who competes and everyone's numbers change.

What changes

Metadata. MethodMetadata gains method_class (model / system) and tags (with-llm, closed-source-api). method_type stays what it was, a result-shape discriminator deciding which parquet load_results reads and whether HPO simulation applies. Systems record as baselines like any other non-config run, so overloading it would have touched about 30 branch points for nothing.

A systems/ package mirroring models/: one folder per system with system.py, hpo.py and info.py, walked into SYSTEM_REGISTRY by discover_systems(). TabFM+ moves out of models/tabfm/, AutoGluon's metadata is consolidated from four scattered modules into systems/autogluon/info.py, and the example's AutoGluonSystemModel is promoted into a real wrapper. TabFM+ is registered in the collection, so it reaches the leaderboard for the first time.

Entrant pools as a subset axis. Three independently selectable system categories (open, llm, api), so every combination is published as its own evaluation: eight pools (models, open, llm, api, open_llm, open_api, llm_api, open_llm_api) over a 480-cell grid. eval_all.py leaves nips2025_utils on the way past and splits into evaluation/{entrants,subset_grid,eval_all}.py, next to the LeaderboardReporter it drives.

Reference Pipeline becomes System, typed from method_class rather than name prefixes. That retires the ❓ Other trap the update-leaderboard skill has a pre-check for: previously AutoGluon and PORTFOLIO were hardcoded prefixes and every new system landed in Other until someone remembered to extend the table.

Static PNGs stop shipping. They were 80.1 of the 105 MB in the Space's data/ and the only reason it needed Git LFS, while every figure already has an explorer that renders by default and exports its own SVG/PDF/PNG. A pool drops from ~105 MB to ~24.5 MB, so four pools land under today's single-pool footprint. BeyondArena is untouched and keeps its PNGs.

Docs: AGENTS.md sections on systems and entrant pools, a new add-system skill, and refreshes to upload-method and update-leaderboard.

Decisions worth a second opinion

  • Eight pools, independent checkboxes. A cumulative four-stop ladder cannot express "LLM-based systems but not the plain open-source ones", which is a question people actually have. The cost is that each new category doubles the artifact count.
  • Portfolio-N200-4h is typed as a system. It is a multi-model pipeline like AutoGluon but a TabArena-internal construct rather than an external one. Easy to flip.
  • Dropping the static figures from the site. They are still rendered into raw_website_artifacts/ for paper use; only the copy into the published layout goes away.

New systems, and two bugs they surfaced

The AutoGluon 1.6 presets from #466 land here in the systems/ layout rather than in contexts/: both are MethodMetadata.system(...) in systems/autogluon/info.py, the dated module keeps only its list, and autogluon_info points at 1.6 extreme. TabFM+ is processed and uploaded to r2 (suite tabarena-2026-07-07, 816 tasks over 51 datasets). It stays verified=False: the run predates the 2026-07-13 rerun that stopped reloading models from disk around inference, so its time_infer reads high next to the models beside it on the Pareto front.

Registering them exposed two defects in this branch's own work.

Systems without a pinned reference-line color never reached the leaderboard. get_pool_reference_lines intersected the pool's admitted methods with a two-entry color map, and that list is not only what the figures draw: LeaderboardReporter.eval keeps a non-config row only when its method maps to a config framework type or is named in baselines. AutoGluon 1.6 and TabFM+ were absent from all 454 CSVs of the first generation pass, with nothing logged. It now returns every admitted system and cycles a color list whose first two entries keep AutoGluon 1.4 and 1.5 in their existing colors.

Seven of the eight pools had no pool name in their figure headlines. The generator's label map still listed the four keys from the earlier design, and _subset_label drops any segment it cannot name, so the one part of the label that says which field the numbers came from went missing. It reads ENTRANT_POOLS now, and an unknown entrants_* segment raises.

Not in this PR

The companion changes to the leaderboard-testing Space (the control band at the top of the page, the "Who's competing?" and "I care about" selectors, tag chips, the entrants API axis) live in that repo.

Wrappers for LightAutoML, FLAML, TabPFN-3-API and TabPFN-Thinking are deferred; the add-system skill covers them. Until an open LLM-based system or a hosted API exists, the llm and api categories have no entrant, so those four pools are identical to models-only and the Space renders their toggles as "coming soon".

Testing

1417 passed, 2 skipped. ruff check and ruff format --check clean on every touched file. New tests cover the metadata fields and their two serialization forms, the systems registry, pool admission for each of the eight pools, the subset grid and its folder layout, and system typing in website_format.

The artifacts are regenerated at the official 200 bootstrap rounds. Verified across all eight pools: the four admitting open carry 84 rows with all five systems, the other four carry 79 with none, and TabM (tuned + ensembled) moves 1422 to 1420 between them, which is the recompute doing its job.

Also verified from a clean git worktree checkout of HEAD that both systems discover and all three appear in the collection. That check exists because a bare autogluon/ pattern in .gitignore matched at any depth and silently swallowed the whole systems/autogluon/ package: git status never showed it and git add on the parent skipped it. Fixed by anchoring the pattern; without that catch the branch would only have worked in the tree it was written in.

🤖 Generated with Claude Code

LennartPurucker and others added 9 commits August 5, 2026 14:42
TabArena needs to compare whole systems (AutoGluon, TabFM+, agents, hosted
APIs) alongside individual models, and a reader needs to know which entrants
use an LLM or sit behind a closed API before trusting a comparison.

`method_type` stays a result-shape discriminator: it decides which parquet
`load_results` reads and whether HPO simulation applies, and a system's results
are recorded as a baseline like any other non-config run. So the entrant class
goes in a separate `method_class` field, with `tags` carrying the properties
the leaderboard filters and labels on.

`tags` is a tuple in `to_info_dict` because that dict becomes a DataFrame cell
in `MethodMetadataCollection.info`, where a list is unhashable and breaks the
`drop_duplicates`/`.eq` comparison in `website_format.strict_merge`. The YAML
writers need the opposite, so `_to_yaml_dict` flattens tuples to lists:
`yaml.safe_dump` raises on a tuple outright, and `yaml.dump` would write a
`!!python/tuple` that only `unsafe_load` reads back. Two test helpers were
writing `yaml.dump(to_info_dict())` directly; they now use the real writer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Systems had no home. TabFM+ sat inside models/tabfm/ despite not being an
AutoGluon model, the AutoGluon metadata was spread over baselines/info.py and
three dated context modules, and the only worked example of a system was an
inline class in an examples/ script.

tabarena/systems/ now mirrors tabarena/models/: one subpackage per system with
system.py, hpo.py and info.py, walked into SYSTEM_REGISTRY by discover_systems().
SystemInfo is the ModelInfo counterpart and asserts method_class="system", so a
system that would classify wrong downstream fails at import instead.

Systems stay out of the model registry deliberately: they carry no ag_key and no
search space, and they run through the bundle's system_experiments mode.

AutoGluon's metadata is now declared once in systems/autogluon/info.py; the dated
context modules import from there and keep only their per-suite lists. The
example's AutoGluonSystemModel is promoted to systems/autogluon/system.py, with
the example keeping its inline copy as teaching material plus a pointer.

Portfolio-N200-4h keeps method_type="portfolio" (it reads a different parquet)
but gains method_class="system": like AutoGluon it is a multi-model pipeline.

TabFM+ is registered in the TabArena collection, so it reaches the leaderboard
for the first time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion/

Every headline TabArena number is relative to who competes: Elo is pairwise over
the participants, improvability is 1 - best_error_in_pool / error, and the ranks
are positions in the field. So "show me models only" cannot be a filter over one
published table, the way the site currently implies. Today AutoGluon sits in the
Elo pool while the Pareto figure, the cross-subset overview and the win-rate
matrix all hide it, which means the models-only view is built on numbers that
were not computed models-only.

The entrant pool therefore becomes a subset axis alongside imputation and splits,
with its own artifact tree per pool. Four pools, cumulative: models only, plus
open-source systems, plus systems with LLMs, plus closed-source API systems. Two
independent checkboxes would imply a fifth field ("closed API, no LLM") that has
no folder, so the leaderboard will render this as one four-stop selector.

The reference lines drawn in a pool's figures are now derived from that pool
rather than hardcoded, so the models-only pool gets none instead of lines for
AutoGluon variants that are not in its field.

eval_all.py leaves nips2025_utils on the way past. It splits into three modules
under evaluation/, next to the LeaderboardReporter it drives: entrants.py (who
competes), subset_grid.py (the grid and its folder layout, shared with the
tuning-trajectory plots so the two cannot disagree), and eval_all.py itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Reference Pipeline" was the old framing where AutoGluon tagged along beside the
models. Now that systems are their own entrant class the family is called
System, with a 🧰 pill.

More than a rename: the family was inferred from name prefixes, with "AutoGluon"
and "PORTFOLIO" hardcoded in get_model_family. That is the trap the
update-leaderboard skill has a pre-check for, because every new system landed in
"❓ Other" until someone remembered to extend the table. add_metadata now reads
method_class off the row it already holds, and the figure paths (which see only
rendered names) get the declared set via system_display_names.

website_leaderboard.csv gains MethodClass and Tags so the leaderboard can render
system chips and the API can expose them. add_metadata's missing-method branch
returned a different key set than its happy path, which the caller assigns to a
fixed column list; both now agree.

The win-rate matrix no longer hides systems on load. Which entrants compete is
decided upstream by the pool now, so if a system is in the matrix the reader
chose a pool containing it, and hiding it would contradict that choice.

The train-time Pareto explorer has been generated all along and never shipped;
process_one_folder now copies it, which is most of what the "I care about"
selector needs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
They were 80.1 of the 105 MB in the Space's data/, and the only reason it needed
Git LFS. Every one of them has a self-contained HTML explorer that the site
renders by default, with its own SVG / PDF / PNG export in the panel header, so
the static view was a second copy of what the reader already had one click away.

The figures are still rendered into raw_website_artifacts/ for paper use. Only
the copy into the published layout goes away, along with the PNG-to-zip step.

This is what makes four entrant pools affordable: a pool drops from ~105 MB to
~24.5 MB, so all four land under today's single-pool footprint and data/ carries
no binaries at all.

BeyondArena is untouched. It has its own copy path and ships PNGs because most
of its figures have no explorer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A reader meets a system in the table, so that is where it has to say what to
weigh: 🤖 with LLMs, 🔒 closed-source API, each with hover text explaining why
it matters.

TAG_SPECS lives in website_format next to the family constants and is handed to
the generated table through its config, rather than restated in the JS template.
Tables generated before the Tags column existed still build; the chips are
simply absent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AGENTS.md gains two sections: what separates a model from a system and how to
declare one (method_class, the two-tag vocabulary, the systems/ layout), and why
the entrant pool has to be a recompute rather than a filter.

The add-system skill is the sibling of add-model. Its first section is the
model-or-system question, because that decides everything downstream, and it
spells out the tag choice rather than leaving it to be guessed: tags decide which
pools a system competes in, and a wrong one silently misplaces it.

update-leaderboard: the model-type pre-check no longer applies to systems (they
are typed from method_class), the artifact counts are now four pools of 60 with
no PNGs, and the stale-unzipped-PNG gotcha is retired for data/ while it still
holds for data_beyondarena/.

upload-method: the raw-data inspector cannot tell a system from any other
baseline, since the runner records both identically, so both the skill and the
generated snippet now say so and point at MethodMetadata.system.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A bare `autogluon/` pattern, meant for an AutoGluon checkout at the repo root,
matches at any depth. It silently excluded the whole new
packages/tabarena/src/tabarena/systems/autogluon/ package: `git status` never
listed it as untracked and `git add` on the parent directory skipped it without
complaint.

The result was a branch that only worked in the tree it was written in. A fresh
clone would fail at import, since baselines/info.py, three dated context modules
and contexts/tabarena/methods.py all read AutoGluon's metadata from there.

Anchoring the pattern to `/autogluon/` keeps the original intent and stops it
reaching into the package tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Belongs with the previous commit; it was dropped from it because a verification
command I ran in between (`git --work-tree=<tmp> checkout HEAD -- .`) writes to
the main index and reverted the staged change.

The rescued files are tracked either way, since an explicit `git add` overrides
an ignore. Without this the pattern still matches at any depth, so the next file
added under any `autogluon/` directory would disappear the same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LennartPurucker
LennartPurucker requested a review from Innixma August 6, 2026 07:59
@LennartPurucker

Copy link
Copy Markdown
Collaborator Author

@Innixma fyi here is the PR for the system API re-design. A lot of the leaderboard and plotting code is still in flux from my side right now but the way we would store and treat them inside of TabArena is open for discussion, also if we want to keep the hpo.py style etc

LennartPurucker and others added 3 commits August 6, 2026 08:04
The four cumulative pools could not express "LLM-based systems but not the plain
open-source ones", which is a question people actually have. Systems now fall
into three independent categories (open / with-LLM / closed-source-API) and every
combination is published, so the leaderboard can offer three checkboxes instead
of one four-stop ladder.

A system carrying several tags belongs to several categories and needs all of
them selected, so a closed-API LLM system never shows up on the strength of a
property the reader excluded.

The cost is the grid: 8 pools instead of 4, so 480 cells instead of 240 and
about twice the generation time. Affordable only because the static PNGs are
gone; adding a fourth category would double it again.

The System family keeps 📊 rather than moving to 🧰, so it reads the same as it
always has in the table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both explorers already offered Elo and Improvability on their own y-axis
selector and simply opened on the first one. They now accept a metric over the
existing postMessage channel, so the leaderboard's "I care about" control can
decide which one every panel leads with. Doing it at runtime rather than
generating an artifact per metric matters: the published grid is already 480
cells, and this would have doubled it for a choice the charts can make.

Separately, an empty Tags cell round-trips through CSV as NaN, and `str(nan)` is
the string "nan" — so `str(v or "")` gave every untagged system a tag called
"nan". Tags are read with an explicit null check now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ding

The Pareto and trajectory explorers already accepted a metric over postMessage;
the leaderboard-overview explorer uses a different template and did not, so it
could never follow the site's "I care about" control. Its dropdown handler and
the message now share one setMetric.

The tag hover text says what actually matters about each caveat: for an LLM in
the loop, that it may already have seen the test data, rather than the vaguer
"training data cannot be audited"; for a hosted API, that we cannot inspect what
ran, dropping the reproducibility claim.

Prose in the user-visible strings follows AGENTS.md's writing rules: em dashes
out of the table template's tooltips and headings.

The two skills were still describing four cumulative entrant pools. They now
describe the eight independent combinations, with the artifact counts to match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@Innixma Innixma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally seems good to me! We can always adjust after, so I'm fine with merging.

LennartPurucker and others added 4 commits August 6, 2026 22:31
Ports the AutoGluon 1.6 extreme / noncommercial entries (suite
tabarena-2026-08-05, artifacts already on r2) into the systems layout rather
than contexts/: their MethodMetadata now lives in systems/autogluon/info.py with
method_class="system", and the dated contexts module keeps only its list, as the
other AutoGluon suites do. autogluon_info points at the 1.6 extreme run.

TabFM+ is processed and uploaded: suite tabarena-2026-07-07 (816 tasks over 51
datasets), cache_type="r2". compute stays "gpu" by hand, since the jobs
requested one GPU each but the raw results record num_gpus=0. verified stays
False: the run predates the 2026-07-13 rerun that stopped reloading models from
disk around inference, so its time_infer reads high next to the reruns.

The registry test matched AutoGluon by the dated method id of whichever run
autogluon_info pointed at; it now matches the wrapper class instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The label map still listed the four pool keys from an earlier design
(entrants_systems_open and friends). There are now eight, one per combination of
the three system categories, and _subset_label drops any segment it cannot name,
so seven of the eight pools produced explorer headlines with no pool in them --
the one part of the label that says which field the numbers were computed
against. The map now reads the pools themselves, and an unknown entrants_*
segment raises instead of disappearing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
get_pool_reference_lines intersected the pool's admitted methods with a
two-entry color map, so it returned AutoGluon 1.4 and 1.5 and nothing else. That
list is not only what the figures draw as reference lines: eval() keeps a
non-config row only when its method maps to a config framework type or is named
in baselines, and a system is neither. So AutoGluon 1.6 and TabFM+ were dropped
from every published number in every pool -- 0 of 454 leaderboard CSVs mentioned
them, with no warning anywhere.

It now returns every system the pool admits and cycles a color list, whose first
two entries keep AutoGluon 1.4 and 1.5 in the colors they have always been drawn
in. Tested per pool, plus a guard that the widest pool's reference lines cover
every system in the shipped collection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The coupling that dropped AutoGluon 1.6 and TabFM+ from every published number
is not visible from either side on its own, so name it where someone registering
a system will read it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LennartPurucker and others added 10 commits August 7, 2026 09:40
A pool that admits systems put them in the leaderboard table but not in the
plots: entrants_open had 84 leaderboard rows and 73 Pareto points, the missing
eleven being the six banned KNN/LR variants and all five systems. That is the
same split the entrant pools exist to remove -- a system moving every other
number while the figures pretend it is not there.

evaluate_single passed plot_with_baselines=False, which kept systems out of the
Pareto scatter and the win-rate matrix, and the trajectory figure passed
include_baselines=False. Both are now on. A pool that admits no system has no
baseline row, so models-only is unaffected, and the trajectory metadata list is
already pool-filtered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The overview kept systems out of `byMethod` and drew them as dashed threshold
lines across the plot, with their names in the legend rather than on the axis.
That is the reference-pipeline framing: a system as a line the models are
measured against, not as one of the entrants.

Systems are now ordinary entries. They group, sort, filter, toggle by family
chip and colour their axis label like any other method, and their single point
gets the Default bar style. The variant toggles cannot hide them, since a
system has no tuning variant to toggle; the tooltip labels that row "Whole
pipeline" instead of printing a bare colon.

Removes the threshold-line drawing, its sticky axis tags, the dash-pattern
legend entries and the parallel `state.systems` selection set the chips had to
keep in step with `state.methods`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A system fell through to the Default variant style, so it was drawn in the same
blue as every model's default bar and read as one. It is not a tuning variant of
anything, so it now takes the System family hue and the full bar width, and the
legend carries a System swatch next to the three variant swatches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The inference timing note stays: the run predates the 2026-07-13 rerun that
stopped reloading models from disk around inference, so its time_infer still
reads high next to the reruns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ric changes

Two fixes to the same two charts.

A system carries its whole configuration in its name ("AutoGluon 1.6
(noncommercial, 4h)"), which is several times the width they assumed. The
overview cut it with an ellipsis; the Pareto explorer ran it off the plot
whenever its point sat in the right-hand third, and under-counted how far it
reached when spacing labels apart. Both now measure the text they are about to
draw, through one shared helper. The overview breaks a name at its spaces and
widens the columns until the widest line fits, so the ellipsis is only reached
past MAX_NAME_SLOT. The Pareto explorer puts a label on whichever side of its
point has room for the whole name and de-overlaps on measured spans.

The Pareto explorer also computed its highlighted set once, from the metric it
opened on, and kept it as the y-axis changed. Each metric has its own front --
EXAONE-Tabular leads on Elo for regression and is mid-field on relative gain --
so methods were drawn on the front and greyed out at the same time. The set now
follows the metric until the reader picks methods themselves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The interactive matrix is built to compare model-variant pairs: it offers a
toggle per variant and a "One per model" control that collapses them. Neither
worked. The reporter fed it the best-variant-per-model frame, so there was
nothing to collapse, and it recognised "(tuned + ensembled)" while the reporter
writes the tag as "(T+E)", so those rows read as variant-less and the toggles
came down to a lone "Default".

The matrix is now computed over every variant. Win rates are pairwise and per
task, so the one-per-model matrix the static figure and the CSV keep showing is
exactly a submatrix of it -- identical numbers, no second computation. Cells and
means are rounded to four decimals before they reach the page, which is more
precision than it prints and keeps the bigger matrix from dominating the file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Widening every column to fit the widest wrapped line cost more than the
ellipsis did: fewer columns fit on screen, so the chart reads worse overall
than it did with a couple of long system names trimmed.

Back to one line per name, trimmed to the slot. The measured labels on the
Pareto fronts and the shared text measurer stay.
A system column stands alone -- there are no variants nested inside it -- so drawing it at the
full width of a Tuned + Ensembled bar only made it read as heavier than everything beside it.
An explorer stamps data-theme="light" while paper view is on and puts the embedding page's
own choice back when it is switched off. That choice was read on the way *in*, so a page
configured to open out of paper view (the leaderboard table) cleared the stamp on its first
call and then followed the viewer's OS preference, rendering light inside the always-dark
site. Read it once at setup instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LennartPurucker and others added 16 commits August 7, 2026 21:31
The aggregate figure asks compute_tuning_trajectories_leaderboard for Elo, which is a rating
over many tasks and says nothing useful about a single one. Everything a per-dataset
trajectory needs is an average over that dataset's splits, so compute_per_dataset_trajectories
does it in one vectorized pass over every dataset at once rather than one leaderboard per
dataset. Improvability keeps the definition the rest of the site uses, and the field it is
measured against is the same one the aggregate figure plots.

The subset/fill/exclude-imputed preprocessing the two share moves into
_subset_trajectory_data so they cannot drift.

Written for the cells with no task or dataset restriction only: a dataset's own numbers do not
depend on which other datasets share its leaderboard, so 32 frames cover the grid's 480 cells.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The leaderboard answers "which method is best on average". This answers the question a reader
asks next: where did it win, and where did it lose.

A master-detail page. The list is one row per dataset, carrying the contender's rank and its
gap to the best method there plus a strip showing the whole field with a star on the contender;
it sorts by any column, searches, and filters by task and size. Selecting a dataset fills the
detail pane below with that dataset's tuning trajectories and its full ranking, and the arrow
keys step through the list without the chart moving.

Two scales needed care. The strip is log(1 + gap): most fields bunch inside a few percent of
the best while one or two collapsed models sit at 80%, and linear puts everything that matters
in the first two pixels. The chart's y-axis stops at the 90th percentile for the same reason,
with the points above it pinned to the top edge as hollow triangles rather than dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Like the leaderboard overview and table, it needs nothing beyond the CSVs it sits next to, so
a styling fix costs a conversion re-run rather than a re-evaluation. It is built for the cells
that carry a per-dataset trajectory frame, which is the evaluation's own marker for "this cell
is not restricted by task or dataset size".

The dataset metadata is the same for every cell, so the generator reads it once and hands it
down instead of the conversion loading it 480 times.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…wn metric

Three fixes to the per-dataset browser, all about what a mark means:

The green diamond on the winner is gone. It drew a method twice — the winner is by definition
the one whose gap is zero, so its own family-coloured dot was already sitting at the left edge
and the diamond was painted on top of it. It read as a method because it was one, and its green
was close enough to the Tree-based family hue to make that worse. The axis starts at the best
method by construction and the Winner column names it.

The contender's star now takes one fixed colour whoever the contender is, rather than that
method's family hue, which made it compete with the field it exists to stand out from. Yellow
rather than gold: the nearest family colour is the System orange, and an amber star sat close
enough to reintroduce the same confusion against a different family.

The chart opens on the dataset's own metric. Improvability is the axis the aggregate figure
needs to make datasets comparable; on one dataset it is the second question. Safe as a default
only because the axis clips at the 90th percentile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…set list

Three changes to the per-dataset browser.

The row count was the dataset's own size, which is not a number any model here saw:
GiveMeSomeCredit read 150,000 while every method trained on 100,000 of it. The column now
reports the largest training split, so it agrees with the size filter beside it — which was
already keyed on that, making "Rows 129,880" next to a pressed "10k - 100k" chip a
contradiction. The detail pane shows both, since the gap between them is itself informative.

The list is resizable: it and the detail chart together are taller than a laptop screen, and
which of the two you want more of depends on what you are doing. It fits its rows up to a
ceiling the drag handle sets, and filtering never undoes that choice. Two measurement traps on
the way: the box has no layout on the first render, and `scrollHeight` is never smaller than
the element's own height, so neither can report the content's size on its own.

The keyboard hint now says the arrow keys need a click first. The page is a sandboxed frame, so
it receives no key events at all until the reader has clicked inside it, and without that
sentence the keys simply look broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The list could be resized and scrolled, but neither said so. The browser's own resize grip is a
few pixels in one corner and nobody finds it, so it is replaced by a bar spanning the list that
says what it does and takes the arrow keys; the cut-off rows now fade under a "more below"
label instead of looking like the end of the list; and the scrollbar thumb moved off the
almost-invisible --pt-muted.

"Fit table and figure to screen" sizes both against the visible band. It has to move both: on a
laptop the detail pane alone is taller than the viewport, so a fit that only shrank the list
could do nothing but collapse it. The chart takes what it can up to its normal height, the list
gets the rest, and neither goes below a readable floor. The frame cannot measure the band
itself -- it is sized to its own content -- so the host sends it.

The dataset's facts move under the chart they describe and gain a tinted panel, with the name
leading them. Above the chart, as a run-on line of grey text, they were skipped over; and the
name was already on screen, highlighted in the selected row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…idth

`scrollIntoView({block: "nearest"})` stops as soon as a row is anywhere inside the scroll box,
so stepping down parked the selection under the "more below" fade: selected in the DOM,
unreadable on screen. Rows get a scroll margin instead, which scrollIntoView honours, at both
ends -- the sticky header covered rows stepped to upward for the same reason.

The page's own side padding drops from the shared 12px to 8px. It fills a panel as an
application rather than sitting in one as a figure, and the panel around it was already adding
its own inset; `body.paper` keeps the roomier figure padding, since there it is one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ar colour

Bar widths are relative *within* a column — tuned + ensembled 1.0, tuned 0.8, default 0.6 — so
any column missing the outermost variant drew thin beside its neighbours for a reason nobody
could read: a model with no tuned run, a system, or every column at once as soon as the reader
switches a variant off. Each column is now scaled so its widest bar fills the slot; the nesting
inside it is unchanged.

The system bar took the System *family* hue, which sat close enough to the tuned bar's orange to
be read as one. It gets its own token in the variant palette instead. Wine, because every other
hue in that chart is already spoken for: orange is Tuned, green is Tuned + Ens., blue is
Default, and purple is the Foundation Model family, whose colour the method names under the
axis are set in — which rules out magenta too, near enough to that purple to be taken for it.

The family colour itself is unchanged, and System now appears in the family legend. Systems
were filtered out of it, so their names were printed in a colour the legend never explained.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The column reported how far the contender was behind the best method, which is 0.0% for every
dataset the contender wins — silent about whether it won by a hair or a mile, on exactly the
datasets a reader is most curious about.

It now reads from the contender's own point of view: negative against the best method when it
trails, positive against the runner-up when it wins. Both are the same quantity — how much lower
the better error is than the worse one, as a fraction of the worse one — so the halves stay
comparable and only the sign changes. A lead is drawn in the optimal colour, being the exception
worth spotting, and clicking the column sorts worst-first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every family has a mark colour and a darkened ink for text on the light surface, and darkening
works for purple, green, blue and grey — each stays a readable version of itself. Orange does
not: at that lightness it reads as brown, so in paper view a system's name no longer matched
the swatch beside it while every other family did.

Saturating instead of darkening holds the hue, and at 5.2:1 on white it is better contrast than
the brown it replaces, so nothing is traded for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wine was too heavy against the pastels it sits among on the paper surface. Rosa keeps a hue no
family or variant claims while matching the weight of the other three light-mode bars, and the
dark counterpart steps down from it the way each of those pairs does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The overview legend listed "System" twice: once in the row describing how a method was produced
and again in the family row, in two different colours, which reads as a contradiction rather
than as two facts. The first is now "End-to-end", the phrasing the site's own copy already uses
for systems owning their pipeline, and it says why none of the three regimes beside it apply.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A chart sizes itself from its container and lays its labels out from measured text. In a
container that is not laid out yet -- a collapsed section, a hidden tab, a panel Gradio is
mid-render on -- the container reports 0 and every string measures 0 too. The chart then pinned
itself to its 360px minimum and, believing every name to be zero wide, stacked them on top of
each other. That is the state a reader finds when the panel finally appears.

Nothing healed it. Recovery hung on `window.onresize`, which only fires when the viewport
changes, and the text measurer cached its zeros -- in the Pareto template on a cache that lives
for the page, so every later redraw read the same zeros back and re-stacked the labels.

Three changes: refuse to draw until the box has a width, never cache a zero measurement, and
watch the chart's own box with a ResizeObserver rather than the viewport, since that is what
actually changes when a hidden panel is revealed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An imputed score is a default RandomForest's result standing in for a model that could not run
on that dataset. Averaged over 51 datasets that is a fair penalty, but on the one dataset it
belongs to it claims a measurement that never happened -- so the per-dataset browser now drops
those pairs, from the list, the field strip, the ranking and the trajectory chart alike. The
model keeps its place on every dataset where it did run.

The ranks and gaps are computed after the drop, so they are over the methods that actually ran,
and the field size now varies by dataset -- 2 to 8 methods missing on each in the current data.
The pane says how many could not run, so a smaller field reads as information rather than as an
inconsistency.

The page also opens on the leaderboard's own leader now. The old fallback picked the best mean
rank, which this change biases: with imputed results gone, that average is taken over each
method's own datasets, so a model that runs on a handful and does well there outranks one that
runs everywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six cases the previous fixtures could not reach, since every row in them was unimputed: a pair
imputed on one dataset disappears from that dataset and stays on the others, in both the results
and the trajectory frame; the ranks close up over the methods that actually ran; the per-dataset
count of what could not run reaches the page; and an explicit contender beats the mean-rank
fallback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LennartPurucker
LennartPurucker merged commit 22ddb7b into main Aug 8, 2026
8 checks passed
@LennartPurucker
LennartPurucker deleted the systems-as-entrants branch August 8, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants