Bus fare cap: an earned gap, and the near-miss that nearly made it false (#106) - #108
Open
vahid-ahmadi wants to merge 1 commit into
Open
Bus fare cap: an earned gap, and the near-miss that nearly made it false (#106)#108vahid-ahmadi wants to merge 1 commit into
vahid-ahmadi wants to merge 1 commit into
Conversation
…lse (#106) The £2/£3 bus fare cap cannot be scored at the certified pin. The verdict matters less than how it was reached, because the first pass at it was WRONG. That first pass searched the parameter tree, found gov.dft has only rail and spending, and concluded bus was absent from the model. It is not. bus_subsidy_spending is a VARIABLE, and a parameter-only search misses it — the same shape as the two false engine gaps in #100 and #101, caught this time before publishing rather than after. What is actually there: dft_subsidy_spending adds = [rail_subsidy_spending, bus_subsidy_spending] So bus subsidy is a household-level quantity and its incidence IS modelled. The gap is narrower and more useful than "PolicyEngine does not do buses": bus_subsidy_spending is a PURE INPUT — no formula, no parameter behind it — where rail has the price/quantity split a reform needs (rail_usage x gov.dft.rail.fare_index). The gap is a missing FACTORISATION, not a missing subject area, which is why the upstream ask is small and precedented: give bus what rail already has. The validator enforces the lesson rather than the conclusion: - a gap claim needs a NAME SEARCH, and one that covers variables as well as parameters (--probe re-runs both against a real engine and refuses drift) - the registry must say what the engine DOES have; omitting the present-but-unusable machinery overstates the gap - has_formula must be False — that single fact is what the verdict rests on, so if it changes upstream the probe fails loudly instead of leaving a stale opinion in the repo - the nearest lever (gov.contrib.policyengine.economy.transport) must be named AND ruled out, or a reader finds it and assumes nobody looked - no staged value claims: the DfT-commissioned evaluation figures belong to their originators (#86/#91) One honest detail pinned in the tests: the search pattern's "bus" matches gov.hmrc.business_rates. It is a substring false positive, not a bus lever, and it is recorded as such. #105 is the scoreable half of the pair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016HuXJFVme8HRbnke2Ey3Me
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.
Closes #106. Branches from
main— independent of the other UK PRs.The £2/£3 bus fare cap cannot be scored at the certified pin. The verdict matters less than how it was reached, because my first pass at it was wrong — and it was wrong in exactly the way #100 and #101 were wrong.
The near-miss
That first pass searched the parameter tree, found
gov.dfthas onlyrailandspending, and concluded bus was absent from the model.It isn't.
bus_subsidy_spendingis a variable, and a parameter-only search misses it:Bus subsidy is a household-level quantity and its incidence is modelled. Caught before publishing this time, rather than in review afterwards.
The gap that survives
Narrower and more useful than "PolicyEngine doesn't do buses":
rail_usagegov.dft.rail.fare_indexrail_usage × fare_indexbus_subsidy_spendingis a pure input — no formula, no parameter behind it. So the gap is a missing factorisation, not a missing subject area. That makes the upstream ask small and precedented: give bus what rail already has (bus_usage,gov.dft.bus.fare_index, and the product between them). It would make every future fare-cap question scoreable, not just this one.The validator enforces the lesson, not the conclusion
--resolvecan't catch a false gap — anot_expressibleentry carries no path to resolve. So:--probere-runs both against a real engine and refuses drift.has_formulamust beFalse. That single fact is what the verdict rests on — if it changes upstream, the probe fails loudly rather than leaving a stale opinion in the repo.gov.contrib.policyengine.economy.transport, "raise transport expenses by this percentage", currently 0) must be named and ruled out. It moves all transport, isn't bus-specific, and isn't a fare cap — using it would model something else and label it as the measure. Unruled-out, a reader finds it and assumes nobody looked.One honest detail pinned in the tests: the search pattern's
busmatchesgov.hmrc.business_rates. Substring false positive, not a bus lever, recorded as such so nobody mistakes it later.Why hold it at all
The £3 cap runs to March 2027, so it's live for the 28 October Budget, and DfT has a published independent evaluation of the £2 cap. The claims are worth holding even while the counterpart isn't computable — that's what a mode-1 lane with a named gap is for.
Gates
277 tests (14 new),
ruff format --checkclean, twobuild_dbruns agree oncontent_hash, no drift.#107 is the scoreable half of the pair.