diff --git a/changelog.d/2046.fixed.md b/changelog.d/2046.fixed.md new file mode 100644 index 000000000..0538770f0 --- /dev/null +++ b/changelog.d/2046.fixed.md @@ -0,0 +1 @@ +- Count joint tenants who form a single household with the household head's family in each other's Housing Benefit size criteria, together with every occupier counted for any of them, and stop treating them as lacking exclusive use of the rooms they share (new input `joint_tenant_in_household_head_household`; HB Regs 2006 reg 13D(12), LHA Guidance Manual para 2.100). diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_joint_tenants_single_household.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_joint_tenants_single_household.yaml new file mode 100644 index 000000000..ef6b02b17 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_joint_tenants_single_household.yaml @@ -0,0 +1,560 @@ +# Joint tenants who form a single household. +# +# Housing Benefit size criteria (HB Regs 2006 and HB (SPC) Regs 2006 reg +# 13D(3)) count "occupiers", which reg 13D(12) defines as the persons who +# occupy the dwelling as their home "except for any joint tenant who is not a +# member of the claimant's household". So a joint tenant who is a member of +# the claimant's household is an occupier. DWP: +# - LHA Guidance Manual (April 2014) para 2.030 lists "joint-tenants who are a +# member of the customer's household, for example their partner or children +# who are now adults" among occupiers; para 2.031 excludes those who are not +# ("household is not defined but should be given its everyday meaning"). +# - Para 2.100: Lee, his partner and their two children under 10 are joint +# tenants with Lee's mother and form a single household; "the three bedroom +# LHA rate applies to both Lee and his mother because they form a single +# household". +# - Para 2.123 example 3: Andrew, his wife and their three children under 10 +# are joint tenants with Andrew's father George, in a single household; "The +# four bedroom LHA rate ... applies to both Andrew and George". +# Joint tenants in separate households each count only their own household +# (para 2.093; HB circular A21/2013 para 25). +# +# The input joint_tenant_in_household_head_household marks a family sharing +# the rent that forms a single household with the household head's family. +# The families of a single household count each other, and each counts every +# occupier counted for any of them (boarders and lodgers of the head, and +# non-dependants residing with any of them), so all get the same size +# criteria apart from the claimant's own additional bedroom as a qualifying +# parent or carer (13D(3A)(b)). Non-dependant deductions and the young +# individual's test still follow non_dependant_normally_resides_with: a joint +# tenant is never a non-dependant (reg 3(2)(d)). +# +# A room shared with "a member of his household" still counts as the +# claimant's own for 13D(2)(b), so the families of a single household do not +# lack exclusive use because of each other. +# +# Universal Credit has no equivalent: joint tenants who are separately liable +# (UC Sch 4 para 24(1); ADM F3031) are not in each other's extended benefit +# unit (para 9), so LHA_allowed_bedrooms is unchanged. + +- name: Lee and his mother form a single household and each get the three-bedroom rate (LHA Guidance Manual 2.100) + period: 2026 + input: + people: + lee: + age: 32 + is_male: true + is_household_head: true + lee_partner: + age: 31 + is_male: false + lee_son: + age: 4 + is_male: true + lee_daughter: + age: 7 + is_male: false + mother: + age: 60 + is_male: false + benunits: + lee_family: + members: [lee, lee_partner, lee_son, lee_daughter] + mother_family: + members: [mother] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + households: + home: + members: [lee, lee_partner, lee_son, lee_daughter, mother] + # £150 a week. + rent: 7_800 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # Lee: one bedroom for the couple, one for the two children under 10, one + # for his mother. His mother: one for herself, one for the couple (13D(3)(a)), + # one for the children. + housing_benefit_LHA_allowed_bedrooms: [3, 3] + housing_benefit_LHA_additional_bedrooms: [0, 0] + housing_benefit_LHA_category: [D, D] + housing_benefit_shares_accommodation: [false, false] + # Joint tenants are not each other's non-dependants (reg 3(2)(d)). + housing_benefit_claimant_has_non_dependant: [false, false] + # UC: each joint tenant's own extended benefit unit. + LHA_allowed_bedrooms: [2, 1] + +- name: The three-bedroom rate applies to both when Lee's mother is the household head + period: 2026 + input: + people: + mother: + age: 60 + is_male: false + is_household_head: true + lee: + age: 32 + is_male: true + lee_partner: + age: 31 + is_male: false + lee_son: + age: 4 + is_male: true + lee_daughter: + age: 7 + is_male: false + benunits: + mother_family: + members: [mother] + lee_family: + members: [lee, lee_partner, lee_son, lee_daughter] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + households: + home: + members: [mother, lee, lee_partner, lee_son, lee_daughter] + rent: 7_800 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + housing_benefit_LHA_allowed_bedrooms: [3, 3] + housing_benefit_LHA_category: [D, D] + housing_benefit_shares_accommodation: [false, false] + LHA_allowed_bedrooms: [1, 2] + +- name: In separate households Lee and his mother each count only their own (LHA Guidance Manual 2.093) + period: 2026 + input: + people: + lee: + age: 32 + is_male: true + is_household_head: true + lee_partner: + age: 31 + is_male: false + lee_son: + age: 4 + is_male: true + lee_daughter: + age: 7 + is_male: false + mother: + age: 60 + is_male: false + benunits: + lee_family: + members: [lee, lee_partner, lee_son, lee_daughter] + mother_family: + members: [mother] + liable_for_share_of_household_rent: true + households: + home: + members: [lee, lee_partner, lee_son, lee_daughter, mother] + rent: 7_800 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + joint_tenant_in_household_head_household: [false, false] + # Each joint tenant's own household: Lee's family two bedrooms; his + # mother one, and she shares the dwelling's rooms with a joint tenant + # outside her household, so the shared accommodation rate applies + # (13D(2)(a)(ii)). + housing_benefit_LHA_allowed_bedrooms: [2, 1] + housing_benefit_LHA_category: [C, A] + housing_benefit_shares_accommodation: [true, true] + LHA_allowed_bedrooms: [2, 1] + +- name: Andrew and his father George get the four-bedroom rate (LHA Guidance Manual 2.123 example 3) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + andrew: + age: 36 + is_male: true + is_household_head: true + andrew_wife: + age: 35 + is_male: false + child_1: + age: 2 + is_male: true + child_2: + age: 5 + is_male: false + child_3: + age: 8 + is_male: true + george: + age: 64 + is_male: true + benunits: + andrew_family: + members: [andrew, andrew_wife, child_1, child_2, child_3] + # The authority apportions £150 of the £200 a week to Andrew and £50 + # to George. + share_of_household_rent: 0.75 + george_family: + members: [george] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + share_of_household_rent: 0.25 + households: + home: + members: [andrew, andrew_wife, child_1, child_2, child_3, george] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # One bedroom for the couple, two for three children under 10, one for + # George: four bedrooms for each claim. + housing_benefit_LHA_allowed_bedrooms: [4, 4] + housing_benefit_LHA_category: [E, E] + share_of_household_rent: [0.75, 0.25] + LHA_allowed_bedrooms: [3, 1] + +- name: A young individual in a single household keeps the shared accommodation rate + period: 2026 + input: + people: + mother: + age: 58 + is_male: false + is_household_head: true + son: + age: 25 + is_male: true + benunits: + mother_family: + members: [mother] + son_family: + members: [son] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + households: + home: + members: [mother, son] + rent: 7_800 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # Each counts the other in the size criteria, but the son, a single + # claimant under 35, has no non-dependant residing with him (his mother + # is a joint tenant, reg 3(2)(d)), so 13D(2)(a)(i) gives him the shared + # accommodation rate. + housing_benefit_LHA_allowed_bedrooms: [2, 2] + is_housing_benefit_young_individual: [false, true] + housing_benefit_claimant_has_non_dependant: [false, false] + housing_benefit_shares_accommodation: [false, false] + housing_benefit_LHA_category: [C, A] + +- name: A joint tenant outside the single household counts no one and no one counts them + period: 2026 + input: + people: + head: + age: 45 + is_household_head: true + sister: + age: 43 + friend: + age: 44 + benunits: + head_family: + members: [head] + sister_family: + members: [sister] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + friend_family: + members: [friend] + liable_for_share_of_household_rent: true + households: + home: + members: [head, sister, friend] + rent: 15_600 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # The head and her sister count each other; the friend, a joint tenant in + # neither's household, is no one's occupier (13D(12)). + housing_benefit_LHA_allowed_bedrooms: [2, 2, 1] + # All three share rooms with a joint tenant outside their household. + housing_benefit_shares_accommodation: [true, true, true] + housing_benefit_LHA_category: [C, C, A] + +- name: Two joint tenants both in the head's household count each other and the head + period: 2026 + input: + people: + head: + age: 60 + is_household_head: true + daughter: + age: 40 + is_male: false + son: + age: 38 + is_male: true + benunits: + head_family: + members: [head] + daughter_family: + members: [daughter] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + son_family: + members: [son] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + households: + home: + members: [head, daughter, son] + rent: 15_600 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + housing_benefit_LHA_allowed_bedrooms: [3, 3, 3] + housing_benefit_shares_accommodation: [false, false, false] + housing_benefit_LHA_category: [D, D, D] + LHA_allowed_bedrooms: [1, 1, 1] + +- name: A single household counts the head's lodger and another family's non-dependant for every joint tenant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + mother: + age: 60 + is_male: false + is_household_head: true + lee: + age: 36 + is_male: true + lodger: + age: 40 + rent_paid_as_lodger: 5_200 + kim: + age: 30 + is_male: false + household_benefits_individual_non_dep_deduction: 1_000 + council_tax_reduction_individual_non_dep_deduction: 300 + benunits: + mother_family: + members: [mother] + lee_family: + members: [lee] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + lodger_family: + members: [lodger] + kim_family: + members: [kim] + # Kim, Lee's friend, normally resides with Lee only. + non_dependant_normally_resides_with: OTHER_JOINT_OCCUPIERS + households: + home: + members: [mother, lee, lodger, kim] + rent: 15_600 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # Mother and Lee: themselves, each other, the lodger who pays the mother + # and Kim. Every occupier of the single household counts for both. The + # lodger's and Kim's own claims count only their own families. + housing_benefit_LHA_allowed_bedrooms: [4, 4, 1, 1] + housing_benefit_LHA_category: [E, E, A, A] + # Kim is Lee's non-dependant only: he bears the whole deduction (HBGM A5 + # para 5.622; CTR Sch 1 para 8(5)), and only he has a non-dependant + # residing with him. + housing_benefit_claimant_has_non_dependant: [false, true, false, false] + housing_benefit_non_dep_deductions: [0, 1_000, 0, 0] + council_tax_reduction_non_dep_deductions: [0, 300, 0, 0] + +- name: The same occupiers without a single household count only as before + period: 2026 + absolute_error_margin: 0.01 + input: + people: + mother: + age: 60 + is_male: false + is_household_head: true + lee: + age: 36 + is_male: true + lodger: + age: 40 + rent_paid_as_lodger: 5_200 + kim: + age: 30 + is_male: false + household_benefits_individual_non_dep_deduction: 1_000 + council_tax_reduction_individual_non_dep_deduction: 300 + benunits: + mother_family: + members: [mother] + lee_family: + members: [lee] + liable_for_share_of_household_rent: true + lodger_family: + members: [lodger] + kim_family: + members: [kim] + non_dependant_normally_resides_with: OTHER_JOINT_OCCUPIERS + households: + home: + members: [mother, lee, lodger, kim] + rent: 15_600 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # Mother: herself and her lodger. Lee: himself and Kim. + housing_benefit_LHA_allowed_bedrooms: [2, 2, 1, 1] + housing_benefit_LHA_category: [C, C, A, A] + housing_benefit_claimant_has_non_dependant: [false, true, false, false] + housing_benefit_non_dep_deductions: [0, 1_000, 0, 0] + council_tax_reduction_non_dep_deductions: [0, 300, 0, 0] + +- name: A joint tenant in the claimant's household who needs overnight care gives each an additional bedroom + period: 2026 + input: + people: + lee: + age: 32 + is_male: true + is_household_head: true + lee_partner: + age: 31 + is_male: false + lee_son: + age: 4 + is_male: true + lee_daughter: + age: 7 + is_male: false + mother: + age: 70 + is_male: false + meets_lha_overnight_care_condition: true + benunits: + lee_family: + members: [lee, lee_partner, lee_son, lee_daughter] + mother_family: + members: [mother] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + households: + home: + members: [lee, lee_partner, lee_son, lee_daughter, mother] + rent: 7_800 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # Lee: 13D(3A)(a)(iii), an occupier other than the claimant or partner; + # his mother: (a)(i), herself. + housing_benefit_LHA_additional_bedrooms: [1, 1] + housing_benefit_LHA_allowed_bedrooms: [4, 4] + housing_benefit_LHA_category: [E, E] + +- name: A foster child placed with the head is no one's occupier and the carer's bedroom is the head's alone + period: 2026 + input: + people: + head: + age: 45 + is_male: false + is_household_head: true + foster_child: + age: 8 + is_male: true + is_looked_after_by_local_authority: true + sister: + age: 40 + is_male: false + benunits: + head_family: + members: [head, foster_child] + sister_family: + members: [sister] + liable_for_share_of_household_rent: true + joint_tenant_in_household_head_household: true + households: + home: + members: [head, foster_child, sister] + rent: 15_600 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # The foster child does not occupy the dwelling (reg 21(3); LHA Guidance + # Manual para 2.033). The head is a qualifying parent or carer with one + # additional bedroom (13D(3A)(b)), which applies to the claimant and + # partner only, so not to her sister's claim. + is_child_or_young_person_placed_with_family: [false, true, false] + housing_benefit_LHA_additional_bedrooms: [1, 0] + housing_benefit_LHA_allowed_bedrooms: [3, 2] + housing_benefit_LHA_category: [D, C] + +- name: The input has no effect on a family that does not share the rent + period: 2026 + absolute_error_margin: 0.01 + input: + people: + john: + age: 30 + is_household_head: true + alex: + age: 30 + peter: + age: 30 + household_benefits_individual_non_dep_deduction: 1_000 + benunits: + john_family: + members: [john] + joint_tenant_in_household_head_household: true + alex_family: + members: [alex] + liable_for_share_of_household_rent: true + peter_family: + members: [peter] + joint_tenant_in_household_head_household: true + households: + home: + members: [john, alex, peter] + rent: 20_800 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + # As in LHA Guidance Manual 2.110: the household head and the + # non-dependant are not sharers of the rent, so John and Alex remain in + # separate households, each counting Peter. + housing_benefit_LHA_allowed_bedrooms: [2, 2, 1] + housing_benefit_shares_accommodation: [true, true, false] + housing_benefit_non_dep_deductions: [500, 500, 0] + +- name: The household head's own family marked as sharing the rent does not lack exclusive use + period: 2026 + input: + people: + head: + age: 40 + is_household_head: true + benunits: + head_family: + members: [head] + # The head's family is always liable for the rent; the input on it + # alone does not make the rent shared. + liable_for_share_of_household_rent: true + households: + home: + members: [head] + rent: 7_800 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + housing_benefit_shares_accommodation: false + housing_benefit_LHA_allowed_bedrooms: 1 + # One bedroom with exclusive use, aged 40: the one-bedroom rate. + housing_benefit_LHA_category: B diff --git a/policyengine_uk/tests/test_joint_tenants_single_household_properties.py b/policyengine_uk/tests/test_joint_tenants_single_household_properties.py new file mode 100644 index 000000000..09d2a81e6 --- /dev/null +++ b/policyengine_uk/tests/test_joint_tenants_single_household_properties.py @@ -0,0 +1,464 @@ +"""Property-based tests for joint tenants who form a single household with +the household head's family (joint_tenant_in_household_head_household) in the +Housing Benefit size criteria (HB Regs 2006 reg 13D(3), (3A), (12); LHA +Guidance Manual para 2.100). + +Invariants, for any generated population of households (a head's family, +up to three families sharing the rent, each in the head's household or not, +non-dependant families with any residence value, a lodger, children, foster +children and people who need overnight care): + +1. Reference: every family's Housing Benefit bedrooms and additional + bedrooms equal a count made by the test itself. Members of a single + household (the head's family and every sharer in its household, once + there is one) count each other's families and every occupier counted for + any of them; anyone else counts as before (boarders and lodgers for the + head, non-dependants for the joint occupiers they reside with). Children's + rooms come from a brute-force pairing. +2. No-op: the input set to false everywhere gives the same results as the + default; set to true on a family that does not share the rent (the + head's, a non-dependant's or a lodger's), it changes nothing. +3. Symmetry: every family in a single household has the same size criteria + before additional bedrooms (13D(3)), and the same additional bedrooms + where no foster child is placed in the household (13D(3A)(b) and + (a)(iv) are the claimant's own). +4. Monotonicity: forming a single household never lowers any family's + bedrooms, and changes nothing for a family outside it. +5. Exclusive use (13D(2)(b)): where every sharer is in the head's household, + no joint occupier lacks exclusive use because of the rent sharing; where + one is not, every joint occupier liable for rent does. +6. Invariance: Universal Credit, the non-dependant deductions, the + young individual and the Housing Benefit non-dependant test never depend + on the input (a joint tenant is never a non-dependant, reg 3(2)(d)). +""" + +from functools import lru_cache + +import numpy as np +from hypothesis import HealthCheck, assume, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2026 +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +RESIDENCE = [ + "EVERY_JOINT_OCCUPIER", + "HOUSEHOLD_HEAD_FAMILY", + "OTHER_JOINT_OCCUPIERS", +] +OVERNIGHT = st.sampled_from([False, False, False, True]) +MONEY_TOLERANCE = 0.01 + +# (age, is_male, is_foster_child, needs overnight care) +child = st.tuples( + st.integers(0, 15), + st.booleans(), + st.sampled_from([False, False, False, True]), + OVERNIGHT, +) + + +@st.composite +def family(draw): + return dict( + adults=draw( + st.lists( + st.tuples(st.integers(18, 85), st.booleans(), OVERNIGHT), + min_size=1, + max_size=2, + ) + ), + children=draw(st.lists(child, min_size=0, max_size=2)), + ) + + +@st.composite +def households(draw): + return dict( + head=draw(family()), + sharers=draw( + st.lists(st.tuples(family(), st.booleans()), min_size=0, max_size=3) + ), + non_dependants=draw( + st.lists( + st.tuples(family(), st.sampled_from(RESIDENCE)), + min_size=0, + max_size=2, + ) + ), + lodger=draw(st.booleans()), + rent=draw(st.floats(2_000, 30_000, allow_nan=False)), + ) + + +population = st.lists(households(), min_size=1, max_size=4) + + +def build(population, flags="as_drawn"): + """One simulation for the whole population, with each family's role and + whether it sits in a single household. + + ``flags`` is "as_drawn" (each sharer's drawn value), "unset" (input + absent), "false" (false everywhere) or "inert" (drawn values on sharers, + and true on every family that does not share the rent).""" + people, benunits, homes, roles, single = {}, {}, {}, [], [] + for h, house in enumerate(population): + members = [] + any_joins = any(joins for _, joins in house["sharers"]) + + def add_family(name, fam, role, joins, extra): + ids = [] + for i, (age, male, overnight) in enumerate(fam["adults"]): + pid = f"{name}_adult_{i}" + people[pid] = { + "age": {YEAR: age}, + "is_male": {YEAR: male}, + "is_claimant_or_partner": {YEAR: True}, + "is_household_head": {YEAR: False}, + "meets_lha_overnight_care_condition": {YEAR: overnight}, + } + ids.append(pid) + for i, (age, male, foster, overnight) in enumerate(fam["children"]): + pid = f"{name}_child_{i}" + people[pid] = { + "age": {YEAR: age}, + "is_male": {YEAR: male}, + "is_claimant_or_partner": {YEAR: False}, + "is_household_head": {YEAR: False}, + "is_looked_after_by_local_authority": {YEAR: foster}, + "meets_lha_overnight_care_condition": {YEAR: overnight}, + } + ids.append(pid) + flag = { + "as_drawn": joins if role == "sharer" else None, + "unset": None, + "false": False, + "inert": joins if role == "sharer" else True, + }[flags] + if flag is not None: + extra = { + **extra, + "joint_tenant_in_household_head_household": {YEAR: flag}, + } + benunits[name] = {"members": ids, **extra} + members.extend(ids) + roles.append(role) + in_single = flags in ("as_drawn", "inert") and ( + (role == "head" and any_joins) or (role == "sharer" and joins) + ) + single.append(in_single) + return ids + + head_ids = add_family(f"h{h}_head", house["head"], "head", False, {}) + people[head_ids[0]]["is_household_head"] = {YEAR: True} + for f, (sharer, joins) in enumerate(house["sharers"]): + add_family( + f"h{h}_s{f}", + sharer, + "sharer", + joins, + {"liable_for_share_of_household_rent": {YEAR: True}}, + ) + for f, (non_dep, residence) in enumerate(house["non_dependants"]): + add_family( + f"h{h}_n{f}", + non_dep, + "non_dependant", + False, + {"non_dependant_normally_resides_with": {YEAR: residence}}, + ) + if house["lodger"]: + ids = add_family( + f"h{h}_l", + dict(adults=[(40, True, False)], children=[]), + "lodger", + False, + {}, + ) + people[ids[0]]["rent_paid_as_lodger"] = {YEAR: 4_000} + homes[f"h{h}"] = { + "members": members, + "rent": {YEAR: house["rent"]}, + "tenure_type": {YEAR: "RENT_PRIVATELY"}, + "brma": {YEAR: "MAIDSTONE"}, + } + situation = {"people": people, "benunits": benunits, "households": homes} + return Simulation(situation=situation), np.array(roles), np.array(single) + + +def calc(sim, variable): + return np.asarray(sim.calculate(variable, YEAR), dtype=float) + + +def assert_same(left, right, variable): + """The variable's values agree in both simulations: money to the penny, + and enums exactly.""" + a = np.asarray(left.calculate(variable, YEAR)) + b = np.asarray(right.calculate(variable, YEAR)) + if a.dtype.kind in "biuf": + np.testing.assert_allclose(a, b, atol=MONEY_TOLERANCE, err_msg=variable) + else: + np.testing.assert_array_equal(a, b, err_msg=variable) + + +def benunit_household(sim): + person_benunit = sim.populations["benunit"].members_entity_id + person_household = sim.populations["household"].members_entity_id + out = np.zeros(sim.populations["benunit"].count, dtype=int) + out[person_benunit] = person_household + return out + + +@lru_cache(maxsize=None) +def fewest_rooms(children): + """Brute force: the fewest rooms holding ``children`` (a sorted tuple of + (age, is_male)) two to a room, where two may share if they are of the + same sex or both under 10.""" + if not children: + return 0 + first, rest = children[0], children[1:] + best = 1 + fewest_rooms(rest) + for i, other in enumerate(rest): + if first[1] == other[1] or (first[0] < 10 and other[0] < 10): + best = min(best, 1 + fewest_rooms(rest[:i] + rest[i + 1 :])) + return best + + +def reference_bedrooms(house, flags="as_drawn"): + """The size criteria (bedrooms, additional bedrooms) of each family in + the household, in build order, counted independently of the model.""" + sharers = house["sharers"] + joins = [j and flags in ("as_drawn", "inert") for _, j in sharers] + families = [("head", house["head"], None)] + families += [("sharer", fam, None) for fam, _ in sharers] + families += [("non_dependant", fam, r) for fam, r in house["non_dependants"]] + if house["lodger"]: + families.append(("lodger", dict(adults=[(40, True, False)], children=[]), None)) + single = [False] * len(families) + if any(joins): + single[0] = True + for s, j in enumerate(joins): + single[1 + s] = j + has_sharer = bool(sharers) + + def residence_families(residence): + """Indices of the joint occupiers a non-dependant resides with.""" + head = {0} + sharer_indices = set(range(1, 1 + len(sharers))) + if residence == "EVERY_JOINT_OCCUPIER": + return head | sharer_indices + if residence == "HOUSEHOLD_HEAD_FAMILY": + return head + return sharer_indices if has_sharer else head + + def counted_for(index): + """Indices of the other families whose members occupy family + ``index``'s dwelling in its size criteria.""" + role = families[index][0] + if role not in ("head", "sharer"): + return set() + group = ( + {i for i in range(len(families)) if single[i]} if single[index] else {index} + ) + others = {i for i in group if i != index} + for i, (other_role, _, residence) in enumerate(families): + if other_role == "lodger" and 0 in group: + others.add(i) + if other_role == "non_dependant" and residence_families(residence) & group: + others.add(i) + return others + + out = [] + for index, (role, fam, _) in enumerate(families): + # The claimant or couple. + rooms = 1 + children = [(a, m) for a, m, foster, _ in fam["children"] if not foster] + # 13D(3A)(a)(i), (ii), (iv): the family's own members, including a + # foster child placed with it. + overnight = any(o for _, _, o in fam["adults"]) or any( + o for *_, o in fam["children"] + ) + carer = any(foster for _, _, foster, _ in fam["children"]) + for i in counted_for(index): + other = families[i][1] + # 13D(3)(a), (b): a couple, or a single adult, has one bedroom. + rooms += 1 + children += [(a, m) for a, m, foster, _ in other["children"] if not foster] + # (a)(iii): another occupier, not a child placed with them. + overnight |= any(o for _, _, o in other["adults"]) or any( + o for _, _, foster, o in other["children"] if not foster + ) + rooms += fewest_rooms(tuple(sorted(children))) + additional = int(overnight) + int(carer) + out.append((rooms + additional, additional)) + return out + + +@PROPERTY_SETTINGS +@given(population) +def test_bedrooms_match_a_reference_count(population): + sim, roles, _ = build(population) + bedrooms = calc(sim, "housing_benefit_LHA_allowed_bedrooms") + additional = calc(sim, "housing_benefit_LHA_additional_bedrooms") + expected = [pair for house in population for pair in reference_bedrooms(house)] + np.testing.assert_array_equal(bedrooms, [b for b, _ in expected]) + np.testing.assert_array_equal(additional, [a for _, a in expected]) + + +@PROPERTY_SETTINGS +@given(population) +def test_the_default_and_a_flag_off_the_rent_sharers_are_no_ops(population): + unset, _, _ = build(population, flags="unset") + false, _, _ = build(population, flags="false") + drawn, _, _ = build(population, flags="as_drawn") + inert, _, _ = build(population, flags="inert") + for variable in [ + "housing_benefit_LHA_allowed_bedrooms", + "housing_benefit_LHA_additional_bedrooms", + "housing_benefit_LHA_category", + "housing_benefit_shares_accommodation", + "housing_benefit_non_dep_deductions", + "council_tax_reduction_non_dep_deductions", + "LHA_allowed_bedrooms", + "housing_benefit", + ]: + assert_same(false, unset, variable) + assert_same(inert, drawn, variable) + + +@PROPERTY_SETTINGS +@given(population) +def test_a_single_household_has_the_same_size_criteria(population): + assume(any(any(j for _, j in house["sharers"]) for house in population)) + sim, _, single = build(population) + household = benunit_household(sim) + bedrooms = calc(sim, "housing_benefit_LHA_allowed_bedrooms") + additional = calc(sim, "housing_benefit_LHA_additional_bedrooms") + no_foster_child = [ + not any( + foster + for fam in [house["head"]] + + [f for f, _ in house["sharers"]] + + [f for f, _ in house["non_dependants"]] + for _, _, foster, _ in fam["children"] + ) + for house in population + ] + for h in np.unique(household[single]): + members = single & (household == h) + before_additional = (bedrooms - additional)[members] + assert np.all(before_additional == before_additional[0]) + if no_foster_child[h]: + assert np.all(bedrooms[members] == bedrooms[members][0]) + + +@PROPERTY_SETTINGS +@given(population) +def test_a_single_household_never_lowers_bedrooms(population): + joined, _, single = build(population) + apart, _, _ = build(population, flags="false") + for variable in [ + "housing_benefit_LHA_allowed_bedrooms", + "housing_benefit_LHA_additional_bedrooms", + ]: + with_single = calc(joined, variable) + without = calc(apart, variable) + assert np.all(with_single >= without) + np.testing.assert_array_equal(with_single[~single], without[~single]) + + +@PROPERTY_SETTINGS +@given(population) +def test_exclusive_use_follows_the_household(population): + sim, roles, single = build(population) + household = benunit_household(sim) + shares = calc(sim, "housing_benefit_shares_accommodation").astype(bool) + liable = calc(sim, "benunit_is_rent_liable").astype(bool) + joint_occupier = (roles == "head") | (roles == "sharer") + separate_sharer = (roles == "sharer") & ~single + for h in range(len(population)): + in_household = household == h + families = joint_occupier & in_household + if not np.any((roles == "sharer") & in_household): + continue + if np.any(separate_sharer & in_household): + # A joint tenant outside someone's household shares their rooms. + assert np.all(shares[families & liable]) + else: + # Every joint occupier is in the head's household. + assert not np.any(shares[families]) + + +@PROPERTY_SETTINGS +@given(population) +def test_universal_credit_deductions_and_non_dependants_do_not_depend_on_the_input( + population, +): + joined, _, _ = build(population) + apart, _, _ = build(population, flags="false") + for variable in [ + "LHA_allowed_bedrooms", + "LHA_additional_bedrooms", + "uc_non_dep_deductions", + "lha_renter_has_non_dependant", + "housing_benefit_non_dep_deductions", + "council_tax_reduction_non_dep_deductions", + "housing_benefit_claimant_has_non_dependant", + "is_housing_benefit_young_individual", + ]: + assert_same(joined, apart, variable) + + +def test_the_input_has_no_effect_without_a_household_head(): + """A single household needs the household head's family, so where no one + is the household head the input changes nothing, even on a family that + shares the rent and with a lodger present.""" + + def situation(joins): + people = { + name: { + "age": {YEAR: age}, + "is_household_head": {YEAR: False}, + **extra, + } + for name, age, extra in [ + ("older", 60, {}), + ("sharer", 40, {}), + ("lodger", 35, {"rent_paid_as_lodger": {YEAR: 4_000}}), + ] + } + benunits = { + "older_family": {"members": ["older"]}, + "sharer_family": { + "members": ["sharer"], + "liable_for_share_of_household_rent": {YEAR: True}, + "joint_tenant_in_household_head_household": {YEAR: joins}, + }, + "lodger_family": {"members": ["lodger"]}, + } + households = { + "home": { + "members": list(people), + "rent": {YEAR: 15_600}, + "tenure_type": {YEAR: "RENT_PRIVATELY"}, + "brma": {YEAR: "MAIDSTONE"}, + } + } + return Simulation( + situation={"people": people, "benunits": benunits, "households": households} + ) + + joined, apart = situation(True), situation(False) + for variable in [ + "housing_benefit_LHA_allowed_bedrooms", + "housing_benefit_LHA_additional_bedrooms", + "housing_benefit_LHA_category", + "housing_benefit_shares_accommodation", + ]: + assert_same(joined, apart, variable) diff --git a/policyengine_uk/variables/gov/dwp/LHA_allowed_bedrooms.py b/policyengine_uk/variables/gov/dwp/LHA_allowed_bedrooms.py index f676631a6..d881aa273 100644 --- a/policyengine_uk/variables/gov/dwp/LHA_allowed_bedrooms.py +++ b/policyengine_uk/variables/gov/dwp/LHA_allowed_bedrooms.py @@ -40,8 +40,10 @@ def bedrooms_for_children( other families of the household who count in the household head's family's size criteria, such as a non-dependant's child; they share rooms with the head family's own children. ``count_other``, if given, maps a - person mask to the number of those people counted for each family, in - place of counting them all for the household head's family. + person mask to the number of those people counted for each family as + occupiers outside it, in place of counting them all for the household + head's family; it never counts the family's own members, so + ``other_child`` may include them. """ person = benunit.members age = person("age", period) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_additional_bedrooms.py b/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_additional_bedrooms.py index fdca7dc70..57df88a13 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_additional_bedrooms.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_additional_bedrooms.py @@ -18,8 +18,11 @@ class housing_benefit_LHA_additional_bedrooms(Variable): "housing_benefit_LHA_allowed_bedrooms: boarders and lodgers and their " "children for the household head's family, and non-dependants and " "their children for each joint occupier they normally reside with, " - "but not a sharer of the rent or a child placed with them as a foster " - "child or for adoption. Before 1 April 2017 only the claimant and " + "but not a sharer of the rent outside the claimant's household or a " + "child placed with them as a foster child or for adoption. Joint " + "occupiers who form a single household count each other's families " + "and every occupier counted for any of them. Before 1 April 2017 only " + "the claimant and " "partner counted for overnight care. The definitions require a " "bedroom in the dwelling for " "the carer or the foster child; the model does not observe the " @@ -39,17 +42,17 @@ def formula(benunit, period, parameters): # HB Regs 2006 reg 13D(3A)(a)(iii): "a person ... who occupies the # claimant's dwelling as their home". (iii) does not use the defined # term "occupiers", but the model applies 13D(12)'s exclusion of a - # joint tenant outside the claimant's household and, following DWP, + # joint tenant outside the claimant's household (so a joint tenant in + # the claimant's household counts) and, following DWP, # of a child placed with another family as a foster child or for # adoption (see housing_benefit_LHA_allowed_bedrooms). A3/2017 frames # the 2017 extension as a room for carers of a disabled child or # non-dependant, and A21/2013 para 25 bases private-sector joint # tenants' size criteria on each tenant's own household. - head_family = person.benunit.any(person("is_household_head", period)) - sharer = person.benunit("liable_for_share_of_household_rent", period) placed = person("is_child_or_young_person_placed_with_family", period) - other = overnight_care & ~head_family & ~sharer & ~placed - others = housing_benefit_other_occupiers(benunit, period, other) + others = housing_benefit_other_occupiers( + benunit, period, overnight_care & ~placed + ) # Reg 13D(3A)(a)(i)-(iv): the claimant, partner, other members of the # family, and a child or young person for whom the claimant or # partner is a qualifying parent or carer. Before 1 April 2017 (SI diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_allowed_bedrooms.py b/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_allowed_bedrooms.py index 3fbdba58d..5cdb8d894 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_allowed_bedrooms.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit_LHA_allowed_bedrooms.py @@ -2,30 +2,61 @@ from policyengine_uk.variables.gov.dwp.LHA_allowed_bedrooms import ( bedrooms_for_children, ) +from policyengine_uk.variables.household.consumption.rent.joint_tenant_in_household_head_household import ( + in_joint_tenants_single_household, +) from policyengine_uk.variables.household.consumption.rent.non_dependant_normally_resides_with import ( non_dependants_residing_with, + non_dependants_residing_with_any, ) -def housing_benefit_other_occupiers(benunit, period, other_occupier): - """For each family, the sum of ``other_occupier`` (a count or weight for - each occupier outside every family liable for the household's rent, zero - for anyone else) over the people who occupy its dwelling (HB Regs 2006 - reg 13D(12)). A boarder or lodger, who - pays the householder, counts for the household head's family. A - non-dependant counts for each joint occupier they normally reside with - (LHA Guidance Manual paras 2.093 and 2.110; see - non_dependant_normally_resides_with).""" +def housing_benefit_other_occupiers(benunit, period, occupier): + """For each family, the sum of ``occupier`` (a count or weight for each + person who occupies the dwelling as their home, zero for anyone else) + over the people outside the family who are occupiers of its dwelling in + its size criteria (HB Regs 2006 reg 13D(12)): + + - A family sharing the rent does not count another, because a joint + tenant outside the claimant's household is not an occupier. + - A boarder or lodger, who pays the householder, counts for the household + head's family. + - A non-dependant counts for each joint occupier they normally reside + with (LHA Guidance Manual paras 2.093 and 2.110; see + non_dependant_normally_resides_with). + - Joint occupiers who form a single household (see + joint_tenant_in_household_head_household) count each other's members, + as members of the claimant's household (para 2.100), and each counts + every occupier counted for any of them, so all have the same + occupiers. + """ person = benunit.members - boarder_or_lodger = person("pays_rent_to_householder", period) - head_family = benunit.any(person("is_household_head", period)) - of_householder = head_family * benunit.max( - person.household.sum(other_occupier * boarder_or_lodger) + in_head_family = person.benunit.any(person("is_household_head", period)) + in_sharer_family = person.benunit("liable_for_share_of_household_rent", period) + outside_joint_occupiers = ~in_head_family & ~in_sharer_family + pays_householder = person("pays_rent_to_householder", period) + head_family = benunit.any(in_head_family) + boarders_and_lodgers = benunit.max( + person.household.sum(occupier * outside_joint_occupiers * pays_householder) + ) + non_dependant = occupier * ~pays_householder + separate = head_family * boarders_and_lodgers + non_dependants_residing_with( + benunit, period, non_dependant ) - non_dependants = non_dependants_residing_with( - benunit, period, other_occupier * ~boarder_or_lodger + single = in_joint_tenants_single_household(benunit, period) + # The other families of the single household: its members' total less + # the family's own. + joint_tenants = benunit.max( + person.household.sum(occupier * benunit.project(single)) + ) - benunit.sum(occupier) + # The household head's family is in the single household, so its + # boarders and lodgers count for every family in it. + shared = ( + joint_tenants + + boarders_and_lodgers + + non_dependants_residing_with_any(benunit, period, non_dependant, single) ) - return of_householder + non_dependants + return where(single, shared, separate) class housing_benefit_LHA_allowed_bedrooms(Variable): @@ -43,7 +74,8 @@ class housing_benefit_LHA_allowed_bedrooms(Variable): "unit aged 16 or over who is not the claimant or partner adds a " "bedroom, such as a young person in full-time education; so do a " "householder's boarder or lodger and a non-dependant, but a sharer " - "of the rent does not. A couple, such as a non-dependant and their " + "of the rent does not, unless it forms a single household with the " + "household head's family. A couple, such as a non-dependant and their " "partner, has one bedroom between them. The children of a " "non-dependant, boarder or " "lodger are occupiers too, and share rooms with the claimant's " @@ -53,8 +85,11 @@ class housing_benefit_LHA_allowed_bedrooms(Variable): "for each joint occupier they normally reside with, by default every " "one (see non_dependant_normally_resides_with), while a boarder or " "lodger, who pays the household head, counts for the head's family " - "only. A boarder's or lodger's own claim counts only their own " - "family." + "only. Joint occupiers who form a single household (see " + "joint_tenant_in_household_head_household) count each other's " + "families, and each counts every occupier counted for any of them, " + "so all have the same occupiers. A boarder's or lodger's own claim " + "counts only their own family." ) definition_period = YEAR reference = ( @@ -83,17 +118,16 @@ def formula(benunit, period, parameters): family_rooms = benunit.sum( aged_16_or_over & ~person("is_claimant_or_partner", period) & occupier ) - head_family = person.benunit.any(person("is_household_head", period)) - sharer = person.benunit("liable_for_share_of_household_rent", period) - # Reg 13D(3) for occupiers outside the family, as defined in 13D(12): - # everyone in a non-dependant's, boarder's or lodger's family, - # including their children. - other_occupier = ~head_family & ~sharer & occupier - # Reg 13D(3)(a): a couple, here the claimant and partner of an - # occupier's family, has one bedroom; (b): every other occupier aged - # 16 or over has their own. Each member of a couple counts as half, - # and only where both members are counted, so the halves always pair. - counted = other_occupier & aged_16_or_over + # Reg 13D(3) for occupiers outside the family, as defined in 13D(12) + # (see housing_benefit_other_occupiers): everyone in a + # non-dependant's, boarder's or lodger's family, including their + # children, and the families of joint tenants in the claimant's + # household. Reg 13D(3)(a): a couple, here the claimant and partner + # of an occupier's family, has one bedroom; (b): every other occupier + # aged 16 or over has their own. Each member of a couple counts as + # half, and only where both members are counted, so the halves always + # pair. + counted = occupier & aged_16_or_over claimant_or_partner = counted & person("is_claimant_or_partner", period) couple = (person.benunit.sum(claimant_or_partner) == 2) & person.benunit( "is_couple", period @@ -108,7 +142,7 @@ def formula(benunit, period, parameters): benunit, period, own_child=occupier, - other_child=other_occupier, + other_child=occupier, count_other=lambda mask: housing_benefit_other_occupiers( benunit, period, mask ), diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit_shares_accommodation.py b/policyengine_uk/variables/gov/dwp/housing_benefit_shares_accommodation.py index fd4031f38..97063ad8c 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit_shares_accommodation.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit_shares_accommodation.py @@ -1,4 +1,7 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.household.consumption.rent.joint_tenant_in_household_head_household import ( + in_joint_tenants_single_household, +) class housing_benefit_shares_accommodation(Variable): @@ -11,7 +14,10 @@ class housing_benefit_shares_accommodation(Variable): "the family's own household, its non-dependants or people who pay it " "rent still count as exclusive. A boarder or lodger shares the " "householder's rooms, and in a household whose rent is shared every " - "liable family shares the others' rooms. The household input " + "liable family shares the rooms of the liable families outside its " + "own household. Joint occupiers who form a single household (see " + "joint_tenant_in_household_head_household) do not share rooms with " + "each other in this sense. The household input " "is_shared_accommodation marks any other case." ) definition_period = YEAR @@ -27,10 +33,27 @@ def formula(benunit, period, parameters): person.household("is_shared_accommodation", period) ) boarder_or_lodger = benunit.any(person("pays_rent_to_householder", period)) - rent_is_shared = benunit.any( - person.household.any( - person.benunit("liable_for_share_of_household_rent", period) - ) + # A family sharing the rent with the household head's family. The + # head's family is always liable for the rent, so the input on it + # alone does not make the rent shared. + in_sharer_family = person.benunit( + "liable_for_share_of_household_rent", period + ) & ~person.benunit.any(person("is_household_head", period)) + rent_is_shared = benunit.any(person.household.any(in_sharer_family)) + # A joint tenant in the claimant's household is a member of it, so + # the families of a single household share rooms with a joint + # occupier outside it only: a family sharing the rent that is not in + # the household head's household. + single = in_joint_tenants_single_household(benunit, period) + outside_single_household = in_sharer_family & ~benunit.project(single) + shares_with_another_household = where( + single, + benunit.any(person.household.any(outside_single_household)), + rent_is_shared, ) liable = benunit("benunit_is_rent_liable", period) - return household_input | boarder_or_lodger | (rent_is_shared & liable) + return ( + household_input + | boarder_or_lodger + | (shares_with_another_household & liable) + ) diff --git a/policyengine_uk/variables/household/consumption/rent/joint_tenant_in_household_head_household.py b/policyengine_uk/variables/household/consumption/rent/joint_tenant_in_household_head_household.py new file mode 100644 index 000000000..f3e6b0565 --- /dev/null +++ b/policyengine_uk/variables/household/consumption/rent/joint_tenant_in_household_head_household.py @@ -0,0 +1,50 @@ +from policyengine_uk.model_api import * + + +class joint_tenant_in_household_head_household(Variable): + value_type = bool + entity = BenUnit + label = "Joint tenant in the household head's household" + documentation = ( + "Whether this family, which shares liability for the household's rent " + "with the household head's family (see " + "liable_for_share_of_household_rent), forms a single household with " + "the head's family: for example, an adult son who is a joint tenant " + "with his mother and keeps a common household with her. This is a " + "question of fact. Housing Benefit counts a joint tenant who is a " + "member of the claimant's household as an occupier in the size " + "criteria, but not one who is not, so the families of a single " + "household count each other, and all of them have the same " + "occupiers. The default is a separate household. The input has no " + "effect for a family that does not share the rent, and Universal " + "Credit does not use it." + ) + definition_period = YEAR + default_value = False + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/13D", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/13D", + "https://assets.publishing.service.gov.uk/media/5a758238ed915d6faf2b38a2/lha-guidance-manual.pdf#page=26", + "https://assets.publishing.service.gov.uk/media/5a758238ed915d6faf2b38a2/lha-guidance-manual.pdf#page=34", + ) + + +def in_joint_tenants_single_household(benunit, period): + """For each family, whether it belongs to a single household of two or + more joint occupiers: the household head's family and every family + sharing the rent that forms a single household with it + (joint_tenant_in_household_head_household), where there is at least one + such family. In a household with no household head the input has no + effect.""" + person = benunit.members + in_head_family = person.benunit.any(person("is_household_head", period)) + has_head = person.household.any(in_head_family) + in_joining_family = ( + person.benunit("joint_tenant_in_household_head_household", period) + & person.benunit("liable_for_share_of_household_rent", period) + & ~in_head_family + & has_head + ) + head_family = benunit.any(in_head_family) + someone_joins = benunit.any(person.household.any(in_joining_family)) + return benunit.any(in_joining_family) | (head_family & someone_joins) diff --git a/policyengine_uk/variables/household/consumption/rent/non_dependant_normally_resides_with.py b/policyengine_uk/variables/household/consumption/rent/non_dependant_normally_resides_with.py index 4df32b9c2..d60dabe7e 100644 --- a/policyengine_uk/variables/household/consumption/rent/non_dependant_normally_resides_with.py +++ b/policyengine_uk/variables/household/consumption/rent/non_dependant_normally_resides_with.py @@ -29,10 +29,15 @@ class non_dependant_normally_resides_with(Variable): "deduction. The default is every joint occupier. With more than one " "family sharing the rent with the household head's family, the third " "value covers all of those families together. Where no family shares " - "the rent, every value means the household head's family. Universal " - "Credit does not use this input: a non-dependant counts in one Universal " - "Credit claim only, which the model gives to the household head's " - "family (see is_non_dependant_of_household_head)." + "the rent, every value means the household head's family. Where joint " + "occupiers form a single household (see " + "joint_tenant_in_household_head_household), the size criteria of each " + "count a non-dependant of any of them, but this input still decides " + "who bears the deduction and whether a young individual has a " + "non-dependant. Universal Credit does not use this input: a " + "non-dependant counts in one Universal Credit claim only, which the " + "model gives to the household head's family (see " + "is_non_dependant_of_household_head)." ) definition_period = YEAR reference = ( @@ -103,6 +108,24 @@ def non_dependants_residing_with(benunit, period, non_dependant): return count +def non_dependants_residing_with_any(benunit, period, non_dependant, families): + """For each family, the sum of ``non_dependant`` (as in + non_dependants_residing_with) over the household's people who normally + reside with at least one of the families marked by ``families``.""" + person = benunit.members + residence = person.benunit("non_dependant_normally_resides_with", period) + source = non_dependant * _outside_joint_occupiers(benunit, period) + count = 0 + for value, joint_occupier in _joint_occupiers_by_residence(benunit, period): + marked = joint_occupier & families + resides_with_one = ( + _household_total_over_families(benunit, period, 1, marked) > 0 + ) + in_household = benunit.max(person.household.sum(source * (residence == value))) + count = count + resides_with_one * in_household + return count + + def apportioned_non_dependant_deductions( benunit, period, deductions, equally, every_joint_occupier_part ):