Skip to content

Correct Scottish income tax limits and rates from 2018-19 #2783

Closed

Description

@MaxGhenis

On 20 February 2018 the Scottish Parliament set a 21% intermediate rate up to £43,430 of income and a 41% higher rate from there to £150,000, for 2018-19 (gov.scot, Official Report). With the £11,850 personal allowance, that puts the higher rate limit at £31,580 of taxable income.

policyengine_uk/parameters/gov/hmrc/income_tax/rates/scotland/rates.yaml holds £31,500 and 40% from April 2017 until April 2021. The file has no entry at any limit for 2019-20 or 2020-21, so those years repeat the 2018-19 limits.

Reproduction

On main (2da212a), a single adult in Scotland with £50,000 of employment income in 2018 has £38,150 of taxable income and pays £9,133.50 of income tax. The rates the Scottish Parliament set give £9,184.00:

Band Taxable income Rate Tax
Starter £2,000 19% £380.00
Basic £10,150 20% £2,030.00
Intermediate £19,430 21% £4,080.30
Higher £6,570 41% £2,693.70
Total £38,150 £9,184.00

Fix

Check every Scottish limit and rate for 2018-19, 2019-20, 2020-21 and 2021-22 against that year's resolution, correct the file, cite the resolution on each dated value, and add YAML cases worked from each resolution.

🤖 Generated with Claude Code

Activity

  1. added
    bugSomething isn't working
    parametersAdding, changing or removing model parameters
    on Nov 1, 2026
  2. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    Picking this up for the UK hub. Reply here if you're mid-change and I'll stop.


    Generated by Claude Code

  3. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    PR_TITLE.txt

    Set Scottish income tax limits and rates for 2018-19 to 2021-22
    

    Generated by Claude Code

  4. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    PR_BODY.md

    Fixes #2783
    
    Reproducer: policyengine_uk/tests/test_scottish_income_tax_rates.py::test_scottish_rates_and_limits_match_each_resolution, policyengine_uk/tests/policy/baseline/finance/tax/income_tax/scottish_rate_resolutions.yaml
    
    axiom: hub fills this line
    
    ## What and why
    
    Each year the Scottish Parliament sets the Scottish rates and limits for one tax year by a Scottish rate resolution (Scotland Act 1998 s. 80C(1) and (3)). PolicyEngine applied different rates or limits in four tax years:
    
    | Tax year | Motion | Value | Before | After |
    |---|---|---|---|---|
    | 2018-19 | S5M-10397 (c), (d) | Intermediate rate limit, where the higher rate starts | £31,500 | £31,580 |
    | 2018-19 to 2020-21 | S5M-10397, S5M-15879, S5M-21090 (d) | Higher rate | 40% | 41% |
    | 2019-20 | S5M-15879 (a)-(c) | Starter, basic and intermediate rate limits | £2,000, £12,150, £31,500 | £2,049, £12,444, £30,930 |
    | 2020-21 | S5M-21090 (a)-(c) | Starter, basic and intermediate rate limits | £2,000, £12,150, £31,500 | £2,085, £12,658, £30,930 |
    | 2021-22 | S5M-24225 (a), (b) | Starter and basic rate limits | £2,306, £13,991 | £2,097, £12,726 |
    
    PolicyEngine applied 40% through 2020-21, reused the 2018-19 limits in 2019-20 and 2020-21, and applied the 2024-25 starter and basic rate limits (S6M-12252) in 2021-22. The 2017-18 (S5M-03912), 2022-23 (S6M-03019) and 2023-24 (S6M-07853) values already matched, and so did 2024-25 to 2026-27.
    
    I cite Parliament's motion and paragraph beside each rate and limit from 2017-18 to 2026-27, and list every resolution from 2017-18 in the references. I left the personal allowance, the rest-of-UK rates, the uprating metadata, the announced freeze through 2028-29 and every formula as they were, and moved Scottish income tax's verified start year in `programs.yaml` from 2019 to 2017.
    
    I merged #2350 (at `e5b16d14bbf9456945986565798902b720d222f3`) into this branch, and the PR opens after #2350 lands. Without #2350, the model with the correct limits denies the Marriage Allowance to Scottish couples the law gives it to. Over `e5b16d14b`, this branch changes five files: `rates.yaml`, `programs.yaml`, `changelog.d/2783.fixed.md`, `scottish_rate_resolutions.yaml` and `test_scottish_income_tax_rates.py`. It changes no formula.
    
    ### Limits of income after the allowance
    
    In the 2017-18 to 2023-24 motions, Parliament charges each rate on "income" up to a limit. It sets those limits on income after the personal allowance: ITA 2007 s. 23 takes the allowance off at Step 3 and charges tax at each rate at Step 4. From 2024-25 Parliament says so in the motions, where s. 11A charges this income "above the personal allowance". I kept the limits in that form in `rates.yaml`, as before.
    
    The Scottish Government's table for each year (gov.scot) adds the standard personal allowance to each limit, and its figures agree with the limits in every year from 2017-18 to 2026-27. s. 35(2) withdraws £1 of the allowance for every £2 of income above £100,000, so none remains at the £150,000 and £125,140 tops, and there gov.scot's figure equals the limit. For example:
    
    | Tax year | Allowance | Resolution limits (income after the allowance) | gov.scot band tops (income) |
    |---|---|---|---|
    | 2018-19 | £11,850 | £2,000, £12,150, £31,580, £150,000 | £13,850, £24,000, £43,430, £150,000 |
    | 2019-20 | £12,500 | £2,049, £12,444, £30,930, £150,000 | £14,549, £24,944, £43,430, £150,000 |
    | 2020-21 | £12,500 | £2,085, £12,658, £30,930, £150,000 | £14,585, £25,158, £43,430, £150,000 |
    | 2021-22 | £12,570 | £2,097, £12,726, £31,092, £150,000 | £14,667, £25,296, £43,662, £150,000 |
    
    `test_scottish_limits_plus_personal_allowance_match_gov_scot_tables` checks this for every year against the model's personal allowance.
    
    ### Model years
    
    `convert_to_fiscal_year_parameters` (`policyengine_uk/utils/parameters.py`) gives each year label the value in force on 30 April, so a value dated 6 April reaches that year's label: 2019-04-06 sets model year 2019 (2019-20). The tests read the model year. They also read the statutory tree on 6 April, on every date `rates.yaml` gives within the tax year and on the following 5 April, and check that no rate or limit takes effect before 6 April 2017.
    
    ### Readers
    
    Ten files read this scale: `earned_income_tax`, `property_income_tax`, `tax_band`, `meets_marriage_allowance_income_conditions` (from #2350), `uc_income_tax` (`policyengine_uk/utils/uc_income_tax.py`), `uc_minimum_income_floor_income_tax`, `private_pension_contributions_tax`, `carers_allowance_earnings`, `esa_exempt_work_earnings` and `pension_credit_self_employment_deductions`. They read brackets by position, and `first_extended_scottish_threshold` (`policyengine_uk/utils/income_tax.py`) and `tax_band` tell the schedules apart by their number of limits. I kept the order and number of brackets in every year (three in 2017-18, five from 2018-19 to 2023-24, six from 2024-25), so each reader still finds each band where it was. On `main`, `tax_band` puts a Scottish taxpayer in the band above once their taxable income, savings included, reaches a Scottish limit (`income < threshold`), and the Marriage Allowance test reads that band. #2350 replaces both.
    
    ### What moves
    
    I compared this branch at `9f2863f4573735df82847c341db8d590d510d448` with #2350 at `e5b16d14bbf9456945986565798902b720d222f3` for 2015 to 2030, using 930 hand-built units. I built 465 single adults and 465 married couples with a spouse earning £5,000, across Scotland, the North East and Wales. Each adult earns one of 31 pay amounts from £0 to £160,000, with pay alone or with £5,000 of dividends, £5,000 or £29,500 of savings, or £10,000 of property income.
    
    - `income_tax` and `household_net_income` move only in Scotland, and only in 2018 (138 of 930 units), 2019 (251), 2020 (252) and 2021 (243). Nothing moves in 2015-2017 or 2022-2030, or outside Scotland.
    - On pay alone, for a single adult:
    
      | Pay | 2018-19 | 2019-20 | 2020-21 | 2021-22 |
      |---|---|---|---|---|
      | £20,000 | 0 | −£0.49 | −£0.85 | +£2.09 |
      | £35,000 | 0 | −£3.43 | −£5.93 | +£14.74 |
      | £43,430 | −£15.20 | −£3.43 | −£5.93 | +£14.74 |
      | £50,000 | +£50.50 | +£170.57 | +£168.07 | +£14.74 |
      | £100,000 | +£550.50 | +£670.57 | +£668.07 | +£14.74 |
      | £150,000 and £160,000 | +£1,169.00 | +£1,295.57 | +£1,293.07 | +£14.74 |
    
    - Parliament charges a Scottish taxpayer's savings and dividends at the rest-of-UK rates (ITA 2007 ss. 11D(6) and 13(5)), and `savings_income_tax` and `dividend_income_tax` move in no unit, single or couple, in any year. `earned_income_tax` moves (130, 240, 240 and 230 units in 2018 to 2021), and so does `property_income_tax` (30, 46, 50 and 22), since property income bears the Scottish rates. In 2018-19 a single adult with £45,000 of pay pays £0.50 more on the pay (£7,133.50 to £7,134.00), and £5,000 of dividends or savings on top still costs £975 or £1,630. With £10,000 of property income instead, the tax on the property income rises from £4,000 to £4,100, and their income tax by £100.50, from £11,133.50 to £11,234.00.
    - `tax_band` moves for Scottish taxpayers between an old and a new limit: 2 units in 2018, 30 in 2019, 32 in 2020 and 8 in 2021.
    - The Marriage Allowance changes in 19 couple-year observations across 10 of the couples. In 2018-19 the earner with £43,430 of pay (£31,580 after the allowance) gains £1,190: their last pounds now stay at the intermediate rate. In 2019-20 and 2020-21, the same nine earners in each year, with £30,931 to £31,500 after the allowance (pay of £43,431, £43,662 or £44,000, alone or with £5,000 of dividends or savings), lose £1,250: their last pounds now bear the 41% higher rate, which s. 55B(2)(b) doesn't list.
    
    I used no survey data, so the run gives no aggregate.
    
    ### Marriage Allowance and #2350
    
    #2350 replaces `main`'s band test. Its `tax_band` bands a Scottish taxpayer's non-savings income on the Scottish limits and their savings and dividends on the rest-of-UK limits, and keeps income at a limit in the band below. Its Marriage Allowance test no longer reads `tax_band`: it applies the rate conditions of ITA 2007 s. 55B(2)(b) and (ba) to the person's income directly. Without it, the model with the correct limits denies the allowance to couples the law gives it to. On this PR's own commits before the merge (`eff166f46`), a 2019-20 Scottish earner with £43,430 of pay (£30,930 after the allowance, exactly the intermediate rate limit) and a spouse on £5,000 lands in the higher band and gets no allowance. So does an earner with £14,000 of pay and £29,500 of savings, whose savings stay inside the £37,500 basic rate limit.
    
    The couple cases in `scottish_rate_resolutions.yaml` cover 2018-19, 2019-20 and 2020-21. An earner at the limit, and one with pay and savings, can receive the allowance, and one £1 above the limit cannot. Twin couples who make no election pay tax at the rates Parliament set. At `eff166f46` all five claim cases at the limit or with savings fail.
    
    The model takes the transferred amount off the earner's income (#1947). Parliament instead gives the earner a tax reduction of 20% of it, at Step 6 of s. 23 (s. 55A(2), s. 55B(1) and (3)). So a claiming couple's income tax on this branch differs from the law's. The model column comes from the differential at `9f2863f45`, where each spouse pays no tax and the earner receives the transfer shown:
    
    | Tax year | Earner's income | Law | This branch | Difference |
    |---|---|---|---|---|
    | 2018-19 | £43,430 of pay | £6,252.30 | £6,240.40 | −£11.90 |
    | 2019-20 | £43,430 of pay | £6,100.37 | £6,087.87 | −£12.50 |
    | 2019-20 | £14,000 of pay, £29,500 of savings | £5,035.00 | £4,797.50 | −£237.50 |
    | 2020-21 | £43,430 of pay | £6,097.87 | £6,085.37 | −£12.50 |
    | 2020-21 | £14,000 of pay, £29,500 of savings | £5,035.00 | £4,797.50 | −£237.50 |
    
    The tests assert eligibility, the amount transferred and the tax before the reduction, and none of these totals.
    
    ## Invariants
    
    For every input:
    
    1. PolicyEngine gives each model year from 2017 to 2026 the rates and limits of that year's resolution, in the same order.
    2. The statutory tree gives each resolution's rates and limits on every day from 6 April to the following 5 April (s. 80C(3)(b), ITA 2007 s. 4(3)), and dates no Scottish rate or limit before 6 April 2017.
    3. Each limit plus the year's standard personal allowance equals gov.scot's figure where the allowance applies in full; above £100,000 plus twice the allowance, the limit equals gov.scot's figure.
    4. A Scottish taxpayer's income tax on pay alone equals the resolution's bands applied to pay less the personal allowance (s. 35), in every year from 2017-18 to 2026-27.
    5. A Scottish taxpayer's income tax on pay alone never falls as pay rises.
    6. No taxpayer outside Scotland, and no year outside 2018-19 to 2021-22, moves.
    
    `test_scottish_income_tax_rates.py` tests 1 to 3 on every year, and 4 and 5 with Hypothesis on hand-built Scottish taxpayers: 4 examples of 48 people with any whole-pound pay up to £320,000, each read in all ten years. The differential above checks 6.
    
    ## Test plan
    
    - [x] For policy changes: YAML tests whose expected values come from an external source: `scottish_rate_resolutions.yaml`, 41 cases worked by hand. 29 cover pay alone, from S5M-10397, S5M-15879, S5M-21090 and S5M-24225 with each year's personal allowance, among them the issue's £50,000 of pay in 2018-19 (£9,184.00; `main` gives £9,133.50). 12 cover married couples, from those motions and ITA 2007 ss. 10, 12, 12A, 12B, 35 and 55B.
    - [x] `ruff format --check .`, `ruff check .` and `ruff check --select F811,F821 policyengine_uk` (0.17.2) pass at `9f2863f45`; `git diff --check` reports nothing.
    - [x] Changelog fragment `changelog.d/2783.fixed.md`.
    
    I ran these locally without survey data, at `9f2863f4573735df82847c341db8d590d510d448`:
    
    | Tests | Result |
    |---|---|
    | `scottish_rate_resolutions.yaml` | 41 passed |
    | `test_scottish_income_tax_rates.py`, with its Hypothesis test | 40 passed |
    | #2350's `scottish_rates.yaml` | 36 passed |
    | `meets_marriage_allowance_income_conditions.yaml` | 26 passed |
    | `policyengine_uk/tests/policy/reforms/cps/marriage_tax_reforms.yaml` | 10 passed |
    | #2350's `test_tax_band_scottish_properties.py` (2017 to 2026) | 5 passed |
    | `test_uc_minimum_income_floor_properties.py::test_notional_deductions_equal_tax_on_threshold_as_only_income`, alone | 1 passed in 222 s (231 s wall, including start-up) |
    | `test_uc_minimum_income_floor_properties.py::test_more_earnings_never_lower_earned_income_or_raise_uc`, alone | 1 passed in 86 s (93 s wall) |
    | `policyengine_uk/tests/code_health` | 3,086 passed |
    
    The file's other four UC properties (`test_floor_never_lowers_earned_income`, `test_floor_holds_for_singles_and_couples`, `test_top_up_never_takes_couple_above_couple_threshold`, `test_matches_closed_form_of_regulation_62`) passed at `d07335b96`, before #2350 moved; I didn't rerun them. CI runs the file.
    
    On #2350 alone (`e5b16d14b`, with this branch's two test files copied in), `scottish_rate_resolutions.yaml` gives 28 failures and 13 passes, and `test_scottish_income_tax_rates.py`, without its Hypothesis test, gives 12 failures (the 2018 to 2021 rows of the table, whole-year and gov.scot tests) and 27 passes. The cases that pass sit where the two schedules agree, such as £1,000 of taxable income at 19%, or where the old limits give the same answer, such as the couples with pay of £14,000 and savings of £29,500.
    
    I ran these at `eff166f46`, before merging #2350:
    
    | Tests | Result |
    |---|---|
    | `tests/policy/baseline/finance/tax/income_tax/` | 58 passed |
    | `tests/policy/baseline/gov/hmrc/income_tax/` | 472 passed, in two runs (the first stopped at its 25-minute limit inside `gift_aid.yaml`, which the second ran whole) |
    | The 13 YAML files outside the income tax folders that mention Scotland and a year from 2018 to 2021 (Universal Credit unearned income and minimum income floor, early years entitlement, Scottish Child Payment, Carer Support Payment, council tax reduction, energy bills rebate, LBTT, LTT, SDLT, Housing Benefit, household) | 151 passed |
    | `test_pension_annual_allowance_properties.py::test_charge_matches_band_by_band_reference` (2020 among its years) | 1 passed |
    | `test_pension_credit_earnings_disregard_properties.py::test_scottish_basic_rate_band_is_twenty_percent_in_every_year` | 1 passed |
    
    No existing test's expected value changes.
    
    I checked that the new tests reject scratch mutations of `rates.yaml`, none of them committed. At `9f2863f45`, a harness copies the tree, applies each mutation, and runs the table, whole-year, gov.scot and first-date tests (34 tests, all passing on the unmutated copy). Every one of 64 mutations fails:
    
    - each of the 41 finite limits raised by £1;
    - each year's 6 April values moved to 1 April, for 2017-18 to 2022-23;
    - a £2,050 starter rate limit for July 2019 only;
    - each of the 11 dated rates raised by one point;
    - a higher rate of 42% for July 2019 only;
    - one-day basic rate limit changes on 7 April 2019, 5 April 2020 and 5 April 2027;
    - the 2017-18 values copied to 6 April 2016.
    
    The July 2019 limit and rate fail `test_each_resolution_holds_for_its_whole_tax_year[2019]`, the 1 April 2017 start and the 2016 copy fail `test_scottish_rates_take_effect_on_6_april_2017`, and the 1 April moves for 2018-19 to 2022-23 fail the previous year's whole-year test. At `eff166f46` four more mutations each fail both the table tests and the YAML cases: the higher rate back at 40% until 2021-22 fails 6 table tests and 6 YAML cases; the 2018-19 limit kept for 2019-20 fails 3 and 2; `main`'s 2021-22 basic rate limit fails 3 and 3; and the 2019-20 limits dated 1 May fail 3 and 7. The Hypothesis test catches the third change too, failing in 2021.
    
    ## Merge notes
    
    `git merge-tree --write-tree --name-only` from `9f2863f4573735df82847c341db8d590d510d448`, in both orders:
    
    | Other head | Result |
    |---|---|
    | `main` at `6c6e229a51fbd804dab2062dac1f94af04c609c2` | Clean, same tree in both orders. |
    | #2670 at `3f67112c834ae8b26c42357fe7e7854657bd2a94` | Clean, same tree in both orders. |
    | #2106's `claude/fix-2106-allowance-order-979j96` at `def18d3a3043d1b65e74de2a69b169d4f0a550db` | Conflict in `policyengine_uk/variables/gov/hmrc/income_tax/bracketized_earned_income/non_savings_income_after_allowances.py`. |
    | #1963 at `69c919f6054023fa5a51b42d1ffbbcb0078e8082` | Conflicts in seven files under `policyengine_uk/`: `parameters/gov/hmrc/income_tax/income_tax_subtractions.yaml`, `reforms/cps/marriage_tax_reforms.py`, `tests/policy/baseline/gov/hmrc/income_tax/allowances/meets_marriage_allowance_income_conditions.yaml`, `tests/test_marriage_allowance_cps_switch.py`, `variables/gov/hmrc/income_tax/allowances/meets_marriage_allowance_income_conditions.py`, `variables/gov/hmrc/income_tax/allowances/uses_property_allowance.py` and `variables/gov/hmrc/income_tax/reliefs/property_finance_cost_relief.py`. |
    
    #2350's head `e5b16d14b` alone gives the same conflicting files against #2106 and #1963, so they come from #2350, not from this branch's five files.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    

    Generated by Claude Code

  5. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    AXIOM_ISSUE.md

    Title: Encode the Scottish income tax rates and limits for 2017-18 to 2023-24 from each year's Scottish rate resolution
    
    Labels: pe-parity
    
    PolicyEngine UK corrects its Scottish income tax rates and limits for 2018-19 to 2021-22 in PolicyEngine/policyengine-uk#2783 (PR: <PE PR>; branch head `9f2863f4573735df82847c341db8d590d510d448`), and checks every year from 2017-18 to 2026-27 against that year's Scottish rate resolution. rulespec-uk does not yet hold any of the years before 2026-27.
    
    On `main` (`0497350`), only `uk/statutes/income_tax/individual/scottish_income_tax_oracle_pipeline.yaml` carries Scottish rates. Its six rates and five limits each have one version, `effective_from: '2026-01-01'`, sourced from the 2026-27 resolution (`uk/guidance/scottish-government/scottish-rate-resolution-2026-27/draft-motion/block-1`). No module encodes the resolutions for 2017-18 to 2025-26.
    
    ## Blocker
    
    The pinned corpus holds no Scottish rate resolution before 2026-27, so the signed encoder cannot cite any year this issue asks for. A maintainer also approves each billed run of the encoder, and nobody writes RuleSpec by hand.
    
    We checked over plain HTTP:
    
    - rulespec-uk `main` (`0497350d74f9d0f7272b88e08019e66366d3be58`) pins, in `.axiom/toolchain.toml`, release `uk-rulespec-2026-10-25-pe-child-definitions` with content sha256 `76b42156de23f2c000dab657dafacfb5b96ec9fe5670a75563574bd00793048c`.
    - That release's manifest, `manifests/releases/uk-rulespec-2026-10-25-pe-child-definitions.json` in axiom-corpus at `9d174d88119fd6e6ace02d7c94eec7bb47e363f0`, lists 35 UK scopes. We did not retrieve the signed release object itself, so we have not checked its content against the pinned sha256.
    - Those scopes' corpus locks name 35 UK provision files. We fetched the 33 that carry a git blob, and each matched the sha256 its lock records. The other two (`statute` and `regulation` `2026-09-28-uk-pe-child-definitions`) have no git blob in their lock, so we could not fetch them; their scopes hold definitions of a child and a couple.
    - The 33 files hold rows for ITA 2007 ss. 6, 10, 11A, 23 and 35, Scotland Act 1998 s. 80C, and one Scottish rate resolution, `uk/guidance/scottish-government/scottish-rate-resolution-2026-27/draft-motion/block-1`. They hold no row for any resolution from 2017-18 to 2025-26.
    - axiom-corpus's ingest manifest `manifests/uk-scottish-rate-resolution-official-documents.yaml` lists one document, the 2026-27 draft motion. The pinned release leaves out the `2026-07-05-uk-scottish-income-tax` scope, which ingested it.
    
    Axiom can unblock this by ingesting the seven motions below (parliament.scot motion pages, or gov.scot's draft motions for the same years), pinning a release that holds them and approving an encoder run.
    
    ## Provisions
    
    | Proposed module path | Corpus citation (for Axiom to ingest) | What it adds |
    |---|---|---|
    | `uk/statutes/income_tax/individual/scottish_income_tax_oracle_pipeline.yaml` (existing) | One row per resolution, for example `uk/guidance/scottish-parliament/scottish-rate-resolution-2018-19/motion/S5M-10397`, with `uk/statute/ukpga/1998/46/80C` and `uk/statute/ukpga/2007/3/11A` | A dated version of each Scottish rate and limit for each tax year from 2017-18 to 2023-24, and the three-rate schedule of 2017-18 |
    
    We propose these paths and citation paths as targets; the corpus sets the citation paths and the encoder binds the source expressions. rulespec-uk lacks 2024-25 (S6M-12252) and 2025-26 (S6M-16531) too; the same ingest can carry them.
    
    ### The law
    
    Scotland Act 1998 s. 80C(1): "The Scottish Parliament may by resolution (a “Scottish rate resolution”) set the Scottish basic rate, and any other rates, for the purposes of section 11A of the Income Tax Act 2007". (3): "A Scottish rate resolution applies— (a) for only one tax year, and (b) for the whole of that year." ITA 2007 s. 4(3): "A tax year begins on 6 April and ends on the following 5 April."
    
    The Scottish Parliament agreed each motion below on the date shown (parliament.scot, Votes and motions, "Result ... Agreed"). Quoted from https://www.parliament.scot/chamber-and-committees/votes-and-motions/<motion>:
    
    - 2017-18, S5M-03912, agreed 21 February 2017: "That the Parliament agrees that, for the purposes of section 11A of the Income Tax Act 2007 (which provides for income tax to be charged at Scottish rates on certain non-savings and non-dividend income of a Scottish taxpayer), the Scottish rates and limits for the tax year 2017-18 are as follows— (a) the Scottish basic rate is 20%, charged on income up to a Scottish basic rate limit of £31,500, (b) a Scottish higher rate of 40%, charged on income above that Scottish basic rate limit and up to a Scottish higher rate limit of £150,000, and (c) a Scottish additional rate of 45%, charged on income above that Scottish higher rate limit."
    - 2018-19, S5M-10397, agreed 20 February 2018: "... the Scottish rates and limits for the tax year 2018-19 are as follows— (a) a starter rate of 19%, charged on income up to a limit of £2,000, (b) the Scottish basic rate is 20%, charged on income above £2,000 and up to a limit of £12,150, (c) an intermediate rate of 21%, charged on income above £12,150 and up to a limit of £31,580, (d) a higher rate of 41%, charged on income above £31,580 and up to a limit of £150,000, and (e) a top rate of 46%, charged on income above £150,000."
    - 2019-20, S5M-15879, agreed 19 February 2019: "... the Scottish rates and limits for the tax year 2019-20 are as follows— (a) a starter rate of 19%, charged on income up to a limit of £2,049, (b) the Scottish basic rate is 20%, charged on income above £2,049 and up to a limit of £12,444, (c) an intermediate rate of 21%, charged on income above £12,444 and up to a limit of £30,930, (d) a higher rate of 41%, charged on income above £30,930 and up to a limit of £150,000, and (e) a top rate of 46%, charged on income above £150,000."
    - 2020-21, S5M-21090, agreed 4 March 2020: "... the Scottish rates and limits for the tax year 2020-21 are as follows— (a) a starter rate of 19%, charged on income up to a limit of £2,085, (b) the Scottish basic rate is 20%, charged on income above £2,085 and up to a limit of £12,658, (c) an intermediate rate of 21%, charged on income above £12,658 and up to a limit of £30,930, (d) a higher rate of 41%, charged on income above £30,930 and up to a limit of £150,000, and (e) a top rate of 46%, charged on income above £150,000."
    - 2021-22, S5M-24225, agreed 25 February 2021: "... the Scottish rates and limits for the tax year 2021-22 are as follows— (a) a starter rate of 19%, charged on income up to a limit of £2,097, (b) the Scottish basic rate is 20%, charged on income above £2,097 and up to a limit of £12,726, (c) an intermediate rate of 21%, charged on income above £12,726 and up to a limit of £31,092, (d) a higher rate of 41%, charged on income above £31,092 and up to a limit of £150,000, and (e) a top rate of 46%, charged on income above £150,000."
    - 2022-23, S6M-03019, agreed 2 February 2022: "... the Scottish rates and limits for the tax year 2022-23 are as follows— (a) a starter rate of 19%, charged on income up to a limit of £2,162, (b) the Scottish basic rate is 20%, charged on income above £2,162 and up to a limit of £13,118, (c) an intermediate rate of 21%, charged on income above £13,118 and up to a limit of £31,092, (d) a higher rate of 41%, charged on income above £31,092 and up to a limit of £150,000, and (e) a top rate of 46%, charged on income above £150,000."
    - 2023-24, S6M-07853, agreed 9 February 2023: "That the Parliament agrees that, for the purposes of section 11A of the Income Tax Act 2007 (which provides for Income Tax to be charged at Scottish rates on certain non-savings and non-dividend income of a Scottish taxpayer), the Scottish rates and limits for the tax year 2023-24 are as follows— (a) a starter rate of 19%, charged on income up to a limit of £2,162, (b) the Scottish basic rate is 20%, charged on income above £2,162 and up to a limit of £13,118, (c) an intermediate rate of 21%, charged on income above £13,118 and up to a limit of £31,092, (d) a higher rate of 42%, charged on income above £31,092 and up to a limit of £125,140, and (e) a top rate of 47%, charged on income above £125,140."
    
    The 2018-19 to 2022-23 motions open with the same words as the 2017-18 one, which we shorten here to "...".
    
    In these seven motions Parliament charges each rate on "income" up to a limit. ITA 2007 s. 23 deducts the personal allowance at Step 3 and charges tax at each rate at Step 4, so the limits apply to income after the allowance. From 2024-25 Parliament says so in the motions' opening words: S6M-12252 describes s. 11A as charging certain income of a Scottish taxpayer "to be charged above the personal allowance". gov.scot's table for each year adds the standard personal allowance to each limit; for 2018-19, for example, "Over £11,850 to £13,850", "Over £13,850 to £24,000", "Over £24,000 to £43,430", "Over £43,430 to £150,000" and "Over £150,000", with an £11,850 allowance (https://www.gov.scot/publications/scottish-income-tax-2018-2019/). The £150,000 and £125,140 tops carry no allowance: s. 35(2) withdraws £1 of it for every £2 of income above £100,000, so none remains there, and gov.scot's figure equals the limit.
    
    ### Required outputs
    
    For each tax year from 2017-18 to 2023-24, dated from 6 April of that year (s. 80C(3), ITA 2007 s. 4(3)):
    
    - the rates: `uk_scotpit_starter_rate`, `uk_scotpit_basic_rate`, `uk_scotpit_intermediate_rate`, `uk_scotpit_higher_rate` and the top rate (the "Scottish additional rate" in 2017-18), with no starter or intermediate rate in 2017-18;
    - the limits of income at which each rate stops;
    - the income tax a Scottish taxpayer pays on non-savings, non-dividend income, from those bands applied to income less the personal allowance.
    
    | Tax year | Motion | Starter 19% to | Basic 20% to | Intermediate 21% to | Higher rate and limit | Top rate |
    |---|---|---|---|---|---|---|
    | 2017-18 | S5M-03912 | none | £31,500 | none | 40% to £150,000 | 45% |
    | 2018-19 | S5M-10397 | £2,000 | £12,150 | £31,580 | 41% to £150,000 | 46% |
    | 2019-20 | S5M-15879 | £2,049 | £12,444 | £30,930 | 41% to £150,000 | 46% |
    | 2020-21 | S5M-21090 | £2,085 | £12,658 | £30,930 | 41% to £150,000 | 46% |
    | 2021-22 | S5M-24225 | £2,097 | £12,726 | £31,092 | 41% to £150,000 | 46% |
    | 2022-23 | S6M-03019 | £2,162 | £13,118 | £31,092 | 41% to £150,000 | 46% |
    | 2023-24 | S6M-07853 | £2,162 | £13,118 | £31,092 | 42% to £125,140 | 47% |
    
    ### review_finding (paste as-is)
    
    > Encode the Scottish rate resolutions for 2017-18 to 2023-24 (corpus uk/guidance/scottish-parliament/scottish-rate-resolution-<year>/motion/<motion>, once ingested: S5M-03912, S5M-10397, S5M-15879, S5M-21090, S5M-24225, S6M-03019, S6M-07853) as dated versions in the existing module uk/statutes/income_tax/individual/scottish_income_tax_oracle_pipeline.yaml. That module carries one version of each rate and limit, effective_from 2026-01-01, sourced from the 2026-27 resolution, and none for an earlier year. The Scottish Parliament adopts each resolution for one whole tax year under Scotland Act 1998 s. 80C(3). Each tax year runs from 6 April to the following 5 April (ITA 2007 s. 4(3)); date each year's version from 6 April and keep the existing 2026-27 version. Entity Person. Parliament charges each rate on "income" up to a limit; ITA 2007 s. 23 deducts the personal allowance at Step 3 before it charges the rates at Step 4, so apply the limits to income less the personal allowance. Income exactly at a limit bears the lower rate ("up to a limit of"); income above it bears the next. Do not hand-write RuleSpec.
    >
    > Required outputs, each with verbatim proof excerpts from the corpus provision:
    >
    > 1. Rates (Rate): for 2018-19 to 2023-24, the starter, Scottish basic, intermediate, higher and top rates in paragraphs (a) to (e); for 2017-18, the Scottish basic rate of 20%, higher rate of 40% and additional rate of 45% in paragraphs (a) to (c), with no starter or intermediate rate.
    > 2. Limits (Money): for each year, the limit of income at which each rate stops, as the table in this issue gives it, from the same paragraphs.
    > 3. Scottish income tax on non-savings, non-dividend income (Money): the bands applied to that income less the personal allowance. The pipeline's `uk_scotpit_personal_allowance_base_amount` has one version, from 2026-01-01. Use dated allowances of £11,500 (2017-18), £11,850 (2018-19), £12,500 (2019-20 and 2020-21) and £12,570 (2021-22 to 2023-24), with the s. 35(2) and (3) taper. Ingest ITA 2007 s. 35(1) for those years.
    
    ### Companion tests
    
    Each case has one Scottish taxpayer aged 40 with pay only. We worked each value by hand from the motions above, with each year's standard personal allowance: £11,500 in 2017-18 (gov.scot's 2017-18 table, https://www.gov.scot/news/income-tax-rates-set/), and from ITA 2007 s. 35(1) as at 6 April of each later year, £11,850 in 2018-19, £12,500 in 2019-20 and 2020-21 and £12,570 from 2021-22. s. 35(2) withdraws £1 of the allowance for every £2 of income above £100,000, and s. 35(3) rounds what remains up to a whole pound, so none remains at £130,000 and above. The 2018-19 to 2021-22 cases match the pay cases in policyengine-uk's `policyengine_uk/tests/policy/baseline/finance/tax/income_tax/scottish_rate_resolutions.yaml`; `policyengine_uk/tests/test_scottish_income_tax_rates.py` checks the other years against the same motions. Both files pass at `9f2863f4573735df82847c341db8d590d510d448`.
    
    | Tax year | Pay | Taxable income | Income tax | Working |
    |---|---|---|---|---|
    | 2017-18 | £43,000 | £31,500 | £6,300.00 | 31,500 x 20% |
    | 2017-18 | £43,001 | £31,501 | £6,300.40 | 6,300 + 1 x 40% |
    | 2017-18 | £50,000 | £38,500 | £9,100.00 | 6,300 + 7,000 x 40% |
    | 2017-18 | £160,000 | £160,000 | £58,200.00 | 6,300 + 118,500 x 40% + 10,000 x 45% |
    | 2018-19 | £13,000 | £1,150 | £218.50 | 1,150 x 19% |
    | 2018-19 | £20,000 | £8,150 | £1,610.00 | 380 + 6,150 x 20% |
    | 2018-19 | £30,000 | £18,150 | £3,670.00 | 380 + 2,030 + 6,000 x 21% |
    | 2018-19 | £43,430 | £31,580 | £6,490.30 | 380 + 2,030 + 19,430 x 21% |
    | 2018-19 | £50,000 | £38,150 | £9,184.00 | 6,490.30 + 6,570 x 41% |
    | 2018-19 | £160,000 | £160,000 | £59,642.50 | 6,490.30 + 118,420 x 41% + 10,000 x 46% |
    | 2019-20 | £13,500 | £1,000 | £190.00 | 1,000 x 19% |
    | 2019-20 | £14,549 | £2,049 | £389.31 | 2,049 x 19% |
    | 2019-20 | £20,000 | £7,500 | £1,479.51 | 389.31 + 5,451 x 20% |
    | 2019-20 | £24,944 | £12,444 | £2,468.31 | 389.31 + 10,395 x 20% |
    | 2019-20 | £35,000 | £22,500 | £4,580.07 | 2,468.31 + 10,056 x 21% |
    | 2019-20 | £43,430 | £30,930 | £6,350.37 | 2,468.31 + 18,486 x 21% |
    | 2019-20 | £50,000 | £37,500 | £9,044.07 | 6,350.37 + 6,570 x 41% |
    | 2019-20 | £160,000 | £160,000 | £59,769.07 | 6,350.37 + 119,070 x 41% + 10,000 x 46% |
    | 2020-21 | £13,500 | £1,000 | £190.00 | 1,000 x 19% |
    | 2020-21 | £14,585 | £2,085 | £396.15 | 2,085 x 19% |
    | 2020-21 | £20,000 | £7,500 | £1,479.15 | 396.15 + 5,415 x 20% |
    | 2020-21 | £25,158 | £12,658 | £2,510.75 | 396.15 + 10,573 x 20% |
    | 2020-21 | £35,000 | £22,500 | £4,577.57 | 2,510.75 + 9,842 x 21% |
    | 2020-21 | £43,430 | £30,930 | £6,347.87 | 2,510.75 + 18,272 x 21% |
    | 2020-21 | £50,000 | £37,500 | £9,041.57 | 6,347.87 + 6,570 x 41% |
    | 2020-21 | £160,000 | £160,000 | £59,766.57 | 6,347.87 + 119,070 x 41% + 10,000 x 46% |
    | 2021-22 | £13,570 | £1,000 | £190.00 | 1,000 x 19% |
    | 2021-22 | £14,667 | £2,097 | £398.43 | 2,097 x 19% |
    | 2021-22 | £20,000 | £7,430 | £1,465.03 | 398.43 + 5,333 x 20% |
    | 2021-22 | £25,296 | £12,726 | £2,524.23 | 398.43 + 10,629 x 20% |
    | 2021-22 | £35,000 | £22,430 | £4,562.07 | 2,524.23 + 9,704 x 21% |
    | 2021-22 | £50,000 | £37,430 | £8,979.67 | 2,524.23 + 18,366 x 21% + 6,338 x 41% |
    | 2021-22 | £160,000 | £160,000 | £59,733.37 | 6,381.09 + 118,908 x 41% + 10,000 x 46% |
    | 2022-23 | £14,732 | £2,162 | £410.78 | 2,162 x 19% |
    | 2022-23 | £25,688 | £13,118 | £2,601.98 | 410.78 + 10,956 x 20% |
    | 2022-23 | £43,662 | £31,092 | £6,376.52 | 2,601.98 + 17,974 x 21% |
    | 2022-23 | £50,000 | £37,430 | £8,975.10 | 6,376.52 + 6,338 x 41% |
    | 2023-24 | £43,662 | £31,092 | £6,376.52 | 2,601.98 + 17,974 x 21% |
    | 2023-24 | £50,000 | £37,430 | £9,038.48 | 6,376.52 + 6,338 x 42% |
    | 2023-24 | £130,000 | £130,000 | £48,160.88 | 6,376.52 + 94,048 x 42% + 4,860 x 47% |
    | 2023-24 | £150,000 | £150,000 | £57,560.88 | 6,376.52 + 94,048 x 42% + 24,860 x 47% |
    
    The UK Parliament sets the personal allowance, a reserved matter, outside these resolutions.
    

    Generated by Claude Code

  6. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    Ready for hub review

    Head: 9f2863f4573735df82847c341db8d590d510d448 on claude/restack-2783-scottish-rates-r0nfbo (the harness pins this session's pushes to that branch). No PR opened. I didn't push to fix-2783-scottish-rates (still 9aa5ee4c9) or fix-2350-scottish-tax-band (still e5b16d14b).

    Before starting I found no comments on #2783 and no reviews or review comments on #2828. fix-2350-scottish-tax-band got a commit about 2 h 45 min before I started (e5b16d14b, 16:17 UTC). That is the head this brief names, so I went ahead rather than stopping. Nothing has moved on either branch since.

    Commits

    Findings and resolutions

    Finding Resolution
    P3: Axiom placeholder The body's line is now axiom: hub fills this line.
    P3: couple-years The body says "The Marriage Allowance changes in 19 couple-year observations across 10 of the couples." I also added "the same nine earners in each year", which the fresh run confirms: rows 545–547, 550–552 and 555–557 in both 2019 and 2020, plus row 540 in 2018.
    P3: YAML header top-limit exception scottish_rate_resolutions.yaml:11–13 uses the review's text.
    Voice: PR_BODY.md:42 I dropped the "Position alone…" lead-in, so the paragraph now ends with the review's sentence. I checked it against main's tax_band (income < threshold on net income less allowances) and main's Marriage Allowance test (it reads tax_band), so the sentence stays true.
    Voice: PR_BODY.md:46 I used the review's text with one change: it compares with #2350 at e5b16d14b, not 3af8cb785, and names this head. The old base is stale.
    Voice: PR_BODY.md:70 I used the review's text.
    Voice: rates.yaml:11–15, :71, :92, :106–107 I used the review's text at rates.yaml:11–15, :71–72, :93–94 and :108–110.
    Voice: Python test :13–17, :129–131, :150 I used the review's text at test_scottish_income_tax_rates.py:13–17, :129–132 and the docstring at :151.
    Voice: AXIOM_ISSUE.md:69, :75 I used the review's text. For :75 I kept the opening clause saying the pipeline's allowance has one version from 2026-01-01 as its own sentence, because it explains why dated allowances are needed.

    Other edits I made because the merged tree made the old text false or stale:

    Differential

    I ran the reviewer's differential.py, verify_differential.py and differential_static_counts.py with only the paths changed. The base was e5b16d14bbf9456945986565798902b720d222f3 and the head 9f2863f4573735df82847c341db8d590d510d448. Each run printed its imported policyengine_uk/__init__.py, which confirms the scratch worktree for each tree. Both runs covered 930 units over 2015–2030.

    verify_differential.py printed PASS. Every assertion holds unchanged against the new base:

    • income_tax, unit_income_tax and household_net_income moved in 138/251/252/243 units in 2018–2021.
    • earned_income_tax moved in 130/240/240/230 units and property_income_tax in 30/46/50/22.
    • Savings and dividend tax are identical in every unit and year.
    • tax_band moved in 2/30/32/8 units.
    • The Marriage Allowance moved in 1/9/9 units: 19 couple-years across 10 couples.
    • Every result is exactly equal outside Scotland and outside 2018–2021.
    • All seven pay-only rows match the body.
    • The 2018 £45,000 component examples hold: £7,133.50 → £7,134.00; dividends £975; savings £1,630; property £4,000 → £4,100; total £11,133.50 → £11,234.00.
    • All five Marriage Allowance is booked on the recipient as the transferor's unused allowance, not the fixed s.55B transfer #1947 totals hold (£6,240.40, £6,087.87, £4,797.50, £6,085.37, £4,797.50), each with zero spouse tax and the expected transfer and eligibility.

    differential_static_counts.py gave 19 observations across 10 couples and no sub-half-penny differences.

    Tests at 9f2863f45

    Test Result
    scottish_rate_resolutions.yaml 41 passed
    scottish_rates.yaml 36 passed
    meets_marriage_allowance_income_conditions.yaml 26 passed
    reforms/cps/marriage_tax_reforms.yaml 10 passed
    test_tax_band_scottish_properties.py + test_scottish_income_tax_rates.py (with Hypothesis), one -n 4 run 45 passed (5 + 40) in 59 s
    test_notional_deductions_equal_tax_on_threshold_as_only_income, alone, no xdist 1 passed, 222 s pytest (231 s wall)
    test_more_earnings_never_lower_earned_income_or_raise_uc, alone, no xdist 1 passed, 86 s pytest (93 s wall)
    policyengine_uk/tests/code_health -n 4 3,086 passed
    ruff@0.17.2 format --check . 2,167 files already formatted
    ruff@0.17.2 check . All checks passed
    ruff@0.17.2 check --select F811,F821 policyengine_uk All checks passed
    git diff --check Clean, for both e5b16d14b..HEAD and main..HEAD

    The policyengine-core CLI collected the YAML files directly, with no bridge needed.

    Revert and mutation checks

    Revert. I took e5b16d14b's tree and copied in this branch's two test files, then confirmed that the copied tree differs from the head only in rates.yaml and programs.yaml.

    • scottish_rate_resolutions.yaml gives 28 failed / 13 passed.
    • test_scottish_income_tax_rates.py without Hypothesis gives 12 failed / 27 passed: the 2018–2021 rows of the table, whole-year and gov.scot tests.

    My first YAML attempt passed 41 because the console script imported the editable install rather than the copied tree. I discarded that run and reran with PYTHONPATH pointing at the copy, which __file__ confirms.

    Mutations. A new harness copies the head tree and applies each of 64 scratch mutations to rates.yaml, matching the list in the body. For each one it runs the table, whole-year, first-date and gov.scot tests (34 tests). The unmutated control passes 34 of 34, and all 64 mutations fail.

    • The July 2019 limit and rate mutations fail test_each_resolution_holds_for_its_whole_tax_year[2019].
    • The 1 April 2017 start and the 2016 copy fail test_scottish_rates_take_effect_on_6_april_2017.
    • The 1 April moves for 2018–2022 fail the previous year's whole-year test.
    • The one-day mutations fail the whole-year test for 2019, 2019 and 2026 respectively.

    The harness ran with -x, so it records only the first failing test for each mutation.

    Merge-tree

    I ran git merge-tree --write-tree --name-only from 9f2863f4573735df82847c341db8d590d510d448 in both orders.

    Other head Result
    main 6c6e229a51fbd804dab2062dac1f94af04c609c2 (still origin/main) Clean; same tree 2854b211… in both orders
    #2670 3f67112c834ae8b26c42357fe7e7854657bd2a94 Clean; same tree in both orders
    #2106 def18d3a3043d1b65e74de2a69b169d4f0a550db Conflicts in variables/gov/hmrc/income_tax/bracketized_earned_income/non_savings_income_after_allowances.py
    #1963 69c919f6054023fa5a51b42d1ffbbcb0078e8082 Conflicts in the 7 files listed in the body

    e5b16d14b alone conflicts in the same files against #2106 and #1963, so those conflicts are #2350's.

    Skipped

    • No survey-data tests.
    • I didn't rerun the other four UC minimum income floor properties or the broader eff166f46 suites. The body labels those as older-commit results.
    • I didn't rerun the four extra eff166f46 mutations (which ran against the table tests and YAML cases); the body labels them with that commit.
    • No CI ran, because no PR is open.

    Judgment calls


    Generated by Claude Code

  7. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    Ready for hub review

    Verdict: APPROVE WITH NITS at 9f2863f4573735df82847c341db8d590d510d448

    This reviews git diff e5b16d14bbf9456945986565798902b720d222f3..9f2863f4573735df82847c341db8d590d510d448 and git show 9f2863f45. I found no P1 or P2. The merge brings #2350 in unchanged, and the rates and limits match the seven saved resolutions. The differential, the revert and the mutation results reproduce. Three P3s concern the hub documents' figures and wording. I checked the head out detached in scratch worktrees, with HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset. I used no survey data.

    Findings

    P3: the Marriage Allowance YAML count is stale

    PR_BODY.md:114 (the table of results at 9f2863f45) says meets_marriage_allowance_income_conditions.yaml | 26 passed, and the hand-off gives the same count. That was the count at 3af8cb785. #2350 added five cases between 3af8cb785 and e5b16d14b, so the file holds 31 cases at the head, and all 31 pass.

    $ for c in 3af8cb785 9aa5ee4c9 e5b16d14b 9f2863f45; do echo "$c $(git show $c:policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/meets_marriage_allowance_income_conditions.yaml | grep -c '^- name:')"; done
    3af8cb785 26
    9aa5ee4c9 26
    e5b16d14b 31
    9f2863f45 31
    $ uv run policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/meets_marriage_allowance_income_conditions.yaml -c policyengine_uk
    ======================== 31 passed, 1 warning in 9.80s =========================
    

    Replacement for PR_BODY.md:114:

    | `meets_marriage_allowance_income_conditions.yaml` | 31 passed |
    

    The other counts in that table match #2350's files at e5b16d14b (36, 10 and 5) and my runs.

    P3: the #2106 merge note names a branch that isn't #2830's head

    PR_BODY.md:157 names #2106's claude/fix-2106-allowance-order-979j96 at def18d3a…. The brief named that head. #2830, the PR for #2106, has head branch fix-2106-allowance-order at 70633f0897957186ff9128aac91eb98ea9d7c5ec, pushed at 17:40 UTC on 2 November. def18d3a3 is an ancestor of 70633f089. The result doesn't change: both heads conflict in the same file only, and e5b16d14b alone conflicts in that same file.

    $ gh api repos/PolicyEngine/policyengine-uk/pulls/2830 --jq '"\(.head.ref) \(.head.sha)"'
    fix-2106-allowance-order 70633f0897957186ff9128aac91eb98ea9d7c5ec
    $ git merge-tree --write-tree --name-only 9f2863f4573735df82847c341db8d590d510d448 70633f0897957186ff9128aac91eb98ea9d7c5ec
    e07ec4f4fb131044fa0d9b49d3d747a44a0cf2d5
    policyengine_uk/variables/gov/hmrc/income_tax/bracketized_earned_income/non_savings_income_after_allowances.py
    ...   (exit 1; the same file in the other order and from e5b16d14b)
    

    Replacement row for PR_BODY.md:157:

    | #2830 (#2106) at `70633f0897957186ff9128aac91eb98ea9d7c5ec` | Conflict in `policyengine_uk/variables/gov/hmrc/income_tax/bracketized_earned_income/non_savings_income_after_allowances.py`. |
    

    The sentence after the table, "#2350's head e5b16d14b alone gives the same conflicting files against #2106 and #1963", stays true for both #2106 heads.

    P3: the new text still has a document or a statute as the actor in places

    The author used the previous review's replacement text, but these sentences still give the action to a website, a document or a statute, which breaks the writing rules. None of them changes a number.

    • scottish_rate_resolutions.yaml:11–13: "gov.scot adds the standard allowance to the lower limits. At the £150,000 top, s. 35(2) leaves no allowance, so gov.scot's figure equals the limit." Replacement:
      # £100,000, leaving none at £160,000. The Scottish Government's tables
      # (gov.scot) add the standard allowance to the lower limits. No allowance
      # remains at the £150,000 top (s. 35(2)), so the tables give the limit itself.
      
    • rates.yaml:9–10: "gov.scot's tables add the standard personal allowance to each limit, except at the £150,000 and £125,140 tops, where s. 35(2) leaves no allowance." Replacement:
      # The Scottish Government's tables (gov.scot) add the standard personal
      # allowance to each limit, except at the £150,000 and £125,140 tops, where no
      # allowance remains (s. 35(2)).
      
    • test_scottish_income_tax_rates.py:13: "We transcribe…" describes how the data was produced. Replacement:
      # The Scottish Parliament's rates and limits for each year, paragraph by
      # paragraph, from each motion at
      # https://www.parliament.scot/chamber-and-committees/votes-and-motions/<motion>.
      # Each pair gives the rate and the upper taxable-income limit; `None` marks the
      # final rate. Taxable income excludes the personal allowance.
      
    • PR_BODY.md:29: "The Scottish Government's table for each year (gov.scot) adds the standard personal allowance to each limit" gives the action to the table. Replacement opening: "The Scottish Government adds the standard personal allowance to each limit in its table for each year (gov.scot), and its figures agree with the limits in every year from 2017-18 to 2026-27."

    Older sentences that the delta didn't touch, such as "s. 35(1) sets the allowance" and "s. 35(2) withdraws £1…", follow the same pattern. I leave them to the hub's judgement.

    Checked and sound

    The merge. ac8a67339 has parents 9aa5ee4c9 and e5b16d14b. git diff --stat e5b16d14b ac8a67339 lists only the five files (changelog.d/2783.fixed.md, rates.yaml, programs.yaml, scottish_rate_resolutions.yaml, test_scottish_income_tax_rates.py). Each of those five blobs in ac8a67339 equals its blob in 9aa5ee4c9. That diff touches no non-test .py file, so every other file, #2350's helper and its callers included, matches e5b16d14b byte for byte. 9f2863f45 changes only comments and a docstring in three files (+25 −20).

    The previous round's findings.

    Finding Status Evidence
    P3 Axiom placeholder Closed, as the brief directs: axiom: hub fills this line (PR_BODY.md:5). The hub still files the issue. Posted body
    P3 couple-years Closed (PR_BODY.md:66). "The same nine earners in each year" holds. Differential below: rows 545–547, 550–552 and 555–557 in 2019 and 2020, and row 540 in 2018
    P3 YAML header top exception Closed in substance (scottish_rate_resolutions.yaml:11–13). The voice nit is above. git show 9f2863f45
    Voice PR_BODY.md:42 Closed. I checked the sentence against main (6c6e229a5): tax_band.formula_2018_06_01 bands Scottish taxpayers by income < threshold, where income = net_income - allowances, savings included. main's meets_marriage_allowance_income_conditions reads tax_band against eligible_bands. git show 6c6e229a:…/tax_band.py, …/meets_marriage_allowance_income_conditions.py
    Voice PR_BODY.md:46 Closed, with the base corrected to e5b16d14b. The correction is right. Posted body
    Voice PR_BODY.md:70 Closed Posted body
    Voice rates.yaml:11–15, :71, :92, :106–107 Closed at :11–15, :71–72, :93–94 and :108–110 git show 9f2863f45
    Voice Python :13–17, :129–131, :150 Closed. The "We transcribe" wording is noted above. git show 9f2863f45
    Voice YAML header :11–12 Closed git show 9f2863f45
    Voice AXIOM_ISSUE.md:69, :75 Closed. Line 69 matches the review's text. Line 75 keeps the one-version sentence and then uses the review's text. Both are accurate. Posted AXIOM_ISSUE.md

    What the author changed beyond the review's text.

    The differential. I ran the previous reviewer's differential.py unchanged, once in each tree, each with its own uv sync venv. The base venv printed …/base/policyengine_uk/__init__.py and the head venv …/head/…. PYTHONPATH doesn't override the editable install, so I used separate venvs. Each run covered 930 units over 2015–2030. verify_differential.py, with only its paths changed, printed PASS, so every assertion holds:

    • income_tax, unit_income_tax and household_net_income move in 138, 251, 252 and 243 units in 2018 to 2021, and in no unit outside Scotland or outside those years;
    • savings and dividend tax are identical in every unit and year;
    • tax_band moves in 2, 30, 32 and 8 units;
    • the Marriage Allowance moves in 1, 9 and 9 units, with 19 couple-years across 10 distinct couples;
    • the seven pay-only rows hold;
    • the 2018 £45,000 component examples hold (£7,133.50 → £7,134.00, £975, £1,630, £4,000 → £4,100, £11,133.50 → £11,234.00);
    • the five Marriage Allowance is booked on the recipient as the transferor's unused allowance, not the fixed s.55B transfer #1947 totals hold (£6,240.40, £6,087.87, £4,797.50, £6,085.37 and £4,797.50), each with zero spouse tax, the stated transfer and eligibility.

    differential_static_counts.py on the fresh arrays gives allowance_observations: 19, distinct_couples_with_allowance_change: 10 (rows 540, 545, 546, 547, 550, 551, 552, 555, 556 and 557), and no differences at or below half a penny. The body's "£30,931 to £31,500 after the allowance (pay of £43,431, £43,662 or £44,000…)" matches those rows.

    The revert. I copied the head's two test files onto e5b16d14b's worktree, ran them and restored the worktree.

    • scottish_rate_resolutions.yaml gave 28 failed and 13 passed.
    • test_scottish_income_tax_rates.py -k "not pay_is_taxed" gave 12 failed and 27 passed. The failures are the 2018 to 2021 rows of …match_each_resolution, …whole_tax_year and …gov_scot_tables.

    Both match the hand-off and PR_BODY.md:123.

    The mutations. I ran 14 mutations of rates.yaml on a separate scratch worktree, one at a time, with -k "match_each_resolution or whole_tax_year or 6_april_2017 or gov_scot". The unmutated control passed 31 of 31 (the author's 34 also includes the three thresholds_match_the_resolution cases). I restored the worktree afterwards, and git status reports it clean. All 14 mutations were killed:

    Mutation Tests that failed
    Starter limit £2,050 for July 2019 only whole_tax_year[2019]
    Higher rate 42% for July 2019 only whole_tax_year[2019]
    Every 2017-04-06 date moved to 1 April 2017 take_effect_on_6_april_2017
    2017 basic limit copied to 6 April 2016 take_effect_on_6_april_2017
    2019-04-06 dates moved to 1 April whole_tax_year[2018]
    Basic limit +£1 on 7 April 2019 only whole_tax_year[2019]
    Basic limit +£1 on 5 April 2020 whole_tax_year[2019]
    Basic limit +£1 on 5 April 2027 whole_tax_year[2026]
    2018 intermediate limit £31,581 table, whole-year and gov.scot tests [2018]
    2021 basic limit £12,727 table, whole-year and gov.scot tests [2021]
    2023 higher limit £125,141 table, whole-year and gov.scot tests [2023]
    2017 basic limit £31,501 table, whole-year and gov.scot tests [2017]
    2023 top rate 48% table and whole-year tests [2023]
    2017 higher rate 41% table and whole-year tests [2017]

    These agree with the body's statements about which tests catch the July 2019, 1 April 2017, 2016-copy, 1 April and one-day mutations.

    Tests at 9f2863f45.

    Test Result
    scottish_rate_resolutions.yaml 41 passed (29 single, 12 couple cases)
    scottish_rates.yaml 36 passed
    meets_marriage_allowance_income_conditions.yaml 31 passed
    reforms/cps/marriage_tax_reforms.yaml 10 passed
    test_tax_band_scottish_properties.py + test_scottish_income_tax_rates.py with Hypothesis, -n 4 45 passed in 64 s
    test_notional_deductions_equal_tax_on_threshold_as_only_income, alone, no xdist 1 passed in 294 s (305 s wall)
    test_more_earnings_never_lower_earned_income_or_raise_uc, alone, no xdist 1 passed in 118 s (127 s wall)
    policyengine_uk/tests/code_health -n 4 3,086 passed
    ruff@0.17.2 format --check . / check . / check --select F811,F821 policyengine_uk 2,167 files already formatted / all checks passed / all checks passed
    git diff --check e5b16d14b HEAD Clean

    The UC times differ from the body's 222 s and 86 s. That is expected on different hardware; the body labels its times as its own runs.

    Merges. I ran git merge-tree --write-tree --name-only from 9f2863f45 in both orders. The heads come from gh api repos/PolicyEngine/policyengine-uk/pulls/<n>.

    Other head Result
    main 6c6e229a51fbd804dab2062dac1f94af04c609c2 (unchanged) Clean; tree 2854b211… in both orders
    #2670 3f67112c834ae8b26c42357fe7e7854657bd2a94 Clean; tree e2fe57b1… in both orders
    #2830 (#2106) 70633f0897957186ff9128aac91eb98ea9d7c5ec Conflict in …/bracketized_earned_income/non_savings_income_after_allowances.py only
    def18d3a3043d1b65e74de2a69b169d4f0a550db The same single conflict
    #1963 69c919f6054023fa5a51b42d1ffbbcb0078e8082 The seven files in the body's merge notes, exactly
    #2828 e5b16d14bbf9456945986565798902b720d222f3 Clean (it is an ancestor)

    e5b16d14b alone gives the same conflicting files against #2830, def18d3a3 and #1963.

    AXIOM_ISSUE.md and the rates.

    • All seven quotations match the saved resolutions character for character. Five are quoted after the elided opening, and the elided opening is identical in each. The S6M-07853 quotation keeps its capital "Income Tax".
    • The agreed dates (21 February 2017, 20 February 2018, 19 February 2019, 4 March 2020, 25 February 2021, 2 February 2022 and 9 February 2023) match each motion's "Status".
    • The provisions table and every dated value in rates.yaml for 2017-18 to 2023-24 match the motions' rates and limits.
    • An independent Decimal recomputation reproduces all 41 companion rows (taxable income and tax), using the allowances the issue states and s. 35(2)/(3) as the issue quotes them.
    • The body's gov.scot band tops for 2018-19 to 2021-22 equal each limit plus the allowance, apart from the £150,000 top.

    Readers. The ten readers in PR_BODY.md:46 match grep -rl "scotland.rates" over the non-test code at the head (utils/parameters.py also matches, as the year converter).

    What I couldn't check

    • The 2024-25 quotation from S6M-12252 ("to be charged above the personal allowance") and gov.scot's 2018-19 table wording: neither source is in this brief, and I don't quote law from memory.
    • The rulespec-uk and axiom-corpus facts in AXIOM_ISSUE.md (0497350, 9d174d88, the 35 scopes, the 33 hashes): those repositories are outside this session's GitHub scope, and the delta changed none of them.
    • The eff166f46 results (broader suites, the four extra mutations, and the statement that all five claim cases fail there). I didn't rerun them; the body labels them with that commit.
    • The other four UC minimum income floor properties, which the body labels d07335b96.
    • The hand-off's logs on the hub's machine. I reran every figure above instead.
    • CI: no PR is open.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparametersAdding, changing or removing model parameters

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions