From a971e332a77b39d1ca50f7b423f040889f61b61a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 6 Nov 2026 06:03:48 -0500 Subject: [PATCH 1/2] Add Maryland FAMLI payroll contributions Maryland's Family and Medical Leave Insurance contributions begin January 1, 2027. Maryland Labor set the total rate at 0.9% of wages up to the Social Security wage base for 2027. Employees pay half (Lab. & Empl. s. 8.3-601(e)), and employers of 15 or more employees pay the other half. The contributions enter employee_state_payroll_tax and the employer-side state payroll tax totals, so the employee side lowers household net income through employee_payroll_tax, as the other state paid-leave programs do. Co-Authored-By: Claude Opus 5.5 --- changelog.d/md-famli-contributions.added.md | 1 + .../md/tax/payroll/famli/employee_share.yaml | 22 +++ .../famli/employer_headcount_threshold.yaml | 23 +++ .../md/tax/payroll/famli/total_rate.yaml | 27 ++++ .../md/tax/payroll/famli/integration.yaml | 148 ++++++++++++++++++ .../famli/md_employee_famli_contribution.yaml | 107 +++++++++++++ .../famli/md_employer_famli_contribution.yaml | 89 +++++++++++ .../payroll/famli/md_famli_taxable_wages.yaml | 119 ++++++++++++++ .../famli/md_employee_famli_contribution.py | 25 +++ .../famli/md_employer_famli_contribution.py | 28 ++++ .../payroll/famli/md_famli_taxable_wages.py | 49 ++++++ .../payroll/md_employee_state_payroll_tax.py | 14 ++ ...d_employer_additional_state_payroll_tax.py | 14 ++ .../tax/payroll/employee_state_payroll_tax.py | 1 + .../employer_additional_state_payroll_tax.py | 1 + ...oyer_total_additional_state_payroll_tax.py | 12 +- 16 files changed, 679 insertions(+), 1 deletion(-) create mode 100644 changelog.d/md-famli-contributions.added.md create mode 100644 policyengine_us/parameters/gov/states/md/tax/payroll/famli/employee_share.yaml create mode 100644 policyengine_us/parameters/gov/states/md/tax/payroll/famli/employer_headcount_threshold.yaml create mode 100644 policyengine_us/parameters/gov/states/md/tax/payroll/famli/total_rate.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employer_famli_contribution.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.yaml create mode 100644 policyengine_us/variables/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.py create mode 100644 policyengine_us/variables/gov/states/md/tax/payroll/famli/md_employer_famli_contribution.py create mode 100644 policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py create mode 100644 policyengine_us/variables/gov/states/md/tax/payroll/md_employee_state_payroll_tax.py create mode 100644 policyengine_us/variables/gov/states/md/tax/payroll/md_employer_additional_state_payroll_tax.py diff --git a/changelog.d/md-famli-contributions.added.md b/changelog.d/md-famli-contributions.added.md new file mode 100644 index 00000000000..770d6954788 --- /dev/null +++ b/changelog.d/md-famli-contributions.added.md @@ -0,0 +1 @@ +Add Maryland Family and Medical Leave Insurance payroll contributions, which begin January 1, 2027 at 0.9% of wages up to the Social Security wage base, split equally between employees and employers of 15 or more employees. diff --git a/policyengine_us/parameters/gov/states/md/tax/payroll/famli/employee_share.yaml b/policyengine_us/parameters/gov/states/md/tax/payroll/famli/employee_share.yaml new file mode 100644 index 00000000000..6150783ba9d --- /dev/null +++ b/policyengine_us/parameters/gov/states/md/tax/payroll/famli/employee_share.yaml @@ -0,0 +1,22 @@ +description: Maryland charges employees this share of the total contribution rate under the Family and Medical Leave Insurance program. +values: + 0000-01-01: 0 + # An employer of 15 or more employees pays the other half. An employer may + # elect to pay some or all of this share, and may not deduct more than it. + 2027-01-01: 0.5 + +metadata: + period: year + unit: /1 + label: Maryland FAMLI employee share of the total contribution rate + reference: + - title: Md. Code, Labor and Employment § 8.3-601(e) + href: https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8.3-601&enactments=false + - title: COMAR 09.42.02.05, employer contributions + href: https://regs.maryland.gov/us/md/exec/comar/09.42.02.05 + - title: COMAR 09.42.02.07, failure to deduct from payroll + href: https://regs.maryland.gov/us/md/exec/comar/09.42.02.07 + - title: Maryland Department of Labor, FAMLI Frequently Asked Questions on contributions (updated October 2026), question 1 + href: https://paidleave.maryland.gov/files/famli-faqs_-contribution-questions.pdf#page=1 + - title: Maryland Department of Labor, Make contributions, How contribution amounts are split + href: https://paidleave.maryland.gov/employers/make-contributions/ diff --git a/policyengine_us/parameters/gov/states/md/tax/payroll/famli/employer_headcount_threshold.yaml b/policyengine_us/parameters/gov/states/md/tax/payroll/famli/employer_headcount_threshold.yaml new file mode 100644 index 00000000000..d09e57cb1b2 --- /dev/null +++ b/policyengine_us/parameters/gov/states/md/tax/payroll/famli/employer_headcount_threshold.yaml @@ -0,0 +1,23 @@ +description: Maryland requires employers with at least this many employees to pay the employer share under the Family and Medical Leave Insurance program. +values: + 0000-01-01: 0 + # A smaller employer owes no employer share but still remits the employee + # share. The count covers every employee paid wages, inside or outside + # Maryland. + 2027-01-01: 15 + +metadata: + period: year + unit: person + label: Maryland FAMLI employer share headcount threshold + reference: + - title: Md. Code, Labor and Employment § 8.3-601(a)(1)(i), (e)(1)(i) + href: https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8.3-601&enactments=false + - title: COMAR 09.42.02.01B(1), small employer + href: https://regs.maryland.gov/us/md/exec/comar/09.42.02.01 + - title: COMAR 09.42.02.06, employer size + href: https://regs.maryland.gov/us/md/exec/comar/09.42.02.06 + - title: "Maryland Department of Labor, FAMLI policy guidance on small employers, Control E-26-002 (September 18, 2026) #page 1-2" + href: https://paidleave.maryland.gov/files/famli-policy-guidance-on-small-employers.pdf#page=1 + - title: Maryland Department of Labor, FAMLI Frequently Asked Questions on contributions (updated October 2026), question 1 + href: https://paidleave.maryland.gov/files/famli-faqs_-contribution-questions.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/md/tax/payroll/famli/total_rate.yaml b/policyengine_us/parameters/gov/states/md/tax/payroll/famli/total_rate.yaml new file mode 100644 index 00000000000..3aa8840fbcf --- /dev/null +++ b/policyengine_us/parameters/gov/states/md/tax/payroll/famli/total_rate.yaml @@ -0,0 +1,27 @@ +description: Maryland applies this total contribution rate to wages up to the Social Security wage base under the Family and Medical Leave Insurance program. +values: + 0000-01-01: 0 + # The Secretary of Labor sets the rate; the statute caps it at 1.2% of wages + # and fixes no number. Maryland Labor set 0.9% for wages paid in 2027. The + # Secretary sets the 2028 rate by November 1, 2027. + 2027-01-01: 0.009 + +metadata: + period: year + unit: /1 + label: Maryland FAMLI total contribution rate + reference: + - title: Md. Code, Labor and Employment § 8.3-601(a)(1), (b), (d) + href: https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8.3-601&enactments=false + - title: "Laws of Maryland 2025, Chapter 363 (HB 102), Section 1, amending § 8.3-601 to start contributions on January 1, 2027 #page 3-4" + href: https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_363_hb0102t.pdf#page=3 + - title: COMAR 09.42.02.01B(3), total rate of contribution + href: https://regs.maryland.gov/us/md/exec/comar/09.42.02.01 + - title: COMAR 09.42.02.08A, quarterly contribution calculation + href: https://regs.maryland.gov/us/md/exec/comar/09.42.02.08 + - title: Maryland Department of Labor, FAMLI Frequently Asked Questions on contributions (updated October 2026), question 1 + href: https://paidleave.maryland.gov/files/famli-faqs_-contribution-questions.pdf#page=1 + - title: Maryland Department of Labor, Make contributions, How FAMLI contribution rates will be set + href: https://paidleave.maryland.gov/employers/make-contributions/ + - title: Maryland Department of Labor, FAMLI policy guidance on small employers, Control E-26-002 (September 18, 2026), footnote 1 + href: https://paidleave.maryland.gov/files/famli-policy-guidance-on-small-employers.pdf#page=1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/integration.yaml new file mode 100644 index 00000000000..773bd1cfe9c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/integration.yaml @@ -0,0 +1,148 @@ +# How Maryland FAMLI contributions reach payroll tax totals and household net +# income. The net income cases set income taxes, credits, benefits and health +# costs to zero, so net income is wages less employee payroll tax. +# Social Security is 6.2% and Medicare 1.45% of wages. + +- name: Case 1, in 2026 the worker owes only federal payroll tax. + period: 2026 + input: + employment_income: 50_000 + state_code: MD + income_tax_before_refundable_credits: 0 + household_state_tax_before_refundable_credits: 0 + local_income_tax_before_refundable_credits: 0 + household_refundable_tax_credits: 0 + household_benefits: 0 + household_health_costs: 0 + output: + md_employee_state_payroll_tax: 0 + employee_state_payroll_tax: 0 + # 50,000 * 6.2% = 3,100; 50,000 * 1.45% = 725 + employee_payroll_tax: 3_825 + household_tax_before_refundable_credits: 3_825 + # 50,000 - 3,825 = 46,175 + household_net_income: 46_175 + +- name: Case 2, in 2027 the employee contribution lowers net income dollar for dollar. + period: 2027 + input: + employment_income: 50_000 + state_code: MD + income_tax_before_refundable_credits: 0 + household_state_tax_before_refundable_credits: 0 + local_income_tax_before_refundable_credits: 0 + household_refundable_tax_credits: 0 + household_benefits: 0 + household_health_costs: 0 + output: + md_employee_state_payroll_tax: 225 + employee_state_payroll_tax: 225 + # 3,100 + 725 + 225 = 4,050 + employee_payroll_tax: 4_050 + spm_unit_payroll_tax: 4_050 + household_tax_before_refundable_credits: 4_050 + # 50,000 - 4,050 = 45,950 + household_net_income: 45_950 + +- name: Case 3, a Virginia household with the same wages owes no contribution. + period: 2027 + input: + employment_income: 50_000 + state_code: VA + income_tax_before_refundable_credits: 0 + household_state_tax_before_refundable_credits: 0 + local_income_tax_before_refundable_credits: 0 + household_refundable_tax_credits: 0 + household_benefits: 0 + household_health_costs: 0 + output: + md_employee_state_payroll_tax: 0 + employee_state_payroll_tax: 0 + employee_payroll_tax: 3_825 + household_net_income: 46_175 + +- name: Case 4, a married couple's contributions add up on the tax unit. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 50_000 + person2: + age: 40 + employment_income: 250_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MD + output: + md_employee_state_payroll_tax: [225, 865.35] + # 225 + 865.35 = 1,090.35 + employee_state_payroll_tax: 1_090.35 + +- name: Case 5, the employer contribution joins Maryland unemployment tax on the employer side. + period: 2027 + input: + employment_income: 50_000 + state_code: MD + output: + md_employer_additional_state_payroll_tax: 225 + employer_additional_state_payroll_tax: 225 + # 8,500 * 2.6% = 221 of unemployment tax, plus the 225 contribution. + employer_state_payroll_tax: 446 + +- name: Case 6, the employer contribution stays out of employee payroll tax. + period: 2027 + input: + employment_income: 50_000 + employer_headcount: 14 + state_code: MD + output: + md_employer_additional_state_payroll_tax: 0 + employer_additional_state_payroll_tax: 0 + # 8,500 * 2.6% = 221 of unemployment tax alone. + employer_state_payroll_tax: 221 + # 3,100 + 725 + 225: the employee share stands without an employer share. + employee_payroll_tax: 4_050 + +- name: Case 7, the employer-level total is 0.45% of the employer's taxable wages. + period: 2027 + input: + employer_total_taxable_earnings_for_social_security: 1_000_000 + employer_headcount: 40 + state_code: MD + output: + # 1,000,000 * 0.9% * 50% = 4,500 + employer_total_additional_state_payroll_tax: 4_500 + +- name: Case 8, the employer-level total is zero for an employer of 14 employees. + period: 2027 + input: + employer_total_taxable_earnings_for_social_security: 1_000_000 + employer_headcount: 14 + state_code: MD + output: + employer_total_additional_state_payroll_tax: 0 + +- name: Case 9, the employer-level total applies at exactly 15 employees. + period: 2027 + input: + employer_total_taxable_earnings_for_social_security: 1_000_000 + employer_headcount: 15 + state_code: MD + output: + # 1,000,000 * 0.9% * 50% = 4,500 + employer_total_additional_state_payroll_tax: 4_500 + +- name: Case 10, the employer-level total is zero in 2026. + period: 2026 + input: + employer_total_taxable_earnings_for_social_security: 1_000_000 + employer_headcount: 40 + state_code: MD + output: + employer_total_additional_state_payroll_tax: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.yaml new file mode 100644 index 00000000000..4c3b129ecb3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.yaml @@ -0,0 +1,107 @@ +# Contributions begin January 1, 2027 (Md. Code, Lab. & Empl. s. +# 8.3-601(a)(1)(i)). Maryland Labor's 2027 total rate is 0.9%, and the employee +# share is 50% of it (s. 8.3-601(e)(1)(ii)), so employees pay 0.45% of taxable +# wages. Taxable wages stop at the 2027 Social Security wage base of $192,300. + +- name: Case 1, no contribution in 2026. + period: 2026 + input: + employment_income: 50_000 + state_code: MD + output: + md_employee_famli_contribution: 0 + +- name: Case 2, 0.45% of wages in 2027. + period: 2027 + input: + employment_income: 50_000 + state_code: MD + output: + # 50,000 * 0.9% * 50% = 225 + md_employee_famli_contribution: 225 + +- name: Case 3, the contribution stops at the 2027 wage base. + period: 2027 + absolute_error_margin: 0.01 + input: + employment_income: 250_000 + state_code: MD + output: + # 192,300 * 0.9% * 50% = 865.35 + md_employee_famli_contribution: 865.35 + +- name: Case 4, wages exactly at the 2027 wage base pay the most. + period: 2027 + absolute_error_margin: 0.01 + input: + employment_income: 192_300 + state_code: MD + output: + # 192,300 * 0.9% * 50% = 865.35 + md_employee_famli_contribution: 865.35 + +- name: Case 5, an employee of an employer below 15 employees pays the same share. + period: 2027 + input: + employment_income: 50_000 + employer_headcount: 14 + state_code: MD + output: + # Section 8.3-601(e)(1)(ii) reaches each employee of any employer. + # 50,000 * 0.9% * 50% = 225 + md_employee_famli_contribution: 225 + +- name: Case 6, the contribution uses wages after cafeteria plan amounts. + period: 2027 + input: + employment_income: 60_000 + pre_tax_health_insurance_premiums: 5_000 + health_savings_account_payroll_contributions: 1_000 + traditional_401k_contributions: 4_000 + state_code: MD + output: + # (60,000 - 5,000 - 1,000) * 0.9% * 50% = 243. The 401(k) deferral stays in. + md_employee_famli_contribution: 243 + +- name: Case 7, no wages, no contribution. + period: 2027 + input: + employment_income: 0 + state_code: MD + output: + md_employee_famli_contribution: 0 + +- name: Case 8, self-employment income carries no contribution. + period: 2027 + input: + self_employment_income: 100_000 + state_code: MD + output: + md_employee_famli_contribution: 0 + +- name: Case 9, no contribution outside Maryland. + period: 2027 + input: + employment_income: 50_000 + state_code: VA + output: + md_employee_famli_contribution: 0 + +- name: Case 10, two earners each pay on their own wages. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 50_000 + person2: + age: 40 + employment_income: 250_000 + households: + household: + members: [person1, person2] + state_code: MD + output: + # 50,000 * 0.45% = 225; 192,300 * 0.45% = 865.35 + md_employee_famli_contribution: [225, 865.35] diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employer_famli_contribution.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employer_famli_contribution.yaml new file mode 100644 index 00000000000..a4903f73962 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employer_famli_contribution.yaml @@ -0,0 +1,89 @@ +# Contributions begin January 1, 2027 (Md. Code, Lab. & Empl. s. +# 8.3-601(a)(1)(i)). An employer of 15 or more employees pays 50% of the 0.9% +# total rate, or 0.45% of taxable wages (s. 8.3-601(e)(1)(i)). A smaller employer +# owes no employer share (COMAR 09.42.02.06D). Taxable wages stop at the 2027 +# Social Security wage base of $192,300. + +- name: Case 1, no contribution in 2026. + period: 2026 + input: + employment_income: 50_000 + state_code: MD + output: + md_employer_famli_contribution: 0 + +- name: Case 2, 0.45% of wages in 2027 at the default employer size. + period: 2027 + input: + employment_income: 50_000 + state_code: MD + output: + # 50,000 * 0.9% * 50% = 225 + md_employer_famli_contribution: 225 + +- name: Case 3, an employer of 14 employees owes no employer share. + period: 2027 + input: + employment_income: 50_000 + employer_headcount: 14 + state_code: MD + output: + md_employer_famli_contribution: 0 + +- name: Case 4, an employer of exactly 15 employees owes the employer share. + period: 2027 + input: + employment_income: 50_000 + employer_headcount: 15 + state_code: MD + output: + # 50,000 * 0.9% * 50% = 225 + md_employer_famli_contribution: 225 + +- name: Case 5, the employer contribution stops at the 2027 wage base. + period: 2027 + absolute_error_margin: 0.01 + input: + employment_income: 250_000 + state_code: MD + output: + # 192,300 * 0.9% * 50% = 865.35 + md_employer_famli_contribution: 865.35 + +- name: Case 6, no employer contribution in 2026 at any employer size. + period: 2026 + input: + employment_income: 50_000 + employer_headcount: 500 + state_code: MD + output: + md_employer_famli_contribution: 0 + +- name: Case 7, no employer contribution outside Maryland. + period: 2027 + input: + employment_income: 50_000 + state_code: VA + output: + md_employer_famli_contribution: 0 + +- name: Case 8, each worker's employer is tested on its own size. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 50_000 + employer_headcount: 14 + person2: + age: 40 + employment_income: 250_000 + employer_headcount: 15 + households: + household: + members: [person1, person2] + state_code: MD + output: + # person1's employer is below 15; person2's pays 192,300 * 0.45% = 865.35. + md_employer_famli_contribution: [0, 865.35] diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.yaml new file mode 100644 index 00000000000..cdc5343f43e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.yaml @@ -0,0 +1,119 @@ +# Md. Code, Lab. & Empl. s. 8.3-601(a)(1)(ii)2 applies the contribution rate to +# wages up to the Social Security wage base, and COMAR 09.42.02.01B(2) takes +# that base from the Federal Insurance Contributions Act maximum for the year: +# $184,500 in 2026 and $192,300 in 2027. Section 8.3-101(r) takes wages from the +# unemployment insurance definition in s. 8-101(aa). Maryland Labor's employer +# guide reads that definition to exclude cafeteria plan salary reductions and +# include 401(k) deferrals. + +- name: Case 1, wages below the wage base are all taxable. + period: 2027 + input: + employment_income: 50_000 + state_code: MD + output: + md_famli_taxable_wages: 50_000 + +- name: Case 2, wages exactly at the 2027 wage base are all taxable. + period: 2027 + input: + employment_income: 192_300 + state_code: MD + output: + md_famli_taxable_wages: 192_300 + +- name: Case 3, wages one dollar above the 2027 wage base stop at the base. + period: 2027 + input: + employment_income: 192_301 + state_code: MD + output: + md_famli_taxable_wages: 192_300 + +- name: Case 4, wages well above the 2027 wage base stop at the base. + period: 2027 + input: + employment_income: 250_000 + state_code: MD + output: + md_famli_taxable_wages: 192_300 + +- name: Case 5, the wage base is the one for the year the wages are paid. + period: 2026 + input: + employment_income: 250_000 + state_code: MD + output: + md_famli_taxable_wages: 184_500 + +- name: Case 6, cafeteria plan and dependent care amounts come out, and 401(k) deferrals stay in. + period: 2027 + input: + employment_income: 60_000 + pre_tax_health_insurance_premiums: 5_000 + health_savings_account_payroll_contributions: 1_000 + dependent_care_employer_benefits: 2_000 + traditional_401k_contributions: 4_000 + state_code: MD + output: + # 60,000 - 5,000 - 1,000 - 2,000 = 52,000. The 4,000 deferral stays in. + md_famli_taxable_wages: 52_000 + +- name: Case 7, the cap applies after the pre-tax amounts come out. + period: 2027 + input: + employment_income: 195_000 + pre_tax_health_insurance_premiums: 5_000 + state_code: MD + output: + # 195,000 - 5,000 = 190,000, below the 192,300 base. + md_famli_taxable_wages: 190_000 + +- name: Case 8, tips within employment income count as wages. + period: 2027 + input: + employment_income: 50_000 + tip_income: 5_000 + state_code: MD + output: + md_famli_taxable_wages: 50_000 + +- name: Case 9, self-employment income is not wages. + period: 2027 + input: + self_employment_income: 100_000 + state_code: MD + output: + md_famli_taxable_wages: 0 + +- name: Case 10, no wages. + period: 2027 + input: + employment_income: 0 + state_code: MD + output: + md_famli_taxable_wages: 0 + +- name: Case 11, a household outside Maryland has no Maryland taxable wages. + period: 2027 + input: + employment_income: 50_000 + state_code: VA + output: + md_famli_taxable_wages: 0 + +- name: Case 12, the employer-level base is the employer's Social Security taxable earnings. + period: 2027 + input: + employer_total_taxable_earnings_for_social_security: 1_000_000 + state_code: MD + output: + employer_total_md_famli_taxable_wages: 1_000_000 + +- name: Case 13, the employer-level base is zero outside Maryland. + period: 2027 + input: + employer_total_taxable_earnings_for_social_security: 1_000_000 + state_code: VA + output: + employer_total_md_famli_taxable_wages: 0 diff --git a/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.py b/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.py new file mode 100644 index 00000000000..c65629245ea --- /dev/null +++ b/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class md_employee_famli_contribution(Variable): + value_type = float + entity = Person + label = "Maryland employee FAMLI contribution" + documentation = ( + "Employee-side Maryland Family and Medical Leave Insurance " + "contribution. The employer must deduct the employee share unless it " + "elects to pay some or all of it (Md. Code, Lab. & Empl. s. " + "8.3-601(e)(2)); this assumes the employer deducts the full share." + ) + definition_period = YEAR + unit = USD + defined_for = StateCode.MD + reference = ( + "https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8.3-601&enactments=false", + "https://regs.maryland.gov/us/md/exec/comar/09.42.02.05", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.states.md.tax.payroll.famli + taxable_wages = person("md_famli_taxable_wages", period) + return p.total_rate * p.employee_share * taxable_wages diff --git a/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_employer_famli_contribution.py b/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_employer_famli_contribution.py new file mode 100644 index 00000000000..0e89e27fed9 --- /dev/null +++ b/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_employer_famli_contribution.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class md_employer_famli_contribution(Variable): + value_type = float + entity = Person + label = "Maryland employer FAMLI contribution" + documentation = ( + "Employer-side Maryland Family and Medical Leave Insurance " + "contribution, owed only by employers of 15 or more employees. This " + "uses one annual employer_headcount for the size test, which Maryland " + "applies quarterly in 2027 and from an annual average after that " + "(COMAR 09.42.02.06), and it assumes the employer pays only its own " + "share." + ) + definition_period = YEAR + unit = USD + defined_for = StateCode.MD + reference = ( + "https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8.3-601&enactments=false", + "https://regs.maryland.gov/us/md/exec/comar/09.42.02.06", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.states.md.tax.payroll.famli + liable = person("employer_headcount", period) >= p.employer_headcount_threshold + taxable_wages = person("md_famli_taxable_wages", period) + return where(liable, p.total_rate * (1 - p.employee_share) * taxable_wages, 0) diff --git a/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py b/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py new file mode 100644 index 00000000000..4c4fb18ab79 --- /dev/null +++ b/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py @@ -0,0 +1,49 @@ +from policyengine_us.model_api import * + + +class md_famli_taxable_wages(Variable): + value_type = float + entity = Person + label = "Maryland FAMLI taxable wages" + documentation = ( + "Wages subject to Maryland Family and Medical Leave Insurance " + "contributions: Maryland unemployment insurance wages, capped at the " + "Social Security wage base. Those wages include 401(k) deferrals and " + "exclude cafeteria plan and dependent care benefits, as " + "payroll_tax_gross_wages does. Household state proxies work state, " + "and one person's wages proxy the cap that applies separately to each " + "job." + ) + definition_period = YEAR + unit = USD + defined_for = StateCode.MD + reference = ( + "https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8.3-601&enactments=false", + "https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8.3-101&enactments=false", + "https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8-101&enactments=false", + "https://regs.maryland.gov/us/md/exec/comar/09.42.02.04", + "https://labor.maryland.gov/employment/empguide/empguide.pdf#page=19", + ) + + def formula(person, period, parameters): + return min_( + person("payroll_tax_gross_wages", period), + parameters(period).gov.irs.payroll.social_security.cap, + ) + + +class employer_total_md_famli_taxable_wages(Variable): + value_type = float + entity = Person + label = "Employer total Maryland FAMLI taxable wages" + documentation = ( + "Aggregate employer wages subject to Maryland Family and Medical " + "Leave Insurance contributions for employer-only payroll tax " + "calculations." + ) + definition_period = YEAR + unit = USD + defined_for = StateCode.MD + + def formula(person, period, parameters): + return person("employer_total_taxable_earnings_for_social_security", period) diff --git a/policyengine_us/variables/gov/states/md/tax/payroll/md_employee_state_payroll_tax.py b/policyengine_us/variables/gov/states/md/tax/payroll/md_employee_state_payroll_tax.py new file mode 100644 index 00000000000..dd7fa0212ff --- /dev/null +++ b/policyengine_us/variables/gov/states/md/tax/payroll/md_employee_state_payroll_tax.py @@ -0,0 +1,14 @@ +from policyengine_us.model_api import * + + +class md_employee_state_payroll_tax(Variable): + value_type = float + entity = Person + label = "Maryland employee state payroll tax" + documentation = ( + "Employee-side Maryland Family and Medical Leave Insurance contributions." + ) + definition_period = YEAR + unit = USD + defined_for = StateCode.MD + adds = ["md_employee_famli_contribution"] diff --git a/policyengine_us/variables/gov/states/md/tax/payroll/md_employer_additional_state_payroll_tax.py b/policyengine_us/variables/gov/states/md/tax/payroll/md_employer_additional_state_payroll_tax.py new file mode 100644 index 00000000000..2a259ee37c9 --- /dev/null +++ b/policyengine_us/variables/gov/states/md/tax/payroll/md_employer_additional_state_payroll_tax.py @@ -0,0 +1,14 @@ +from policyengine_us.model_api import * + + +class md_employer_additional_state_payroll_tax(Variable): + value_type = float + entity = Person + label = "Maryland employer additional state payroll tax" + documentation = ( + "Employer-side Maryland Family and Medical Leave Insurance contributions." + ) + definition_period = YEAR + unit = USD + defined_for = StateCode.MD + adds = ["md_employer_famli_contribution"] diff --git a/policyengine_us/variables/gov/states/tax/payroll/employee_state_payroll_tax.py b/policyengine_us/variables/gov/states/tax/payroll/employee_state_payroll_tax.py index 7956a031799..a30418d8506 100644 --- a/policyengine_us/variables/gov/states/tax/payroll/employee_state_payroll_tax.py +++ b/policyengine_us/variables/gov/states/tax/payroll/employee_state_payroll_tax.py @@ -6,6 +6,7 @@ "ct_employee_state_payroll_tax", "de_employee_state_payroll_tax", "ma_employee_state_payroll_tax", + "md_employee_state_payroll_tax", "me_employee_state_payroll_tax", "mn_employee_state_payroll_tax", "nj_employee_state_payroll_tax", diff --git a/policyengine_us/variables/gov/states/tax/payroll/employer_additional_state_payroll_tax.py b/policyengine_us/variables/gov/states/tax/payroll/employer_additional_state_payroll_tax.py index 0f81b0058e2..6f750cc98fd 100644 --- a/policyengine_us/variables/gov/states/tax/payroll/employer_additional_state_payroll_tax.py +++ b/policyengine_us/variables/gov/states/tax/payroll/employer_additional_state_payroll_tax.py @@ -6,6 +6,7 @@ "dc_employer_additional_state_payroll_tax", "de_employer_additional_state_payroll_tax", "ma_employer_additional_state_payroll_tax", + "md_employer_additional_state_payroll_tax", "me_employer_additional_state_payroll_tax", "or_employer_additional_state_payroll_tax", "ri_employer_additional_state_payroll_tax", diff --git a/policyengine_us/variables/gov/states/tax/payroll/employer_total_additional_state_payroll_tax.py b/policyengine_us/variables/gov/states/tax/payroll/employer_total_additional_state_payroll_tax.py index 13102382f5a..4feeb9114e1 100644 --- a/policyengine_us/variables/gov/states/tax/payroll/employer_total_additional_state_payroll_tax.py +++ b/policyengine_us/variables/gov/states/tax/payroll/employer_total_additional_state_payroll_tax.py @@ -66,6 +66,15 @@ def formula(person, period, parameters): 0, ) + md_p = parameters(period).gov.states.md.tax.payroll.famli + md = where( + headcount >= md_p.employer_headcount_threshold, + md_p.total_rate + * (1 - md_p.employee_share) + * person("employer_total_md_famli_taxable_wages", period), + 0, + ) + me_p = parameters(period).gov.states.me.tax.payroll.paid_leave me = where( headcount >= me_p.employer_headcount_threshold, @@ -111,12 +120,13 @@ def formula(person, period, parameters): state_code == StateCode.DC, state_code == StateCode.DE, state_code == StateCode.MA, + state_code == StateCode.MD, state_code == StateCode.ME, state_code == StateCode.OR, state_code == StateCode.RI, state_code == StateCode.VT, state_code == StateCode.WA, ], - [ca, co, dc, de, ma, me, or_tax, ri, vt, wa], + [ca, co, dc, de, ma, md, me, or_tax, ri, vt, wa], default=0, ) From ba65f7dd2a22911548b1b6f8ab55b7b101ab5d75 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 6 Nov 2026 06:22:12 -0500 Subject: [PATCH 2/2] Record the FAMLI 401(k) reading and add invariant tests Maryland's wage definition can be read to exclude a deferral paid to a qualified trust, while COMAR 09.32.01.14A(2)(a) leaves out only deferred compensation not deducted from salary, and Maryland Labor's employer guide calls elective 401(k) deferrals taxable. The model follows the regulation and the guide. The reading goes into source_conflicts/candidates.yaml with short excerpts and YAML cases that pin it. The new invariant tests check what holds for every input: the wage base, the employee and employer split, bounds, monotonicity, geography, the flow into payroll tax and net income, and agreement between the worker-level and employer-level paths. Co-Authored-By: Claude Opus 5.5 --- .../tests/core/test_md_famli_invariants.py | 376 ++++++++++++++++++ .../famli/md_employee_famli_contribution.yaml | 10 + .../payroll/famli/md_famli_taxable_wages.yaml | 16 +- .../payroll/famli/md_famli_taxable_wages.py | 1 + source_conflicts/candidates.yaml | 35 ++ .../us-md/comar-09-32-01-14-excerpt.txt | 4 + .../us-md/gle-8-101-aa-3-iv-excerpt.txt | 5 + .../us-md/ui-employer-guide-p19-excerpt.txt | 4 + 8 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 policyengine_us/tests/core/test_md_famli_invariants.py create mode 100644 source_conflicts/sources/us-md/comar-09-32-01-14-excerpt.txt create mode 100644 source_conflicts/sources/us-md/gle-8-101-aa-3-iv-excerpt.txt create mode 100644 source_conflicts/sources/us-md/ui-employer-guide-p19-excerpt.txt diff --git a/policyengine_us/tests/core/test_md_famli_invariants.py b/policyengine_us/tests/core/test_md_famli_invariants.py new file mode 100644 index 00000000000..166c896332c --- /dev/null +++ b/policyengine_us/tests/core/test_md_famli_invariants.py @@ -0,0 +1,376 @@ +"""Invariants for Maryland Family and Medical Leave Insurance contributions. + +The YAML cases under gov/states/md/tax/payroll/famli pin hand-computed values +at chosen points. A YAML case can't compare one person's output with another's, +so this file checks what must hold for every input: twins that differ in one +fact, and the worker-level and employer-level paths against each other. The +expected values come from Md. Code, Lab. & Empl. s. 8.3-601 and Maryland Labor's +0.9% rate, not from the parameters. + +Hypothesis draws batches of workers, each alone in a household, across wage +levels, pre-tax amounts and employer sizes. Each batch runs as one vectorized +simulation, and each worker enters it as four twins: as drawn, with higher +wages, with a larger employer, and in Virginia, which has no such program. + +1. Nothing before 2027: both shares and the state payroll tax are zero in 2026. +2. Base: taxable wages equal gross wages less cafeteria plan and dependent care + amounts, floored at zero and capped at the Social Security wage base. 401(k) + deferrals stay in. +3. Split and conservation: the employee pays 0.45% of the base at any employer. + At 15 or more employees the two shares sum to 0.9% of the base. Below 15 the + employer owes nothing. +4. Bounds: in 2027 each share runs from 0 to 0.45% of the $192,300 wage base. +5. Monotonicity: both shares rise weakly with wages. The employer share rises + weakly with employer size, and the employee share ignores it. +6. Geography: every Maryland amount is zero outside Maryland. +7. Flow: employee_state_payroll_tax, employee_payroll_tax and + spm_unit_payroll_tax exceed the Virginia twin's by the employee share, and + household_net_income falls short of it by that share. The employer share + leaves net income unchanged. A second simulation without the income tax, + benefit and health cost overrides shows the same fall in net income. +8. Differential: for a one-worker employer, the employer-level total equals the + worker-level employer share. +9. Parameters: the total rate never exceeds the 1.2% statutory cap, and the + employee share stays within 0 to 1. + +The batches set income taxes, credits, benefits and health costs to zero, as +the YAML integration cases do, which keeps them cheap. Only the check in point 7 +without those overrides runs the whole model, on five households. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation +from policyengine_us.system import system + +TOLERANCE = 0.01 # dollars +# Net income is a sum of float32 amounts, which lose cents at high incomes. +NET_INCOME_TOLERANCE = 0.25 # dollars + +TOTAL_RATE = 0.009 +EMPLOYEE_RATE = 0.0045 +EMPLOYER_RATE = 0.0045 +HEADCOUNT_THRESHOLD = 15 +RATE_CAP = 0.012 +WAGE_BASE = {2026: 184_500, 2027: 192_300} + +TAX_UNIT_OVERRIDES = [ + "income_tax_before_refundable_credits", + "local_income_tax_before_refundable_credits", +] +HOUSEHOLD_OVERRIDES = [ + "household_state_tax_before_refundable_credits", + "household_refundable_tax_credits", + "household_benefits", + "household_health_costs", +] +DRAWN_INPUTS = [ + "employment_income", + "pre_tax_health_insurance_premiums", + "health_savings_account_payroll_contributions", + "dependent_care_employer_benefits", + "traditional_401k_contributions", + "employer_headcount", +] +PERSON_OUTPUTS = [ + "md_famli_taxable_wages", + "md_employee_famli_contribution", + "md_employer_famli_contribution", + "md_employee_state_payroll_tax", + "md_employer_additional_state_payroll_tax", + "employer_additional_state_payroll_tax", + "employer_total_additional_state_payroll_tax", + "employer_total_md_famli_taxable_wages", +] +UNIT_OUTPUTS = [ + "employee_state_payroll_tax", + "employee_payroll_tax", + "spm_unit_payroll_tax", + "household_net_income", +] + +# Twin order within each drawn worker. +AS_DRAWN, MORE_WAGES, MORE_EMPLOYEES, IN_VIRGINIA = range(4) +N_TWINS = 4 + +# Wages at and around the 2026 and 2027 bases, then anything up to well above. +wages = st.one_of( + st.sampled_from( + [0.0, 1.0, 184_499.0, 184_500.0, 184_501.0, 192_299.0, 192_300.0, 192_301.0] + ), + st.integers(0, 400_000).map(float), +) +small_amount = st.one_of(st.just(0.0), st.integers(1, 12_000).map(float)) +headcount = st.one_of(st.sampled_from([1, 14, 15, 16]), st.integers(1, 5_000)) + + +@st.composite +def workers(draw): + return { + "employment_income": draw(wages), + "pre_tax_health_insurance_premiums": draw(small_amount), + "health_savings_account_payroll_contributions": draw(small_amount), + "dependent_care_employer_benefits": draw(small_amount), + "traditional_401k_contributions": draw(small_amount), + "employer_headcount": draw(headcount), + "raise": float(draw(st.integers(1, 100_000))), + "more_employees": draw(st.integers(1, 100)), + } + + +def _gross_wages(worker): + """Independent wage base before the cap: 401(k) deferrals stay in.""" + return max( + 0.0, + worker["employment_income"] + - worker["pre_tax_health_insurance_premiums"] + - worker["health_savings_account_payroll_contributions"] + - worker["dependent_care_employer_benefits"], + ) + + +def _twins(worker, year): + drawn = { + **{name: worker[name] for name in DRAWN_INPUTS}, + # The employer-level path takes a one-worker employer's capped wages. + "employer_total_taxable_earnings_for_social_security": min( + _gross_wages(worker), WAGE_BASE[year] + ), + } + return [ + ("MD", drawn), + ( + "MD", + { + **drawn, + "employment_income": drawn["employment_income"] + worker["raise"], + }, + ), + ( + "MD", + { + **drawn, + "employer_headcount": drawn["employer_headcount"] + + worker["more_employees"], + }, + ), + ("VA", drawn), + ] + + +def _situation(rows, year, *, overrides=True): + plurals = ["tax_units", "spm_units", "families", "marital_units", "households"] + people, groups = {}, {plural: {} for plural in plurals} + for i, (state, inputs) in enumerate(rows): + name = f"person_{i}" + people[name] = { + "age": {year: 40}, + **{key: {year: value} for key, value in inputs.items()}, + } + for plural in plurals: + groups[plural][f"{plural}_{i}"] = {"members": [name]} + groups["households"][f"households_{i}"]["state_code"] = {year: state} + if overrides: + for key in TAX_UNIT_OVERRIDES: + groups["tax_units"][f"tax_units_{i}"][key] = {year: 0} + for key in HOUSEHOLD_OVERRIDES: + groups["households"][f"households_{i}"][key] = {year: 0} + return {"people": people, **groups} + + +def _run(rows, year): + sim = Simulation(situation=_situation(rows, year)) + return { + name: np.asarray(sim.calculate(name, year), dtype=float) + for name in PERSON_OUTPUTS + UNIT_OUTPUTS + } + + +def _rows(batch, year): + return [twin for worker in batch for twin in _twins(worker, year)] + + +def _at(run, name, twin): + return run[name][twin::N_TWINS] + + +def _check_2027(batch): + year = 2027 + run = _run(_rows(batch, year), year) + cap = WAGE_BASE[year] + gross = np.array([_gross_wages(w) for w in batch]) + base = np.minimum(gross, cap) + size = np.array([w["employer_headcount"] for w in batch]) + + taxable = _at(run, "md_famli_taxable_wages", AS_DRAWN) + employee = _at(run, "md_employee_famli_contribution", AS_DRAWN) + employer = _at(run, "md_employer_famli_contribution", AS_DRAWN) + + # 2. Base, with the 401(k) deferrals left in. + np.testing.assert_allclose(taxable, base, atol=TOLERANCE) + assert (taxable >= 0).all() and (taxable <= cap + TOLERANCE).all() + + # 3. Split and conservation. + liable = size >= HEADCOUNT_THRESHOLD + np.testing.assert_allclose(employee, EMPLOYEE_RATE * base, atol=TOLERANCE) + np.testing.assert_allclose( + employer, np.where(liable, EMPLOYER_RATE * base, 0), atol=TOLERANCE + ) + np.testing.assert_allclose( + (employee + employer)[liable], TOTAL_RATE * base[liable], atol=TOLERANCE + ) + + # 4. Bounds. + most = EMPLOYEE_RATE * cap + TOLERANCE + for name in ["md_employee_famli_contribution", "md_employer_famli_contribution"]: + assert (run[name] >= 0).all() and (run[name] <= most).all(), name + + # 5. Monotonicity in wages and in employer size. + for name in ["md_employee_famli_contribution", "md_employer_famli_contribution"]: + assert ( + _at(run, name, MORE_WAGES) >= _at(run, name, AS_DRAWN) - TOLERANCE + ).all(), name + assert ( + _at(run, "md_employer_famli_contribution", MORE_EMPLOYEES) + >= employer - TOLERANCE + ).all() + np.testing.assert_allclose( + _at(run, "md_employee_famli_contribution", MORE_EMPLOYEES), + employee, + atol=TOLERANCE, + ) + + # 6. Geography. + for name in PERSON_OUTPUTS: + np.testing.assert_array_equal(_at(run, name, IN_VIRGINIA), 0, err_msg=name) + + # 7. Flow, against the Virginia twin with the same wages. + for name in [ + "employee_state_payroll_tax", + "employee_payroll_tax", + "spm_unit_payroll_tax", + ]: + np.testing.assert_allclose( + _at(run, name, AS_DRAWN) - _at(run, name, IN_VIRGINIA), + employee, + atol=TOLERANCE, + err_msg=name, + ) + np.testing.assert_allclose( + _at(run, "household_net_income", IN_VIRGINIA) + - _at(run, "household_net_income", AS_DRAWN), + employee, + atol=NET_INCOME_TOLERANCE, + ) + # A larger employer can move the employer share, never net income. + np.testing.assert_allclose( + _at(run, "household_net_income", MORE_EMPLOYEES), + _at(run, "household_net_income", AS_DRAWN), + atol=NET_INCOME_TOLERANCE, + ) + + # 8. Worker-level and employer-level paths agree for a one-worker employer. + np.testing.assert_allclose( + _at(run, "employer_total_md_famli_taxable_wages", AS_DRAWN), + base, + atol=TOLERANCE, + ) + np.testing.assert_allclose( + _at(run, "employer_total_additional_state_payroll_tax", AS_DRAWN), + _at(run, "employer_additional_state_payroll_tax", AS_DRAWN), + atol=TOLERANCE, + ) + np.testing.assert_allclose( + _at(run, "employer_additional_state_payroll_tax", AS_DRAWN), + employer, + atol=TOLERANCE, + ) + + +def _check_2026(batch): + year = 2026 + run = _run(_rows(batch, year), year) + + # 1. The base exists, but no contribution does. + for name in [ + "md_employee_famli_contribution", + "md_employer_famli_contribution", + "md_employee_state_payroll_tax", + "md_employer_additional_state_payroll_tax", + "employer_additional_state_payroll_tax", + "employer_total_additional_state_payroll_tax", + "employee_state_payroll_tax", + ]: + np.testing.assert_array_equal(run[name], 0, err_msg=name) + np.testing.assert_allclose( + _at(run, "employee_payroll_tax", AS_DRAWN), + _at(run, "employee_payroll_tax", IN_VIRGINIA), + atol=TOLERANCE, + ) + assert (run["md_famli_taxable_wages"] <= WAGE_BASE[year] + TOLERANCE).all() + + +# A batch's cost is mostly per-variable overhead, so each example is a large +# batch and there are few examples. +SETTINGS = dict( + max_examples=5, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +@settings(**SETTINGS) +@given(st.lists(workers(), min_size=10, max_size=40)) +def test_contribution_invariants_2027(batch): + _check_2027(batch) + + +@settings(**SETTINGS) +@given(st.lists(workers(), min_size=10, max_size=40)) +def test_no_contribution_in_2026(batch): + _check_2026(batch) + + +def test_net_income_falls_by_the_employee_share_without_overrides(): + # The batches above zero out income taxes and benefits. These households + # run the whole model, with and without the employee share, and the only + # difference in net income is that share. + year = 2027 + wage_levels = [15_000.0, 50_000.0, 120_000.0, 192_300.0, 300_000.0] + rows = [("MD", {"employment_income": level}) for level in wage_levels] + situation = _situation(rows, year, overrides=False) + base = Simulation(situation=situation) + no_share = Simulation(situation=situation) + no_share.set_input( + "md_employee_state_payroll_tax", year, np.zeros(len(wage_levels)) + ) + share = np.minimum(wage_levels, WAGE_BASE[year]) * EMPLOYEE_RATE + np.testing.assert_allclose( + np.asarray(base.calculate("md_employee_state_payroll_tax", year), dtype=float), + share, + atol=TOLERANCE, + ) + np.testing.assert_allclose( + np.asarray(no_share.calculate("household_net_income", year), dtype=float) + - np.asarray(base.calculate("household_net_income", year), dtype=float), + share, + atol=NET_INCOME_TOLERANCE, + ) + + +def test_parameters_stay_within_the_statute(): + # s. 8.3-601(a)(1)(ii) caps the total rate at 1.2%, whatever the Secretary + # sets each year. + famli = system.parameters.gov.states.md.tax.payroll.famli + rates = [entry.value for entry in famli.total_rate.values_list] + shares = [entry.value for entry in famli.employee_share.values_list] + assert rates and all(0 <= rate <= RATE_CAP for rate in rates) + assert shares and all(0 <= share <= 1 for share in shares) + assert famli.total_rate("2026-12-31") == 0 + assert famli.total_rate("2027-01-01") == pytest.approx(TOTAL_RATE) + assert famli.employee_share("2027-01-01") == pytest.approx(0.5) + assert famli.employer_headcount_threshold("2027-01-01") == HEADCOUNT_THRESHOLD diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.yaml index 4c3b129ecb3..abba7d999d5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_employee_famli_contribution.yaml @@ -105,3 +105,13 @@ output: # 50,000 * 0.45% = 225; 192,300 * 0.45% = 865.35 md_employee_famli_contribution: [225, 865.35] + +- name: Case 11, a 401(k) deferral taken from salary stays in the contribution base. + period: 2027 + input: + employment_income: 60_000 + traditional_401k_contributions: 4_000 + state_code: MD + output: + # 60,000 * 0.9% * 50% = 270. Taking the deferral out would give 252. + md_employee_famli_contribution: 270 diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.yaml index cdc5343f43e..0f505fe56cd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.yaml @@ -2,9 +2,10 @@ # wages up to the Social Security wage base, and COMAR 09.42.02.01B(2) takes # that base from the Federal Insurance Contributions Act maximum for the year: # $184,500 in 2026 and $192,300 in 2027. Section 8.3-101(r) takes wages from the -# unemployment insurance definition in s. 8-101(aa). Maryland Labor's employer -# guide reads that definition to exclude cafeteria plan salary reductions and -# include 401(k) deferrals. +# unemployment insurance definition in s. 8-101(aa). COMAR 09.32.01.14A(2)(a) +# leaves out deferred compensation only when it is not deducted from salary, and +# Maryland Labor's employer guide excludes cafeteria plan salary reductions and +# includes 401(k) deferrals. - name: Case 1, wages below the wage base are all taxable. period: 2027 @@ -117,3 +118,12 @@ state_code: VA output: employer_total_md_famli_taxable_wages: 0 + +- name: Case 14, a 401(k) deferral taken from salary stays in taxable wages. + period: 2027 + input: + employment_income: 60_000 + traditional_401k_contributions: 4_000 + state_code: MD + output: + md_famli_taxable_wages: 60_000 diff --git a/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py b/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py index 4c4fb18ab79..833ff13052f 100644 --- a/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py +++ b/policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py @@ -22,6 +22,7 @@ class md_famli_taxable_wages(Variable): "https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8.3-101&enactments=false", "https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8-101&enactments=false", "https://regs.maryland.gov/us/md/exec/comar/09.42.02.04", + "https://regs.maryland.gov/us/md/exec/comar/09.32.01.14", "https://labor.maryland.gov/employment/empguide/empguide.pdf#page=19", ) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 43d9aa13941..79b9fab2aec 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -4376,6 +4376,41 @@ candidates: state: ruled note: 'Ruled statute-first in October 2026; verify whether the chart''s zero column applies to dependent exemptions or only the filer''s own exemption. Code not yet on main: md_personal_exemptions_count.' +- id: us-md:famli-wages-401k-deferrals + program: md-famli + years: + from: 2027 + type: calendar + question: Do an employee's elective 401(k) deferrals count as wages for Maryland Family and Medical Leave Insurance + contributions? + sources: + - kind: statute + citation: Md. Code, Lab. & Empl. s. 8-101(aa)(1), (aa)(3)(iv)1, adopted by s. 8.3-101(r) + url: https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8-101&enactments=false + - kind: regulation + citation: COMAR 09.32.01.14A(2)(a) + url: https://regs.maryland.gov/us/md/exec/comar/09.32.01.14 + - kind: guidance + citation: Maryland Labor, Employer's Quick Reference Guide, Taxable Contributions under IRS Section 125 Cafeteria + Plans and 401K plans + url: https://labor.maryland.gov/employment/empguide/empguide.pdf#page=19 + readings: + statute: A deferral the employer pays into a qualified trust on the employee's behalf is a payment to such a trust, + so it falls outside wages. + other: A deferral taken from salary stays in wages. The regulation leaves out only deferred compensation not deducted + from salary, and the guide calls elective 401(k) deferrals taxable. + example: $60,000 of salary with a $4,000 deferral gives a $270 employee contribution under the regulation and guide, + versus $252 if the deferral comes out. + pe_us: + follows: gap-fill + code: + - policyengine_us/variables/gov/states/md/tax/payroll/famli/md_famli_taxable_wages.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10674 + state: ruled + note: The statute does not separate employer contributions from salary reductions, and the code follows the regulation + and the agency's guide, under the regulations-are-law and agency-breaks-ties principles. Short excerpts of all three + texts are in source_conflicts/sources/us-md/. - id: us-md:meap-garrett-oil-level-four program: liheap years: diff --git a/source_conflicts/sources/us-md/comar-09-32-01-14-excerpt.txt b/source_conflicts/sources/us-md/comar-09-32-01-14-excerpt.txt new file mode 100644 index 00000000000..933735f9238 --- /dev/null +++ b/source_conflicts/sources/us-md/comar-09-32-01-14-excerpt.txt @@ -0,0 +1,4 @@ +Source: https://regs.maryland.gov/us/md/exec/comar/09.32.01.14 +Short excerpt of COMAR 09.32.01.14A(2)(a), copied from the official HTML page. + +Into a deferred compensation plan that are not deducted from an employee's salary diff --git a/source_conflicts/sources/us-md/gle-8-101-aa-3-iv-excerpt.txt b/source_conflicts/sources/us-md/gle-8-101-aa-3-iv-excerpt.txt new file mode 100644 index 00000000000..559d7709e6b --- /dev/null +++ b/source_conflicts/sources/us-md/gle-8-101-aa-3-iv-excerpt.txt @@ -0,0 +1,5 @@ +Source: https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gle§ion=8-101&enactments=false +Short excerpts of s. 8-101(aa)(3)(iv) and item 1 of it, copied from the official HTML page. + +any payment made to or on behalf of an employee +from or to a trust exempt from tax under § 401(a) diff --git a/source_conflicts/sources/us-md/ui-employer-guide-p19-excerpt.txt b/source_conflicts/sources/us-md/ui-employer-guide-p19-excerpt.txt new file mode 100644 index 00000000000..040f06914c9 --- /dev/null +++ b/source_conflicts/sources/us-md/ui-employer-guide-p19-excerpt.txt @@ -0,0 +1,4 @@ +Source: https://labor.maryland.gov/employment/empguide/empguide.pdf#page=19 +Short excerpt of page 19, text extracted with pdftotext (poppler). + +Elective employee contributions and deferrals under IRS section 401k plans are taxable.