diff --git a/changelog.d/1948.added.md b/changelog.d/1948.added.md new file mode 100644 index 0000000000..dce4bb3eb6 --- /dev/null +++ b/changelog.d/1948.added.md @@ -0,0 +1,7 @@ +- Universal Credit now applies regulation 77 to a person who stands as sole owner or partner of a company carrying on a trade or property business. + - A trading company's income counts as their self-employed earnings on top of any pay from it. A company loss is set against the profit of any trade they carry on themselves, and a negative total counts as nil. + - Only a claimant's or partner's company earnings reduce the award or count towards the benefit cap earnings exemption. + - The minimum income floor applies when the trade is their main employment. + - The company's capital replaces the value of their shares. Trade assets are disregarded while they work in the trade. In households with several benefit units, the shares leave the household's capital before it is shared out. + - The intermediaries (IR35) exclusion follows reg. 77(5) as in force: Chapters 8 and 9 of ITEPA Part 2 until 27 November 2018, and Chapters 8 to 10 from main-employment activities from 28 November 2018. Annual simulations take the rule in force at the start of the year, so 2018 uses the earlier rule throughout. + - New `owned_company_*` inputs describe the company. Every input defaults to no such company, so existing datasets are unchanged. diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/company_owner/intermediary_exclusion/chapters.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/company_owner/intermediary_exclusion/chapters.yaml new file mode 100644 index 0000000000..835331544e --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/company_owner/intermediary_exclusion/chapters.yaml @@ -0,0 +1,27 @@ +description: > + Chapters of Part 2 of the Income Tax (Earnings and Pensions) Act 2003 under + which employed earnings from a company take the person out of Universal + Credit's company owner rule. Chapter 10 was added from 28 November 2018. + Annual simulations use the rule in force at the start of each tax year, so + the 2018-19 tax year uses the earlier rule throughout. +metadata: + label: Universal Credit company owner rule excluded intermediary chapters + period: year + unit: list + reference: + - title: The Universal Credit Regulations 2013 reg. 77(5) (as made) + href: https://www.legislation.gov.uk/uksi/2013/376/regulation/77/made + - title: The Universal Credit and Jobseeker's Allowance (Miscellaneous Amendments) Regulations 2018 reg. 3(5) + href: https://www.legislation.gov.uk/uksi/2018/1129/regulation/3/made + - title: The Universal Credit and Jobseeker's Allowance (Miscellaneous Amendments) Regulations (Northern Ireland) 2018 reg. 3(7) + href: https://www.legislation.gov.uk/nisr/2018/187/regulation/3/made + - title: The Universal Credit Regulations 2013 reg. 77(5) + href: https://www.legislation.gov.uk/uksi/2013/376/regulation/77 +values: + 2013-04-29: + - CHAPTER_8 + - CHAPTER_9 + 2018-11-28: + - CHAPTER_8 + - CHAPTER_9 + - CHAPTER_10 diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/company_owner/intermediary_exclusion/requires_main_employment.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/company_owner/intermediary_exclusion/requires_main_employment.yaml new file mode 100644 index 0000000000..b902859fd4 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/company_owner/intermediary_exclusion/requires_main_employment.yaml @@ -0,0 +1,22 @@ +description: > + Whether employed earnings under the intermediaries rules take the person out + of Universal Credit's company owner rule only when they derive from the + person's main employment. As made, any such earnings did; from 28 November + 2018 only those derived from activities that are the person's main + employment do. + Annual simulations use the rule in force at the start of each tax year, so + the 2018-19 tax year uses the earlier rule throughout. +metadata: + label: Universal Credit company owner intermediary exclusion requires main employment + period: year + unit: bool + reference: + - title: The Universal Credit Regulations 2013 reg. 77(5) (as made) + href: https://www.legislation.gov.uk/uksi/2013/376/regulation/77/made + - title: The Universal Credit and Jobseeker's Allowance (Miscellaneous Amendments) Regulations 2018 reg. 3(5) + href: https://www.legislation.gov.uk/uksi/2018/1129/regulation/3/made + - title: The Universal Credit and Jobseeker's Allowance (Miscellaneous Amendments) Regulations (Northern Ireland) 2018 reg. 3(7) + href: https://www.legislation.gov.uk/nisr/2018/187/regulation/3/made +values: + 2013-04-29: false + 2018-11-28: true diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/company/uc_company_analogous_business.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/company/uc_company_analogous_business.yaml new file mode 100644 index 0000000000..1a551f8e79 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/company/uc_company_analogous_business.yaml @@ -0,0 +1,622 @@ +# The Universal Credit Regulations 2013 reg. 77 (company analogous to a +# partnership or one person business), as explained in DWP's Advice for +# Decision Making Chapter H4, paras. H4360-H4377. Expected values are worked +# by hand from the regulation; the capital cases use the same figures as the +# Axiom encoding's tests (rulespec-uk uk/regulations/uksi/2013/376/77.test.yaml). +# Company earnings are not taxable income, so no income tax or National +# Insurance comes off them, and other earnings in these cases are below the +# personal allowance and the primary threshold, so earned income before the +# floor equals gross earnings. + +- name: Sole owner of a trading company whose main employment it is has the floor applied to the company income + # H4374-H4375 and Example 2 (Claudia): the company's income is the owner's + # self-employed earnings (reg. 77(3)(b)) and, as the trade is their main + # employment, they are treated as gainfully self-employed so the minimum + # income floor applies (reg. 77(3)(c)). max(6,000, 20,000) = 20,000. + period: 2026 + absolute_error_margin: 0 + input: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_is_main_employment: true + owned_company_income_share: 6_000 + uc_minimum_income_floor: 20_000 + output: + uc_company_owner_treatment_applies: true + uc_company_self_employed_earnings: 6_000 + uc_company_gainful_self_employment: true + uc_mif_applies: true + uc_individual_earned_income_before_mif: 6_000 + uc_individual_earned_income: 20_000 + +- name: A partner-like shareholder in a start-up period has only their share of the company income counted + # H4376 Example 1 (Ezgjan): a 50% owner with meaningful influence, working + # 60 hours a week with no salary, in a start-up period. Reg. 62(5) keeps + # the floor off during the start-up period; their half of 24,000 counts. + period: 2026 + absolute_error_margin: 0 + input: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_is_main_employment: true + owned_company_income_share: 24_000 * 0.5 + uc_is_in_startup_period: true + uc_minimum_income_floor: 20_000 + output: + uc_company_gainful_self_employment: true + uc_mif_applies: false + uc_individual_earned_income: 12_000 + +- name: Company income is counted in addition to the owner's pay as its director + # Reg. 77(4): 9,000 of director's pay (employed earnings) plus 15,000 of + # company income is 24,000, above the 20,000 floor. + period: 2026 + absolute_error_margin: 0 + input: + age: 40 + employment_income: 9_000 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_is_main_employment: true + owned_company_income_share: 15_000 + uc_minimum_income_floor: 20_000 + output: + uc_company_self_employed_earnings: 15_000 + uc_individual_earned_income: 24_000 + +- name: When the company is not the owner's main employment its income counts but no floor applies + # Reg. 77(3)(b) applies to any owner of a trading company; reg. 77(3)(c) + # only when the trade is the main employment. 12,000 + 1,000 = 13,000, + # below the 20,000 floor, which does not apply. + period: 2026 + absolute_error_margin: 0 + input: + age: 40 + employment_income: 12_000 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_is_main_employment: false + owned_company_income_share: 1_000 + uc_minimum_income_floor: 20_000 + output: + uc_company_gainful_self_employment: false + uc_mif_applies: false + uc_individual_earned_income: 13_000 + +- name: The company income is tapered like other earnings + # A single claimant with no children and no limited capability for work has + # no work allowance (reg. 22). Earned income is the 8,000 floor (above the + # 6,000 of company income) and the taper takes 55% of it (reg. 22(1)(b)), + # which is less than the maximum amount, so the reduction is not capped. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_is_main_employment: true + owned_company_income_share: 6_000 + uc_minimum_income_floor: 8_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + uc_earned_income: 8_000 + uc_income_reduction: 8_000 * 0.55 + +- name: A property company gives capital but no earnings + # H4365-H4367 (Pablo): a major shareholder in a company letting a seaside + # cottage. The shares (90,000, held within corporate_wealth) are disregarded + # and the company's capital (the 120,000 cottage) is counted instead + # (reg. 77(2)). Reg. 77(3), including the trade asset disregard and the + # earnings rule, applies only to a trade (H4376). 2,000 + 90,000 - 90,000 + # + 120,000 = 122,000. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_property_business: true + is_engaged_in_owned_company_trade: true + owned_company_is_main_employment: true + owned_company_income_share: 10_000 + owned_company_capital: 120_000 + owned_company_trade_assets: 120_000 + owned_company_holding_value: 90_000 + benunits: + benunit: + members: person + households: + household: + members: person + savings: 2_000 + corporate_wealth: 90_000 + output: + uc_company_owner_treatment_applies: [true] + uc_company_self_employed_earnings: [0] + uc_company_gainful_self_employment: [false] + uc_company_capital: [120_000] + uc_company_holding_disregard: [90_000] + uc_individual_earned_income: [0] + uc_assessable_capital: 122_000 + +- name: Trade assets are disregarded while the owner works in the trade + # Reg. 77(2) and (3)(a); Axiom test trade_company_main_employment_treats_capital_income_and_mif. + # Company capital 100,000 less 40,000 of assets used wholly and + # exclusively for the trade = 60,000; the 25,000 holding is disregarded. + # 3,000 + 25,000 - 25,000 + 60,000 = 63,000. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + is_engaged_in_owned_company_trade: true + owned_company_capital: 100_000 + owned_company_trade_assets: 40_000 + owned_company_holding_value: 25_000 + benunits: + benunit: + members: person + households: + household: + members: person + savings: 3_000 + corporate_wealth: 25_000 + output: + uc_company_capital: [60_000] + uc_company_holding_disregard: [25_000] + uc_assessable_capital: 63_000 + +- name: Trade assets count when the owner does not work in the trade + # Reg. 77(3)(a) disregards trade assets only "while they are engaged in + # activities in the course of that trade"; Axiom test + # trade_assets_not_disregarded_when_person_not_engaged_in_trade_activities. + # 90,000 counted; 20,000 holding disregarded. 20,000 - 20,000 + 90,000. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + is_engaged_in_owned_company_trade: false + owned_company_capital: 90_000 + owned_company_trade_assets: 30_000 + owned_company_holding_value: 20_000 + benunits: + benunit: + members: person + households: + household: + members: person + corporate_wealth: 20_000 + output: + uc_company_capital: [90_000] + uc_assessable_capital: 90_000 + +- name: Income that is employed earnings through an intermediary from the main employment takes the owner out of the rule + # Reg. 77(5): nothing in reg. 77 applies, so the shares count as ordinary + # capital and the company income is not self-employed earnings. Axiom test + # intermediary_employed_earnings_main_employment_excludes_regulation. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_intermediary_earnings_chapter: CHAPTER_8 + owned_company_intermediary_earnings_from_main_employment: true + owned_company_is_main_employment: true + is_engaged_in_owned_company_trade: true + owned_company_income_share: 36_000 + owned_company_capital: 100_000 + owned_company_trade_assets: 40_000 + owned_company_holding_value: 25_000 + benunits: + benunit: + members: person + households: + household: + members: person + corporate_wealth: 25_000 + output: + uc_company_intermediary_exclusion_applies: [true] + uc_company_owner_treatment_applies: [false] + uc_company_self_employed_earnings: [0] + uc_company_gainful_self_employment: [false] + uc_mif_applies: [false] + uc_company_capital: [0] + uc_company_holding_disregard: [0] + uc_assessable_capital: 25_000 + +- name: An investor in a company with many shareholders is not treated as its owner + # H4364: such a shareholder has no influence over the running of the + # company, so reg. 77 does not apply and the shares are ordinary capital + # (reg. 46). Company facts recorded for them have no effect. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: false + owned_company_carries_on_trade: true + owned_company_is_main_employment: true + owned_company_income_share: 50_000 + owned_company_capital: 1_000_000 + owned_company_holding_value: 12_000 + benunits: + benunit: + members: person + households: + household: + members: person + corporate_wealth: 12_000 + output: + uc_company_owner_treatment_applies: [false] + uc_individual_earned_income: [0] + uc_assessable_capital: 12_000 + +- name: A company carrying on neither a trade nor a property business is outside the rule + # Reg. 77(1) needs a company "carrying on a trade or a property business". + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_capital: 50_000 + owned_company_holding_value: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + corporate_wealth: 10_000 + output: + uc_company_owner_treatment_applies: [false] + uc_assessable_capital: 10_000 + +- name: Each partner's company is attributed to the couple's capital + # Joint claimants' capital is combined against one limit (Welfare Reform + # Act 2012 s. 5(2)(a); UC Regs 2013 reg. 18(1)(b)). Two 50% owners of the same + # trading company, neither working in it: each is treated as holding half + # of the 30,000 company capital and each 5,000 holding is disregarded. + # 10,000 + 15,000 + 15,000 - 5,000 - 5,000 = 30,000. + period: 2026 + absolute_error_margin: 0 + input: + people: + a: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_capital: 15_000 + owned_company_holding_value: 5_000 + b: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_capital: 15_000 + owned_company_holding_value: 5_000 + benunits: + benunit: + members: [a, b] + households: + household: + members: [a, b] + corporate_wealth: 10_000 + output: + uc_assessable_capital: 30_000 + +- name: The company's capital can take the owner over the capital limit + # 1,000 of savings and a disregarded 1,000 holding, but the company holds + # 20,000 of cash not used wholly and exclusively for its trade: assessable + # capital 1,000 + 1,000 - 1,000 + 20,000 = 21,000 exceeds the 16,000 limit + # (reg. 18(1); Welfare Reform Act 2012 s. 5(1)(a)), so no award. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + is_engaged_in_owned_company_trade: true + owned_company_capital: 20_000 + owned_company_holding_value: 1_000 + benunits: + benunit: + members: person + households: + household: + members: person + savings: 1_000 + corporate_wealth: 1_000 + output: + uc_assessable_capital: 21_000 + is_uc_eligible: false + universal_credit: 0 + +- name: Since November 2018 intermediary earnings exclude only when they come from the main employment + # Reg. 77(5) as substituted by SI 2018/1129 reg. 3(5) from 28 November 2018: + # Chapter 8 income from a side engagement does not take a trading-company + # owner out of the rule. + period: 2026 + absolute_error_margin: 0 + input: + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_intermediary_earnings_chapter: CHAPTER_8 + owned_company_intermediary_earnings_from_main_employment: false + owned_company_income_share: 10_000 + output: + uc_company_intermediary_exclusion_applies: false + uc_company_owner_treatment_applies: true + uc_company_self_employed_earnings: 10_000 + +- name: Before November 2018 any Chapter 8 or 9 intermediary earnings excluded the owner + # Reg. 77(5) as made: "This regulation does not apply where the person + # derives income from the company that is employed earnings by virtue of + # Chapter 8 ... or Chapter 9 ... of Part 2 of ITEPA", with no main + # employment condition. + period: 2017 + absolute_error_margin: 0 + input: + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_intermediary_earnings_chapter: CHAPTER_9 + owned_company_intermediary_earnings_from_main_employment: false + owned_company_income_share: 10_000 + output: + uc_company_intermediary_exclusion_applies: true + uc_company_owner_treatment_applies: false + uc_company_self_employed_earnings: 0 + +- name: Before November 2018 Chapter 10 earnings did not exclude the owner + # Chapter 10 was added to reg. 77(5) only from 28 November 2018. + period: 2017 + absolute_error_margin: 0 + input: + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_intermediary_earnings_chapter: CHAPTER_10 + owned_company_intermediary_earnings_from_main_employment: true + owned_company_income_share: 10_000 + output: + uc_company_intermediary_exclusion_applies: false + uc_company_owner_treatment_applies: true + uc_company_self_employed_earnings: 10_000 + +- name: A company loss makes self-employed earnings nil and is not set against the owner's pay + # Reg. 57(2): if the result of steps 1 to 3 "is nil or a negative amount, + # the amount of the person's self-employed earnings ... is nil". A 10,000 + # loss leaves the 12,000 of director's pay intact (reg. 77(4)). + period: 2026 + absolute_error_margin: 0 + input: + age: 40 + employment_income: 12_000 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_income_share: -10_000 + output: + uc_company_self_employed_earnings: -10_000 + uc_individual_earned_income: 12_000 + +- name: A company loss is set against the profit of the owner's own trade + # Reg. 77(1) treats the owner as the company's sole owner, so its trade is + # one more trade they carry on. Reg. 57(2) step 2 adds together the profit + # or loss of each trade before the nil rule: 8,000 of profit from their own + # trade less the company's 3,000 loss is 5,000. Start-up period, so no floor. + period: 2026 + absolute_error_margin: 0 + input: + age: 40 + self_employment_income: 8_000 + uc_is_in_startup_period: true + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_income_share: -3_000 + output: + uc_individual_earned_income_before_mif: 5_000 + uc_individual_earned_income: 5_000 + +- name: Working in the trade as one's main employment is engagement in it + # Reg. 77(3)(a) disregards trade assets "while they are engaged in + # activities in the course of that trade"; a person whose main employment + # is that trade is. 100,000 - 40,000 = 60,000 treated as possessed. + period: 2026 + absolute_error_margin: 0 + input: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_is_main_employment: true + is_engaged_in_owned_company_trade: false + owned_company_capital: 100_000 + owned_company_trade_assets: 40_000 + output: + uc_company_capital: 60_000 + +- name: In a household of two benunits the disregarded holding leaves the household pool before it is shared + # The household has 10,000 of savings and the owner's 30,000 of shares + # (within corporate_wealth). Reg. 77(2) disregards the shares, so 10,000 + # remains, shared between the two unreported adults: 5,000 each. The + # company has no other capital. + period: 2026 + absolute_error_margin: 0 + input: + people: + owner: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_holding_value: 30_000 + lodger: + age: 30 + benunits: + owner_unit: + members: [owner] + lodger_unit: + members: [lodger] + households: + household: + members: [owner, lodger] + savings: 10_000 + corporate_wealth: 30_000 + output: + household_uc_unreported_company_holdings: 30_000 + uc_assessable_capital: [5_000, 5_000] + +- name: A benunit that reports its capital has its own holding taken off what it reports + # Reported capital of 50,000 includes the 30,000 holding: 20,000 remains, + # plus 15,000 of company capital not used in the trade = 35,000. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_holding_value: 30_000 + owned_company_capital: 15_000 + benunits: + benunit: + members: person + uc_reported_capital: 50_000 + households: + household: + members: person + output: + uc_assessable_capital: 35_000 + +- name: Company earnings count towards the benefit cap earnings exemption + # UC Regs 2013 reg. 82(1)(a): the cap does not apply where earned income is + # at least the threshold. Reg. 77(3)(b) earnings are self-employed earnings + # (the model's threshold is 10,152 a year); only income deemed by the + # minimum income floor is excluded (reg. 82(4)). 20,000 of company income, + # not in a start-up period but not the main employment, so no floor. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_income_share: 20_000 + benunits: + benunit: + members: person + households: + household: + members: person + output: + is_benefit_cap_exempt_earnings: true + +- name: A dependant's company earnings are not the claimant's earned income + # Reg. 22(1)(b) deducts "the claimant's earned income (or, in the case of + # joint claimants, their combined earned income)", and reg. 82(1)(a) tests + # the same for the benefit cap. A qualifying young person who owns a + # trading company is not a claimant: their 20,000 of company income is + # their own self-employed earnings but neither reduces the award nor + # exempts the household from the cap. + period: 2026 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + student: + age: 17 + is_in_non_advanced_education: true + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_income_share: 20_000 + benunits: + benunit: + members: [parent, student] + households: + household: + members: [parent, student] + output: + is_uc_assessed_claimant: [true, false] + uc_company_self_employed_earnings: [0, 20_000] + uc_earned_income: 0 + is_benefit_cap_exempt_earnings: false + +- name: Company earnings below the benefit cap earnings threshold leave the cap in place + # Reg. 82(1)(a): the earnings exception needs earned income of at least the + # threshold (10,152 a year in the model). 10,000 of company income falls + # short; 10,152 meets it, so a rise in company income can lift the cap and + # raise the award. + period: 2026 + absolute_error_margin: 0 + input: + people: + low: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_income_share: 10_000 + high: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_income_share: 10_152 + benunits: + low_unit: + members: [low] + high_unit: + members: [high] + households: + low_household: + members: [low] + high_household: + members: [high] + output: + is_benefit_cap_exempt_earnings: [false, true] + +- name: A holding is disregarded only from the corporate wealth that holds it + # Reg. 77(2) disregards "the value of the person's holding in the + # company", which is shares, so it comes out of the household's + # corporate_wealth and cannot remove savings. A 10,000 holding entered with + # 10,000 of savings and no corporate_wealth leaves the 10,000 of savings. + period: 2026 + absolute_error_margin: 0 + input: + people: + person: + age: 40 + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_holding_value: 10_000 + benunits: + benunit: + members: person + households: + household: + members: person + savings: 10_000 + output: + household_uc_unreported_company_holdings: 0 + uc_assessable_capital: 10_000 diff --git a/policyengine_uk/tests/test_uc_company_owner_axiom_parity.py b/policyengine_uk/tests/test_uc_company_owner_axiom_parity.py new file mode 100644 index 0000000000..f5fffcb42c --- /dev/null +++ b/policyengine_uk/tests/test_uc_company_owner_axiom_parity.py @@ -0,0 +1,221 @@ +"""Golden-case parity: Universal Credit reg. 77 against the Axiom encoding. + +The four cases are copied from the tests of TheAxiomFoundation/rulespec-uk +uk/regulations/uksi/2013/376/77.test.yaml (commit 0644db8), which encodes the +Universal Credit Regulations 2013 reg. 77 independently of this model. Axiom +works in monthly assessment periods; income amounts here are the monthly +figures times 12, and capital amounts are unchanged. This mirrors Axiom's +expected outputs; it does not run Axiom, so it will not notice if that +encoding changes. + +Outputs compared: the 77(5) exclusion, treatment, the trade-asset +disregard, capital treated as possessed, the holding disregard, the 77(3)(b) +self-employed earnings, the 77(4) total of those earnings and director pay +(through the model's earned income), gainful self-employment and the +minimum income floor trigger. + +Axiom input -> policyengine-uk input: +- person_stands_in_position_analogous_to_sole_owner_or_partner_in_relation_to_company + -> stands_as_sole_owner_or_partner_of_company +- company_carries_on_trade -> owned_company_carries_on_trade +- company_carries_on_property_business_within_meaning_of_corporation_tax_act_2009_section_204 + -> owned_company_carries_on_property_business +- person_derives_company_income_that_is_employed_earnings_by_itepa_part_2_chapter_8/9/10 + -> owned_company_intermediary_earnings_chapter +- intermediary_employed_earnings_derived_from_person_main_employment_activities + -> owned_company_intermediary_earnings_from_main_employment +- company_capital_value_or_person_share_value -> owned_company_capital +- company_trade_asset_value_or_person_share_used_wholly_and_exclusively_for_trade + -> owned_company_trade_assets +- person_engaged_in_activities_in_course_of_company_trade + -> is_engaged_in_owned_company_trade +- person_holding_value_in_company -> owned_company_holding_value +- company_income_or_person_share_calculated_as_self_employed_earnings_under_regulation_57 + -> owned_company_income_share +- person_employed_earnings_as_director_or_employee_of_company -> employment_income +- person_activities_in_course_of_company_trade_are_main_employment + -> owned_company_is_main_employment +""" + +import pytest + +from policyengine_uk import Simulation + +YEAR = 2026 +MONTHS = 12 + +CASES = { + "trade_company_main_employment_treats_capital_income_and_mif": dict( + inputs=dict( + stands=True, + trade=True, + property=False, + chapter="NONE", + chapter_main=False, + capital=100_000, + trade_assets=40_000, + engaged=True, + holding=25_000, + income=3_000, + director_pay=1_200, + main=True, + ), + outputs=dict( + applies=True, + trade_assets_disregarded=40_000, + capital=60_000, + holding_disregarded=25_000, + company_earnings=3_000, + company_and_director_earnings=4_200, + gainful=True, + mif=True, + ), + ), + "intermediary_employed_earnings_main_employment_excludes_regulation": dict( + inputs=dict( + stands=True, + trade=True, + property=False, + chapter="CHAPTER_8", + chapter_main=True, + capital=100_000, + trade_assets=40_000, + engaged=True, + holding=25_000, + income=3_000, + director_pay=1_200, + main=True, + ), + outputs=dict( + applies=False, + trade_assets_disregarded=0, + capital=0, + holding_disregarded=0, + company_earnings=0, + company_and_director_earnings=0, + gainful=False, + mif=False, + ), + ), + "property_business_treatment_applies_without_trade_income_rules": dict( + inputs=dict( + stands=True, + trade=False, + property=True, + chapter="NONE", + chapter_main=False, + capital=50_000, + trade_assets=5_000, + engaged=True, + holding=10_000, + income=2_000, + director_pay=800, + main=False, + ), + outputs=dict( + applies=True, + trade_assets_disregarded=0, + capital=50_000, + holding_disregarded=10_000, + company_earnings=0, + company_and_director_earnings=0, + gainful=False, + mif=False, + ), + ), + "trade_assets_not_disregarded_when_person_not_engaged_in_trade_activities": dict( + inputs=dict( + stands=True, + trade=True, + property=False, + chapter="NONE", + chapter_main=False, + capital=90_000, + trade_assets=30_000, + engaged=False, + holding=20_000, + income=2_500, + director_pay=500, + main=False, + ), + outputs=dict( + applies=True, + trade_assets_disregarded=0, + capital=90_000, + holding_disregarded=20_000, + company_earnings=2_500, + company_and_director_earnings=3_000, + gainful=False, + mif=False, + ), + ), +} + + +@pytest.mark.parametrize("name", sorted(CASES)) +def test_reg_77_matches_axiom(name): + i, o = CASES[name]["inputs"], CASES[name]["outputs"] + person = { + "age": 40, + "stands_as_sole_owner_or_partner_of_company": i["stands"], + "owned_company_carries_on_trade": i["trade"], + "owned_company_carries_on_property_business": i["property"], + "owned_company_intermediary_earnings_chapter": i["chapter"], + "owned_company_intermediary_earnings_from_main_employment": i["chapter_main"], + "owned_company_capital": i["capital"], + "owned_company_trade_assets": i["trade_assets"], + "is_engaged_in_owned_company_trade": i["engaged"], + "owned_company_holding_value": i["holding"], + "owned_company_income_share": i["income"] * MONTHS, + "employment_income": i["director_pay"] * MONTHS, + "owned_company_is_main_employment": i["main"], + } + sim = Simulation( + situation={ + "people": {"p": {k: {YEAR: v} for k, v in person.items()}}, + "benunits": {"b": {"members": ["p"]}}, + "households": {"h": {"members": ["p"]}}, + } + ) + + def calc(variable): + return sim.calculate(variable, YEAR)[0] + + assert bool(calc("uc_company_owner_treatment_applies")) == o["applies"] + assert calc("uc_company_capital") == pytest.approx(o["capital"]) + # Axiom's trade-asset disregard is the gap between the company capital + # input and the capital treated as possessed, where the rule applies. + if o["applies"]: + assert i["capital"] - calc("uc_company_capital") == pytest.approx( + o["trade_assets_disregarded"] + ) + assert calc("uc_company_holding_disregard") == pytest.approx( + o["holding_disregarded"] + ) + assert calc("uc_company_self_employed_earnings") == pytest.approx( + o["company_earnings"] * MONTHS + ) + assert bool(calc("uc_company_intermediary_exclusion_applies")) == ( + i["chapter"] != "NONE" and i["chapter_main"] + ) + # Reg. 77(4): the model's earned income adds the company earnings to + # director pay. Axiom's total is gross of the person's own income tax and + # National Insurance, which the model deducts (reg. 55(5), 57(2)), so they + # are added back; these cases have no pension contributions. The totals + # are above the floor, so the floor does not change them. + if o["company_and_director_earnings"]: + own_tax_and_ni = calc("uc_income_tax_on_earnings") + calc( + "uc_national_insurance_on_earnings" + ) + assert calc("pension_contributions") == 0 + assert calc( + "uc_individual_earned_income_before_mif" + ) + own_tax_and_ni == pytest.approx(o["company_and_director_earnings"] * MONTHS) + assert calc("uc_individual_earned_income") == pytest.approx( + calc("uc_individual_earned_income_before_mif") + ) + assert bool(calc("uc_company_gainful_self_employment")) == o["gainful"] + # Axiom's minimum_income_floor_applies_due_to_company_trade is the reg. + # 77(3)(c) trigger; the model's floor also applies to self-employment + # income, which these cases do not have. + assert bool(calc("uc_mif_applies")) == o["mif"] diff --git a/policyengine_uk/tests/test_uc_company_owner_properties.py b/policyengine_uk/tests/test_uc_company_owner_properties.py new file mode 100644 index 0000000000..af1a3ed24e --- /dev/null +++ b/policyengine_uk/tests/test_uc_company_owner_properties.py @@ -0,0 +1,578 @@ +"""Property-based tests for Universal Credit's company owner rule. + +The Universal Credit Regulations 2013 reg. 77 treat a person who stands in a +position analogous to a sole owner or partner of a company carrying on a trade +or a property business as that sole owner or partner: their holding in the +company is disregarded and they are treated as possessing the company's +capital (or their share of it), less assets used wholly and exclusively for a +trade while they work in it (reg. 77(2), (3)(a)); a trading company's income +is their self-employed earnings, in addition to any pay from it (reg. 77(3)(b), +(4)); and if the trade is their main employment the minimum income floor +applies (reg. 77(3)(c)). None of it applies where their income from the +company is employed earnings under the intermediaries rules from their main +employment (reg. 77(5)). + +Invariants, for any generated benefit units: + +1. Inert: when the person is not a company's sole owner or partner, or reg. + 77(5) applies, or the company carries on neither a trade nor a property + business, no company fact changes Universal Credit, its capital or its + earned income. +2. Capital identity: for a benefit unit alone in its household, assessable + capital equals the capital without the rule less the owners' holdings, up + to the household's corporate wealth that holds them, plus each owner's + company capital net of trade assets disregarded while they work in the + trade. +3. Allocation and conservation: in a household of two benefit units that do + not report their capital, the household's capital less every owner's + holding (up to its corporate wealth) is shared between the units in + proportion to their claimants, and each unit adds its own claimants' + company capital; so the units' capital sums to that pool plus the owners' + company capital. +4. Earnings identity: earned income before the floor is pay plus + self-employed earnings, less the person's own relievable pension + contributions, income tax and National Insurance, floored at zero. + Self-employed earnings are the person's own trading profit or loss plus, + for a trading company, the company income share (a loss is negative), + floored at zero (reg. 57(2) steps 1 and 2); a property-only company adds + nothing. +5. Floor: the floor applies exactly to claimants in the all work-related + requirements group, outside a start-up period, who are self-employed or + own a trading company that is their main employment (reg. 62(1), (5), + reg. 77(3)(c)). It never lowers earned income, and a single claimant it + applies to has earned income of at least the floor. +6. Monotone: Universal Credit is non-increasing in the company income share, + among cases where the extra income does not change the benefit cap + earnings exemption (reg. 82(1)(a): crossing the threshold lifts the cap + and can raise the award), and in the company's capital; it is + non-decreasing in the value of the disregarded holding and of disregarded + trade assets. +7. Dependants: company income and capital belonging to a dependant change + neither the award nor its capital, earned income or cap exemption + (reg. 22(1)(b), reg. 82(1)(a)). + +The strategies make most adults owners of a trading company, keep capital +mostly under the £16,000 limit and put earnings around the floor, so each +property is exercised rather than satisfied vacuously. +""" + +import numpy as np +from hypothesis import HealthCheck, 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], +) +COMPANY_FACTS = [ + "stands_as_sole_owner_or_partner_of_company", + "owned_company_carries_on_trade", + "owned_company_carries_on_property_business", + "owned_company_intermediary_earnings_chapter", + "owned_company_intermediary_earnings_from_main_employment", + "owned_company_is_main_employment", + "is_engaged_in_owned_company_trade", + "owned_company_income_share", + "owned_company_capital", + "owned_company_trade_assets", + "owned_company_holding_value", +] +VARIABLES = [ + "universal_credit", + "uc_assessable_capital", + "uc_earned_income", + "is_benefit_cap_exempt_earnings", +] +PERSON_VARIABLES = [ + "uc_individual_earned_income", + "uc_individual_earned_income_before_mif", + "uc_minimum_income_floor", + "uc_mif_applies", + "uc_income_tax_on_earnings", + "uc_national_insurance_on_earnings", + "pension_contributions", + "is_uc_assessed_claimant", +] + +money = st.floats(0, 30_000, allow_nan=False, allow_infinity=False) +company_income = st.floats(-20_000, 40_000, allow_nan=False, allow_infinity=False) +small_capital = st.one_of(st.just(0.0), st.floats(0, 8_000)) + + +def close(a, b): + # Simulation outputs are float32: allow for its rounding at these sizes. + return abs(a - b) <= 1e-2 + 1e-6 * abs(b) + + +@st.composite +def companies(draw): + company_capital = draw(st.one_of(st.just(0.0), st.floats(0, 12_000))) + return dict( + stands_as_sole_owner_or_partner_of_company=draw( + st.sampled_from([True, True, True, False]) + ), + owned_company_carries_on_trade=draw(st.sampled_from([True, True, True, False])), + owned_company_carries_on_property_business=draw( + st.sampled_from([False, False, True]) + ), + owned_company_intermediary_earnings_chapter=draw( + st.sampled_from(["NONE"] * 5 + ["CHAPTER_8", "CHAPTER_9", "CHAPTER_10"]) + ), + owned_company_intermediary_earnings_from_main_employment=draw(st.booleans()), + owned_company_is_main_employment=draw(st.booleans()), + is_engaged_in_owned_company_trade=draw(st.booleans()), + owned_company_income_share=draw(company_income), + owned_company_capital=company_capital, + owned_company_trade_assets=draw(st.floats(0, 1)) * company_capital, + owned_company_holding_value=draw(small_capital), + ) + + +@st.composite +def adults(draw): + return dict( + age=draw(st.integers(25, 60)), + employment_income=draw(st.one_of(st.just(0.0), money)), + self_employment_income=draw(st.one_of(st.just(0.0), st.floats(0, 15_000))), + uc_is_in_startup_period=draw(st.sampled_from([False, False, True])), + company=draw(companies()), + ) + + +@st.composite +def units(draw): + return dict( + adults=draw(st.lists(adults(), min_size=1, max_size=2)), + children=draw(st.integers(0, 2)), + rent=draw(st.floats(0, 15_000)), + savings=draw(small_capital), + corporate_wealth=draw(small_capital), + ) + + +def situation(variants): + """One benefit unit per (unit, company-fact overrides) pair.""" + people, benunits, households = {}, {}, {} + for i, (unit, overrides) in enumerate(variants): + names = [] + for j, adult in enumerate(unit["adults"]): + name = f"p{i}_{j}" + facts = {**adult["company"], **overrides.get(j, {})} + people[name] = { + "age": {YEAR: adult["age"]}, + "employment_income": {YEAR: adult["employment_income"]}, + "self_employment_income": {YEAR: adult["self_employment_income"]}, + "uc_is_in_startup_period": {YEAR: adult["uc_is_in_startup_period"]}, + **{k: {YEAR: v} for k, v in facts.items()}, + } + names.append(name) + for k in range(unit["children"]): + name = f"c{i}_{k}" + people[name] = {"age": {YEAR: 5 + k}} + names.append(name) + benunits[f"b{i}"] = {"members": names, "would_claim_uc": {YEAR: True}} + households[f"h{i}"] = { + "members": names, + "tenure_type": {YEAR: "RENT_PRIVATELY"}, + "rent": {YEAR: unit["rent"]}, + "savings": {YEAR: unit["savings"]}, + "corporate_wealth": {YEAR: unit["corporate_wealth"]}, + } + return {"people": people, "benunits": benunits, "households": households} + + +def calculate(variants): + sim = Simulation(situation=situation(variants)) + out = {v: np.asarray(sim.calculate(v, YEAR)) for v in VARIABLES} + for v in PERSON_VARIABLES: + out[v] = np.asarray(sim.calculate(v, YEAR)) + for v in COMPANY_FACTS + [ + "employment_income", + "self_employment_income", + "uc_is_in_startup_period", + "uc_company_owner_treatment_applies", + ]: + out[v] = np.asarray(sim.calculate(v, YEAR)) + out["all_requirements"] = ( + np.asarray(sim.calculate("uc_work_related_group_apart_from_earnings", YEAR)) + == "ALL_REQUIREMENTS" + ) + # People are laid out unit by unit; record each adult's position. + positions, cursor = [], 0 + for unit, _ in variants: + positions.append(list(range(cursor, cursor + len(unit["adults"])))) + cursor += len(unit["adults"]) + unit["children"] + out["adult_positions"] = positions + return out + + +def _empty(value): + if isinstance(value, bool): + return False + if isinstance(value, str): + return "NONE" + return 0.0 + + +def no_company(unit): + return { + j: {k: _empty(v) for k, v in adult["company"].items()} + for j, adult in enumerate(unit["adults"]) + } + + +def applies(company): + return ( + company["stands_as_sole_owner_or_partner_of_company"] + and ( + company["owned_company_carries_on_trade"] + or company["owned_company_carries_on_property_business"] + ) + and not ( + # Reg. 77(5) as in force from 28 November 2018. + company["owned_company_intermediary_earnings_chapter"] != "NONE" + and company["owned_company_intermediary_earnings_from_main_employment"] + ) + ) + + +def company_capital_of(company): + """Capital treated as possessed under reg. 77(2), net of 77(3)(a).""" + engaged = ( + company["is_engaged_in_owned_company_trade"] + or company["owned_company_is_main_employment"] + ) + disregarded = ( + company["owned_company_trade_assets"] + if company["owned_company_carries_on_trade"] and engaged + else 0.0 + ) + return max(0.0, company["owned_company_capital"] - disregarded) + + +@PROPERTY_SETTINGS +@given(st.lists(units(), min_size=2, max_size=4)) +def test_rule_is_inert_when_it_does_not_apply(unit_list): + # Force every adult outside reg. 77 in one of three ways, then compare + # with the same units carrying no company facts at all. + variants, baselines = [], [] + for i, unit in enumerate(unit_list): + excluded = {} + for j, adult in enumerate(unit["adults"]): + way = (i + j) % 3 + if way == 0: + excluded[j] = {"stands_as_sole_owner_or_partner_of_company": False} + elif way == 1: + excluded[j] = { + "owned_company_intermediary_earnings_chapter": "CHAPTER_10", + "owned_company_intermediary_earnings_from_main_employment": True, + } + else: + excluded[j] = { + "owned_company_carries_on_trade": False, + "owned_company_carries_on_property_business": False, + } + variants.append((unit, excluded)) + baselines.append((unit, no_company(unit))) + got, base = calculate(variants), calculate(baselines) + assert not got["uc_company_owner_treatment_applies"].any() + for v in VARIABLES + ["uc_individual_earned_income", "uc_mif_applies"]: + np.testing.assert_allclose(got[v], base[v], rtol=0, atol=1e-6, err_msg=v) + + +@PROPERTY_SETTINGS +@given(st.lists(units(), min_size=2, max_size=4)) +def test_capital_and_earnings_identities(unit_list): + got = calculate([(u, {}) for u in unit_list]) + base = calculate([(u, no_company(u)) for u in unit_list]) + for i, unit in enumerate(unit_list): + holdings, company_capital = 0.0, 0.0 + for j, adult in enumerate(unit["adults"]): + c = adult["company"] + k = got["adult_positions"][i][j] + assert got["uc_company_owner_treatment_applies"][k] == applies(c) + if applies(c): + holdings += c["owned_company_holding_value"] + company_capital += company_capital_of(c) + # Earnings identity, before the floor: reg. 77(3)(b) adds the + # company's profit or loss to the person's own trade (reg. 57(2) + # step 2) before a negative total becomes nil. + company_profit_or_loss = ( + c["owned_company_income_share"] + if applies(c) and c["owned_company_carries_on_trade"] + else 0.0 + ) + self_employed = max( + 0.0, adult["self_employment_income"] + company_profit_or_loss + ) + deductions = ( + got["pension_contributions"][k] + + got["uc_income_tax_on_earnings"][k] + + got["uc_national_insurance_on_earnings"][k] + ) + before = got["uc_individual_earned_income_before_mif"][k] + assert close( + before, + max(0.0, adult["employment_income"] + self_employed - deductions), + ) + # Floor trigger: reg. 62(1) and (5), with reg. 77(3)(c). + trade_main = ( + applies(c) + and c["owned_company_carries_on_trade"] + and c["owned_company_is_main_employment"] + ) + # The model's stored (float32) self-employment income: a generated + # amount too small for float32 is zero there. + gainful = got["self_employment_income"][k] != 0 or trade_main + assert got["uc_mif_applies"][k] == ( + got["all_requirements"][k] + and gainful + and not adult["uc_is_in_startup_period"] + ) + earned = got["uc_individual_earned_income"][k] + floor_can_apply = ( + got["uc_mif_applies"][k] and got["is_uc_assessed_claimant"][k] + ) + if floor_can_apply: + assert earned >= before - 1e-2 + if len(unit["adults"]) == 1: + floor = got["uc_minimum_income_floor"][k] + assert close(earned, max(before, floor)) + else: + assert close(earned, before) + disregarded = min(holdings, unit["corporate_wealth"]) + expected = base["uc_assessable_capital"][i] - disregarded + company_capital + assert close(got["uc_assessable_capital"][i], expected) + + +def _owner(unit, **facts): + """Make the first adult a sole owner of a trading company.""" + company = { + "stands_as_sole_owner_or_partner_of_company": True, + "owned_company_carries_on_trade": True, + "owned_company_intermediary_earnings_chapter": "NONE", + **facts, + } + return {0: company} + + +@PROPERTY_SETTINGS +@given( + st.lists(units(), min_size=1, max_size=3), + st.floats(1, 30_000), + st.floats(1, 30_000), +) +def test_universal_credit_monotone_in_company_facts(unit_list, bump, asset_bump): + variants = [] + for unit in unit_list: + c = unit["adults"][0]["company"] + low = _owner(unit) + variants += [ + (unit, low), + ( + unit, + _owner( + unit, + owned_company_income_share=c["owned_company_income_share"] + bump, + ), + ), + ( + unit, + _owner(unit, owned_company_capital=c["owned_company_capital"] + bump), + ), + ( + unit, + _owner( + unit, + owned_company_holding_value=c["owned_company_holding_value"] + bump, + ), + ), + ( + unit, + _owner( + unit, + is_engaged_in_owned_company_trade=True, + owned_company_capital=c["owned_company_capital"] + asset_bump, + owned_company_trade_assets=c["owned_company_trade_assets"] + + asset_bump, + ), + ), + (unit, _owner(unit, is_engaged_in_owned_company_trade=True)), + ] + got = calculate(variants) + uc = got["universal_credit"].reshape(-1, 6) + exempt = got["is_benefit_cap_exempt_earnings"].reshape(-1, 6) + tol = 1e-6 + base, more_income, more_capital, more_holding, more_assets, engaged = uc.T + # Extra earnings that lift the benefit cap (reg. 82(1)(a)) can raise the + # award, so income monotonicity holds where the exemption is unchanged. + same_exemption = exempt[:, 0] == exempt[:, 1] + assert (more_income[same_exemption] <= base[same_exemption] + tol).all() + assert (more_capital <= base + tol).all() + assert (more_holding >= base - tol).all() + # Extra capital that is itself a disregarded trade asset changes nothing + # once the owner works in the trade. + np.testing.assert_allclose(more_assets, engaged, rtol=0, atol=1e-6) + + +@PROPERTY_SETTINGS +@given(st.lists(units(), min_size=1, max_size=3), money) +def test_property_company_gives_no_earnings(unit_list, share): + variants = [] + for unit in unit_list: + facts = dict( + owned_company_carries_on_trade=False, + owned_company_carries_on_property_business=True, + ) + variants += [ + ( + unit, + _owner( + unit, + **facts, + owned_company_income_share=0.0, + owned_company_is_main_employment=False, + ), + ), + ( + unit, + _owner( + unit, + **facts, + owned_company_income_share=share, + owned_company_is_main_employment=True, + ), + ), + ] + got = calculate(variants) + for v in ["universal_credit", "uc_earned_income"]: + a, b = got[v].reshape(-1, 2).T + np.testing.assert_allclose(a, b, rtol=0, atol=1e-6, err_msg=v) + + +@PROPERTY_SETTINGS +@given(st.lists(st.tuples(units(), units()), min_size=1, max_size=3)) +def test_household_capital_is_allocated_and_conserved_across_benefit_units(pairs): + # Each household holds two benefit units: the first unit's adults and the + # second's, with the first unit's savings and corporate wealth. No unit + # reports its own capital, so every owner's holding leaves the household + # pool, the rest is shared in proportion to each unit's claimants (all + # adults here), and each unit adds its own claimants' company capital. + people, benunits, households = {}, {}, {} + expected_units, expected_households = [], [] + for h, (first, second) in enumerate(pairs): + names, holdings, unit_company_capital = [], 0.0, [] + for b, unit in enumerate((first, second)): + members, company_capital = [], 0.0 + for j, adult in enumerate(unit["adults"]): + name = f"h{h}_b{b}_p{j}" + c = adult["company"] + people[name] = { + "age": {YEAR: adult["age"]}, + **{k: {YEAR: v} for k, v in c.items()}, + } + if applies(c): + holdings += c["owned_company_holding_value"] + company_capital += company_capital_of(c) + members.append(name) + benunits[f"h{h}_b{b}"] = {"members": members} + unit_company_capital.append(company_capital) + names += members + households[f"h{h}"] = { + "members": names, + "savings": {YEAR: first["savings"]}, + "corporate_wealth": {YEAR: first["corporate_wealth"]}, + } + disregarded = min(holdings, first["corporate_wealth"]) + pool = first["savings"] + first["corporate_wealth"] - disregarded + claimants = [len(first["adults"]), len(second["adults"])] + for n, company_capital in zip(claimants, unit_company_capital): + expected_units.append(pool * n / sum(claimants) + company_capital) + expected_households.append(pool + sum(unit_company_capital)) + sim = Simulation( + situation={"people": people, "benunits": benunits, "households": households} + ) + capital = np.asarray(sim.calculate("uc_assessable_capital", YEAR)) + assert (capital >= 0).all() + for got, want in zip(capital, expected_units): + assert close(got, want) + per_household = capital.reshape(-1, 2).sum(axis=1) + for got, want in zip(per_household, expected_households): + assert close(got, want) + + +@PROPERTY_SETTINGS +@given( + st.lists(units(), min_size=1, max_size=3), + st.floats(1, 40_000), + st.floats(1, 50_000), + st.booleans(), +) +def test_dependant_company_does_not_change_the_award(unit_list, income, capital, main): + # A qualifying young person in each unit is the sole owner of a trading + # company with income and capital. Its income is theirs, not a + # claimant's (reg. 22(1)(b), reg. 82(1)(a)), and only a claimant's or + # partner's company capital counts. With no holding in the household's + # capital, the award matches the same unit without the company. + company = { + "stands_as_sole_owner_or_partner_of_company": True, + "owned_company_carries_on_trade": True, + "owned_company_carries_on_property_business": False, + "owned_company_intermediary_earnings_chapter": "NONE", + "owned_company_intermediary_earnings_from_main_employment": False, + "owned_company_is_main_employment": main, + "is_engaged_in_owned_company_trade": False, + "owned_company_income_share": income, + "owned_company_capital": capital, + "owned_company_trade_assets": 0.0, + "owned_company_holding_value": 0.0, + } + none = {k: _empty(v) for k, v in company.items()} + + def build(facts): + people, benunits, households, dependants = {}, {}, {}, [] + for i, unit in enumerate(unit_list): + names = [] + for j, adult in enumerate(unit["adults"]): + name = f"p{i}_{j}" + people[name] = { + "age": {YEAR: adult["age"]}, + "employment_income": {YEAR: adult["employment_income"]}, + "self_employment_income": {YEAR: adult["self_employment_income"]}, + } + names.append(name) + people[f"d{i}"] = { + "age": {YEAR: 17}, + "is_in_non_advanced_education": {YEAR: True}, + **{k: {YEAR: v} for k, v in facts.items()}, + } + names.append(f"d{i}") + dependants.append(len(people) - 1) + benunits[f"b{i}"] = {"members": names, "would_claim_uc": {YEAR: True}} + households[f"h{i}"] = { + "members": names, + "tenure_type": {YEAR: "RENT_PRIVATELY"}, + "rent": {YEAR: unit["rent"]}, + "savings": {YEAR: unit["savings"]}, + } + sim = Simulation( + situation={ + "people": people, + "benunits": benunits, + "households": households, + } + ) + claimant = np.asarray(sim.calculate("is_uc_assessed_claimant", YEAR)) + assert not claimant[dependants].any() + earnings = np.asarray(sim.calculate("uc_company_self_employed_earnings", YEAR)) + assert (earnings[dependants] == facts["owned_company_income_share"]).all() + return {v: np.asarray(sim.calculate(v, YEAR)) for v in VARIABLES} + + got, base = build(company), build(none) + for v in VARIABLES: + np.testing.assert_allclose(got[v], base[v], rtol=0, atol=1e-6, err_msg=v) diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py index 1bdb797d6a..4f2d4f17d4 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py @@ -28,6 +28,9 @@ def formula(benunit, period, parameters): ( person("employment_income", period) + person("self_employment_income", period) + # A company's income treated as its owner's self-employed + # earnings (UC Regs 2013 reg. 77(3)(b)). + + person("uc_company_self_employed_earnings", period) - person("income_tax", period) - person("national_insurance", period) ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_capital.py b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_capital.py new file mode 100644 index 0000000000..541e637805 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_capital.py @@ -0,0 +1,51 @@ +from policyengine_uk.model_api import * + + +class uc_company_capital(Variable): + value_type = float + entity = Person + label = "Universal Credit capital treated as held from an owned company" + documentation = ( + "The value of the company's capital, or the person's share of it, " + "which Universal Credit treats the person as possessing when they " + "stand as the company's sole owner or partner. Assets used wholly and " + "exclusively for the company's trade are disregarded while the person " + "is engaged in activities in the course of that trade. This follows " + "the regulation. DWP guidance (ADM H4372) instead disregards all of " + "the company capital while the person works in the business, and ADM " + "H1880 paraphrases the test as 'wholly or mainly', the wording of the " + "business-assets disregard in Schedule 10 para. 7." + ) + definition_period = YEAR + unit = GBP + quantity_type = STOCK + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(2) and (3)(a)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="Advice for Decision Making, Chapter H4, paras. H4367 and H4371-H4373", + href="https://www.gov.uk/government/publications/advice-for-decision-making-staff-guide", + ), + ] + + def formula(person, period, parameters): + applies = person("uc_company_owner_treatment_applies", period) + # A person whose main employment is the company's trade is engaged in + # activities in the course of that trade. Reg. 77(3)(a) does not say + # so; it is an inference from "engaged in activities in the course + # of that trade", which work as one's main employment must be. + engaged = person("is_engaged_in_owned_company_trade", period) | person( + "owned_company_is_main_employment", period + ) + trade_assets_disregarded = ( + person("owned_company_carries_on_trade", period) & engaged + ) + disregarded = where( + trade_assets_disregarded, + person("owned_company_trade_assets", period), + 0, + ) + capital = max_(0, person("owned_company_capital", period) - disregarded) + return where(applies, capital, 0) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_gainful_self_employment.py b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_gainful_self_employment.py new file mode 100644 index 0000000000..be94519de0 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_gainful_self_employment.py @@ -0,0 +1,36 @@ +from policyengine_uk.model_api import * + + +class uc_company_gainful_self_employment(Variable): + value_type = bool + entity = Person + label = "Universal Credit treats the person as gainfully self-employed through an owned company" + documentation = ( + "Whether Universal Credit treats the person as in gainful " + "self-employment because they stand as sole owner or partner of a " + "company carrying on a trade and their activities in the course of " + "that trade are their main employment. The minimum income floor then " + "applies (unless they are in a start-up period)." + ) + definition_period = YEAR + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(3)(c)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="The Universal Credit Regulations (Northern Ireland) 2016 regs. 77(3)(c) and 63", + href="https://www.legislation.gov.uk/nisr/2016/216/regulation/77", + ), + dict( + title="Advice for Decision Making, Chapter H4, para. H4375", + href="https://www.gov.uk/government/publications/advice-for-decision-making-staff-guide", + ), + ] + + def formula(person, period, parameters): + return ( + person("uc_company_owner_treatment_applies", period) + & person("owned_company_carries_on_trade", period) + & person("owned_company_is_main_employment", period) + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_holding_disregard.py b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_holding_disregard.py new file mode 100644 index 0000000000..80691ccbc8 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_holding_disregard.py @@ -0,0 +1,26 @@ +from policyengine_uk.model_api import * + + +class uc_company_holding_disregard(Variable): + value_type = float + entity = Person + label = "Universal Credit disregard of a holding in an owned company" + documentation = ( + "The value of the person's holding in the company in which they stand " + "as sole owner or partner, which Universal Credit disregards; the " + "company's own capital is counted instead (uc_company_capital)." + ) + definition_period = YEAR + unit = GBP + quantity_type = STOCK + reference = dict( + title="The Universal Credit Regulations 2013 reg. 77(2)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ) + + def formula(person, period, parameters): + return where( + person("uc_company_owner_treatment_applies", period), + person("owned_company_holding_value", period), + 0, + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_intermediary_exclusion_applies.py b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_intermediary_exclusion_applies.py new file mode 100644 index 0000000000..c95adac4bf --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_intermediary_exclusion_applies.py @@ -0,0 +1,33 @@ +from policyengine_uk.model_api import * + + +class uc_company_intermediary_exclusion_applies(Variable): + value_type = bool + entity = Person + label = "Universal Credit company owner rule excluded by the intermediaries rules" + documentation = ( + "Whether income the person derives from the company is employed " + "earnings under the intermediaries or managed service company rules " + "in a way that takes them out of Universal Credit's company owner " + "rule: Chapter 8 or 9 of Part 2 of ITEPA 2003 until 27 November 2018; " + "Chapter 8, 9 or 10, derived from activities that are the person's " + "main employment, from 28 November 2018." + ) + definition_period = YEAR + reference = dict( + title="The Universal Credit Regulations 2013 reg. 77(5)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ) + + def formula(person, period, parameters): + p = parameters( + period + ).gov.dwp.universal_credit.company_owner.intermediary_exclusion + chapter = person("owned_company_intermediary_earnings_chapter", period) + excluded_chapter = np.isin(chapter.decode_to_str(), p.chapters) + from_main_employment = person( + "owned_company_intermediary_earnings_from_main_employment", period + ) + if p.requires_main_employment: + return excluded_chapter & from_main_employment + return excluded_chapter diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_owner_treatment_applies.py b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_owner_treatment_applies.py new file mode 100644 index 0000000000..dca42718b4 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_owner_treatment_applies.py @@ -0,0 +1,37 @@ +from policyengine_uk.model_api import * + + +class uc_company_owner_treatment_applies(Variable): + value_type = bool + entity = Person + label = "Universal Credit treats the person as a company's sole owner or partner" + documentation = ( + "Whether the person stands in a position analogous to a sole owner or " + "partner in relation to a company that carries on a trade or a " + "property business, so that Universal Credit treats them as that sole " + "owner or partner. This does not apply where income the person " + "derives from the company is employed earnings under the " + "intermediaries or managed service company rules " + "(uc_company_intermediary_exclusion_applies)." + ) + definition_period = YEAR + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(1) and (5)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="The Universal Credit Regulations (Northern Ireland) 2016 reg. 77(1) and (5)", + href="https://www.legislation.gov.uk/nisr/2016/216/regulation/77", + ), + ] + + def formula(person, period, parameters): + carries_on_business = person("owned_company_carries_on_trade", period) | person( + "owned_company_carries_on_property_business", period + ) + return ( + person("stands_as_sole_owner_or_partner_of_company", period) + & carries_on_business + & ~person("uc_company_intermediary_exclusion_applies", period) + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_self_employed_earnings.py b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_self_employed_earnings.py new file mode 100644 index 0000000000..afdc8682f7 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/company/uc_company_self_employed_earnings.py @@ -0,0 +1,47 @@ +from policyengine_uk.model_api import * + + +class uc_company_self_employed_earnings(Variable): + value_type = float + entity = Person + label = "Universal Credit self-employed profit or loss from an owned company" + documentation = ( + "The income of a company carrying on a trade, or the person's share " + "of it, which Universal Credit treats as the person's self-employed " + "earnings when they stand as the company's sole owner or partner. It " + "is in addition to any pay they receive as the company's director or " + "employee. A company that carries on only a property business gives " + "no earnings. Where the minimum income floor applies it is compared " + "with the person's total earned income, including this amount and " + "their pay (reg. 62(2)); DWP guidance (ADM H4375) describes comparing " + "the company income alone. This is the step 1 amount of reg. 57(2) " + "for the company's trade, so a loss is negative here: step 2 adds it " + "to the profit or loss of any trade the person carries on themselves, " + "and the total is nil if negative (uc_individual_earned_income_before_mif). " + "Carrying a loss forward against later profits (reg. 57A) is not " + "modelled." + ) + definition_period = YEAR + unit = GBP + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(3)(b) and (4)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="The Universal Credit Regulations 2013 reg. 57(2)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/57", + ), + dict( + title="Advice for Decision Making, Chapter H4, paras. H4374 and H4376", + href="https://www.gov.uk/government/publications/advice-for-decision-making-staff-guide", + ), + ] + + def formula(person, period, parameters): + trading_company_owner = person( + "uc_company_owner_treatment_applies", period + ) & person("owned_company_carries_on_trade", period) + return where( + trading_company_owner, person("owned_company_income_share", period), 0 + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/household_uc_unreported_company_holdings.py b/policyengine_uk/variables/gov/dwp/universal_credit/household_uc_unreported_company_holdings.py new file mode 100644 index 0000000000..8231ee5c86 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/household_uc_unreported_company_holdings.py @@ -0,0 +1,29 @@ +from policyengine_uk.model_api import * + + +class household_uc_unreported_company_holdings(Variable): + value_type = float + entity = Household + label = "Household company holdings disregarded for Universal Credit, unreported benunits" + documentation = ( + "The disregarded holdings (uc_company_holding_disregard) of people in " + "benunits without reported UC capital. They are taken out of the " + "household's capital before it is shared among those benunits' " + "claimants and partners, so no benunit counts any part of a " + "disregarded holding. A holding is shares, held within the " + "household's corporate_wealth, so no more than that is taken out: a " + "holding entered without matching corporate_wealth cannot remove " + "savings or property." + ) + definition_period = YEAR + unit = GBP + quantity_type = STOCK + + def formula(household, period, parameters): + person = household.members + has_reported_capital = person.benunit("uc_reported_capital", period) >= 0 + holdings = household.sum( + person("uc_company_holding_disregard", period) * ~has_reported_capital + ) + corporate_wealth = max_(0, household("corporate_wealth", period)) + return min_(holdings, corporate_wealth) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_individual_earned_income_before_mif.py b/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_individual_earned_income_before_mif.py index d218583e0b..c59bb4d452 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_individual_earned_income_before_mif.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_individual_earned_income_before_mif.py @@ -62,8 +62,14 @@ def formula(person, period, parameters): if bi.interactions.include_in_means_tests: earnings_components.append("basic_income") # A trading loss makes self-employed earnings nil (reg. 57(2)); it - # is not set against employed earnings. - self_employed = max_(0, person("self_employment_income", period)) + # is not set against employed earnings. A company's income treated + # as the owner's self-employed earnings (reg. 77(3)(b)) is another + # trade, added before that nil rule (reg. 57(2) step 2). + self_employed = max_( + 0, + person("self_employment_income", period) + + person("uc_company_self_employed_earnings", period), + ) earnings = add(person, period, earnings_components) + self_employed # Contributions paid after the person reaches 75 are not relievable # (Finance Act 2004 s. 188(3)(a)), so reg. 55(5)(a) does not deduct diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py index a821162b77..4b84464a86 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_assessable_capital.py @@ -18,7 +18,10 @@ class uc_assessable_capital(Variable): "Reform Act 2012 and regulation 18 of the Universal Credit Regulations. " "Person-level sources, such as a Lifetime ISA, count only for the " "holder's own benunit, and only when the holder is its claimant or " - "partner (is_uc_claimant): a dependant's capital is not the claimant's." + "partner (is_uc_claimant): a dependant's capital is not the claimant's. " + "A claimant or partner who stands as sole owner or partner of a " + "company has their holding in it disregarded and the company's capital " + "counted instead (regulation 77(2))." ) definition_period = YEAR unit = GBP @@ -26,6 +29,7 @@ class uc_assessable_capital(Variable): reference = ( "https://www.legislation.gov.uk/ukpga/2012/5/section/5", "https://www.legislation.gov.uk/uksi/2013/376/regulation/18", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/77", ) def formula(benunit, period, parameters): @@ -47,17 +51,37 @@ def formula(benunit, period, parameters): claimant_divisor = max_(1, household_unreported_claimants) reported_capital = benunit("uc_reported_capital", period) use_reported_capital = reported_capital >= 0 + # A person who stands as sole owner or partner of a company has their + # holding in it disregarded and is treated as possessing the company's + # capital (or their share of it) instead (UC Regs 2013 reg. 77(2), + # (3)(a)). Holdings of people in unreported benunits leave the + # household pool before it is shared out, up to the household's + # corporate_wealth that holds them; a reporting benunit's claimant and + # partner's holdings come off its reported capital, which has no + # breakdown to cap them against. Neither subtraction can take capital + # below zero. Only the claimant and partner's company capital is + # theirs to count. + unreported_holdings = household( + "household_uc_unreported_company_holdings", period + ) residual_household_capital = max_( - 0, household_capital - household_reported_capital + 0, household_capital - household_reported_capital - unreported_holdings ) household_capital_proxy = where( household_unreported_claimants > 0, residual_household_capital * benunit_claimants / claimant_divisor, 0, ) + own_holdings = benunit.sum( + benunit.members("uc_company_holding_disregard", period) + * claimant_or_partner + ) assessed_capital = where( use_reported_capital, - reported_capital, + max_(0, reported_capital - own_holdings), household_capital_proxy + person_capital, ) - return max_(0, assessed_capital) + company_capital = benunit.sum( + benunit.members("uc_company_capital", period) * claimant_or_partner + ) + return max_(0, assessed_capital) + company_capital diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/work_related_requirements/uc_is_in_gainful_self_employment.py b/policyengine_uk/variables/gov/dwp/universal_credit/work_related_requirements/uc_is_in_gainful_self_employment.py index dc14ab3c79..202fa1cb92 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/work_related_requirements/uc_is_in_gainful_self_employment.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/work_related_requirements/uc_is_in_gainful_self_employment.py @@ -21,4 +21,8 @@ class uc_is_in_gainful_self_employment(Variable): def formula(person, period, parameters): # A trade carried on in expectation of profit (reg. 64(c)), so a # trading loss counts (ADM H4503). - return person("self_employment_income", period) != 0 + # A person whose main employment is the trade of a company they stand + # as sole owner or partner of is treated as if in gainful + # self-employment (reg. 77(3)(c)). + company = person("uc_company_gainful_self_employment", period) + return (person("self_employment_income", period) != 0) | company diff --git a/policyengine_uk/variables/input/owned_company/README.md b/policyengine_uk/variables/input/owned_company/README.md new file mode 100644 index 0000000000..784cbe703c --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/README.md @@ -0,0 +1,11 @@ +# Owned company + +Facts about a company in which a person stands in a position analogous to a +sole owner or partner (an owner-manager trading through a limited company, or +a shareholder with meaningful influence in a closely held property company). Universal Credit treats such +a person as the sole owner or partner (Universal Credit Regulations 2013 reg. +77; Universal Credit Regulations (Northern Ireland) 2016 reg. 77); see +`gov/dwp/universal_credit/company/`. + +Every input defaults to "no such company", so datasets that do not carry these +facts are unaffected. diff --git a/policyengine_uk/variables/input/owned_company/is_engaged_in_owned_company_trade.py b/policyengine_uk/variables/input/owned_company/is_engaged_in_owned_company_trade.py new file mode 100644 index 0000000000..7594ad73c0 --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/is_engaged_in_owned_company_trade.py @@ -0,0 +1,25 @@ +from policyengine_uk.model_api import * + + +class is_engaged_in_owned_company_trade(Variable): + value_type = bool + entity = Person + label = "engaged in the trade of an owned company" + documentation = ( + "Whether the person is engaged in activities in the course of the " + "trade of the company in which they stand as sole owner or partner. " + "DWP guidance treats any work for the company, however little (such " + "as taking its telephone messages), as engagement." + ) + definition_period = YEAR + default_value = False + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(3)(a)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="Advice for Decision Making, Chapter H4, para. H4373", + href="https://www.gov.uk/government/publications/advice-for-decision-making-staff-guide", + ), + ] diff --git a/policyengine_uk/variables/input/owned_company/owned_company_capital.py b/policyengine_uk/variables/input/owned_company/owned_company_capital.py new file mode 100644 index 0000000000..8155d67c80 --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_capital.py @@ -0,0 +1,30 @@ +from policyengine_uk.model_api import * + + +class owned_company_capital(Variable): + value_type = float + entity = Person + label = "owned company capital" + documentation = ( + "The value of the capital of the company in which the person stands " + "as sole owner or partner, or the person's share of that value. DWP " + "guidance takes the company's net value (its capital less its " + "liabilities) and a person's share as their fraction of its shares. " + "Includes any assets used wholly and exclusively for the company's " + "trade (recorded again in owned_company_trade_assets)." + ) + definition_period = YEAR + unit = GBP + default_value = 0 + quantity_type = STOCK + uprating = "gov.economic_assumptions.indices.obr.per_capita.gdp" + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(2)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="Advice for Decision Making, Chapter H1, paras. H1892-H1893", + href="https://www.gov.uk/government/publications/advice-for-decision-making-staff-guide", + ), + ] diff --git a/policyengine_uk/variables/input/owned_company/owned_company_carries_on_property_business.py b/policyengine_uk/variables/input/owned_company/owned_company_carries_on_property_business.py new file mode 100644 index 0000000000..d44f14ef31 --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_carries_on_property_business.py @@ -0,0 +1,25 @@ +from policyengine_uk.model_api import * + + +class owned_company_carries_on_property_business(Variable): + value_type = bool + entity = Person + label = "owned company carries on a property business" + documentation = ( + "Whether the company in which the person stands as sole owner or " + "partner carries on a property business within the meaning of section " + "204 of the Corporation Tax Act 2009, such as letting land or " + "buildings it owns as an investment." + ) + definition_period = YEAR + default_value = False + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(1) and (6)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="Corporation Tax Act 2009 s. 204", + href="https://www.legislation.gov.uk/ukpga/2009/4/section/204", + ), + ] diff --git a/policyengine_uk/variables/input/owned_company/owned_company_carries_on_trade.py b/policyengine_uk/variables/input/owned_company/owned_company_carries_on_trade.py new file mode 100644 index 0000000000..f37f2c6d16 --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_carries_on_trade.py @@ -0,0 +1,25 @@ +from policyengine_uk.model_api import * + + +class owned_company_carries_on_trade(Variable): + value_type = bool + entity = Person + label = "owned company carries on a trade" + documentation = ( + "Whether the company in which the person stands as sole owner or " + "partner carries on a trade: a business with a profit-seeking motive, " + "usually providing goods or services to customers on a commercial " + "basis." + ) + definition_period = YEAR + default_value = False + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(1) and (3)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="Advice for Decision Making, Chapter H4, para. H4370", + href="https://www.gov.uk/government/publications/advice-for-decision-making-staff-guide", + ), + ] diff --git a/policyengine_uk/variables/input/owned_company/owned_company_holding_value.py b/policyengine_uk/variables/input/owned_company/owned_company_holding_value.py new file mode 100644 index 0000000000..2624e2cc7e --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_holding_value.py @@ -0,0 +1,22 @@ +from policyengine_uk.model_api import * + + +class owned_company_holding_value(Variable): + value_type = float + entity = Person + label = "value of holding in owned company" + documentation = ( + "The value of the person's holding (their shares) in the company in " + "which they stand as sole owner or partner, to the extent that it is " + "already counted in the household's capital (for example within " + "corporate_wealth) or in uc_reported_capital." + ) + definition_period = YEAR + unit = GBP + default_value = 0 + quantity_type = STOCK + uprating = "gov.economic_assumptions.indices.obr.per_capita.gdp" + reference = dict( + title="The Universal Credit Regulations 2013 reg. 77(2)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ) diff --git a/policyengine_uk/variables/input/owned_company/owned_company_income_share.py b/policyengine_uk/variables/input/owned_company/owned_company_income_share.py new file mode 100644 index 0000000000..09d4ecd1a7 --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_income_share.py @@ -0,0 +1,44 @@ +from policyengine_uk.model_api import * + + +class owned_company_income_share(Variable): + value_type = float + entity = Person + label = "share of owned company income" + documentation = ( + "The income of the company in which the person stands as sole owner " + "or partner, or the person's share of that income, calculated as " + "self-employed earnings would be: the company's actual receipts less " + "its permitted expenses, on a cash basis; negative for a loss. Pay " + "the company gives the person as a director or employee is a company " + "expense here (DWP operational guidance deducts it from the company's " + "net profit so that it is not counted twice) and is recorded in " + "employment_income instead. Corporation tax is not " + "deducted: the self-employed earnings calculation deducts only " + "income tax and National Insurance the person pays, and neither the " + "regulations nor DWP guidance provide for corporation tax. Dividends " + "the person receives from the company are paid out of this income, " + "so they are not added to it. Dividends are never Universal Credit " + "unearned income (UC Regs 2013 reg. 66(1)); a model version that still " + "counts dividend_income as unearned income counts any dividends this " + "person also records there a second time." + ) + definition_period = YEAR + unit = GBP + default_value = 0 + quantity_type = FLOW + uprating = "gov.economic_assumptions.indices.obr.per_capita.mixed_income" + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(3)(b)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="The Universal Credit Regulations 2013 reg. 57", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/57", + ), + dict( + title="DWP Universal Credit guidance: Companies and directors, gainful self-employment (V9, House of Commons Library DEP2025-0769)", + href="https://data.parliament.uk/DepositedPapers/Files/DEP2025-0769/044_Companies_and_directorsgainful_selfemploymentGuidance_V9.pdf", + ), + ] diff --git a/policyengine_uk/variables/input/owned_company/owned_company_intermediary_earnings_chapter.py b/policyengine_uk/variables/input/owned_company/owned_company_intermediary_earnings_chapter.py new file mode 100644 index 0000000000..0306448847 --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_intermediary_earnings_chapter.py @@ -0,0 +1,27 @@ +from policyengine_uk.model_api import * + + +class ITEPAIntermediaryChapter(Enum): + NONE = "None" + CHAPTER_8 = "Chapter 8: workers under arrangements made by intermediaries" + CHAPTER_9 = "Chapter 9: managed service companies" + CHAPTER_10 = "Chapter 10: workers' services provided through intermediaries" + + +class owned_company_intermediary_earnings_chapter(Variable): + value_type = Enum + possible_values = ITEPAIntermediaryChapter + default_value = ITEPAIntermediaryChapter.NONE + entity = Person + label = "owned company income made employed earnings by the intermediaries rules" + documentation = ( + "The chapter of Part 2 of the Income Tax (Earnings and Pensions) Act " + "2003, if any, under which income the person derives from the company " + "in which they stand as sole owner or partner is employed earnings " + "(the IR35 and managed service company rules)." + ) + definition_period = YEAR + reference = dict( + title="Income Tax (Earnings and Pensions) Act 2003 Part 2 Chapters 8-10", + href="https://www.legislation.gov.uk/ukpga/2003/1/part/2", + ) diff --git a/policyengine_uk/variables/input/owned_company/owned_company_intermediary_earnings_from_main_employment.py b/policyengine_uk/variables/input/owned_company/owned_company_intermediary_earnings_from_main_employment.py new file mode 100644 index 0000000000..af77be864c --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_intermediary_earnings_from_main_employment.py @@ -0,0 +1,18 @@ +from policyengine_uk.model_api import * + + +class owned_company_intermediary_earnings_from_main_employment(Variable): + value_type = bool + entity = Person + label = "owned company intermediary earnings derive from main employment" + documentation = ( + "Whether the income recorded in " + "owned_company_intermediary_earnings_chapter derives from activities " + "that are the person's main employment." + ) + definition_period = YEAR + default_value = False + reference = dict( + title="The Universal Credit Regulations 2013 reg. 77(5)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ) diff --git a/policyengine_uk/variables/input/owned_company/owned_company_is_main_employment.py b/policyengine_uk/variables/input/owned_company/owned_company_is_main_employment.py new file mode 100644 index 0000000000..c39d973bc1 --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_is_main_employment.py @@ -0,0 +1,18 @@ +from policyengine_uk.model_api import * + + +class owned_company_is_main_employment(Variable): + value_type = bool + entity = Person + label = "owned company trade is main employment" + documentation = ( + "Whether the person's activities in the course of the trade of the " + "company in which they stand as sole owner or partner are their main " + "employment." + ) + definition_period = YEAR + default_value = False + reference = dict( + title="The Universal Credit Regulations 2013 reg. 77(3)(c)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ) diff --git a/policyengine_uk/variables/input/owned_company/owned_company_trade_assets.py b/policyengine_uk/variables/input/owned_company/owned_company_trade_assets.py new file mode 100644 index 0000000000..160e9263cc --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/owned_company_trade_assets.py @@ -0,0 +1,22 @@ +from policyengine_uk.model_api import * + + +class owned_company_trade_assets(Variable): + value_type = float + entity = Person + label = "owned company trade assets" + documentation = ( + "The part of owned_company_capital made up of the company's assets " + "that are used wholly and exclusively for the purposes of its trade, " + "valued net of any liabilities secured on them so that it is a " + "component of the net owned_company_capital." + ) + definition_period = YEAR + unit = GBP + default_value = 0 + quantity_type = STOCK + uprating = "gov.economic_assumptions.indices.obr.per_capita.gdp" + reference = dict( + title="The Universal Credit Regulations 2013 reg. 77(3)(a)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ) diff --git a/policyengine_uk/variables/input/owned_company/stands_as_sole_owner_or_partner_of_company.py b/policyengine_uk/variables/input/owned_company/stands_as_sole_owner_or_partner_of_company.py new file mode 100644 index 0000000000..bf8ab44f7d --- /dev/null +++ b/policyengine_uk/variables/input/owned_company/stands_as_sole_owner_or_partner_of_company.py @@ -0,0 +1,30 @@ +from policyengine_uk.model_api import * + + +class stands_as_sole_owner_or_partner_of_company(Variable): + value_type = bool + entity = Person + label = "stands as sole owner or partner of a company" + documentation = ( + "Whether the person stands in a position analogous to that of a sole " + "owner or partner in relation to a company. This is a question of " + "fact. DWP guidance treats a person as like a sole owner when they " + "have total influence over the day-to-day running of the company (for " + "example owning 99% of the shares), and as like a partner when the " + "company has a small number of shareholders and the person has some " + "meaningful influence over its day-to-day running. A shareholder in a " + "company with many shareholders is an investor, not an owner or " + "partner. The person need not work for the company." + ) + definition_period = YEAR + default_value = False + reference = [ + dict( + title="The Universal Credit Regulations 2013 reg. 77(1)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/77", + ), + dict( + title="Advice for Decision Making, Chapter H4, paras. H4362-H4364", + href="https://www.gov.uk/government/publications/advice-for-decision-making-staff-guide", + ), + ]