Repository navigation
fix(dashboard, report): average both arms over the complete pairs - #286
Conversation
Settles the point the review of intel#250 left for a follow-up: the dashboard and the `report model-compare` run summary averaged each arm over its own scored questions, while `report generate`, `report eval` and the pipeline average both over the questions scored in both arms (paired_summary). On an eval file with q1 80/60, q2 40/40 and q3 100/- the dashboard and model-compare printed 73.3 / 50.0 beside a +10.0 delta; `report generate` prints 60.0 / 50.0 / +10.0. - dashboard/aggregator.py: the snapshot and each question-source group take both arm means and the delta from the pairs (_arm_means). The delta is the difference of the pair means, as in paired_summary, not the mean of the stored per-question delta. - report/model_compare.py: _run_summary averages with_avg and without_avg over the same pairs as delta_avg. - In both, a run that scored one arm only keeps that arm's mean (the dashboard's doc_score and the baseline-only ranking read it); with both arms scored but no pair, neither mean is given. - The stats module docstring, the unreleased intel#250 fragment, the quickstart and the model-compare docs say so; the model-compare goldens and the growth baseline of _load_snapshot are updated. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
@exai-bot /review |
exai-bot
left a comment
There was a problem hiding this comment.
Note
AI-generated review of 8c8fd399977f, requested by @antonwolfy, using the default review skill. It can be wrong: verify a finding before acting on it. React with a thumbs up or down on a finding to tell the bot whether it helped.
Model us.anthropic.claude-sonnet-5-5, effort default, 11 of 80 turns, 0.5 of 40 minutes, 186k in / 2k out tokens.
The change makes the dashboard snapshot, each by_source row and the model-compare run summary average both arms over the questions scored in both, as report generate and paired_summary do. I found no defects. In _arm_means and _run_summary, the no-pair cases behave as the description says. A one-arm run keeps its mean. Two arms that never share a question give None, so the dashboard status reads no-data. The new tests cover the pair, one-arm and no-pair cases for both consumers.
No issues found.
For a human reviewer
- Product call: a run with both arms scored but no shared question now reads
no-dataon the dashboard instead of showing two means.
Not verified
I did not run the tests or regenerate the golden fixture model_compare_sections.json. I did not check its re-recorded numbers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #286 +/- ##
==========================================
+ Coverage 93.26% 93.27% +0.01%
==========================================
Files 272 272
Lines 35117 35119 +2
Branches 5964 5964
==========================================
+ Hits 32752 32759 +7
+ Misses 1547 1546 -1
+ Partials 818 814 -4
🚀 New features to boost your workflow:
|
The same point as the previous commit (the review of intel#250: an arm mean over its own scored questions beside a delta over the pairs), at its other site in `report model-compare`: each By Difficulty row averaged the context arm over every context-scored question in the bucket, while the delta beside it covers the pairs only. On a bucket with q1 80/60, q2 40/40 and q3 100/- the row printed 73.3 | +10.0; it now prints 60.0 | +10.0, the run summary's numbers on the same questions. - report/model_compare.py: _difficulty_cells takes the context mean and the delta from _run_summary, so a bucket follows the overall summary's rule: with pairs, the pair means; context arm only, its context mean and no delta; both arms scored but no pair, no mean. A bucket with no context score still shows the baseline, as before. - The model-compare docs and the unreleased fragment name the difficulty rows; the goldens (the By Difficulty context cell of eight sections), the growth baseline of model_compare.py and its count in the records guard (one condition-key read fewer) are lowered. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
@exai-bot /rereview |
|
@antonwolfy I could not read that command: unknown command |
|
@exai-bot /review |
exai-bot
left a comment
There was a problem hiding this comment.
Note
AI-generated review of 347bb9c444f6, requested by @antonwolfy, using the default review skill. It can be wrong: verify a finding before acting on it. React with a thumbs up or down on a finding to tell the bot whether it helped.
Model us.anthropic.claude-sonnet-5-5, effort default, 11 of 80 turns, 0.8 of 40 minutes, 391k in / 4k out tokens.
Re-review of 347bb9c444f6: nothing new.
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 10 selected item(s). |
vchamarthi
left a comment
There was a problem hiding this comment.
Approving. The change does what the description says: the dashboard snapshot, each by_source row, the model-compare Overall Summary and the By Difficulty rows now average both arms over the questions scored in both, so the printed delta is the difference of the printed means.
Checked:
- The description's example (q1 80/60, q2 40/40, q3 100/-) through
_run_summaryand_difficulty_cells: 73.3 / 50.0 / +10.0 on main, 60.0 / 50.0 / +10.0 on this head. - The six new tests fail on main and pass on this head.
- A stored delta beside a missing score cannot reach
_run_summary:extract_scoressets the delta only when both scores are present, so pairing ondeltamatches the dashboard's pairing on scores. - One-arm runs, a group with both arms scored and no pair, and empty groups on both trees.
make ci-localon Linux at 347bb9c: 26 passed, 2 failed, 7 skipped.pip-auditfails on thefilelockadvisories, as on main.tests/test_process.py::test_sighup_kills_the_live_groups_tootimed out under the parallel run and passes alone 3/3 on this head and 3/3 on main, so it is a load-sensitive flake outside this diff.
Not looked at: the 3.11, 3.12 and 3.14 legs locally (the PR's CI ran them).
…tel#286) * fix(dashboard, report): average both arms over the complete pairs Settles the point the review of intel#250 left for a follow-up: the dashboard and the `report model-compare` run summary averaged each arm over its own scored questions, while `report generate`, `report eval` and the pipeline average both over the questions scored in both arms (paired_summary). On an eval file with q1 80/60, q2 40/40 and q3 100/- the dashboard and model-compare printed 73.3 / 50.0 beside a +10.0 delta; `report generate` prints 60.0 / 50.0 / +10.0. - dashboard/aggregator.py: the snapshot and each question-source group take both arm means and the delta from the pairs (_arm_means). The delta is the difference of the pair means, as in paired_summary, not the mean of the stored per-question delta. - report/model_compare.py: _run_summary averages with_avg and without_avg over the same pairs as delta_avg. - In both, a run that scored one arm only keeps that arm's mean (the dashboard's doc_score and the baseline-only ranking read it); with both arms scored but no pair, neither mean is given. - The stats module docstring, the unreleased intel#250 fragment, the quickstart and the model-compare docs say so; the model-compare goldens and the growth baseline of _load_snapshot are updated. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> * fix(report): model-compare difficulty rows average over the pairs The same point as the previous commit (the review of intel#250: an arm mean over its own scored questions beside a delta over the pairs), at its other site in `report model-compare`: each By Difficulty row averaged the context arm over every context-scored question in the bucket, while the delta beside it covers the pairs only. On a bucket with q1 80/60, q2 40/40 and q3 100/- the row printed 73.3 | +10.0; it now prints 60.0 | +10.0, the run summary's numbers on the same questions. - report/model_compare.py: _difficulty_cells takes the context mean and the delta from _run_summary, so a bucket follows the overall summary's rule: with pairs, the pair means; context arm only, its context mean and no delta; both arms scored but no pair, no mean. A bucket with no context score still shows the baseline, as before. - The model-compare docs and the unreleased fragment name the difficulty rows; the goldens (the By Difficulty context cell of eight sections), the growth baseline of model_compare.py and its count in the records guard (one condition-key read fewer) are lowered. Signed-off-by: Anton Volkov <antonwolfy@gmail.com> --------- Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Summary
Follow-up to the review of #250, where the dashboard and the
report model-comparerun summary were left averaging each arm over its own scored questions whilereport generate,report evaland the pipeline average both over the complete pairs (review). The author and the reviewers agreed in that thread to keep it for a follow-up, because it changes reported numbers. The model-compare By Difficulty table has the same bug: its context mean covers every context-scored question in a bucket, beside a delta over the pairs.On
main, an eval file with three questions, q1 80/60, q2 40/40 and q3 100/– (no baseline score), gives:report generateProductSnapshot, and eachby_sourcerow)report model-compareOverall Summaryreport model-compareBy Difficulty rowThe dashboard and model-compare context mean covers q3 and the baseline mean and the delta do not, so the delta printed beside them is not their difference. With this PR every row prints 60.0 for the context arm and +10.0 for the delta, and 50.0 where it has a baseline column.
Changes
dashboard/aggregator.py: the snapshot (avg_with_docs,avg_without_docs,avg_delta) and each_compute_by_sourcegroup take their numbers from one helper,_arm_means, which averages both arms over the questions scored in both. The delta is the difference of the pair means, aseval.stats.paired_summarycomputes it. It used to be the mean of the stored per-questiondelta; for a file the judge writes, the two agree.QuestionResult.deltastill carries the stored value.report/model_compare.py_run_summary:with_avgandwithout_avgare over the same rows asdelta_avg.n,n_paired,unpaired,improvedanddegradedare unchanged.report/model_compare.py_difficulty_cells: when a bucket has a context score, the row's context mean and delta come from_run_summaryon the bucket's questions, so a difficulty row follows the same rule as the Overall Summary. A bucket with no context score still shows its baseline mean, marked(baseline), as before.doc_score(and its status band) and model-compare's baseline-only ranking read it. When both arms are scored but never on the same question, neither mean is given (None, rendered—). In the difficulty table, a bucket the run scored in the context arm only keeps its context mean with—for the delta, and a bucket with both arms scored but no pair shows—for both.eval/stats.pymodule docstring: it no longer names the dashboard and model-compare as exceptions.changelog.d/20261026_130000_napetrov_report-eval-generate-agree.md): removed the one sentence saying the dashboard and the model-compare run summary "still average each arm over its own scored questions", which is no longer true.docs/quickstart.md(dashboard step) anddocs/benchmarking-and-comparison.md(model-compare metrics, now with the difficulty rows) say the means are over the questions scored in both arms.tests/fixtures/model_compare_sections.json: re-recorded (DOKIMI_UPDATE_GOLDEN=1). Only the Overall Summary context/baseline cells, the absolute-quality ranking cells and the By Difficulty context cells change. In every changed summary row the context mean minus the baseline mean now equals the printed delta, for example 68.8 / 56.2 / +17.1 → 70.0 / 52.9 / +17.1, and 45.0 / 45.8 / −10.0 → 45.0 / 55.0 / −10.0. In the By Difficulty table one cell changes, the first run'seasycontext mean, in eight sections: 63.3 → 65.0 (seven sections) and 68.8 → 70.0 (pinned_arm).scripts/growth_baseline.json:_load_snapshotdropped from complexity 13 to 10, so its entry is removed;report/model_compare.pydropped from 1209 to 1207 lines.tests/test_records_guard.py:report.model_compare's count of condition-key reads lowered from 17 to 16, since_difficulty_cellsreads one fewer.Risk
The numbers change for any run with a question scored in one arm only:
DASHBOARD.mdanddashboard.json), and with them the good/fair/poor status, which follows the context mean;report model-compare: the context and baseline columns of the Overall Summary and the absolute-quality ranking, and the context column of the By Difficulty table.Runs where every question is scored in both arms print the same numbers as before. A dashboard delta can also change where a stored
deltadisagrees with the two stored scores. A run, source group or difficulty bucket with both arms scored but no question scored in both now shows—where it showed means; on the dashboard such a run reads asno-data. No input that was accepted before is refused now.eval/judge.py(avg_with/avg_without/avg_delta) andcommands/baseline.py_compute_summarywere checked and need no change: every record they average has a delta, which the judge sets only when both arms were judged andbaseline.pyonly when both scores are present, so their means are already over the pairs.Testing
On a Linux review host, at
347bb9c(onmain0a63f4e):make ci-local: 27 passed, 1 failed, 7 skipped (skipped:external-catalog-smoke, which needs docker;test-latest,docs deployand the pip-auditreport, whose job conditions are false on a PR; cli-surface "Package the result";conda-forge-deps, since micromamba is not installed; semgrep secrets).10185 tests passed, 4 skipped; changed-line coverage 100% (22 lines).
The one failure is
pip-audit: it reports ten advisories againstfilelock3.32.7 in the CI lock file (fix versions 4.0.9 / 4.2.1). This PR does not touch dependencies or the lock file, somainfails the same way. It is left for a dependency update.main, the dashboard and model-compare print 73.3 / 50.0 / 10.0 againstreport generate's 60.0 / 50.0 / 10.0; at this head all three print 60.0 / 50.0 / 10.0. The By Difficulty row for the same questions reads| easy | 3 | 73.3 | +10.0 |onmainand| easy | 3 | 60.0 | +10.0 |here.test_dashboard.py::test_aggregator_averages_both_arms_over_the_complete_pairs: the snapshot is 60.0 / 50.0 / 10.0, equal topaired_summaryon the same records;test_dashboard.py::test_by_source_averages_over_the_pairs_and_a_group_without_one_has_no_means: the persona row is 60.0 / 50.0 / 10.0, and a chunk group with one context-only and one baseline-only question givesNonefor all three;test_dashboard.py::test_baseline_only_run_keeps_its_baseline_mean: a run with no context arm still scores 50.0 (fair);test_model_compare.py::test_run_summary_averages_both_arms_over_the_pairs: the Overall Summary row reads4 | 2 (2 unscored) | 60.0 | 50.0 | **+10.0** | 1 | 0;test_model_compare.py::test_run_summary_gives_no_means_for_two_arms_without_a_pair: that row reads—for both means.test_model_compare.py::test_difficulty_row_averages_the_context_arm_over_the_pairs: two pairs (80/60, 40/40) and one context-only question give| easy | 3 | 60.0 | +10.0 |;test_model_compare.py::test_difficulty_row_without_a_pair_shows_no_context_mean: ahardbucket with one context-only and one baseline-only question reads| hard | 2 | — | — |, and amediumbucket scored in the context arm only keeps| medium | 1 | 70.0 | — |.main's code, the six tests of the new behaviour fail. The two difficulty tests also fail on the first commit of this PR, before the difficulty fix. The baseline-only test passes onmain: it guards behaviour that should not change.Nonebranch dropped (by-source); the single-arm fallback dropped (baseline-only); the delta sign flipped (snapshot);Nonebranch dropped (the no-pair test); the single-arm fallback dropped (4 baseline-only goldens intest_model_compare_sections.py);Nonebranch of_run_summarydropped (the no-pair difficulty test,hardshows 80.0); its single-arm fallback dropped (the no-pair difficulty test,mediumshows—).ruff checkon the changed files andmypyon the whole package are clean.test/novelty-all-vs-all-pins,ci/filelock-advisories) onmain: 10189 tests passed, 4 skipped;mypy,ruff checkand the growth check pass.Revert
A plain revert restores the per-arm means. Nothing persisted changes: the dashboard and model-compare outputs are regenerated from the eval files.
Checklist
pytestpassesruff,mypy, and schema checks passAGENTS.md.github/.licenserc.yaml)