Skip to content

Carry the rent boarders and lodgers pay the householder as person inputs - #506

Draft
MaxGhenis wants to merge 2 commits into
frs-cvpay-not-property-incomefrom
frs-boarder-lodger-rent
Draft

MaxGhenis wants to merge 2 commits into
frs-cvpay-not-property-incomefrom
frs-boarder-lodger-rent

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stacked on #503, which stops counting FRS CVPAY as property income for the boarder or lodger who pays it.

This PR keeps CVPAY on the payer's record as two new person inputs, so that policyengine-uk can work out the rent the householder receives. The released data hold this rent only on the payer's record; there is no separate variable for what the householder receives.

  • rent_paid_as_boarder
  • rent_paid_as_lodger

frs_boarder_and_lodger_rent(person) works out each amount from CVPAY. It annualises the weekly amount, floors it at zero, and splits it by CONVBL: a boarder where CONVBL is 1, a lodger otherwise. Nothing else in the dataset changes.

What CVPAY and CONVBL are

Sources: the FRS 2024-25 documentation (UK Data Service SN 9563) and the FRS derived-variable specification (UKDS SN 4149 user guide, volume 2).

  • Question instructions, "Rent within Conventional Households". The block is asked about "each person not related in some way to the HRP (or partner) in the 2nd and subsequent Benefit Units". ConvBL asks whether that person is "a BOARDER: that is someone who pays you a RENT for board AND lodging" (1), "a LODGER: that is someone who pays you a RENT for lodging, but not food" (2), or neither (3). CvPay then asks "How much rent did [name] pay [for board and lodging] last time it was due, after deducting any state benefits to help with rent?"
  • Derived variables BOARDER and LODGER (BENUNIT). The specification defines BOARDER as "The total weekly amount paid by a benefit unit classed as a boarder to the householder for a room and food". It is built from CVPAY, plus HBOTHAMT where the unit gets housing benefit. LODGER is the same for lodgers.
  • Raw data (aggregates). In raw 2023-24 and 2024-25, BOARDER plus LODGER equals the benefit unit's summed CVPAY in every paying unit. Every adult with CVPAY above zero is outside the HRP's benefit unit.
  • Income totals. The FRS gross-income derivation leaves out income from boarders and lodgers "to avoid double counting at a household level".

Invariants and tests

test_frs_boarder_lodger_rent.py checks these properties on 200 seeded random sets of households. The amounts include zeros, negatives and missing values, and CONVBL includes codes 1, 2, 3, 0, -1 and missing.

  1. Conservation. Boarder plus lodger rent equals WEEKS_IN_YEAR × positive CVPAY on every record. The boarder total equals the positive CVPAY of CONVBL 1 records.
  2. Exclusive classes, non-negative. No record has both amounts. Neither amount is ever negative.
  3. Locality. Raising one person's CVPAY by £x raises only that person's amount, by WEEKS_IN_YEAR × £x.
  4. Independence. frs_property_income does not change when CVPAY or CONVBL change. The new amounts do not change when ROYYR1 changes.

Example tests cover a boarder, a lodger, rent with CONVBL 3, 0, -1 or missing (classed as a lodger's), zero, negative and missing CVPAY, and the repeated index labels that create_frs produces by stacking the adult and child tables.

The create_frs smoke test fixture gains CONVBL, which the raw adult table always has. The test runs three times, with no payment, a boarder paying £100 a week and a lodger paying £80 a week, and checks each amount reaches the right column.

Uprating. Both columns are added to uprating_factors.csv and uprating_growth_factors.csv with sublet_income's rows, the per capita GDP index that policyengine-uk gives both inputs. The build materialises the dataset at the calibration year before calibrating, so without these rows the payments would stay at survey-year prices there. Tests check the rows and run uprate_dataset on positive rents; they fail without the rows.

Mutation check. Each mutant fails tests:

Mutant Tests failed (of 1,224 in the two property-income test files)
Swap the classes 203
Drop the annualisation 403
Drop the floor at zero 201
Class every payer as a boarder 199
Put CVPAY back into property income 597

Differential check. I built the base FRS 2024-25 at this branch's parent (0812cd1) and at this branch (87a5f50), then compared every column of every table. Only the two new person columns differ.

Together they total £0.807bn a year (weighted, at survey-year prices). That is 43 survey records, 138k people weighted, which matches the raw CVPAY total. The boarder and lodger totals are not reported separately because one class has fewer than 10 records.

Effect on the model

policyengine-uk skips dataset columns it has no variable for (Simulation.build_from_dataset). rent_paid_as_boarder and rent_paid_as_lodger are not variables in the locked policyengine-uk 2.93.0, so this PR changes no simulation output until a policyengine-uk release defines them. The companion PR is PolicyEngine/policyengine-uk#2002.

Pipeline checks for the two new columns:

  • uprate_dataset uprates them through their new uprating_factors.csv rows when the build materialises the calibration year, and back again when it saves the base-year file.
  • The SPI-donor half copies them from its FRS donors, as it does other non-imputed columns.
  • The second-stage imputation (FRS_ONLY_PERSON_VARIABLES) does not touch them.

Measured effects from a full rebuild with the policyengine-uk change will be added here before this leaves draft.

Weeks per year

The helper annualises weekly CVPAY with the repository's WEEKS_IN_YEAR (365.25 / 7), as for every other weekly FRS amount. policyengine-uk converts annual amounts back to weekly with 52 weeks for its weekly disregards, so a weekly amount reaches them about 0.34% higher. That gap applies to every weekly FRS amount the model tests weekly (earnings, benefits and these payments alike), so it is not special-cased here: #531.

Not in this PR

  • The householder's side: income tax rent-a-room relief, the means-test disregards, and non-dependant status. These are in the policyengine-uk companion PR.
  • The boarder's or lodger's own housing costs. Benefit units after the first still get no rent of their own.

axiom: n/a: data pipeline change (FRS variable mapping), no rule change.

🤖 Generated with Claude Code

FRS CVPAY is the weekly rent a boarder or lodger pays the householder,
held on the payer's own adult record; CONVBL says which they are. #503
stops counting it as the payer's property income. This keeps it, as
`rent_paid_as_boarder` and `rent_paid_as_lodger` on the payer, so
policyengine-uk can work out what the householder receives (the FRS asks
the householder nothing about it).

The helper `frs_boarder_and_lodger_rent` annualises CVPAY, floors it at
zero and classes it as a boarder's where CONVBL is 1 and a lodger's
otherwise. Tests cover conservation, exclusive classes, locality, the
independence of property income, missing and negative codes and the
stacked adult and child index; the create_frs smoke test fixture gains
CONVBL and checks the two columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #506:
- Add rent_paid_as_boarder and rent_paid_as_lodger to uprating_factors.csv
  and uprating_growth_factors.csv with sublet_income's per capita GDP
  index (the index policyengine-uk gives both inputs), so materialising
  the calibration year uprates them like other incomes. Tests check the
  rows and run uprate_dataset on positive rents.
- Parametrise the create_frs smoke test over no payment, a boarder and
  a lodger, so the parser's column wiring is tested with positive CVPAY.
- Say the released data hold the rent only on the payer's record (no
  householder receipt variable), rather than that the householder is
  not asked; document the weeks-per-year convention.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant