Skip to content

fix(detectors): stop the context detector firing on ordinary prose - #59

Open
mazzasaverio wants to merge 2 commits into
ma2za:mainfrom
mazzasaverio:fix/context-detector-overfitting
Open

fix(detectors): stop the context detector firing on ordinary prose#59
mazzasaverio wants to merge 2 commits into
ma2za:mainfrom
mazzasaverio:fix/context-detector-overfitting

Conversation

@mazzasaverio

Copy link
Copy Markdown
Contributor

Stacked on #57, which it contains. The diff against it is detectors/context.py and its new test file.

The contextual identifier patterns were tuned against the ai4privacy evaluation corpus rather than against how documents are written, which is the practice benchmarks/evaluate_quality.py's own integrity notice rules out:

NO HARDCODING: Do not write regex or rules targeting specific strings, names, or artifacts found exclusively in this dataset.
BLIND EVALUATION: Do not inspect the validation split to build rules.

Two problems follow from it.

A label was treated as sufficient evidence

Nothing required the captured value to look like an identifier, so an ordinary English word sitting where the identifier belongs was reported as personal data. On main:

"Please provide identification number before Tuesday"
    -> "before"        NATIONAL_ID
"The applicant Jonathan submitted forms."
    -> "Jonathan"      NATIONAL_ID
"We approved a budget of 1500000000000 lire."
    -> "1500000000000" PAYMENT_CARD
"Your school 100200 is nearby."
    -> "100200"        LOCATION

0.15.0's changelog says the digit constraint was added to the NATIONAL_ID contextual fallback, but it reached only two of the patterns; the national-identity one still matched any six-to-fifteen character run, letters included. Every capture now requires at least one digit.

Several triggers were not labels at all

budget of, contribution of, school, office at, singapore, sending your, proof like a, cung cấp ("provide") and número de impresos ("number of printed forms") do not precede an identifier in any document. They precede one in particular rows of one dataset. They are removed, along with the bare id, serial, reference, identifier and applicant triggers that matched with no number marker at all.

The genuine multilingual labels stay — mã số thuế, căn cước, número de identificación, 身份证号, 护照号, paspor, nomor SIM — and the reference-number family still matches when written as a label: reference number, ticket number, serial no.

Verified both directions

Nine prose cases that must not match and ten labelled identifiers across English, Vietnamese and Chinese that must, all as parametrized regression tests:

"Passport No: X1234567 was issued in Rome."   -> X1234567         NATIONAL_ID
"Tax ID: IT12345678901 for the invoice."      -> IT12345678901    TAX_ID
"Ticket number: TK-9928311 is open."          -> TK-9928311       NATIONAL_ID
"Mã số thuế 0101243150 đã đăng ký."           -> 0101243150       TAX_ID
"Credit card number 4111111111111111 declined." -> 4111111111111111 PAYMENT_CARD

Expect the benchmark number to move

The reported precision figure will fall, because some of these matches were scoring as true positives against the very corpus they were derived from. A detector that fires on "the applicant Jonathan" is not more precise in the field; it scores better on one holdout slice. #59 addresses the harness those numbers come from.

263 passed, 1 skipped
coverage 95.46%
ruff, mypy clean

🤖 Generated with Claude Code

https://claude.ai/code/session_012JiX3zWeEC28kmy5KmAXvf

mazzasaverio and others added 2 commits August 30, 2026 07:28
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 contextual identifier patterns were tuned against the ai4privacy
evaluation corpus rather than against how documents are actually written,
which is the practice the benchmark harness's own integrity notice rules
out. Two problems followed from it.

A label was treated as sufficient evidence on its own, with no requirement
that the captured value look like an identifier. On main:

  "Please provide identification number before Tuesday"
      -> "before"        NATIONAL_ID
  "The applicant Jonathan submitted forms."
      -> "Jonathan"      NATIONAL_ID
  "We approved a budget of 1500000000000 lire."
      -> "1500000000000" PAYMENT_CARD
  "Your school 100200 is nearby."
      -> "100200"        LOCATION

The 0.15.0 digit constraint was added to two patterns but not to the
national-identity one, which still matched any six-to-fifteen character run.
Every capture now requires at least one digit.

Several triggers were also not labels at all. "budget of", "contribution of",
"school", "office at", "singapore", "sending your", "proof like a", "cung
cấp" ("provide") and "número de impresos" ("number of printed forms") do not
precede an identifier in any document; they precede one in particular rows of
one dataset. They are removed, along with the bare "id", "serial",
"reference" and "applicant" triggers that matched without any number marker.
The genuine multilingual labels stay, and "reference" and friends still match
when written as "reference number".

Ten labelled identifiers across English, Vietnamese and Chinese are covered
as regression tests alongside the nine prose cases, so the loosening cannot
come back unnoticed.

This will lower the reported benchmark precision figure, because some of
these matches were scoring as true positives against the corpus they were
derived from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JiX3zWeEC28kmy5KmAXvf
@mazzasaverio
mazzasaverio force-pushed the fix/context-detector-overfitting branch from 87317d0 to b719f9e Compare August 30, 2026 05:33
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.

1 participant