fix(benchmarks): score precision and recall consistently, and realign the docs - #60
Open
mazzasaverio wants to merge 2 commits into
Open
fix(benchmarks): score precision and recall consistently, and realign the docs#60mazzasaverio wants to merge 2 commits into
mazzasaverio wants to merge 2 commits into
Conversation
Three release-gate defects, all reproducible on main. The ONNX backend interpolated the originating exception message into BackendExecutionError. The release gate requires that exceptions do not expose matched values, and PR ma2za#40 established the same invariant for adapters; tokenizer and runtime messages can quote the input they failed on. The message is now fixed text and the cause is dropped, matching how every other adapter and backend in the tree raises. Detection also scanned every character with unicodedata.category on every block, building two N-sized lists even when nothing was stripped. No ASCII character has category Cf, so pure ASCII text now takes a single C-level scan and keeps the source offsets untouched, and non-ASCII text only pays for the rebuild when a format character is actually present. This is 13% of end-to-end processing time on a 64 KiB block. Finally, `uv run ruff format --check .` fails on main: the 0.16.0 OCR commit landed unformatted, so CI has been red across 0.16.0 and 0.17.0 even though both shipped. The two files are reformatted here so the gate passes again. uv.lock still recorded 0.16.0 and is refreshed to 0.17.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012JiX3zWeEC28kmy5KmAXvf
… the docs
The release gate for 0.8.0 onwards is a number produced by
benchmarks/evaluate_quality.py. That number was not precision or recall.
True positives were counted per detection while false negatives were counted
per annotation, so the two sides of the ratio measured different things: one
detection overlapping two adjacent annotations scored a single true positive
yet satisfied both, removing two from the recall denominator. Matching also
ignored labels entirely, so a detection agreeing with an annotation's position
but not its type counted as a hit. And a correct detection of an entity whose
label sits outside SUPPORTED_LABELS was charged as a false positive, which
rewards suppressing valid detections.
Detections and annotations are now paired one to one, largest overlap first, and
a pair must agree on entity type. --span-only restores the label-blind scoring
for comparison. Detections landing on an out-of-scope annotation are reported
separately rather than penalised.
Rescoring the released 0.17.0 code, unchanged, over 300 validation rows with the
ONNX backend:
reported P 0.9475 R 0.8910 F1 0.9184
corrected, spans P 0.9316 R 0.7529 F1 0.8328
corrected, typed P 0.8094 R 0.6536 F1 0.7232
The 90% threshold that 0.14.0 onwards were gated against was never cleared under
scoring that counts one unit on both sides.
Also in this change:
- train_eval.py was a copy of the evaluation script pointed at the train split,
so an improvement measured during development did not necessarily mean the
same thing at evaluation. It now calls the same scoring code, with per-error
detail behind an explain flag.
- benchmarks/datasets/ shadowed the datasets package under type checking and is
renamed to benchmarks/data/. benchmarks is added to mypy's files, which the
release gate already required but the configuration omitted, and strict mypy
now passes across it.
- The roadmap's current-position table stopped at "0.7.0 Next" while 0.17.0 was
released, and claimed a 99.36% coverage floor where pyproject enforces 95.36%.
- 0.17.0 shipped with no changelog entry; one is written from its commits.
- Three broken escapes rendered as literal backslashes in README and CHANGELOG.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JiX3zWeEC28kmy5KmAXvf
mazzasaverio
force-pushed
the
fix/benchmark-metrics-and-docs
branch
from
August 30, 2026 05:33
792250d to
b97e8ac
Compare
Contributor
Author
|
Rebased onto
Under corrected scoring, Two observations that follow from the diff rather than from the numbers, offered as data for whoever reviews this:
Neither point changes this PR's content. They are the reason it exists: when the metric cannot distinguish a change from noise, a release can be attributed to code that is not there and nothing catches it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The release gate from 0.8.0 onwards is a number produced by
benchmarks/evaluate_quality.py. That number was not precision or recall.The scoring was inconsistent on both sides of the ratio
Three problems:
"John Smith"against separateGIVENNAMEandSURNAMEmasks, which this corpus produces constantly — scores one true positive while removing two from the recall denominator. Recall is inflated by construction._g_labelis discarded. A detection agreeing on position but not on entity type is a hit, so finding an email where the corpus annotated a surname scores.SUPPORTED_LABELSare dropped from the truth set, so a correct detection of one becomes a false positive. That rewards suppressing valid detections.Detections and annotations are now paired one to one, largest overlap first, and a pair must agree on entity type via an explicit corpus-label-to-
EntityTypemap.--span-onlyrestores the label-blind scoring for comparison. Out-of-scope hits are reported on their own line instead of being charged as errors.What the released code actually scores
Same
0.17.0code, unchanged, 300 validation rows, ONNX backend enabled:--span-onlyThe gap is measurement, not behaviour. The 90% threshold that 0.13.0 declared cleared, and that 0.14.0 onwards were gated against, was never met under scoring that counts one unit on both sides and checks that a detection agrees with what it is credited for.
I have not touched the gate itself in this PR — where the bar belongs now that the numbers mean something is your call, and it interacts with #58 and #59, which both move detection behaviour.
Also here
train_eval.pywas a copy of the evaluation script pointed at the train split. The integrity notice asks contributors to develop ontrainand measure onvalidation, which only works if both are scored identically; they were not. It is now a thin wrapper over the same code, with per-error detail.benchmarks/datasets/shadowed thedatasetspackage under type checking, renamed tobenchmarks/data/.benchmarkswas never type-checked.files = ["src", "tests", "scripts"]omits it, while the release gate requires "strict mypy passes for source, tests, benchmarks, and scripts". It is added, and strict mypy now passes across it (it did not before: 4 errors).0.7.0 | Nextwith 0.17.0 released; it now lists through 0.17.0. It also claimed a 99.36% coverage floor wherepyproject.tomlenforces 95.36%.\ContextualIdDetector\and\i4privacy/...\(also missing its leadinga).🤖 Generated with Claude Code
https://claude.ai/code/session_012JiX3zWeEC28kmy5KmAXvf