evalaudit 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. evalaudit-0.4.0/CHANGELOG.md +637 -0
  2. {evalaudit-0.3.0 → evalaudit-0.4.0}/PKG-INFO +38 -23
  3. {evalaudit-0.3.0 → evalaudit-0.4.0}/README.md +37 -22
  4. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/_types.py +269 -146
  5. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/agreement.py +6 -1
  6. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/audit.py +292 -39
  7. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/judge.py +46 -7
  8. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/pairwise.py +2 -0
  9. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/power.py +3 -3
  10. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/scores.py +4 -2
  11. {evalaudit-0.3.0 → evalaudit-0.4.0}/pyproject.toml +1 -1
  12. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_agreement.py +219 -22
  13. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_audit.py +917 -23
  14. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_compare.py +2 -2
  15. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_judge.py +211 -18
  16. evalaudit-0.4.0/tests/test_not_shown.py +367 -0
  17. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_pairwise.py +36 -0
  18. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_power.py +39 -1
  19. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_scores.py +20 -3
  20. evalaudit-0.3.0/CHANGELOG.md +0 -255
  21. {evalaudit-0.3.0 → evalaudit-0.4.0}/.github/workflows/tests.yml +0 -0
  22. {evalaudit-0.3.0 → evalaudit-0.4.0}/.gitignore +0 -0
  23. {evalaudit-0.3.0 → evalaudit-0.4.0}/LICENSE +0 -0
  24. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/__init__.py +0 -0
  25. {evalaudit-0.3.0 → evalaudit-0.4.0}/evalaudit/compare.py +0 -0
  26. {evalaudit-0.3.0 → evalaudit-0.4.0}/examples/.gitkeep +0 -0
  27. {evalaudit-0.3.0 → evalaudit-0.4.0}/examples/interval_method_study.py +0 -0
  28. {evalaudit-0.3.0 → evalaudit-0.4.0}/examples/pairwise_cluster_study.py +0 -0
  29. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/__init__.py +0 -0
  30. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_confidence.py +0 -0
  31. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_readme.py +0 -0
  32. {evalaudit-0.3.0 → evalaudit-0.4.0}/tests/test_version.py +0 -0
@@ -0,0 +1,637 @@
1
+ # Changelog
2
+
3
+ Every change to evalaudit that a caller can see is recorded here. The format
4
+ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
+ version numbers follow [semantic versioning](https://semver.org/). Before 1.0
6
+ a minor version can break things, and 0.3.0 does.
7
+
8
+ An entry marked **Breaking** changes what a call that worked on the previous
9
+ release returns, raises or prints. The mark goes on the entry wherever it
10
+ sits, under Changed, Fixed or Removed.
11
+
12
+ 0.3.0 is the first release with a tag. The entries below it were
13
+ reconstructed after the fact. Their dates are PyPI's upload dates, and their
14
+ contents were checked against the published wheels and the commit each wheel
15
+ matches.
16
+
17
+ ## [Unreleased]
18
+
19
+ Everything here is headed for 0.4.0, and nearly all of it changes what a
20
+ summary or a report prints. One rule is behind it. A result that does not
21
+ clear its threshold has failed to show something, and it has not shown the
22
+ opposite. Summaries that described such a result as if the opposite were
23
+ shown, and verdicts decided on a point estimate where an interval existed,
24
+ now say what the data did and did not show. Several `audit` findings change
25
+ severity on data you have already run. Each breaking entry says what it
26
+ was, what it is now and what to do about it. Every entry was checked by
27
+ running the same calls against 0.3.0 from PyPI and against this tree.
28
+
29
+ ### Changed
30
+
31
+ - **Breaking.** `audit` reads rater agreement and judge agreement off the
32
+ interval on alpha. An interval wholly above the threshold keeps "Rater
33
+ agreement holds up" or "The judge tracks the humans", and one wholly below
34
+ it keeps the "below the working threshold" title. An interval running both
35
+ sides of the threshold, or no interval at all, is now a `warning` titled
36
+ "The data cannot show that rater agreement clears the working threshold"
37
+ or "The data cannot show that the judge clears the working threshold".
38
+ 0.3.0 decided on the point estimate. Ratings with alpha 0.730 and an
39
+ interval of 0.466 to 0.933 came back `info` as "Rater agreement holds up",
40
+ and so did any ratings audited with `n_boot=0`. Judge labels with alpha
41
+ 0.581 and an interval of 0.467 to 0.691 came back "Judge-human agreement
42
+ is below the working threshold". Rerun any audit you have sent. A finding
43
+ that moved from `info` to `warning` also changes the report's verdict.
44
+
45
+ - **Breaking.** Without an `effect_of_interest`, `audit` no longer sizes
46
+ power on the margin it measured. 0.3.0 made the observed margin the effect
47
+ at issue, as in "The effect at issue is 5.0 points, the margin this eval
48
+ reports." Power computed on an observed effect is a monotone function of
49
+ the p-value, so on a margin whose interval includes zero the finding came
50
+ out `critical` almost every time and repeated the comparison. The finding
51
+ now reports the design's reach, the smallest difference the eval could
52
+ find at the configured power, and makes no claim about the margin. It is
53
+ a `warning` titled "No effect of interest was stated, so this reports the
54
+ eval's reach", because without a stated effect the audit does not know
55
+ what size of difference matters. Its lead says "This reports what the
56
+ design could find and makes no claim about the margin it measured." On 400
57
+ paired items with a 5.0 point margin and an interval of 1.1% to 9.0%,
58
+ 0.3.0 opened the report with a `critical` power finding and the verdict
59
+ said the conclusion did not hold. Two systems with a margin of exactly
60
+ zero, which 0.3.0 skipped, get the same warning.
61
+
62
+ With one system and no stated effect the check is still skipped, and its
63
+ reason no longer offers the observed margin as a stand-in. It read
64
+ "Nothing to size the eval against. Pass effect_of_interest in the config,
65
+ or scores for two systems so the observed margin can stand in for it."
66
+ and now reads "No effect of interest was stated and there is no comparison
67
+ whose reach could be reported. Pass effect_of_interest in the config, or
68
+ scores for two systems."
69
+
70
+ An audit of two pass-rate systems with no stated effect now always
71
+ carries this warning, so its report never comes back with no warnings.
72
+ Set `effect_of_interest` to the difference the decision turns on.
73
+
74
+ - **Breaking.** With an `effect_of_interest` stated, `audit` puts a power
75
+ finding at `info` in two cases where 0.3.0 made it `critical`, because the
76
+ margin's interval already answers the question power asks.
77
+
78
+ - The margin's interval clears zero. The finding is titled "The margin is
79
+ established, and a tighter estimate would take more items". 0.3.0
80
+ titled it "This eval could not have detected the effect at issue".
81
+ - The margin's interval includes zero and stops short of the stated
82
+ effect in both directions. The finding is titled "The interval on the
83
+ margin rules out the effect at issue".
84
+
85
+ Rerun any audit whose report carried a power finding.
86
+
87
+ - **Breaking.** With an `effect_of_interest` stated, `audit` rewords the
88
+ power finding on a margin whose interval includes zero. Its title is now
89
+ "This eval had under 80% power for the effect at issue", with the
90
+ percentage taken from `power`, because a design
91
+ short of that power can still find the effect, only less often. Its action
92
+ no longer says "No conclusion about an effect this size can be drawn from
93
+ this data, in either direction. A null result here says the eval was too
94
+ small and says nothing about the systems." It reads the margin's interval
95
+ one side at a time and says one of three things. Either "The interval on
96
+ the margin runs past an effect this size in both directions, so the data
97
+ cannot show an effect this size and cannot rule one out.", or "The
98
+ interval on the margin rules out old ahead by this much, and it cannot
99
+ rule out new ahead by this much." with your system names, or for a single
100
+ system "No margin was measured here, so there is no interval to read
101
+ against an effect this size." Anything matching on the old title or action
102
+ will stop matching.
103
+
104
+ - **Breaking.** `AuditReport.summary()` rewords its verdict, which is the
105
+ first line of every report.
106
+
107
+ - With critical findings, "the conclusion this data is being asked to
108
+ support does not hold as stated." is now "this data cannot support the
109
+ conclusion as stated. That does not mean the conclusion is wrong."
110
+ - With warnings and no criticals, "The result stands and the design
111
+ weakens it, so report it with what the warning say attached." is now
112
+ "The checks that ran did not find a problem that leaves the conclusion
113
+ unsupported. That does not mean the eval is sound. Report the result
114
+ with what the warning says attached."
115
+ - With neither, "All 4 checks that ran came back clean, so the eval holds
116
+ up on everything this report could test." is now "The 3 checks that ran
117
+ did not find a problem in what they could test. That does not mean the
118
+ eval is sound." The number changed too. 0.3.0 counted findings, and the
119
+ score check gives one finding per system, so two systems made three
120
+ checks read as four. A single check reads "The one check that ran", and
121
+ a report where nothing could run reads "No check could run, so this
122
+ report has tested nothing."
123
+
124
+ Anything matching on the old verdicts will stop matching. The severity
125
+ counts are unchanged by this entry.
126
+
127
+ - **Breaking.** `audit` rewords several titles and actions.
128
+
129
+ - "No position effect in the pairwise judgements" is now "The data cannot
130
+ show a position effect in the pairwise judgements". Its action opens
131
+ "The data has not cleared the judge of position bias." where it opened
132
+ "Nothing to do here."
133
+ - "No length effect in the judge's choices" is now "The data cannot show
134
+ a length effect in the judge's choices". Its action "Nothing to do
135
+ here." is now "The data has not cleared the judge of rewarding length."
136
+ - An alpha that does not exist is titled "Rater agreement is undefined on
137
+ this data" or "Judge-human agreement is undefined on this data", and the
138
+ lead ends "and this data gives no alpha to hold against it." 0.3.0
139
+ titled it "Rater agreement is below the working threshold" or
140
+ "Judge-human agreement is below the working threshold", and said the
141
+ grades "reproduce less well than the 0.667".
142
+ - The action on a margin that clears zero said "The lower bound is the
143
+ size of the win the data actually supports." When the second system
144
+ leads, the lower bound is the far end of the interval. It now says "The
145
+ end of the interval nearer zero is the smallest margin this data
146
+ supports."
147
+ - The length lead "Two models run here and they point opposite ways." now
148
+ also needs the preference fit's interval to clear zero. 0.3.0 printed it
149
+ off the sign of a preference coefficient whose interval ran over zero,
150
+ and said the judge "picks the longer answer more often" beside an odds
151
+ ratio of 0.94 to 1.40. Such data now gets the plain lead for the
152
+ direction the finding reports.
153
+
154
+ - **Breaking.** Module summaries put every result that falls short of its
155
+ threshold in one form, "so the data cannot show that" followed by "That
156
+ does not mean".
157
+
158
+ - `ComparisonResult.summary()`. "The interval crosses zero, so the data
159
+ cannot confirm that either system is better." is now "The interval
160
+ includes zero, so the data cannot show that either system is better.
161
+ That does not mean the two are level."
162
+ - `PositionBias.summary()`, randomised design. "The interval covers 50%,
163
+ so the data cannot show that position moved the judge." is now "The
164
+ interval includes 50%, so the data cannot show that position moved the
165
+ judge. That does not mean the judge ignores position."
166
+ - `PositionBias.summary()`, both orders. "The flips split evenly across
167
+ the two positions, so this is an unsteady judge rather than a
168
+ position-biased one." is now "That share is not clear of 50% at this
169
+ many flips, so the data cannot show that the flips have a direction.
170
+ That does not mean the judge is free of position bias." 0.3.0 printed
171
+ the old sentence at 7 flips of 9 toward the answer shown first.
172
+ - `LengthBias.summary()`. "The interval covers no effect, so the data
173
+ cannot show that length moved the judge." is now "The interval includes
174
+ an odds ratio of 1, so the data cannot show that length moved the
175
+ judge. That does not mean length plays no part in its choices." For the
176
+ disagreement fit, "That interval covers no effect, so the judge does
177
+ not depart from the humans in the direction of length." is now "That
178
+ interval includes an odds ratio of 1, so the data cannot show that the
179
+ judge breaks with the humans toward longer or shorter answers. That does
180
+ not mean it follows them on length."
181
+ - `AgreementResult.summary()`. "so the data cannot distinguish the raters"
182
+ is now "so the data cannot show that any one rater is pulling alpha
183
+ down" where every leave-one-out alpha is undefined. Where there are
184
+ leave-one-out figures the summary no longer judges the raters at all,
185
+ as the entry on rater dropout below says.
186
+ - `JudgeValidation.summary()`. "the data cannot single out a slice" is now
187
+ "the data cannot show that the judge does worse on any one slice".
188
+ Where the intervals overlap it adds "That does not mean it does equally
189
+ well on all of them."
190
+
191
+ Anything matching on the old sentences will stop matching.
192
+
193
+ - **Breaking.** `AgreementResult.summary()` and `JudgeValidation.summary()`
194
+ name one of Krippendorff's bands only when the whole interval sits in it.
195
+ 0.3.0 named the band from the point estimate, with "That is at or above
196
+ 0.800, the conventional bar for treating coded data as reliable.", "That
197
+ sits between 0.667 and 0.800, which supports tentative conclusions and no
198
+ firm ones." and "That is below 0.667, the conventional floor for drawing
199
+ any conclusion from coded data." These now read "The whole interval sits
200
+ above 0.800", "The whole interval sits between 0.667 and 0.800" and "The
201
+ whole interval sits below 0.667". An interval running over 0.667 reads
202
+ "The interval runs both sides of 0.667, the conventional floor for drawing
203
+ any conclusion from coded data, so the data cannot show that rater
204
+ agreement clears it. That does not mean it falls short of it." In
205
+ `JudgeValidation` the subject is "the judge's agreement with the humans".
206
+ An interval that clears 0.667 and runs over 0.800 gets the same form at
207
+ 0.800, and a summary with no interval names no band. Judge labels with
208
+ alpha 0.801 and an interval of 0.601 to 0.951 printed "That is at or
209
+ above 0.800" in 0.3.0. Anything matching on the old sentences will stop
210
+ matching.
211
+
212
+ - **Breaking.** `AgreementResult.summary()` no longer names a rater. 0.3.0
213
+ named one when dropping them raised alpha by more than half the width of
214
+ the interval on alpha. That compares the shift with the sampling error on
215
+ alpha, and the shift has a sampling error of its own that can be much
216
+ larger. Across simulated evals with three raters who differed only by
217
+ chance, the rule named a rater in 4.2% of 500 when everyone graded every
218
+ item. When the third rater graded a fifth of the items it named one in
219
+ 32.0% of 500, always one of the two who graded everything. In one of those
220
+ evals 0.3.0 printed "Dropping r2 raises alpha to 0.878, a shift of 0.326
221
+ against a sampling error of 0.200." about a rater who graded all 60 items.
222
+ The same rule caught a rater wrong three times as often as the others in
223
+ 72.4% of 500.
224
+
225
+ The summary now lists alpha with each rater left out, as in "Leaving one
226
+ rater out at a time gives alpha of 0.878 without r2 (+0.326), 0.524
227
+ without r3 (-0.028), and 0.483 without r1 (-0.069).", and says "The
228
+ package does not judge whether any of these shifts is larger than noise,
229
+ so it names no rater. A rater near the top of this list is a lead to check
230
+ rather than a finding." It lists five raters at most and points to
231
+ `rater_dropout` for the rest. `top_dropout_rater` always returns None.
232
+ `dropout_is_distinguishable` still computes the old rule, its docstring
233
+ says why the rule was withdrawn, and nothing in the package reads it.
234
+
235
+ The `audit` agreement finding carries the new sentence. Its severity never
236
+ came from this rule, so no finding changes severity. If you acted on a
237
+ rater a summary named, check that rater by hand.
238
+
239
+ - **Breaking.** `PositionBias` puts the Clopper-Pearson interval, the exact
240
+ binomial interval, on the rate it tests against a half, in both designs,
241
+ and decides on it. The exact binomial p-value printed beside it comes from
242
+ the same test, so the two cannot disagree about 50%. 0.3.0 used a Wilson
243
+ interval in the randomised design. Wilson and the exact test disagree at
244
+ 50% in 188 of 20,300 (n, k) cells up to 200 judgements, and in every one
245
+ the Wilson interval clears 50% while the p-value does not. At 11 of 14 in
246
+ the randomised design 0.3.0 printed "(95% CI: 52.4% to 92.4%, exact
247
+ binomial p=0.0574)" and then "The interval clears 50%, so the judge
248
+ favours whichever output it sees first." It now prints "(95% CI: 49.2% to
249
+ 95.3%, exact binomial p=0.0574)" and "The interval includes 50%, so the
250
+ data cannot show that position moved the judge." Every randomised interval
251
+ moves, and a randomised verdict flips wherever the two used to disagree.
252
+
253
+ In the both-orders design 0.3.0 printed the share of flips with a bare
254
+ p-value, "(77.8%, exact binomial p=0.1797).", and decided on that p-value.
255
+ It now prints "(77.8%, 95% CI: 40.0% to 97.2%, exact binomial p=0.1797)."
256
+ and decides on the interval, which gives the same answer as the p-value in
257
+ every case. The consistency rate has no test beside it and keeps its
258
+ Wilson interval. Rerun any position check near the line.
259
+
260
+ ### Added
261
+
262
+ - `BTResult.n_missing_item_ids`, the number of comparisons with no item id.
263
+ It is the last field of the dataclass and defaults to 0, so building a
264
+ `BTResult` by position with 0.3.0's fields still works. `to_elo` carries
265
+ it across.
266
+ - `PositionBias.position_a_ci_low` and `PositionBias.position_a_ci_high`,
267
+ the Clopper-Pearson interval on `position_a_rate`. In the randomised
268
+ design they
269
+ equal `ci_low` and `ci_high`. They are the last two fields and default to
270
+ NaN, so building a `PositionBias` by position with 0.3.0's fields still
271
+ works. A both-orders result built that way has no interval, so its
272
+ `has_position_effect` is False.
273
+
274
+ ### Fixed
275
+
276
+ - **Breaking.** `KappaResult.summary()` said "Krippendorff's alpha does not
277
+ have that problem and handles missing data, so prefer rater_agreement for
278
+ anything you report." Alpha has the problem too. Two raters who agree on
279
+ 90 of 100 items score kappa 0.444 and alpha 0.447 when one category takes
280
+ 90% of the ratings, and 0.800 and 0.801 when both are used equally often.
281
+ It now says "Krippendorff's alpha moves the same way, because it also
282
+ corrects for chance using how often each category is used." and gives
283
+ missing ratings, more than two raters and ordered scales as the reasons to
284
+ prefer `rater_agreement`. The `cohens_kappa` docstring made the same claim
285
+ and now quotes the four figures.
286
+
287
+ - **Breaking.** `ScoreCI.summary()` told the reader to "treat differences
288
+ smaller than that as unresolved." A paired comparison resolves
289
+ differences far smaller than either score's own interval. The sentence
290
+ now reads "To compare this score with another, read the interval on the
291
+ difference, which compare_paired and compare_independent report. Two
292
+ score intervals that overlap do not show the systems are level." The
293
+ `scores` module docstring made the same claim and no longer does.
294
+
295
+ - **Breaking.** `PowerResult.summary()` from `detectable_effect` said
296
+ "anything smaller was out of reach before the first item was graded". A
297
+ difference below the reach can still reach significance, only less often.
298
+ It now says "A smaller difference could still reach significance here,
299
+ with a chance below 80%." Where no difference reaches the requested power,
300
+ "This eval could not have found anything, whatever the two systems really
301
+ do." and, for independent samples, "and this eval could not have found
302
+ anything." are now "A null result from this eval says little about the
303
+ systems." The `detectable_effect` docstring said "An eval could not have
304
+ found anything smaller" and now says "No smaller difference reaches the
305
+ requested power".
306
+
307
+ - **Breaking.** `JudgeValidation.summary()` on a single item said "Every
308
+ label that could be compared was identical" whatever the labels were,
309
+ beside a plain accuracy of 0.0% when they differed, and counted "1 items".
310
+ It now reads "(nominal, 1 item). One item cannot carry a reliability
311
+ estimate, so there is no number to report and no interval around it." The
312
+ count of items set aside reads "1 item was set aside" where it read "1
313
+ items were set aside".
314
+
315
+ - **Breaking.** `BTResult.summary()` under `resample="comparisons"` said
316
+ "these 240 comparisons share 239 items" when one comparison had no item
317
+ id and no two shared anything. Sharing is now counted among the
318
+ comparisons that carry an id. When none share, the sentence is left out.
319
+ When some share and ids are missing, it reads "the 239 comparisons that
320
+ carry an item id share 26 items".
321
+
322
+ - The README example and the prose under it show the new wording.
323
+
324
+ ### Removed
325
+
326
+ - Nothing.
327
+
328
+ ### Known limitations
329
+
330
+ This rule is unchanged since 0.3.0 and is not fixed in this release. The
331
+ figures come from simulations run against 0.3.0 and against this tree, which
332
+ give the same results.
333
+
334
+ - `JudgeValidation` names a slice, and `audit` raises a critical finding on
335
+ it, only when the slice's own interval sits wholly below the interval on
336
+ the overall figure. That reads a difference off two separate intervals
337
+ instead of an interval on the difference, and it is conservative. With
338
+ five slices of 60 items that differ only by chance, it named a slice in
339
+ 0.5% of 200 simulated evals. It misses real differences. A slice where the
340
+ judge flipped 25% of the labels, against 10% in the other four, was named
341
+ in 32.5% of 200. When the summary says the data cannot show that the
342
+ judge does worse on any one slice, this rule is what did not show it.
343
+
344
+ ## [0.3.0] - 2026-09-10
345
+
346
+ 0.3.0 changes what `bradley_terry` reports on data you have already run, and
347
+ it turns several calls that used to return numbers into errors. Each
348
+ breaking entry says what it was, what it is now and what to do about it.
349
+
350
+ ### Changed
351
+
352
+ - **Breaking.** `bradley_terry` reads separability off the interval on the
353
+ gap between two ratings. Before, a pair was separable when its two rating
354
+ intervals did not overlap. Now a pair is separable when the percentile
355
+ interval on the difference between the two ratings excludes zero. Two
356
+ rating intervals can overlap while the gap between them is well measured,
357
+ because both ratings carry the error of the field average they are
358
+ measured against and that error cancels in the gap. So the old test
359
+ undercounted the pairs the data orders, and the pairs it left out were
360
+ never shown to be unordered. There is no option to bring the old rule
361
+ back. `n_separable` and `separable_pairs` can change on data you have
362
+ already run. On the README example they go from 5 of 15 pairs to 7. On
363
+ twelve simulated leaderboards where every item carried one comparison,
364
+ the count went up on five and stayed the same on seven. Nothing changes
365
+ in your code. Rerun, and replace any separability count you have
366
+ published.
367
+
368
+ - **Breaking.** `separable_pairs` has two new columns, `ci_low` and
369
+ `ci_high`, the interval on `difference`. They sit between `difference` and
370
+ `p_a_beats_b`, so code that reads columns by position will read the wrong
371
+ ones. Code that selects columns by name is unaffected.
372
+
373
+ - **Breaking.** `bradley_terry` resamples items by default. Before, the
374
+ bootstrap drew single comparisons, which treats judgements of the same
375
+ prompt as independent. Now it draws item ids with replacement and takes
376
+ every comparison made on each drawn item.
377
+
378
+ Where every item carries one comparison the default draws the same
379
+ resamples from the same seed as 0.2.1, and the ratings, their intervals
380
+ and the win matrix come out identical. Separability can still change,
381
+ for the reason in the entry above.
382
+
383
+ Where items carry several comparisons the intervals widen and fewer pairs
384
+ may separate. On a simulated board of 60 items with 20 comparisons each,
385
+ the rating intervals came out 1.1 to 1.5 times as wide as 0.2.1's. On
386
+ simulated data shaped like MT-Bench's human judgements, the rating
387
+ intervals 0.2.1 reported covered 88% of the time at a nominal 95% once
388
+ prompts shifted model strength by a spread of 0.5 on the log-odds scale,
389
+ and 79% at a spread of 1.0. `examples/pairwise_cluster_study.py` has the
390
+ table for 0.3.0.
391
+
392
+ To get the ratings and rating intervals an earlier version gave on the
393
+ same data and seed, pass `resample="comparisons"`. They come out
394
+ identical to 0.2.1's. `separable_pairs`, `n_separable` and the summary
395
+ follow the 0.3.0 rules, so those can differ.
396
+
397
+ - **Breaking.** `bradley_terry` raises `ValueError` in two cases that 0.2.1
398
+ accepted. Both apply only when `n_boot` is above zero and `resample` is
399
+ left at its default of `"items"`.
400
+
401
+ - Some row has no `item_id`. The item bootstrap has to know which item
402
+ each comparison belongs to.
403
+ - Every row has the same `item_id`, an empty string included. With one
404
+ item, every resample is the full data and every interval would have
405
+ width zero.
406
+
407
+ This is the change most likely to break a working script, so here is what
408
+ to do. Earlier versions required `item_id` and read it for nothing but a
409
+ count, so a constant, a blank or the row number got past the check and
410
+ changed nothing. Look at what yours holds.
411
+
412
+ - It names the prompt each comparison was made on. Fill in any gaps and
413
+ change nothing else. Your intervals now account for comparisons sharing
414
+ a prompt, which is the reason for the change.
415
+ - It is the row number, or anything else unique per row. Nothing raises,
416
+ and the ratings and their intervals come out as they did in 0.2.1.
417
+ - It is a constant or a blank. Record the prompt if you can. If you
418
+ cannot, pass `resample="comparisons"` to keep the intervals you had.
419
+ They come out identical to 0.2.1's on the same seed. They treat every
420
+ judgement as independent and run narrow wherever prompts carry several.
421
+ The summary says so when the column holds a constant or an empty
422
+ string. It says nothing when every `item_id` is missing, since then
423
+ nothing records which comparisons share a prompt.
424
+
425
+ With `n_boot=0` no resampling happens and any `item_id` is accepted.
426
+
427
+ - **Breaking.** `score_ci(method="t")` raises `ValueError` on three kinds of
428
+ input it used to return numbers for. What to do depends on the input.
429
+
430
+ - 0/1 data, including all passes, all fails and a single pass or fail.
431
+ The interval could run past 100%, as 49 passes in 50 did at 94.0% to
432
+ 102.0%, come back with width zero on all passes or all fails, or come
433
+ back as NaN on a single pass. Use `method="wilson"`, which
434
+ `method="auto"` already picks for 0/1 data and which works on a single
435
+ item.
436
+ - A single score that is not 0 or 1. It came back as NaN bounds behind two
437
+ RuntimeWarnings. One score has no spread, and the bootstrap returns
438
+ width zero on it too. Score more items.
439
+ - Continuous scores that are all the same. The interval came back with
440
+ width zero, which claims the observations fixed the mean exactly. Check
441
+ that the scorer is not returning a constant.
442
+
443
+ - **Breaking.** `score_ci(method="wilson")` raises `ValueError` on scores
444
+ that are not 0 or 1. Wilson counts passes, and on other scores it took the
445
+ count by truncating the sum of the scores. Ten scores of 0.99 printed a
446
+ mean of 0.990 beside an interval of 0.596 to 0.982. For scores that vary,
447
+ use `method="bootstrap"`, the interval on a mean, which `method="auto"`
448
+ already picks. Scores that are all the same, like that example, get width
449
+ zero from the bootstrap too, and 0.3.0 does not refuse that.
450
+
451
+ - **Breaking.** `score_ci`, `compare_paired` and `compare_independent`
452
+ raise `ValueError` for a confidence level that is not strictly between 0
453
+ and 1, with the message `confidence must be in (0, 1), got ...` that
454
+ `bradley_terry`, `rater_agreement`, `judge_validation` and `audit` already
455
+ used. Before, what came back at `confidence=1.5` depended on the method.
456
+ The bootstrap in all three raised from inside numpy. Wilson and both t
457
+ intervals returned NaN bounds. The two score intervals returned bounds
458
+ clipped to -100% and 100%. The signed-rank interval in `compare_paired`
459
+ returned an ordinary-looking interval under a "150% CI" label, which is
460
+ the case nothing would have flagged. Pass a level strictly between 0 and
461
+ 1.
462
+
463
+ - **Breaking.** Several summary strings are reworded, so any test or
464
+ pipeline that matches on them will fail.
465
+
466
+ - `BTResult.summary()` defines separable as "the interval on the gap
467
+ between the two ratings excludes zero". For pairs that do not separate
468
+ it says the data does not establish an order for them, that this does
469
+ not mean the models are level, and that more comparisons could separate
470
+ them. The phrases "meaning their intervals do not overlap", "cannot be
471
+ ordered", "an order the data does not carry" and "a ranking of noise"
472
+ are gone.
473
+ - `BTResult.summary()` adds a sentence when `resample="comparisons"` was
474
+ used on data where items carry several comparisons.
475
+ - `AgreementResult.summary()` for a scale nobody varied now reads "This
476
+ comes from a scale nobody varied. It is not perfect agreement." where it
477
+ read "That is not perfect agreement, it is a scale nobody varied."
478
+ - `JudgeValidation.summary()` for a rubric with one label now reads "This
479
+ comes from a rubric with one label in it. It is not perfect agreement."
480
+ where it read "That is not perfect agreement, it is a rubric with one
481
+ label in it."
482
+
483
+ - The Development Status classifier moved from Alpha to Beta.
484
+
485
+ ### Added
486
+
487
+ - `bradley_terry(..., resample="items")`, with `"comparisons"` as the other
488
+ choice.
489
+ - `BTResult.pairs`, one row per pair of models with the gap, the interval on
490
+ the gap and a `separable` column. `separable_pairs` is its separable rows.
491
+ - `BTResult.resample`, recording which unit the bootstrap drew. It and
492
+ `pairs` are the last two fields of the dataclass, so building a
493
+ `BTResult` by position with 0.2.1's fields still works.
494
+ - `to_elo` stretches the gap intervals with the gaps and carries `pairs` and
495
+ `resample` across.
496
+ - `examples/pairwise_cluster_study.py`, in the source distribution, which
497
+ measures how narrow the comparison bootstrap runs on MT-Bench shaped data.
498
+
499
+ ### Fixed
500
+
501
+ - The `scores` module docstring said the interval at n=50 is roughly plus or
502
+ minus 10 points. It is about 10 at an 85% pass rate and about 13 at 50%.
503
+ - The README said every interval in the package is tested by simulation.
504
+ Several were not. It now says which intervals are tested by simulation,
505
+ which are checked against a reference implementation instead, and which
506
+ one is checked by neither.
507
+ - The README said `audit` runs five checks. It runs seven, drawn from five
508
+ modules.
509
+
510
+ ### Removed
511
+
512
+ - `AUDIT.md`, an internal review of the package, is no longer tracked, so
513
+ it is not in the source distribution.
514
+
515
+ ## [0.2.1] - 2026-09-08
516
+
517
+ Reconstructed after the fact. The published files match commit `809a650`.
518
+
519
+ ### Changed
520
+
521
+ - `__version__` is read from the installed package metadata. 0.0.1 and
522
+ 0.2.0 both reported `__version__` as 0.1.0, because a literal in
523
+ `__init__.py` had drifted from `pyproject.toml`.
524
+
525
+ ### Added
526
+
527
+ - `AUDIT.md`, an internal review of the package, in the source
528
+ distribution.
529
+
530
+ ### Fixed
531
+
532
+ - **Breaking.** The length-bias check read every coefficient as a
533
+ preference for long answers. In 0.2.0 a judge that preferred shorter
534
+ answers got the `audit` title "The judge is pulled by how long the answer
535
+ is", and `LengthBias.summary()` printed "A positive coefficient here is
536
+ not bias on its own" beside a negative coefficient. The `audit` finding
537
+ now takes its title, lead, action and verdict from the fit that decides,
538
+ with its own lead for when the two fits point opposite ways, and
539
+ `LengthBias.summary()` has its own sentence for a negative coefficient.
540
+ Anything matching on the old title or sentence will stop matching.
541
+
542
+ ### Removed
543
+
544
+ - Nothing.
545
+
546
+ ## [0.2.0] - 2026-09-07
547
+
548
+ Reconstructed after the fact. The wheel matches commit `242e354`. It was
549
+ uploaded twelve seconds before that commit was made, so it was built from
550
+ the working tree the commit then recorded.
551
+
552
+ ### Changed
553
+
554
+ - The project URLs point at github.com/mokayahezron/evalaudit. 0.1.0 pointed
555
+ at github.com/ondibahezron-glitch/evalaudit.
556
+ - `choix` is a development dependency on Python 3.10 and later, used to
557
+ check the Bradley-Terry fit.
558
+
559
+ ### Added
560
+
561
+ - The `pairwise` module, with `bradley_terry`, `to_elo` and `BTResult`.
562
+ Ratings come with bootstrap intervals and a count of separable pairs.
563
+ Models that fall into groups that never met, and models that never lost
564
+ or never won, get no ratings and a summary naming them. Ties are split or
565
+ dropped, and `ties="davidson"` is refused by name.
566
+ - The README code blocks are run by the test suite and checked against the
567
+ output they show.
568
+
569
+ ### Fixed
570
+
571
+ - Nothing.
572
+
573
+ ### Removed
574
+
575
+ - Nothing.
576
+
577
+ ## [0.1.0] - 2026-09-07
578
+
579
+ Reconstructed after the fact. The wheel matches commit `c1230f1`.
580
+
581
+ ### Changed
582
+
583
+ - Nothing.
584
+
585
+ ### Added
586
+
587
+ - `compare_paired` and `compare_independent`. McNemar with Tango's score
588
+ interval for paired binary data, the paired bootstrap, and the signed-rank
589
+ test with the Hodges-Lehmann interval. The score interval, the bootstrap
590
+ and Welch's t for independent samples.
591
+ - `rater_agreement`, Krippendorff's alpha with a bootstrap interval, an item
592
+ disagreement table and a rater dropout table. `cohens_kappa` and
593
+ `fleiss_kappa`.
594
+ - `judge_validation`, `position_bias` and `length_bias`.
595
+ - `detectable_effect` and `min_sample_size`.
596
+ - `audit`, which runs every check the supplied data supports and ranks the
597
+ findings by severity.
598
+ - The result types are exported from the package, `ScoreCI` among them.
599
+
600
+ ### Fixed
601
+
602
+ - Nothing.
603
+
604
+ ### Removed
605
+
606
+ - Nothing.
607
+
608
+ ## [0.0.1] - 2026-09-04
609
+
610
+ Reconstructed after the fact. The wheel's package files match commits
611
+ `aa8ad97`, `8a4cab0` and `b090c66`, which do not differ in them. Its version
612
+ number first appears in `pyproject.toml` at `b090c66`, committed about an
613
+ hour after the upload. It reported `__version__` as 0.1.0.
614
+
615
+ ### Changed
616
+
617
+ - Nothing.
618
+
619
+ ### Added
620
+
621
+ - `score_ci`, with Wilson, bootstrap and Student-t intervals, and a default
622
+ that picks Wilson for 0/1 data and the bootstrap otherwise.
623
+
624
+ ### Fixed
625
+
626
+ - Nothing.
627
+
628
+ ### Removed
629
+
630
+ - Nothing.
631
+
632
+ [Unreleased]: https://github.com/mokayahezron/evalaudit/compare/v0.3.0...main
633
+ [0.3.0]: https://github.com/mokayahezron/evalaudit/compare/809a650...v0.3.0
634
+ [0.2.1]: https://github.com/mokayahezron/evalaudit/compare/242e354...809a650
635
+ [0.2.0]: https://github.com/mokayahezron/evalaudit/compare/c1230f1...242e354
636
+ [0.1.0]: https://github.com/mokayahezron/evalaudit/compare/b090c66...c1230f1
637
+ [0.0.1]: https://github.com/mokayahezron/evalaudit/tree/b090c66