Repository navigation
Measure dollar typing against committee reports, with a resolver that agrees on 87.5% of them - #845
Conversation
…lver for it ADR 0025 (proposed, civictechdc#796) asks that the figure typed as an account's appropriation equal the committee report's figure for that account. ADR 0009's recall check asks only whether the figure appears under the agency. `committee_agreement.py` measures the strong claim over every account the committee-report answer keys map to a bill node (1,163 of 1,267, all committed fixtures). `classify_bill` as `build_financial_df` uses it agrees on 920 (79.1%); it types 138 wrong figures where the report's figure is in the text, and 67 the report does not confirm. `appropriation_resolver.py` picks the figure the way a paragraph is drafted (an "in all" total; before the first proviso; an explicit "is hereby appropriated"; the figure that closes the purpose clause; loan levels skipped) and otherwise abstains. Its rules were written from six answer-key files only; this commit freezes them before scoring the other eight. Held out (484 accounts): wrong figures 3 -> 0, agreements 401 -> 389, abstentions 13 -> 28. Dev (679): wrong figures 34 -> 2, agreements 519 -> 549. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rite up the result The frozen resolver (d394669) typed no wrong figures on the eight held-out answer keys but abstained where the baseline was right. Reading those abstentions: a figure followed by "shall", "(", "plus" or "together with" can close the purpose clause; a ceiling straight after the purposes (", not to exceed $X, to remain available") is the appropriation stated as a cap; "there is hereby appropriated to the Secretary $X" is an explicit appropriation; and "under this heading" before a figure is part of the purposes, not a limit (the first proviso already bounds the search). All 1,163 mapped accounts: agreements 920 -> 955, wrong figures 37 -> 2, with the same 21 abstentions and 46 figures no single report figure confirms. Held out: 404 agreements against the baseline's 401, 0 wrong figures, now no longer an unbiased estimate, which the README says. tests/test_appropriation_resolver.py pins each drafting rule on a constructed paragraph (each proved red with its rule removed) and, as a slow gate run in CI's remaining-slow shard 2, fails on any wrong figure beyond the two listed with their reasons (gross versus net; one office of an itemised paragraph). The README records the measurement, how far to trust it, and the questions it leaves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ee reports
The account figures exist to be added up: account, agency, department.
rollup_agreement.py checks that against each "Total, <agency>" line in the
thirteen Senate reports' comparative statements. A total is attributed to
the rows above it by the report's own arithmetic (a subtotal contributes
its value and is jumped over; the span is the first run that adds up), so
every attribution is checked by construction; 273 of 406 totals are, and
190 of those name exactly one bill heading. Report totals are net of fee
offsets and rescissions, typed figures gross, so both are compared.
With appropriation_resolver, 121 of 154 complete roll-ups reproduce the
report's total (113 net, 8 gross); classify_bill's first figure does 116 of
159. Hand-traced, the 31 that do not are mostly accounts appropriating more
than one figure ("$345,000,000 and, for carrying out ... $72,515,000"),
money classify_bill leaves untyped, one obligation limitation typed as an
appropriation (FTA, +$14.28 billion, caught only by the roll-up), and report
groupings the bill heads differently.
The README also breaks down the account-level 82%: 185 accounts cannot
agree with any one-figure-per-account method; where one figure can, the
resolver agrees on 97.6%; typing every figure raises the ceiling to 91.7%.
tests/test_rollup_agreement.py pins the attribution (each test proved red
against its mutation) and the counts, and runs in CI's remaining-slow shard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 answer-key accounts were report lines finer than the bill paragraph
they map to, mostly Legislative Branch paragraphs that itemise ("$22,278,891,
including: Office of the Speaker, $6,645,417, ...; ...") or carve out a named
line ("and of which $6,000,000 shall be allocated solely for ... Senate
interns"). One figure per paragraph cannot match them.
`ledger` types each itemised and "of which" line with its own text and its
closing figure, by the same rule as the top line; `resolve_named` looks a
report line up in it by name, only when the name is not the paragraph's
heading. The name finds a line and never chooses a figure, so a wrong match
still scores as wrong. A line's text stops at the next "of which", and a
report name's parenthetical ("(part of allowances and expenses)") is dropped
before matching.
All accounts: 955 -> 1,051 agree (82.1% -> 90.4%), wrong figures 2 -> 1 (the
gross-versus-net account). All 96 new agreements are Legislative Branch, in
DEV; HELD_OUT is unchanged at 83.5%, which the README says along with what
limits it (report-versus-bill differences, answer-key mapping errors in
scripts/build_validation.py, multi-figure accounts).
Tests pin each ledger rule (each proved red with its fix reverted) and a slow
gate holds the ledger to the one known wrong figure and >= 1,051 agreements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ten accounts' committee-report figure was a sum of figures in their
paragraph, each joined by its own phrase: a trust-fund amount "together
with" the top line (five), ", and in addition not to exceed $X" (one), a
second purpose ("$345,000,000 and, for carrying out ... section 437 ...,
$72,515,000", two), and an advance appropriation from last year's act made
available this year ("(in addition to the $4,000,000,000 previously
appropriated under this heading", two). resolve_total adds those pieces to
the top line; all ten agree.
The first attempt lost one account: the Fiscal Service's separate sentence
"In addition, $235,000, to be derived from the Oil Spill Liability Trust
Fund to reimburse ..." is not counted by its report, so "in addition" now
joins an account only within its own sentence (one example each way, which
the README says).
All accounts: 1,051 -> 1,061 agree (91.2%), still one wrong figure (gross
versus net); figures the report contradicts or does not confirm 47 -> 37.
Roll-ups with summed accounts: 124 of 154 agency totals (was 121). Tests
pin each phrase (proved red against the loose "in addition" and without
"together with") and slow gates hold the counts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… not an obligation limitation Read from the bill text: "For payment of obligations incurred ..., $14,279,000,000, to be derived from the Mass Transit Account" is cash to pay obligations made under contract authority, which the report keeps out of new budget authority. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r resolvers' figures measure() decided a typed figure was the paragraph's own (and so excusable as finer_than_node on a multiply mapped paragraph) when it equalled resolve(), resolve_total() or baseline(). An unchanged wrong line figure could then flip from wrong_figure to finer_than_node when one of those other resolvers changed. select_named and select_named_total now return a Selection carrying the figure's scope (the paragraph's own, or one itemised line found by name), and judge() reads it. Counts are unchanged on the committed answer keys: no line selection coincided with another resolver's figure. The new test holds a wrong line selection fixed and makes every other resolver type the same figure; it stays wrong_figure (red with the equality inference restored). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
resolve() searched the whole paragraph for an "in all" total before cutting at the first proviso, so a proviso cap replaced the account's appropriation: "For necessary expenses, $10,000,000: Provided, That payments for travel shall not exceed, in all, $500,000." typed $500,000 from both resolve() and resolve_total(). Both now search only the operative text, which also stops a proviso cap from switching off resolve_total's added pieces. No committed answer-key paragraph has an "in all" only after a proviso, so the account and roll-up counts are unchanged. The new test is red with either function's unrestricted search restored; the pre-proviso in-all test now carries a trailing proviso so the boundary cannot drop it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lative Branch spreadsheet validation_leg_branch.json comes from House_and_Senate_Appropriations_FY_1994_to_present.xlsx (checked against the FY2014-FY2020 enrolled bills), not a committee report, yet the write-up counted its 411 accounts as committee-report agreement. They carry all 96 itemised-line gains. Each result now records its answer key's source, and the summary reports each source on its own line. With summed: committee reports 658/752 (87.5%), the spreadsheet 403/411 (98.1%), combined 1,061/1,163 (91.2%). Committee-report agreement does not move from structured to ledger (648); all 37 figures summed types that its source does not confirm are on committee-report accounts. The README says so, keeping the DEV concentration and the read-holdout disclosures. The summed gate now holds each source's floor in place of the combined one (red when the spreadsheet is attributed to committee reports). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… boundary sits Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The control called judge() directly, so restoring the original equality-based override inside measure() left it green. It now scores a synthetic answer key through measure(): three report lines mapped to one itemised paragraph, the Speaker's given a wrong line, the other two the paragraph's own figure, with and without every other resolver typing the wrong line's figure. Red with the override restored in measure(), and with the inference moved into judge(). Replaces the judge()-level test. The module docstring now names both answer-key sources. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…re its lines are discussed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks for this, @banjtheman. Scoring the typed figure against an independent source is the check ADR 0025 needs, and the README is candid about where the numbers come from and how far to trust them. Review turned up three things worth fixing before this merges. Rather than go back and forth here, I put working fixes in banjtheman#1, opened against your branch so you can merge, change or decline them:
No count moves with the fixes, because neither bug fires on the committed answer keys. What changes is how the results are attributed, plus tests that fail if either bug comes back. Feedback welcome, on the fixes themselves or on whether the per-source framing reads right to you. |
Review fixes for civictechdc#845: line scope, proviso boundary, evidence source
|
Thanks @willhea, this was a really useful review, and I've merged banjtheman#1 into this branch. Before merging I checked the fixes locally:
On the framing: yes, per source reads right to me. Agreement with the Legislative Branch spreadsheet shouldn't be presented as committee-report agreement. I've updated the PR title and description to lead with 87.5% against committee reports, with 98.1% and the combined 91.2% beside it. (Checked with Claude Code.) |
Related issue
Refs #147 (epic: financial semantics research: type each dollar amount before money returns to the report). This adds evidence, not a closing change.
Refs #115 (split each account's money into typed sub-amounts). It covers itemised lines and multi-piece accounts. Each figure's role and effect aren't typed yet.
Refs #796 (proposed ADR 0025). This measures the bar it proposes: the figure typed as an account's appropriation must equal the committee report's figure for that account.
What does this change?
Research only. No product code changes, money stays out of the report, and
classify_bill.py's rules are untouched (#736 copies them into the package and checks parity).At Civic Tech DC night the organizers asked how good the regex classifier's dollar typing is. This answers it, and improves it.
committee_agreement.pyscores the figure typed as each account's appropriation against the answer key's figure, for every account the answer keys (tests/data/validation_*.json) map to a bill node: 1,163 of 1,267, all committed fixtures, under a second to run. It reports each evidence source separately:validation_leg_branch.jsoncomes fromHouse_and_Senate_Appropriations_FY_1994_to_present.xlsx, not a committee report, and is checked against the FY2014–FY2020 enrolled bills. 411 mapped accounts.appropriation_resolver.pyreads a paragraph the way appropriations are drafted:It also does two more things:
ledgertypes each line with its own name ("including: Office of the Speaker, $6,645,417; ..."). The answer key's name is used only to find a line, never to choose a figure, and a figure selected for a line is scored against that line's own figure.resolve_totaladds the pieces an account appropriates in: "together with $X from the trust fund", ", and in addition $X", a second purpose, and an advance appropriation available this year.Review fixes from @willhea (Review fixes for civictechdc/DeltaTrack#845: line scope, proviso boundary, evidence source banjtheman/DeltaTrack#1, merged into this branch):
No count moves, because neither bug fires on the committed answer keys. Tests fail if either comes back.
rollup_agreement.pyadds typed figures up per agency and compares them with the report's "Total, " lines. A total is attributed by the report's own arithmetic, so every attribution is checked by construction. Totals are compared net and gross (report totals net out fee offsets and rescissions).Results (all reproduced by the commands below):
classify_billtodayclassify_billtodayAgainst committee reports alone, this PR agrees on 87.5%. The rise from 82.1% to 90.4% combined is entirely the spreadsheet's itemised office lines, so committee-report agreement doesn't change with them (648 of 752 either way).
The roll-up caught one thing no account check could: a liquidating appropriation typed as new money (FTA: "For payment of obligations incurred …, $14,279,000,000, to be derived from the Mass Transit Account", which the report keeps out of new budget authority).
How far to trust the numbers (the README says this at length):
Deliberately left out:
classify_billtype "In addition to amounts made available elsewhere in this Act, $X is hereby appropriated" (Show each version's dollar amounts, typed, in three report views #736's rules).scripts/build_validation.py).How to test
uv run python docs/research/financial-semantics/committee_agreement.py # every resolver, all answer keys uv run python docs/research/financial-semantics/committee_agreement.py --split held-out uv run python docs/research/financial-semantics/committee_agreement.py --resolver summed --details wrong_figure uv run python docs/research/financial-semantics/rollup_agreement.py --resolver summed --details mismatch uv run pytest tests/test_appropriation_resolver.py tests/test_rollup_agreement.pyWhat I ran and checked:
finer_than_nodeonce another resolver typed its figure.summed, per source: at least 658 committee-report, at least 403 spreadsheet) and the roll-up outcome counts, so the README's numbers can't drift unnoticed.ruff checkandruff format --check: clean.-m slow --deselect tests/test_govinfo_corpus_parity.py): 2192 passed.@willhea @mattzamora, this measures #796's bar against #736's rules. Your read on whether it's the right shape matters most.
Checklist
Closes #...):Refsonly, as this is research evidence that doesn't finish Epic: financial semantics research: type each dollar amount and reconcile totals before money returns to the report #147 or Research: split each account's money into typed sub-amounts and summarise how they changed between versions #115AI assistance
Written with Claude Code (Claude Opus 5.5) at Civic Tech DC night. I reviewed the change and the evidence above.
🤖 Generated with Claude Code