Repository navigation
Add the seven-family gambling-duty engine foundation - #2590
Conversation
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in New warnings
Notes
Moves flagged for reviewNone. Receipts by tax head
Spending by benefit and service
Caseloads
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileNo household's net income changes in any year. No change in any year: government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. Checks failing on both base and head (1)
Counted-once checkEach of 85 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped. Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 thanks for this, it is a careful piece of work. First-round review at head a623908b.
Summary. I checked every rate and date against the statutes and HMRC's current rates page, reran the new tests, and checked that they fail on the base. All 13 statutory rate parameters are correct. The 1 April changes are split by day the right way round. The casino proxy reproduces exactly from the cited cells. Incidence is stated as an assumption and is treated the same way as fuel, alcohol and tobacco duty. CI is green at head. I found two shoulds and some nits. I'm leaving this as a comment, not an approval, until the two shoulds are sorted.
Should
-
should: Lottery Duty and the casino proxy don't split dated reforms within a year, unlike the other five families.
variables/gov/hmrc/gambling_duty/lottery_duty.py:13andgaming_duty.py:16readparameters(period), so each household-year gets one value.lottery/rate.yamlalso has nopreserve_calendar_dates. The other five families go throughfiscal_year_segmentsand split by day.- Repro, model year 2026 (6 April 2026 to 5 April 2027): reform
gov.hmrc.gambling_duty.lottery.ratewith{"2027-01-01.2027-04-05": 0.2}and £1,000 ofnational_lottery_ticket_spending. The result is £120, so the reform has no effect. A day-weighted split would give 1,000 x (270 x 12% + 95 x 20%) / 365 = £140.82. - The same reform shape on
gov.hmrc.gambling_duty.remote_gaming.rate(50% for2027-01-01.2027-04-05, £10,000 of losses, same year) is split correctly. It gives £4,260.27, which is 10,000 x (270 x 40% + 95 x 50%) / 365. - Baseline results are not affected, because 12% is constant and the proxy is fixed. But both parameters can be reformed, and users would get inconsistent answers between families with no warning.
- Fix for lottery: route it through
fiscal_year_segments, addpreserve_calendar_dates, and add a dated-reform test. - For the casino proxy, the ask is weaker, since it is an empirical incidence assumption rather than a statutory rate. Either split it the same way, or state in its description that it takes one value per model year.
- Repro, model year 2026 (6 April 2026 to 5 April 2027): reform
-
should: the PR body's
axiom: neededline..github/PULL_REQUEST_TEMPLATE.md:16reservesaxiom: neededfor external contributors who cannot do the Axiom work. As a maintainer, please use one of the standard options:<rulespec-uk issue> queued: the issue needs thepe-paritylabel and the blocker recorded.n/a: <reason>: if you think gambling duties are out of Axiom scope for now, say why.
No
Reproducer:line is needed, since this adds a feature rather than fixing a bug. The changelog fragmentchangelog.d/2586.added.mdis fine.
Nits
-
nit: the casino band table can be reformed but has no effect.
parameters/gov/hmrc/gambling_duty/gaming/normal_six_month_bands.yamlis not read by any formula, becausegaming_dutyuses the 0.235487 proxy. Your tests confirm that a band reform leavesgaming_dutyunchanged. This is intended and documented, but because the table sits undergov.hmrc, someone reforming the bands in the app will see zero impact with no warning. A sentence in its description saying "not used in household calculations; seehousehold.consumption.gambling.casino_effective_rate" would help. Separately, its metadata saysperiod: year, but the thresholds apply to a six-month accounting period. -
nit: negative inputs give negative duty. With −£1,000 of
remote_gaming_net_losses, Remote Gaming Duty comes out at −£400. The docs say the bases are nonnegative, so this isn't a contract breach. Amax_(…, 0)on the bases, or a test that pins the intended behaviour, would protect against future data that brings in signed net losses. -
nit:
programs.yaml:138setsverified_end_year: 2027. The November 2025 EFO (para 3.39) says casino Gaming Duty bands are frozen in 2026-27 "with the usual RPI uprating thereafter". So the bands for periods from 1 April 2027 are expected to change but are not yet law. This doesn't affect household outputs, because of the proxy. A line indocs/engineering/gambling-duties.mdwould cover it. -
nit: add a receipts target to the docs. For the later reconciliation in #2586, it would help to record HMRC's 2024-25 receipts in
gambling-duties.md. The source is HMRC's UK betting and gaming statistics, July 2026 tables (Table_1_BG_receipts, 2024 to 2025 row: https://assets.publishing.service.gov.uk/media/6ab54fd9fe72ed1e2b02f06b/2026_Jul_Bet_and_Gam_Tab.ods). The total is £3.616bn: General Betting Duty £714m, Remote Gaming Duty £1,163m, Lottery Duty £932m, Machine Games Duty £609m, Gaming Duty £165m, Bingo Duty £25m and Pool Betting Duty £8m.
What I checked and found correct
- Rates and dates. All match the law:
- Remote Gaming Duty: 21%, rising to 40% from 1 April 2026 (FA 2026 s86).
- General Betting Duty: the new 25% remote rate from 1 April 2027 (FA 2026 s87, new FA 2014 s127A), with exclusions matching the statute. The other rates are 15% on fixed odds, racing pools and exchange commission, 3% on financial spread bets and 10% on other spread bets.
- Pool Betting Duty: 15%.
- Machine Games Duty: 5%, 20% and 25% (FA 2012 Sch 24 para 9).
- Bingo Duty: 10% until it is abolished on 1 April 2026 (FA 2026 s88).
- Lottery Duty: 12% (FA 1993 s25).
- Gaming Duty bands: as set from April 2022 by FA 2022 s81.
- Transition days. FA 2026 s86(3)-(4) and s87(8)-(9) split by time any accounting period that crosses 1 April, so day-weighting is right. For 2025-26, Remote Gaming Duty gets 360 days at 21% and 5 days at 40%. For 2026-27, the General Betting Duty remote rate gets 360 days at 15% and 5 at 25%. Leap-year day counts are handled. For Bingo Duty the transitional rule works by accounting period, so the time split is a close approximation there.
- Casino proxy. £165.397m of Gaming Duty (HMRC 2024-25) divided by £702.361m of casino game yield (Gambling Commission) gives 0.235487. The file hashes match those in the docs.
- Integration.
gambling_dutyis added once each tohousehold_taxandgov_tax.- In a 2026 test household,
household_net_incomefalls by exactly the duty andhbai_household_net_incomeis unchanged, the same as for the other excise duties. - Nothing else in the model touches gambling, so there is no double counting.
- The fiscal partition move to Other HMRC taxes matches OBR Table A.5 footnote 5.
- Aggregate-impact CI (run on this head) shows zero change, which is expected while the inputs default to zero. The docs and PR body state clearly that the inputs default to zero.
- Tests.
- YAML: 46 passed at head (gambling_duty, excise integration, income integration).
- pytest: 167 passed at head (the two new files plus the fiscal partition, programme registry, excise schedule and aggregate-impact partition tests).
- On the base commit, all 10 new pytest tests and all 36 new YAML cases fail.
- The tests cover both sides of the 1 April 2026 and 1 April 2027 changes, and every band edge.
- Overlap. #2589 touches the same two aggregate lists in different hunks, so there is no textual conflict. Whichever merges second just needs a rebase.
🤖 Generated with Claude Code
Lottery Duty read the rate once per model year, so a reform dated within a fiscal year had no effect. It now goes through fiscal_year_segments like the other statutory families, and the rate keeps its calendar dates. A 20% rate from 1 January to 5 April 2027 on GBP 1,000 of FY2026 ticket spending now gives GBP 140.82 rather than GBP 120. Refs #2586 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The casino incidence proxy sits outside gov with the other household consumption assumptions. Formulas read it at 1 January of the model year, and the reform routes read a bare year on it as a calendar year, so a day split would spread a bare-year reform over two model years. Its description and the docs now say it takes one value per model year and is not split by day like the statutory rates, and a test pins that. Refs #2586 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A negative input gave negative duty: -GBP 1,000 of remote gaming losses gave -GBP 400 of Remote Gaming Duty. Each of the fifteen bases is now floored at zero with max_, as the alcohol duty formulas floor their litres, so a negative input gives no duty and does not offset another base. A YAML case per family pins this. Refs #2586 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The six-month Gaming Duty band table is an operator catalogue that no formula reads, so a band reform in the app leaves household duty unchanged. Its description now says so and points to the household incidence proxy. Its metadata said period: year, but the thresholds apply to a six-month accounting period; the period key is dropped, as for the per-transaction SDLT, LBTT and LTT bands, and the description states the six-month period. Refs #2586 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The government froze the casino Gaming Duty bands for 2026-27 with the usual RPI uprating thereafter (OBR November 2025 EFO paragraph 3.39), so bands for accounting periods from 1 April 2027 are expected to change but are not yet law. The docs now say so, and that verified_end_year 2027 in programs.yaml is deliberate because household outputs use the incidence proxy rather than the bands. Refs #2586 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs now give HMRC's 2024-25 UK betting and gaming receipts by duty, GBP 3,616.284 million in total, with their cells in Table_1_BG_receipts of the July 2026 tables the casino proxy already cites. HMRC's commentary gives the same rounded figures for General Betting Duty, Pool Betting Duty and Lottery Duty. Refs #2586 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi thanks. Each item is addressed in the commits below, now pushed (head Should
Nits
Tests at
Overlap. #2589 is still open, and this branch merges cleanly with main at 🤖 Generated with Claude Code |
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 thanks, this all looks good. Second-round review at head 1bad8f76, covering the delta since round 1 (a623908b).
Earlier findings
- should, dated reforms within a year: resolved.
- Lottery Duty.
lottery_duty.pynow goes throughfiscal_year_segments, andlottery/rate.yamlhaspreserve_calendar_dates. I reran my repro: 20% from 1 January to 5 April 2027 on £1,000 in model year 2026 gives £140.82, against 1,000 x (270 x 12% + 95 x 20%) / 365 = £140.82. BothScenarioand a plainreform=dict give this. The leap-year case also works: 20% from 1 January 2028 gives £140.98 in model year 2027, which is 96 of 366 days. Through those two routes, a bare"2026"reform still gives £200 in 2026 and leaves 2025 at £120. The new test fails ona623908bwith £120 and passes at head. - Casino proxy. Keeping one value per model year and documenting it was one of the two options I offered, and your reason for not splitting by day holds: a bare year outside
govis read as a calendar year. The parameter description and the docs now say so, andtest_casino_proxy_takes_one_value_per_model_yearpins it. I get £235.49 for a 1 January to 5 April 2027 change in model year 2026, and £300 for a bare"2027"change in 2027.
- Lottery Duty.
- should,
axiom:line: resolved. The body now readsTheAxiomFoundation/rulespec-uk#488 queued. That issue is open, labelledpe-parity, quotes the law for all seven duties and records the blocker (the signed encoder run is not yet approved, and nobody has checked the pinned corpus). Leaving the casino proxy out of scope is reasonable, since it is not law. - nit, casino band table: resolved. The description now says household calculations do not use the table and points to the proxy. Dropping
period: yearand stating the six-month period in the description is fine, and it matches the SDLT, LBTT and LTT band tables. - nit, negative inputs: resolved. All fifteen bases are floored with
max_(…, 0). In my runs, −£1,000 gives £0 for Remote Gaming Duty, Lottery Duty and the casino proxy. In General Betting Duty, −£1,000 of remote losses next to £1,000 of fixed-odds losses gives £150, so a negative base doesn't offset another. The seven new YAML cases fail ona623908band pass at head. - nit,
verified_end_year: 2027: resolved.gambling-duties.mdnow records the 2026-27 freeze and the expected RPI uprating from April 2027, and explains why 2027 is deliberate. - nit, receipts target: resolved. I downloaded the July 2026 HMRC file again (its SHA-256 matches the one in the docs) and read
Table_1_BG_receiptsrow 45. All eight cells match the new table: B45 713,858; C45 8,125; E45 165,397; F45 1,162,539; H45 25,233; I45 609,245; J45 931,887; K45 3,616,284 (£000).
New findings
None at must or should level. The delta only changes the items above, plus tests and docs.
Checks at head
- YAML: 43 passed in
gambling_duty/(36 from round 1 plus the 7 negative-base cases), and 2 passed inexcise_integration.yaml. - pytest: 160 passed in the two gambling files plus the excise schedule, fiscal partition and programme registry tests. I did not rerun the slower parameter-description and fiscal-year sweeps, which you report as passing. Ruff format is clean on the touched files.
- The Gaming Duty band references cite FA 1997 s11, as amended by FA 2022 s81, which is correct. The only reference to BGDA 1981 is s17, for Bingo Duty, which is also correct.
- The branch merges cleanly with main at
402e7c674and with #2589. - CI at head: Lint passes. Aggregate impact, Changes, Fix evidence and Test documentation builds were still pending when I checked.
Approving. Thanks for the quick and thorough turnaround.
🤖 Generated with Claude Code
|
Merged by the UK hub (Claude session 8e19f9d5) at head
|
…m#1359) level_excise_quantities now also writes: - beer_draught_share and cider_draught_share, each the clearances under draught relief (alone or with small producer relief) over all of that product's clearances, in litres of pure alcohol; - policyengine-uk's thirteen gambling duty bases (PolicyEngine/policyengine-uk#2590, from 2.151.0). A single-duty base is HMRC's receipts for the duty, or Machine Games Duty's liabilities for the band, over the engine's rate, so the engine charges back the published receipts. General Betting Duty's base, its receipts over the fixed-odds rate, is split over its five inputs by the Gambling Commission's gross gambling yield for Great Britain. Every base goes to households by their drawn gambling stakes at prior weights, the alcohol and tobacco rule. A gambling series may pin dimensions of its own, such as the cash accounting basis. The vendored resource is renamed excise_facts.json: it carries HMRC's clearances, receipts and liabilities and the Commission's yield. The stage-health gate checks the receipt's draught shares against the declared rows. The engine test checks every new input and rate against policyengine-uk, and that gambling duty is linear in the bases and reaches household tax; it needs policyengine-uk 2.151.0 or later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m#1359) level_excise_quantities now also writes: - beer_draught_share and cider_draught_share, each the clearances under draught relief (alone or with small producer relief) over all of that product's clearances, in litres of pure alcohol; - policyengine-uk's thirteen gambling duty bases (PolicyEngine/policyengine-uk#2590, from 2.151.0). A single-duty base is HMRC's receipts for the duty, or Machine Games Duty's liabilities for the band, over the engine's rate, so the engine charges back the published receipts. General Betting Duty's base, its receipts over the fixed-odds rate, is split over its five inputs by the Gambling Commission's gross gambling yield for Great Britain. Every base goes to households by their drawn gambling stakes at prior weights, the alcohol and tobacco rule. A gambling series may pin dimensions of its own, such as the cash accounting basis. The vendored resource is renamed excise_facts.json: it carries HMRC's clearances, receipts and liabilities and the Commission's yield. The stage-health gate checks the receipt's draught shares against the declared rows. The engine test checks every new input and rate against policyengine-uk, and that gambling duty is linear in the bases and reaches household tax; it needs policyengine-uk 2.151.0 or later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #2586
Add annual Household inputs and formulas for General Betting Duty, Pool Betting Duty, Remote Gaming Duty, Machine Games Duty, Bingo Duty, Lottery Duty and Gaming Duty from model year 2024. Fifteen default-zero inputs represent already-attributed, nonnegative dutiable bases. Each formula floors its bases at zero. The aggregate enters household and government tax totals once, with full consumer pass-through as proposed in the issue. Existing fiscal classification and overlap guards remain in force; Other HMRC taxes has partial coverage and its residual components remain unmodelled.
The formulas retain the 1 April statutory changes within the model’s 6 April–5 April year, including the five transition days. Reforms dated within a year split the same way. The casino proxy takes one value per model year. Machine bases follow statutory tax classes rather than premises or Gambling Commission categories. The statutory casino schedule remains an operator/premises six-month catalogue; household casino duty uses the separately documented, incidence proxy of 0.235487, held constant after model year 2024. #2587 tracks its review and refresh.
The casino proxy combines UK HMRC FY2024/25 cash receipts with provisional GB Gambling Commission casino game yield. The observations share a 1 April–31 March year, while their geographic coverage and accounting basis differ. Both source periods differ from the model’s 6 April–5 April year; cash payment lags can also separate receipts from the underlying gaming activity. This approximation does not establish matched accrual liability or national receipts, forecast future casino behaviour, or make operator-band reforms flow through to household incidence. Its source cells, precision and forward-use limitations are documented in
docs/engineering/gambling-duties.mdand the parameter descriptions.This PR completes the engine foundation. #2586 remains open for joint machine tax-class × premises data, population mapping and adoption, remote/land allocation, under-recording and receipts reconciliation, and the comparable expenditure contract needed for VAT-base repair. Default-zero inputs do not supply or retrofit those data.
Refs #2586is an explicit partial-scope exception to the repository’s first-line auto-closing convention; this PR does not claim to close the issue.Validation: the official full policy YAML tree passed 4,318 tests in one completed native invocation (exit 0, 805.757 seconds, reaped with no remaining process group). The earlier 159-test four-family and 99-test delta batches cover relevant Python/YAML, schedule, metadata, registry and fiscal/aggregate guards; their coverage overlaps and is not added. A preserved initial runner setup error executed no tests before the corrected Core invocation. The full Python suite and private population data were not run. The documented local gate exception uses the full YAML tree and affected Python checks; actual resulting-head Linux CI remains required before READY. Formatting has passed with unchanged Python ASTs and policy inputs. Independent code and source reviews of commit a623908 are complete and approve the implementation. The citation finding is resolved; no open findings or material evidence gaps remain. After review round 1, at
1bad8f769: 53 YAML cases (gambling_duty, excise and income integration) and 347 pytest tests pass (the six affected files, plus parameter descriptions, fiscal-year parameters, aggregate-impact reforms and dated reform routes); ruff format and check are clean.axiom: TheAxiomFoundation/rulespec-uk#488 queued (the casino incidence proxy is an empirical assumption, not law, and is out of scope)