Repository navigation
Lock policyengine-uk 2.174.1 and follow what its releases change - #1511
Conversation
The UK lock moves from 2.165.1 to 2.174.1, the newest release, which carries the SOC 2020 inputs of 2.173.0 (policyengine-uk#2858), so that microcosm#1490 can stack on it (microcosm#1095). uv.lock moves policyengine-uk alone; policyengine-core stays at 3.33.3. Seven new inputs stay at their defaults and are signed in UK_ENGINE_DEFAULT_INPUTS, each with the error it can make, for María's signature: income_not_otherwise_charged (2.165.3), machine_games_standard_rate_higher_stake_net_takings (2.167.0), trading_allowance_claim_status (2.172.0, at UNKNOWN, so #1428's receipts encoding stands until microcosm#1439), soc2020_major_group and soc2020_sub_major_group (2.173.0), and is_in_full_time_education and has_education_maintenance_funding (2.173.2). From 2.174.0 Universal Credit counts each member's cash and stocks and shares ISA balances through counted_person_sources, in the holder's own benefit unit, beside the Lifetime ISA. The concept mapping's liquid-asset reason and its two lockstep tests follow. No economic assumption moves: the vendored uprating pins and the bonus-sacrifice anchor change only their version stamps. The three pyproject floors, the approved lock digest, the engine-core pin test, the concept coverage report (369 to 376 inputs), the stage dependency strings and the version mentions follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-uk 2.168.1 projects the declared energy spending by one retail energy price index per fuel (ENERGY_PRICE_SERIES under ENERGY_PRICES), from the declared period's level to the projected fiscal year's, and removes the single ENERGY_PRICE_INDEX the test imported. The end-to-end test now expects each column's own fuel ratio, calendar 2024 to the fiscal year, and no longer asks a ratio to exceed one: gas prices fall into 2025. The undeclared file still keeps its stored spending in every year. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- frs_take_up and its stage note: is_uc_eligible also needs one claimant to meet the condition of not receiving education (2.173.2), which every claimant meets, since the build flags nobody in full-time education. - uc_capital_coherence and its stage note: from 2.173.3 Council Tax Reduction counts the same property, with owned land, at the same value, and takes the Universal Credit assessment for a working-age applicant with Universal Credit. - frs_property_income: from 2.173.3 the legacy means tests and Council Tax Reduction count sublet_income, not property_income. SUBRENT stays in property_income, where income tax and HBAI income count it, until policyengine-uk-data#508 moves it after policyengine-uk#2002. - docs/uk-lisa-1003.md and docs/uk-isa-subscriptions-1415.md: from 2.174.0 the means tests count each member's ISA balances in the holder's own benefit unit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SPI income stage carries the donors' interest to the frame's year, and the SPI Table 3.7 interest targets reach the calibration year, on the OBR's savings income per person (María's ruling of 2026-10-29, microcosm#1418). policyengine-uk 2.165.2 grows a dataset's savings income by that series only where the dataset declares the rule, and by GDP per head otherwise. On María's ruling of 2026-11-04 the producer now declares obr_savings_income_2022_2025: - the adapter defines the rule, its root attribute and the engine's default, and passes the frame's rule to UKSingleYearDataset, so the stages' in-process projections (dataset_period) follow it too; - national_frame writes the rule in every H5, and a file that declares none loads as GDP per head and keeps that reading when written again; only a rule other than the producer's travels in frame metadata; - graph_terminal's export check refuses a release without the producer's rule, and the serializer registry lists the new writer. The ISA contract engine test's 2025 values move from x1.0462 (GDP per head) to x1.0032 (the OBR series per person). New tests: the engine grows a declared file by the OBR series and an undeclared one by GDP per head, and the stages' in-process projection matches; the writer and loader keep each file's rule and refuse an unknown one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release input coverage manifest follows the stage manifest's digests (the policyengine-uk floor in two stage dependencies and the two restated stage notes). The charter-H2 fixture follows the same stage texts and the adapter's source, which now declares the savings income projection rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From 2.165.2 policyengine-uk's uprating_indices.yaml projects savings_interest_income, other_investment_income and the cash interest in non-cash ISAs by the OBR's savings income per person, which the engine applies in 2022 to 2025 under the rule the producer declares, and by GDP per head in other years. SPI_HISTORY_INDEX_DIFFERS_FROM_PROJECTION names that index for both rebased columns, so the SPI income receipt's dataset_projection_index is the engine's again, and the two engine lockstep tests that compare it with the engine (test_uk_hmrc_uprating, test_uk_spi_income) pass. The two-leg savings interest concept keeps GDP per head as its projection leg: no SPI target has followed it since microcosm#1418, and the uprating test now fails if one does. No compiled target moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review of fd651b3. I read the whole diff against main at 875506e (the branch's merge base), diffed policyengine-uk 2.165.1 against 2.174.1 (variables, parameters and CHANGELOG for every release in between), and read the seven new inputs at the 2.174.1 tag. I did not run tests locally; this rests on reading the code and on CI. Must
Should
Nit
Checked and fine
CI at fd651b3: engine-free (3.13, 3.14) fails on the one test in item 1. lint, wheels, select-countries, installed-emitter, integration-uk and engine-uk (3.13, 3.14) pass. engine-us (3.13, 3.14) is still running. Verdict: the lock bump and the seven defaults hold up. Item 1 turns CI green again. Item 2 should be fixed before this leaves draft, and item 3 before María signs the defaults. 🤖 Generated with Claude Code |
…s rule - Must 1: the changelog fragment and the concept mapping cited the retired package name, which test_us_plan.py's live-tree guard refuses (engine-free red on 3.13 and 3.14). They now cite uk-data#508, uk-data#641 and uk-data#640, as the tree does; frs_spine.py too. - Should 2: the rowwise and ladder paths dropped the savings income projection rule a source H5 declares, so a clone of an undeclared file (any national written before the rule) came out declaring the producer's. rowwise_dataset now reads the rule with the ISA contract, carries it through every step and writes it; a new test ladder-clones a declared and an undeclared H5 and checks the output, the written file and its reload keep each source's rule (it fails when the H5 reader drops the rule). - Should 3: the signed error of income_not_otherwise_charged gave the relieved income as tax. Taxable income is overstated by up to the trading allowance (GBP 1,000; GBP 3,000 from 2027-28), so income tax by that amount at the person's marginal rate. - Nit 4: hmrc_calibration hands the engine the frame's rule too. Producer-built frames carry the producer's rule either way, so the national build's data does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, Vahid. All four items are in 938d4b2.
Tests at 938d4b2: the touched engine-free files ( The arms: the control on 875506e and the bump arm on fd651b3 are running. By the code, 938d4b2 changes no data in the national build: producer-built frames carry the producer's rule either way, and the other changes are text. So the bump arm measures this head's national data, and its results follow in a comment. |
…rocosm-fit (#1490) Imputing a code (an occupation group, an industry) cannot go through the quantile forest, which interpolates between donor values. microcosm-fit gains three country-agnostic pieces for it: - categorical.CategoricalClassifier: a ConditionalModel for one categorical target under the package's weight contract. It fits a regularised histogram gradient-boosted classifier, a multinomial logit on spline features (arcsinh for long-tailed amounts), or their mixture by boosted_weight (1 and 0 fit one learner), and draws by inverse CDF at caller uniforms (predict_from_uniforms), so a build's keyed uniforms fix each row's draw. It refuses a logit that does not converge, categories it never saw, and non-finite numeric predictors. - held_out: grouped stratified folds and the calibration slope (reusing the sign-gate machinery in qrf), out-of-fold probabilities for the model, each learner and the training folds' class shares, and the scores a stage records (log loss, slope, class-share gaps within strata). - donors: keyed nearest-donor choice within cells, falling back to coarser cells, chosen by weight at the caller's uniform. Refit through this API on FRS 2024-25 coded adults (five household-grouped folds), the blend reproduces the measured one: log loss 1.819 against 1.820, pay-band share gap 0.013, slope 1.01. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) frs_employment now reads adult.tab's SOC2020, which codes the SOC 2020 major group as 1000-9000 (0 undefined), and writes policyengine-uk's soc2020_major_group: the SOC2020MajorGroup member names, held once in uk_runtime/occupation_constants.py with the FRS view derived beside them. The FRS asks the employment questions of everyone who has ever worked, so the group is the main job's, or the last job's for an adult not in work (2024-25 question instructions, details of employment, p. 183; PDF p. 184). Child-table people are NONE. An adult with a blank or unknown code refuses the build, naming the codes and never a count, as EMPSTATI does; soc2020 is direct-indexed like sic. The column joins the export allow-list, and a terminal gate, uk_soc2020_major_group_enum_domain, keeps it inside the engine's enum, which policyengine-uk has from 2.173.0. The gate moves the battery's three vintage digests and the release-cut part's two (spine and calibration seam are unchanged); the coverage manifest's source digests and the H2 fixture (a SOC2020 column in the synthetic adult.tab, 30 normalised string columns) are regenerated. Checked on the real FRS 2024-25 adult.tab (digest-verified): every adult maps to its own code's group and every child-table person is NONE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…coherent industry (#1490) The SPI support copies and income band donors are whole FRS households whose claimants and partners then draw SPI incomes, so a drawn row kept its twin's SOC 2020 major group, SIC division and sector, all chosen with the twin's pay. A new person stage, spi_employment_shell, runs after company_cars (so the tape rows' pay is net of car benefits, as FRS pay is) and before spi_housing_shell: - It redraws the major group of every drawn row whose twin had an occupation, or that drew the tape's own PAY or a profit; a drawn row with neither keeps NONE, and FRS rows and undrawn copies are untouched. - It trains on the FRS rows' adults aged 16 and over with an occupation and positive household weight, on age, sex, region, employment-status group and employment, self-employment, investment (savings interest plus dividends) and private pension income: what a drawn row holds of its own. The classifier is microcosm-fit's CategoricalClassifier, the declared equal blend of a regularised boosted classifier and a spline logit, drawn at uniforms keyed on person_id. - Each drawn row copies the SIC division and sector of an FRS adult with the drawn group and status group among the 50 nearest on age, incomes and region, falling back to the group and then to everyone. - The receipt records out-of-fold evidence on five household-grouped folds (log loss for the model, each learner and the constant; the calibration slope; occupation-share gaps within pay bands and status groups; the industry copy's section and public-sector gaps), with counts banded by receipt_counts. A stage-health gate, uk_stage_spi_employment_shell_fit, holds them to declared bounds; it is a population fact check, so the synthetic smoke records it without blocking. Training rows and donors are taken in person_id order, so no draw depends on how the frame orders its rows. On a fixture the knob that caps the boosted learner also turns its early stopping off, since a few households leave classes too thin for the stratified split. The registration moves the derived surfaces: the spec and its packaged projection, the schema branch (every nested block closed), the operation kind, the graph's cells, consumes and reader isolation, the kernel and helper modules, the seed harvest, the gate's registrations and the battery and spine-part digests, the roster pins (53 stages) and the coverage manifest. The H2 fixture is regenerated separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1490) aps_occupation draws each person's soc2020_sub_major_group within the major group that the FRS carry and the SPI employment shell settle. The donors are the Annual Population Survey's adults with a SOC 2020 minor group, April 2024 to March 2025 (UK Data Service SN 9408, End User Licence): the main job's group for people in employment, the last job's otherwise. Employees without a pay figure leave the donors; those with one carry the income weight PIWTA22, and everyone else the person weight PWTA22. Each major group gets microcosm- fit's CategoricalClassifier blend over its sub-major groups. The predictors are age (capped at 80), sex, region, employment-status group, annual pay and SIC 2007 section, the set that held out best on the APS. The tab is caller-supplied (--aps-tab) and pinned by size and sha256. Region comes from GOVTOF, which is numeric and agrees with GOR9DCENSUS2021 on every record; the pinned reader blanks string columns. Draws key on person_source_id, so an undrawn SPI copy draws its FRS twin's group. SPI income recipients key on their own person_id under a separate salt. A person with no major group keeps NONE. The receipt records: - the cleaning's drops and the donors by major group; - held-out evidence on five person folds per major group: the pooled log loss of the model, each learner and the constant; each group's slope; and share gaps within pay bands, regions, age bands and status groups; - the FRS rows' expected shares against the donors'. Two new gates. A stage-health gate, uk_stage_aps_occupation_fit (check sub_major_draw), holds the evidence to proposed bounds. A terminal gate, uk_soc2020_sub_major_group_enum_domain, keeps the column inside the engine's enum. The column joins the export allow-list, and this commit retires its signed NONE default. microcosm-fit's CategoricalClassifier gains unseen_categories="ignore", which gives a level the fit never saw no indicator, as the experiment's one-hot encoding did. It also gains unseen_category_rows; the receipt counts the households that held one. The gates move the battery's three vintage digests and the spine and release-cut parts' two each. The coverage manifest's source digests are regenerated. receipt_counts records that SN 9408's catalogue entry sets no condition beyond the End User Licence (checked 2026-11-04). The H2 fixture tool gains a synthetic APS donor (three per sub-major group) and the 54-stage count; the fixture itself is regenerated once the policyengine-uk bump is measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oyee pay, nesting guard The employment shell's industry copy now draws within one cell: the drawn group crossed with the row's own status group, taking the 50 nearest donors, or all of the cell's donors where it holds fewer. A copied sector is therefore always one the row's status group holds, and every (group, section) pair occurs. The group-only and everyone levels are gone. A row now draws only among the groups that FRS adults of its own status group hold: the other groups' probabilities go to zero and the rest are renormalised. The cell is never empty, and a row whose status group holds none of the groups refuses the stage. On FRS 2024-25 at full scale every such cell holds at least 50 adults, so the restriction removes nothing there. The receipt reports the rows whose draw it restricts, and the donors of another status group (zero by construction). The held-out industry evidence leaves out, and counts, any adult whose cell has no donor in the other folds. Both stages read the SPI tape's PAY (hmrc_spi_pay) as an SPI income recipient's pay. Its employment_income also holds EPB and TAXTERM, and termination pay says nothing of an occupation. In aps_occupation, pay is an employee's on both sides, because the APS asks GRSSWK of employees only. It is zero outside the EMPLOYEE status group on the frame, and zero for non-employee donors. On the pinned APS tab and FRS 2024-25 the stage's held-out figures barely move (pooled log loss 0.5526, against 0.5527 before). The gate's notes now cite slopes of 0.95 to 1.01. A new test holds that no stage after aps_occupation writes either occupation column, so the sub-major group stays nested in the major group on the shipped frame. The operations declare the new pay and draw-support rules. The gate notes move the battery's manifest digest and fingerprint and the spine part's manifest digest, and the coverage manifest is regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Arm results for this PR. Both arms ran locally on 4 November, one at a time, with the same review date (2026-11-04) and ledger (08962f1b). Each arm built a full spine and ran a national calibration. Nothing was uploaded.
Gates and fit
Rows for each release
Where the spines differ
Tests on 938d4b2
|
|
Round 2 at 938d4b2. I read the diff from fd651b3, the new ladder test, both of your replies and the arm results, then rechecked every caller that carries the ISA income contract. I did not run tests locally; this rests on reading the code and on CI.
New findings: none. Checked and fine
CI at 938d4b2: all 11 checks pass (lint, wheels, select-countries, installed-emitter, integration-uk, and engine-free, engine-uk and engine-us on 3.13 and 3.14). Verdict: all four round-1 items are resolved and I found nothing new. From my side, this can leave draft. 🤖 Generated with Claude Code |
…#1512) The charter-H2 fixture now follows the branch: - fixture.json carries the two new stages, and the synthetic APS donor sources/aps.csv (three people per sub-major group, under the codebook's names); - uk_spine.json carries the 54-stage graph. The fixture tool's legacy string audit lists soc2020_sub_major_group beside the major group, so it audits 31 cells, and NORMALIZATION.md follows. test_h2_uk_spine_parity and the integration smoke test pass. The employment shell's note no longer says the stage follows company_cars for its pay. A drawn row's pay predictor is the tape's PAY, which is cash pay without benefits in kind, as FRS pay is; the car benefits sit in EPB, which company_cars carves out of employment_income (Vahid's second review of #1512). The coverage manifest's source digests follow the note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Follow-up to the arm results: the match for 938d4b2 is now measured, not only read from the code. A full control arm on 938d4b2 (a spine and a national calibration, with the same review date and ledger as the other arms) gives the same calibrated dataset as the bump arm on fd651b3:
So the arm results apply to this head without change. |
…olumns (#1512) The head arm's spine peaked at 12.8 GiB, against 9.6 on the control, and the APS read caused it. The shared read_pinned_tab parses the whole 548-column tab in one pass to keep 12 of its columns, a 2.4 GiB transient. aps_occupation now reads through read_pinned_columns. It checks the same size and sha256 pins, then reads only the named columns as text, a chunk of rows at a time, and converts them to numbers as the shared reader does. On the pinned tab the read no longer raises peak memory, takes the same second, and returns an identical table. The licensed-only resource test reads through the new reader, and a synthetic test holds it equal to read_pinned_tab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carry, redraw and impute SOC 2020 occupation in the UK spine (#1490)
The branch carries #1512, merged into it at 86043f2, so main comes in by a merge and no history is rewritten. - spec/sources.yaml: company_cars takes main's version (its notes, and hmrc_spi_pay and hmrc_spi_employment_benefits among its rewrites), and #1490's spi_employment_shell and aps_occupation follow it. - spi_employment_shell's note no longer says that company_cars leaves PAY alone: on main, company_cars moves the part of the car benefits above EPB from PAY into EPB (source_stages.json too). - The coverage manifest and the H2 fixture are regenerated on the merged tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1512, merged into this branch at 86043f2, adds the categorical, donor and held-out exports to microcosm/fit/__init__.py. That module defines fit(), so its bytes are part of fit.qrf@1's implementation hash, and test_fit_qrf_tolerance_source_hash_pin_is_current failed on both engine-free jobs at f06ca86. #1512 was stacked on this branch, so it had no CI run of its own. `.venv/bin/python tools/graph_parity_repin.py fit.qrf` on arm64/darwin/py3.14 (Python 3.14.6), the fixture's authoring platform, with the locked numpy 2.4.6, pandas 3.0.3, quantile-forest 1.4.2 and scikit-learn 1.8.0: implementation_hash 8d4dfdf3 -> c3c8c99e (the value CI computed) arm64/darwin/py3.14 9c6faf71 -> b8acf52a (produced: graph executed here) x86_64/linux/py3.13 a74f0e59 -> d9ff6813 (derived) x86_64/linux/py3.14 a86ca61c -> c925ebf4 (derived) Before it wrote, the tool reproduced all three old keys from the old hash. direct.csv does not change on any platform (7b8dbd56 before and after), so the change is additive: the kernel draws what it drew before. pins.json is the only file this commit changes. The same tool, run on calibrate and simulate, rewrites their pins byte for byte. No other pin moves: the seed protocol attests microcosm.fit.qrf, not the package's __init__, and the one other kernel whose hash moves with #1512 (uk.full.certification-readiness@1, through microcosm.data.contract) has no pinned value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1512 adds UKAPSOccupationStageTransform, which declares fit_weight_records. The #1428 roster pins the stage transforms that declare this hook, and a new one must be reviewed. Thus test_only_the_reviewed_stage_transforms_declare_fit_weight_records failed on both engine-free jobs at bf12c39 (1 failed, 26112 passed). The review against the #1428 rule (record each fit, or declare nothing): - The stage fits one sub-major-group model for each of the 9 SOC 2020 major groups. A major group with no APS donors raises before its fit, so the stage cannot declare the hook and fit nothing. - Each fit records one FitWeightRecord named <stage>:<output>:<major group>, so each name is unique. - Each record carries the kind that microcosm.fit resolved. The fits use the APS donor weights, so the kind is "explicit", not "none". test_uk_aps_occupation.py asserts 9 records of that kind. - Before a run, the property returns an empty tuple, as etb_vat does. The roster is the only change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since this branch last merged main, lcfs_consumption reads the NTS vehicle tab (#1426) and aps_occupation the APS April 2024-March 2025 person tab (#1511, #1490), so a cut from the merged head would refuse at the spine. The runner now extracts vehicle_eul_2002-2024.tab from the hosted NTS zip it already fetches, fetches the APS tab from aps_2024_25.zip (UKDS SN 9408, a repack of the extracted files) at policyengine-uk-data-private ebebd1e0, and passes --nts-vehicle-tab and --aps-tab. A new test fails if a stage pins a licensed tab the runner does not fetch and pass. The gate battery's registry check imports microcosm.data.gate_signing_keys, which brings the microcosm.data package into the transport bindings' source closure; main's binding-identity test now lists those modules. The doc's inputs table names the new tabs and the Chronicle 64753803 artifact, now hosted under chronicle-uk/ in microcosm-uk-build-inputs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
This PR also carries #1512 (the occupation imputation), which was merged into this branch at 86043f2. Closes #1490.
The UK lock moves from policyengine-uk 2.165.1 to 2.174.1, the newest release on PyPI (microcosm#1095). 2.174.1 carries the SOC 2020 occupation inputs of 2.173.0 (PolicyEngine/policyengine-uk#2858), so #1490's occupation PR can stack on this one. policyengine-core stays at 3.33.3;
uv.lockmoves policyengine-uk alone. The plan, with what each release from 2.165.2 needs, is María's of 4 November; she took its recommended options. The branch is rebased on main after #1391 (875506e8a), and its generated files are regenerated on the merged tree.The seven new inputs, signed at their defaults
Each is in
UK_ENGINE_DEFAULT_INPUTSwith the error it can make, for María's signature:income_not_otherwise_charged(2.165.3): the trading allowance relieves no casual income, as before. Set income_not_otherwise_charged to FRS odd-job income policyengine-uk-data#641 sets it from the FRS odd-job income; its port waits for the odd-job annualisation fix (Odd-job income: 4-week receipts are annualised as weekly amounts, and only for jobs done in the last 7 days policyengine-uk-data#640).machine_games_standard_rate_higher_stake_net_takings(2.167.0): the 25% rate on stakes above £1 from April 2027 scores nothing. The split needs the Gambling Commission's yield by machine category in Chronicle (UK: data fixes for the Budget 2026 household measures #1418, item 5).trading_allowance_claim_status(2.172.0), at UNKNOWN: the engine's current treatment, so SPI donor savings rebased to SPI's 2023-24 bands (savings follow-up to #1418) #1428's receipts encoding stands until UK trading allowance: store the claim status once policyengine-uk#2765 is released #1439.soc2020_major_groupandsoc2020_sub_major_group(2.173.0), at NONE: UK: carry the FRS occupation (SOC 2020 major group), and redraw it on the SPI channel #1490 carries the FRS major group and removes its entry.is_in_full_time_educationandhas_education_maintenance_funding(2.173.2), at False: Universal Credit's condition that a claimant is not receiving education excludes nobody.What the build's engine reads return now
obr_savings_income_2022_2025, in every H5 it writes and in every dataset it hands the engine in process. The engine then grows savings interest, other investment income and ISA interest by the OBR's savings income per person in 2022-23 to 2025-26, the path on which the SPI income stage and the SPI Table 3.7 interest targets already grow (María's ruling of 2026-10-29). Before, it grew the frame's interest to 2025 by GDP per head. A file that declares no rule still loads as GDP per head and keeps that reading, and the export check refuses a release without the producer's rule. The SPI income receipt names the engine's new projection index for savings interest and other investment income, as 2.165.2'suprating_indices.yamldoes; the older two-leg savings interest concept keeps GDP per head, and no SPI target follows it.sublet_incomeless £20 a week and no longer countproperty_income; Council Tax Reduction counts land and property other than the home as capital. The build keeps the FRS sub-letting rent (SUBRENT) inproperty_income, as Move SUBRENT from property_income to sublet_income, after policyengine-uk reads sublet_income policyengine-uk-data#508 orders: income tax and HBAI income still count it, and the means tests count it once that issue moves it, after Model rent from boarders, lodgers and sub-tenants for the householder policyengine-uk#2002.What does not move
UK_HBAI_GROSS_INCOME_DEDUCTIONSkeeps its parity: policyengine-uk's HBAI subtractions do not change.Texts and derived files
The texts that describe the engine follow 2.174.1:
frs_take_upand its stage note (the education condition),uc_capital_coherenceand its stage note (Council Tax Reduction's capital),frs_property_income(SUBRENT) and the LISA and ISA docs. The concept coverage report (369 to 376 inputs), the release input coverage manifest, the charter-H2 fixture, the cited versions andAPPROVED_UV_LOCK_SHA256are regenerated.Measurement
Two arms run on this machine, one at a time: a control on main
875506e8aand this branch's head, each a spine and a calibration, at review date 2026-11-04 on feed08962f1b. Their results follow in a comment: both arms' gates, the fit, the first stage where the spines differ and whether the redraws follow the engine-computed predictors, and the rows each release moves, with rows of the SPI channel as classes. The control is also #1095's owed first whole build with #1426 and #1391 on the current base.Tests
test_uk_hmrc_uprating.py,test_uk_spi_income.py): 2.165.2 moved that index to the OBR series. The last commit follows it, and both files then pass. The full tier runs again on the rebased head beside the control arm; its result follows with the arms.--junitxml): the 30 files this branch touches, 2,531 tests, none failed or skipped; Calibrate the QRF sign gates out of fold, for every gated UK imputation #1391's touched files with this branch's SPI and uprating files and microcosm-data's contract and dense release files, 18 files, 1,001 tests, one skipped because the licensed SPI donor is not staged on this machine. No gate digest moved.tools/ci_test_plan.py verify;tools/spec_engine_coverage.py --check;tools/refresh_concept_coverage.py --engine policyengine-uk --check; both pin tools and the coverage manifest with--check; the H2 fixture regenerated on the merged tree (before the rebase a second regeneration was byte-identical), and the H parity test; ruff on the edited files.Signatures
Awaiting María's signature: the seven signed defaults, with the error each can make (
UK_ENGINE_DEFAULT_INPUTS).Not in this PR
Disclosure
No figure in this PR, its commits or its changelog rests on survey records: the growth rates quoted are policyengine-uk's parameters. Measured rows of the SPI channel are given as classes only.
🤖 Generated with Claude Code