Skip to content

The claims gate could not see the preprint, and EUR reconciled as USD - #31

Merged
jeremiahsay merged 3 commits into
mainfrom
fix/check-claims-coverage
Sep 20, 2026
Merged

jeremiahsay merged 3 commits into
mainfrom
fix/check-claims-coverage

Conversation

@jeremiahsay

Copy link
Copy Markdown
Collaborator

Two fixes found while reviewing #30, plus the published figures they move.

1. The claims gate could not see the preprint

check_claims.py printed paper ok while checking none of the 53 percentages in paper/main.tex. strip_markup unescapes \% — but only after stripping TeX comments with a bare %.*, which treats the % in 45.7\% as a comment start and deletes the rest of the line. Every figure after the first percentage in a table row, and the tail of the abstract, vanished before claims_in ever saw them.

Two more gaps sat under the same docstring claim of checking "the published study pages, the preprint and the dataset card":

  • README.md carries the same headline tables as FINDINGS.md and was not a surface at all.
  • --offline silently drops the four live guide pages, then printed the unqualified "every published number". It now names what it skipped.

43% first-named and 93% of answers naming one of the top three had no figure behind them — they were passing only by rounding to the same integer as an unrelated figure. Both now come from aeo/results.json.

Coverage: 64 → 139 claims offline, 249 → 285 online.

2. Two different currencies must never reconcile

Every entry in units.CURRENCY carries a scale of 1.0, because the benchmark holds no exchange rates — an FX rate is a price on a date, not a unit conversion. That made EUR, GBP and USD interchangeable, so a model answering 85.00 EUR per tonne CO2e scored 85 against a USD/tCO2e truth and counted as an answer rather than an unscoreable one. sek: None was already refusing this way by having no scale at all; the rest were reconciling by accident.

parse_unit now records which currency a numerator is, and reconcile refuses a mismatched pair with a reason. Same currency, either spelling, is unaffected.

Three answers were being scored this way, all gemini-3.6-flash, all carbon_pricing.* — EU ETS 85.00, Spain 15.00, Netherlands 51.00 EUR/tCO2e against a USD truth. One of the three was being counted correct.

This is the same bug class as #23. The guard proposed in #30 only covers a currency written before the number, and all three live instances are written after it.

Published figures brought back into line

scoreable          315 -> 312     within_10pct   41.9% -> 42.0%
unscoreable         85 ->  88     within_50pct   78.1% -> 77.9%
correct            132 -> 131     conf. wrong    21.9% -> 22.1%
pct of all 467    28.3% -> 28.1%

Updated in FINDINGS.md, README.md, paper/main.tex (the abstract's range and the results table) and the page/ snapshots. No other model moves and paired.json is untouched.

results/absolute.json fed the pct_of_all column with no script behind it, so a scorer change moved it silently and --check-fresh could not see it. absolute.py now generates it — it reproduced the committed file exactly on the old scorer, model order and all — and check_fresh reruns it.

Also corrected while verifying: the by-category page said 867 pooled scored answers. The true figure was 886 before this change and is 883 after. CLAIM_RE only matches counts with a thousands separator, so the gate never saw it.

Verification

From a clean clone at the branch tip:

verify/test_claims_gate.py PASS
verify/test_currency_mismatch.py PASS
verify/figures.py --check-fresh PASS
verify/dataset.py PASS
verify/licences.py PASS
verify/check_claims.py --offline PASS
compare.py / paired.py / absolute.py rerun results/ byte-identical

Both new tests go red without their fix — the gate test returns [] instead of three percentages on the old comment ordering.

check_claims.py online fails on guide:accuracy (41.9%, 28.3%), and that is the correct signal: the live pages still carry the old figures. Live post ids are 9316 (accuracy) and 9327 (by-category) — note 9327, not the page/9322-body.html snapshot, which is superseded. That deploy is not in this PR.

Not in scope

Opening separately: unenumerated dollar codes (A$, C$, R$, S$) collapsing to USD, and reconcile not comparing numerator dimensions (USD 5 per kWh against a kg CO2e/kWh truth). Neither has a live hit today.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRueWxopDXHoWY2dPvsmLG

jeremiahsay and others added 2 commits September 20, 2026 08:45
`check_claims.py` printed "paper  ok" while checking none of the 53
percentages in paper/main.tex. `strip_markup` unescaped `\%` -- but only
AFTER stripping TeX comments with a bare `%.*`, which treats the `%` in
`45.7\%` as a comment start and deletes the rest of the line. Every figure
after the first percentage in a table row, and the tail of the abstract,
vanished before `claims_in` ever saw them. Strip only an unescaped `%`.

The gate's docstring claims it checks "the published study pages, the
preprint and the dataset card". Three further gaps sat under that claim:

- README.md carries the same headline tables as FINDINGS.md and was not a
  surface at all. Added as `repo-readme`.
- `--offline` silently drops the four live guide pages, then printed the
  unqualified "every published number". It now names what it skipped and
  says to run online before publishing a number.
- "43% first-named" and "93% of answers name one of those three" had no
  figure behind them; they passed only by rounding to the same integer as
  an unrelated figure. Both now come from aeo/results.json --
  `named_first_pct`, and `aeo.top3.named_in` as a union over answers.

A seed value (`seed 20260910`) is an identifier, not a claim.

Coverage: 64 -> 139 claims offline, 249 -> 285 online. Both modes green,
`figures.py --check-fresh` green, and the two allowlist entries added are
the only kind that belongs there -- a person's hand adjudication, which no
script can produce.

verify/test_claims_gate.py pins all of it, and goes red on the old
ordering: the table-row test returns [] instead of three percentages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRueWxopDXHoWY2dPvsmLG
Every entry in units.CURRENCY carries a scale of 1.0, because the benchmark
holds no exchange rates -- an FX rate is a price on a date, not a unit
conversion. That made EUR, GBP and USD interchangeable, so a model answering
"85.00 EUR per tonne CO2e" scored 85 against a USD/tCO2e truth, wrong by
whatever the rate happened to be and counted as an answer rather than an
unscoreable one. `sek: None` was already refusing this way by having no scale
at all; the rest were reconciling by accident.

parse_unit now records WHICH currency a numerator is, and reconcile refuses a
mismatched pair with a reason. Same currency, either spelling, is unaffected.

This is the same bug class as #23 (a currency the extractor drops), found
while reviewing #30: the guard proposed there only covered a currency written
BEFORE the number, and all three live hits are written after it.

Three answers were being scored, all gemini-3.6-flash, all carbon_pricing.*:
EU ETS 85.00, Spain 15.00, Netherlands 51.00 EUR/tCO2e against a USD truth.
One of the three was being counted correct.

Published figures brought back into line with the scorer:

  scoreable          315 -> 312     within_10pct   41.9% -> 42.0%
  unscoreable         85 ->  88     within_50pct   78.1% -> 77.9%
  correct            132 -> 131     conf. wrong    21.9% -> 22.1%
  pct of all 467    28.3% -> 28.1%

in FINDINGS.md, README.md, paper/main.tex (the abstract's range and the
results table) and the page/ snapshots. No other model moves, and paired.json
is untouched.

results/absolute.json fed the `pct_of_all` column with no script behind it, so
a scorer change moved it silently and --check-fresh could not see it. absolute.py
now generates it -- it reproduced the committed file exactly on the old scorer
before this change, model order and all -- and check_fresh reruns it.

Also corrected while verifying: the by-category page said 867 pooled scored
answers. The true figure was 886 before this change and is 883 after; 867 is
from an earlier scorer version. CLAIM_RE only matches counts with a thousands
separator, so the gate never saw it.

verify/test_currency_mismatch.py covers it, including the three answers as the
models actually wrote them, and goes red without the fix.

NOT DONE, needs server access: the live pages still show the old gemini
figures. `check_claims.py` (online) fails on guide:accuracy with 41.9% and
28.3%, which is the correct signal. Live post ids are 9316 (accuracy) and
9327 (by-category) -- note 9327, not the page/9322-body.html snapshot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRueWxopDXHoWY2dPvsmLG
FINDINGS.md's scorer section opens "It was wrong six times, and each is
recorded because on a benchmark the corrections matter more than the
headline." The EUR-as-USD fix in the previous commit is a seventh, and
leaving it out fails that document's own standard — so it is now entry 7,
and README.md and the paper abstract say seven.

Two details worth being straight about rather than smoothing over:

- Six of the seven were found BEFORE the number was published. This one was
  not; it was found after, while reviewing a contributor's fix for the
  related extraction bug. The paragraph now says so instead of keeping the
  tidier claim.
- The same section ended "mostly currency (a SEK answer against a USD
  truth)", which implied currency mismatches were handled. Only SEK was, and
  only by accident -- it is the one entry in CURRENCY with no scale. EUR and
  GBP were converted at 1.0. Reworded to say what the scorer now does.

The bug-log entry quotes 41.9%, which no script produces any more. That is
the one shape an allowlist entry is for, and it goes in `findings` next to
the existing 46.7% for the same reason -- a historical figure in a narrated
correction, with the current value named in the reason.

Also: nothing ran verify/test_*.py. Three test files existed across this
branch and #30 and CI executed none of them, which is worse than having no
tests because it reads as cover. claims.yml now has a scorer-tests job that
discovers them by glob and fails if the glob matches nothing -- a listed
filename would have gone stale the first time one was renamed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRueWxopDXHoWY2dPvsmLG
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