Repository navigation
fix(vision-suite): render the observed range, not mean ±(max−min)/2 - #196
Conversation
summarize_reps.py printed float cells as `mean ±(max-min)/2`. A ± interval is symmetric about what precedes it, so that describes [min, max] only when the mean is the midrange. At n=2 the mean always IS the midrange, which is why this read correctly for a whole campaign before it bit. At n=5 it does not. The qwen3.8 name_bbox arm rendered `0.742 ±0.103` — an interval of [0.639, 0.846] that excludes the observed minimum of 0.631 and claims headroom above a maximum of 0.838 that no run reached. The excluded value is the one the campaign's own argument quotes: "think-on's lowest value across five runs is 0.631". The table and the prose disagreed, and the prose was right. Scene IoU had the same defect, excluding a run that scored 1.000. Float cells now print `mean [lo–hi]`, mirroring the int branch and doing what the module docstring already promised: "the range is what a reader can actually reason about". The spread report is regenerated too. It ranked every metric in one list, so four finetext counts moving by 2 crowded out an IoU moving by 0.207 — and a reader who wanted the IoU spread then reached for it by hand, which is how the halved 0.103 reached the campaign record's prose as if it were the spread. Ratios and counts are now listed separately, each ranked in its own units. The campaign record is re-rendered from the same seven score files: no measurement changed. Two prose corrections follow from it. The noise-floor figure was stated at half its true width (0.103 for 0.207). And the name_bbox argument claimed "the ~0.17 gap exceeds think-on's own 0.103 spread", which is false against the real 0.207 — the conclusion stands, because it rests on non-overlap (every think-on run above every think-off run), which is the stronger test and needs no comparison to a spread at all. That clause is replaced rather than repaired. Tests pin the property that matters: the rendered interval must contain every observation, over four value sets including both published ones. Mutation- tested — restoring ±spread/2, halving the returned spread, and collapsing the two unit groups each turn them red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewing as consolidator. This is a good catch on your own work and the diagnosis of why it hid is better than the fix itself.
That is the sharpest line in the PR. A defect that is provably absent at n=2 and appears at n≥3 is exactly the kind that survives a review pass: the reviewer checks it against the data in front of them, and the data in front of them cannot exhibit it. Worth carrying beyond this file — the repo has more than one convention that has only ever been exercised at n=1 or n=2. The self-inconsistency is the part I would want future readers to notice: the table asserted an interval that excluded 0.631 while the prose two paragraphs down quoted "think-on's lowest value across five runs is 0.631". A document arguing against its own table is a strong signal, and it went unnoticed because nobody re-derives a table they just generated. The ranking bug is the more interesting half
Ranking incommensurable units in one list is a defect that produces plausible output — a spread report that looks complete and silently omits the metric the reader came for. And the consequence you trace is the one that matters: a reader wanting the IoU spread reached for it by hand and picked up the halved Separating ratios from counts, each ranked in its own units, is right. One note for my sideADR 0029 requires MLX/CUDA think-on results to report spread rather than point values, and names No objection. Fixes a real defect, explains the mechanism that concealed it, and pins it with a test. |
Found by the label audit on #195. My bug, from #183 the same night.
The defect
summarize_reps.pyrendered float cells asmean ±(max−min)/2. A±interval is symmetric about what precedes it, so that describes[min, max]only when the mean is the midrange. At n=2 the mean always is — which is why this read correctly for a whole campaign before it bit.At n=5 it does not:
name_bboxthink-on0.742 ±0.103scene IoUthink-on0.987 ±0.011The excluded 0.631 is the value the campaign's own argument quotes — "think-on's lowest value across five runs is 0.631". The table and the prose disagreed, and the prose was right.
Float cells now print
mean [lo–hi], mirroring the int branch and doing what the docstring already promised: "the range is what a reader can actually reason about".The spread report, which is how it spread
cell()returns the true spread, but the report ranked every metric in one list — so four finetext counts moving by2crowded out an IoU moving by0.207, and the ratios never printed at all:A reader who wanted the IoU spread reached for it by hand — and picked up the halved
0.103from the±cell. That is how a wrong number entered the record's prose. Ratios and counts are now ranked separately, in their own units.The campaign record
Re-rendered from the same seven score files — no measurement changed — and every cell in the doc now matches a fresh render character for character. Two prose corrections follow:
name_bbox" → 0.207.name_bboxargument was false. It read "the arms never overlap, and the ~0.17 gap exceeds think-on's own 0.103 spread". Against the real 0.207 spread, the 0.171 gap in means does not exceed it. The clause is replaced, not repaired — the conclusion stands on non-overlap (every think-on run scored above every think-off run), which is the stronger test and needs no comparison to a spread. A wide spread cannot explain away a gap that no pair of runs crosses.That cell has now been called real, retracted as noise, confirmed, and has its supporting statistic corrected. The conclusion has not moved since the n=5 repeat; what moved is the quality of the argument for it.
Verification
Tests pin the property that actually matters — the rendered interval must contain every observation — over four value sets including both published ones. Mutation-tested:
±spread/2They live in
test_summarizers.py, which #195 wired into CI, so this class of defect is now checked on every vision-suite change rather than found by audit.