From b1d40c7416e0d7804d22ba28ef69fac2132afa2c Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Wed, 13 May 2026 13:06:23 -0400 Subject: [PATCH 1/9] Start al-ui implementation (ref #8282) From e06074f4c71f46cfc01117f014b3ec63b08e90e8 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Wed, 13 May 2026 18:04:51 -0400 Subject: [PATCH 2/9] Implement Alabama Unemployment Insurance (al_ui) (ref #8282) Adds the Alabama Unemployment Compensation program (Code of Alabama Title 25, Chapter 4) with monetary eligibility, weekly benefit amount, sliding-scale duration tied to state unemployment rate, dual-cap maximum benefit, partial-week earnings disregard, and the 1-week waiting period. All parameters effective 2020-01-01 per Act 2019-204. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../eligibility/bpw_to_hqw_multiplier.yaml | 16 + .../eligibility/quarters_with_wages.yaml | 16 + .../al/dol/unemployment_insurance/index.yaml | 9 + .../mba/bpw_fraction.yaml | 14 + .../mba/duration_weeks.yaml | 45 +++ .../partial/disregard_rate.yaml | 18 + .../state_unemployment_rate.yaml | 89 +++++ .../unemployment_insurance/waiting_weeks.yaml | 16 + .../dol/unemployment_insurance/wba/max.yaml | 16 + .../wba/min_threshold.yaml | 14 + .../al/dol/unemployment_insurance/al_ui.yaml | 246 +++++++++++++ .../al_ui_max_weeks.yaml | 206 +++++++++++ .../al_ui_maximum_benefit_amount.yaml | 242 +++++++++++++ .../al_ui_monetarily_eligible.yaml | 180 ++++++++++ .../al_ui_partial_weekly_benefit.yaml | 252 +++++++++++++ .../al_ui_unrounded_wba.yaml | 122 +++++++ .../al_ui_weekly_benefit_amount.yaml | 284 +++++++++++++++ .../unemployment_insurance/integration.yaml | 337 ++++++++++++++++++ .../set_state_unemployment_rate.py | 60 ++++ .../al/dol/unemployment_insurance/al_ui.py | 27 ++ .../al_ui_base_period_wages.py | 12 + .../al_ui_high_quarter_wages.py | 12 + .../unemployment_insurance/al_ui_max_weeks.py | 20 ++ .../al_ui_maximum_benefit_amount.py | 23 ++ .../al_ui_monetarily_eligible.py | 28 ++ .../al_ui_partial_weekly_benefit.py | 24 ++ .../al_ui_quarters_with_wages.py | 11 + .../al_ui_second_high_quarter_wages.py | 12 + .../al_ui_unrounded_wba.py | 16 + .../al_ui_weekly_benefit_amount.py | 20 ++ .../al_ui_weekly_earnings.py | 15 + .../gov/states/unemployment_compensation.py | 1 + 32 files changed, 2403 insertions(+) create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/index.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/mba/bpw_fraction.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/mba/duration_weeks.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/state_unemployment_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/waiting_weeks.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/max.yaml create mode 100644 policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/min_threshold.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_maximum_benefit_amount.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_monetarily_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_unrounded_wba.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_weekly_benefit_amount.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/set_state_unemployment_rate.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_base_period_wages.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_high_quarter_wages.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_maximum_benefit_amount.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_monetarily_eligible.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_quarters_with_wages.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_second_high_quarter_wages.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_unrounded_wba.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_benefit_amount.py create mode 100644 policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_earnings.py diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml new file mode 100644 index 00000000000..3ea012cd1e1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml @@ -0,0 +1,16 @@ +description: Alabama requires total base period wages to be at least this multiple of high quarter wages under the Unemployment Compensation program. + +values: + 2020-01-01: 1.5 + +metadata: + unit: /1 + period: year + label: Alabama UI base period wages to high quarter wages multiplier + reference: + - title: Code of Alabama § 25-4-77(a)(4)(a) + href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/ + - title: Alabama UC Benefit Rights and Responsibilities Handbook + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=5 + - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-2 + href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml new file mode 100644 index 00000000000..b61770e8731 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml @@ -0,0 +1,16 @@ +description: Alabama requires wages to have been paid in at least this many calendar quarters of the base period under the Unemployment Compensation program. + +values: + 2020-01-01: 2 + +metadata: + unit: int + period: year + label: Alabama UI required quarters with wages + reference: + - title: Code of Alabama § 25-4-77(a)(4) + href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/ + - title: Alabama UC Benefit Rights and Responsibilities Handbook + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=5 + - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-2 + href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/index.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/index.yaml new file mode 100644 index 00000000000..620fdcc6732 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/index.yaml @@ -0,0 +1,9 @@ +metadata: + propagate_metadata_to_children: true + economy: false + household: true + reference: + - title: Code of Alabama 1975, Title 25, Chapter 4 — Unemployment Compensation + href: https://law.justia.com/codes/alabama/title-25/chapter-4/ + - title: Alabama UC Benefit Rights and Responsibilities Handbook + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/mba/bpw_fraction.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/mba/bpw_fraction.yaml new file mode 100644 index 00000000000..ac9558a6ad7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/mba/bpw_fraction.yaml @@ -0,0 +1,14 @@ +description: Alabama limits the maximum benefit amount to this share of base period wages under the Unemployment Compensation program. + +values: + 2020-01-01: 0.25 + +metadata: + unit: /1 + period: year + label: Alabama UI maximum benefit base period wages fraction + reference: + - title: Code of Alabama § 25-4-74(a) + href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/ + - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-11 + href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=26 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/mba/duration_weeks.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/mba/duration_weeks.yaml new file mode 100644 index 00000000000..218ec08bdb7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/mba/duration_weeks.yaml @@ -0,0 +1,45 @@ +description: Alabama provides this maximum number of weeks of regular benefits under the Unemployment Compensation program. + +brackets: + - threshold: + 2020-01-01: 0 + amount: + 2020-01-01: 14 + - threshold: + 2020-01-01: 0.0651 # Above 6.5% + amount: + 2020-01-01: 15 + - threshold: + 2020-01-01: 0.0701 # Above 7.0% + amount: + 2020-01-01: 16 + - threshold: + 2020-01-01: 0.0751 # Above 7.5% + amount: + 2020-01-01: 17 + - threshold: + 2020-01-01: 0.0801 # Above 8.0% + amount: + 2020-01-01: 18 + - threshold: + 2020-01-01: 0.0851 # Above 8.5% + amount: + 2020-01-01: 19 + - threshold: + 2020-01-01: 0.0901 # Above 9.0% (statute caps at 20 weeks for AAU >= 9.5%) + amount: + 2020-01-01: 20 + +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: week + period: year + label: Alabama UI maximum weeks of regular benefits by unemployment rate + reference: + - title: Code of Alabama § 25-4-74(a) + href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/ + - title: Alabama Act 2019-204 summary + href: https://alabamaretail.org/news/unemployment-comp-weeks-weekly-benefits/ + - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-12 + href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=29 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml new file mode 100644 index 00000000000..7b82f7bb7d0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml @@ -0,0 +1,18 @@ +description: Alabama excludes this share of the weekly benefit amount from countable earnings before reducing partial unemployment benefits under the Unemployment Compensation program. + +values: + 2020-01-01: 0.3333333333 # One-third of WBA per § 25-4-72; Admin Code 480-4-3-.11 + +metadata: + unit: /1 + period: year + label: Alabama UI partial unemployment earnings disregard rate + reference: + - title: Code of Alabama § 25-4-72 + href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ + - title: Ala. Admin. Code 480-4-3-.11 — Claims for Partial Unemployment + href: https://admincode.legislature.state.al.us/administrative-code/480-4-3-.11 + - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-8 + href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=20 + - title: NELP — New Alabama Unemployment Insurance Law Makes Work Pay + href: https://www.nelp.org/new-alabama-unemployment-insurance-law-makes-work-pay/ diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/state_unemployment_rate.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/state_unemployment_rate.yaml new file mode 100644 index 00000000000..e7057317fca --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/state_unemployment_rate.yaml @@ -0,0 +1,89 @@ +description: Alabama uses this seasonally-adjusted statewide average unemployment rate under the Unemployment Compensation program. + +values: + 2020-01-01: 0.028 + 2020-02-01: 0.030 + 2020-03-01: 0.031 + 2020-04-01: 0.138 + 2020-05-01: 0.111 + 2020-06-01: 0.087 + 2020-07-01: 0.079 + 2020-08-01: 0.067 + 2020-09-01: 0.059 + 2020-10-01: 0.045 + 2020-11-01: 0.041 + 2020-12-01: 0.038 + 2021-01-01: 0.036 + 2021-02-01: 0.034 + 2021-03-01: 0.033 + 2021-04-01: 0.032 + 2021-05-01: 0.031 + 2021-06-01: 0.030 + 2021-07-01: 0.029 + 2021-08-01: 0.029 + 2021-09-01: 0.029 + 2021-10-01: 0.030 + 2021-11-01: 0.030 + 2021-12-01: 0.030 + 2022-01-01: 0.029 + 2022-02-01: 0.028 + 2022-03-01: 0.027 + 2022-04-01: 0.026 + 2022-05-01: 0.026 + 2022-06-01: 0.026 + 2022-07-01: 0.026 + 2022-08-01: 0.026 + 2022-09-01: 0.026 + 2022-10-01: 0.026 + 2022-11-01: 0.026 + 2022-12-01: 0.026 + 2023-01-01: 0.025 + 2023-02-01: 0.024 + 2023-03-01: 0.023 + 2023-04-01: 0.023 + 2023-05-01: 0.023 + 2023-06-01: 0.023 + 2023-07-01: 0.024 + 2023-08-01: 0.025 + 2023-09-01: 0.025 + 2023-10-01: 0.026 + 2023-11-01: 0.027 + 2023-12-01: 0.028 + 2024-01-01: 0.029 + 2024-02-01: 0.029 + 2024-03-01: 0.029 + 2024-04-01: 0.029 + 2024-05-01: 0.030 + 2024-06-01: 0.030 + 2024-07-01: 0.031 + 2024-08-01: 0.032 + 2024-09-01: 0.032 + 2024-10-01: 0.032 + 2024-11-01: 0.033 + 2024-12-01: 0.033 + 2025-01-01: 0.033 + 2025-02-01: 0.033 + 2025-03-01: 0.033 + 2025-04-01: 0.033 + 2025-05-01: 0.033 + 2025-06-01: 0.034 + 2025-07-01: 0.034 + 2025-08-01: 0.035 + 2025-09-01: 0.035 + 2025-10-01: 0.035 + 2025-11-01: 0.035 + 2025-12-01: 0.035 + 2026-01-01: 0.035 + 2026-02-01: 0.035 + 2026-03-01: 0.035 + 2026-04-01: 0.035 + +metadata: + unit: /1 + period: month + label: Alabama UI state unemployment rate + reference: + - title: FRED — Unemployment Rate in Alabama (ALUR) + href: https://fred.stlouisfed.org/series/ALUR + - title: U.S. Bureau of Labor Statistics — Local Area Unemployment Statistics (LAUS) + href: https://www.bls.gov/lau/ diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/waiting_weeks.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/waiting_weeks.yaml new file mode 100644 index 00000000000..1a98091cc08 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/waiting_weeks.yaml @@ -0,0 +1,16 @@ +description: Alabama excludes this many initial weeks from compensability under the Unemployment Compensation program. + +values: + 2020-01-01: 1 + +metadata: + unit: week + period: year + label: Alabama UI waiting weeks + reference: + - title: Alabama UC Benefit Rights and Responsibilities Handbook + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=6 + - title: Ala. Admin. Code Chapter 480-4-3 — Benefits + href: https://admincode.legislature.state.al.us/administrative-code/480-4-3 + - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-7 + href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/max.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/max.yaml new file mode 100644 index 00000000000..532dbb937c6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/max.yaml @@ -0,0 +1,16 @@ +description: Alabama provides this amount as the maximum weekly benefit under the Unemployment Compensation program. + +values: + 2020-01-01: 275 + +metadata: + unit: currency-USD + period: week + label: Alabama UI maximum weekly benefit amount + reference: + - title: Code of Alabama § 25-4-72(b)(5) + href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ + - title: Alabama Act 2019-204 summary + href: https://alabamaretail.org/news/unemployment-comp-weeks-weekly-benefits/ + - title: Alabama UC Benefit Rights and Responsibilities Handbook + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/min_threshold.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/min_threshold.yaml new file mode 100644 index 00000000000..cfd36bb49d8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/min_threshold.yaml @@ -0,0 +1,14 @@ +description: Alabama sets this amount as the minimum unrounded weekly benefit threshold under the Unemployment Compensation program. + +values: + 2020-01-01: 44.5 + +metadata: + unit: currency-USD + period: week + label: Alabama UI minimum unrounded weekly benefit threshold + reference: + - title: Code of Alabama § 25-4-72(b)(2) + href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ + - title: Alabama UC Benefit Rights and Responsibilities Handbook + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml new file mode 100644 index 00000000000..8800fa1eec2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml @@ -0,0 +1,246 @@ +# Tests for Alabama UI annual benefit (§§ 25-4-72, 25-4-74). +# Formula: +# payable_weeks = min(max(0, weeks_unemployed - waiting_weeks), max_weeks) +# al_ui = min(partial_weekly_benefit * payable_weeks, maximum_benefit_amount) +# - 1-week waiting week subtracted (BRR p.6; eff 2012-08-01). +# - Annual benefit capped at MBA from § 25-4-74(a). +# References: +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/ + +- name: Case 1, full 14 weeks at $275 WBA - hits N x WBA cap. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + # weeks_unemployed >= max_weeks + 1 so the entire duration is paid. + weeks_unemployed: 20 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # UR 2024 ~ 2.9% -> 14 weeks; WBA capped at 275. + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 275 + # MBA = min(14 * 275, 0.25 * 40,000) = min(3,850, 10,000) = 3,850. + al_ui_maximum_benefit_amount: 3_850 + # weeks_unemployed 20 - waiting 1 = 19, capped at 14 -> 14 weeks paid. + # Annual = 275 * 14 = 3,850. + al_ui: 3_850 + +- name: Case 2, annual capped at MBA when weeks_unemployed x WBA exceeds MBA. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # WBA = 100 (from 2,613 each). 14 * 100 = 1,400 if N*WBA binds. + # BPW = 5,000 -> 0.25 * 5,000 = 1,250 -> MBA = 1,250. + al_ui_high_quarter_wages: 2_613 + al_ui_second_high_quarter_wages: 2_613 + al_ui_base_period_wages: 5_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 20 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 100 + # MBA = min(14 * 100, 0.25 * 5,000) = min(1,400, 1,250) = 1,250. + al_ui_maximum_benefit_amount: 1_250 + # weekly_payable * (20 - 1) = 100 * 19 = 1,900 -> capped at 1,250. + al_ui: 1_250 + +- name: Case 3, monetarily ineligible (1 quarter only) - $0 annual benefit. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 8_000 + al_ui_second_high_quarter_wages: 0 + al_ui_base_period_wages: 8_000 + al_ui_quarters_with_wages: 1 + al_ui_weekly_earnings: 0 + weeks_unemployed: 14 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_monetarily_eligible: false + al_ui_weekly_benefit_amount: 0 + al_ui: 0 + +- name: Case 4, zero weeks unemployed - $0 annual benefit. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 0 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_monetarily_eligible: true + al_ui_weekly_benefit_amount: 275 + # payable_weeks = min(max(0, 0 - 1), 14) = 0 -> al_ui = 0. + al_ui: 0 + +- name: Case 5, exactly 1 week unemployed - waiting week consumes it - $0. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 1 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # payable_weeks = min(max(0, 1 - 1), 14) = 0 -> al_ui = 0. + al_ui: 0 + +- name: Case 6, mid-duration unemployment - 10 paid weeks after waiting. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 11 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # payable_weeks = min(max(0, 11 - 1), 14) = min(10, 14) = 10. + # Annual = 275 * 10 = 2,750; MBA = 3,850 (not binding). + al_ui: 2_750 + +- name: Case 7, zero HQW (no wages) - ineligible - $0 annual benefit. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 0 + al_ui_second_high_quarter_wages: 0 + al_ui_base_period_wages: 0 + al_ui_quarters_with_wages: 0 + al_ui_weekly_earnings: 0 + weeks_unemployed: 14 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Zero quarters -> ineligible -> al_ui = 0 (defined_for gate). + # Confirms graceful handling of no-wage claimants (no crash). + al_ui_monetarily_eligible: false + al_ui_weekly_benefit_amount: 0 + al_ui_maximum_benefit_amount: 0 + al_ui: 0 + +- name: Case 8, zero BPW with positive HQW - fails 1.5x test - ineligible. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # Pathological: HQW positive but BPW zero (inconsistent inputs). + # The formula must not crash; eligibility correctly fails. + al_ui_high_quarter_wages: 8_000 + al_ui_second_high_quarter_wages: 4_000 + al_ui_base_period_wages: 0 + al_ui_quarters_with_wages: 2 + al_ui_weekly_earnings: 0 + weeks_unemployed: 10 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # BPW 0 < 1.5 * 8,000 = 12,000 -> fails § 25-4-77(a)(4)(a). + al_ui_monetarily_eligible: false + al_ui: 0 + +- name: Case 9, negative weekly earnings (defensive) - full WBA paid each week. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + # Negative weekly earnings should not inflate the benefit. + al_ui_weekly_earnings: -100 + weeks_unemployed: 11 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # max_(earnings - disregard, 0) clamps negative to 0 -> partial = WBA. + al_ui_partial_weekly_benefit: 275 + # payable_weeks = 10. Annual = 275 * 10 = 2,750. + al_ui: 2_750 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml new file mode 100644 index 00000000000..a52e8f06156 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml @@ -0,0 +1,206 @@ +# Tests for Alabama UI maximum benefit weeks (§ 25-4-74(a)). +# The duration is set by a single_amount bracket on the statewide average +# unemployment rate (AAU): +# ≤6.5% -> 14 weeks +# >6.5%-≤7.0% -> 15 +# >7.0%-≤7.5% -> 16 +# >7.5%-≤8.0% -> 17 +# >8.0%-≤8.5% -> 18 +# >8.5%-≤9.0% -> 19 +# ≥9.5% -> 20 +# The bracket is encoded with thresholds at 0, 0.0651, 0.0701, 0.0751, 0.0801, +# 0.0851, 0.0901 -> 14/15/16/17/18/19/20 weeks. Each non-baseline rate is +# pinned via a reform in `set_state_unemployment_rate.py`. The natural +# baseline (2024 FRED ALUR ~ 2.9%) hits bracket 1 and is exercised in +# integration.yaml. +# +# Reference: +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/ + +- name: Case 1, UR at 6.5% (boundary of first bracket) - 14 weeks. + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_6_5pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.065 < 0.0651 -> first bracket -> 14 weeks (statute: ≤ 6.5%). + al_ui_max_weeks: 14 + +- name: Case 2, UR at 7.0% - 15 weeks (top of second bracket). + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_7pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.0651 <= 0.07 < 0.0701 -> bracket 2 -> 15 weeks (statute: ≤ 7.0%). + al_ui_max_weeks: 15 + +- name: Case 3, UR at 7.5% - 16 weeks (top of third bracket). + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_7_5pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.0701 <= 0.075 < 0.0751 -> bracket 3 -> 16 weeks. + al_ui_max_weeks: 16 + +- name: Case 4, UR at 8.0% - 17 weeks. + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_8pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.0751 <= 0.08 < 0.0801 -> bracket 4 -> 17 weeks. + al_ui_max_weeks: 17 + +- name: Case 5, UR at 8.5% - 18 weeks. + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_8_5pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.0801 <= 0.085 < 0.0851 -> bracket 5 -> 18 weeks. + al_ui_max_weeks: 18 + +- name: Case 6, UR at 9.0% - 19 weeks. + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_9pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.0851 <= 0.09 < 0.0901 -> bracket 6 -> 19 weeks. + al_ui_max_weeks: 19 + +- name: Case 7, UR at 9.5% - 20 weeks (cap). + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_9_5pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.095 >= 0.0901 -> bracket 7 -> 20 weeks (statute caps at 20 weeks + # for AAU >= 9.5%). + al_ui_max_weeks: 20 + +- name: Case 8, UR at 12% (well above 9.5%) - still 20 weeks (capped). + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_12pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Highest bracket: 20 weeks (cap). + al_ui_max_weeks: 20 + +- name: Case 9, natural FRED UR for 2024 (~2.9%) - 14 weeks without reform. + period: 2024 + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 2024-01 UR = 0.029 from FRED ALUR -> first bracket -> 14 weeks. + al_ui_max_weeks: 14 + +- name: Case 10, UR at 6.51% (just above 6.5% boundary) - 15 weeks. + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_6_51pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.0651 hits the second bracket threshold exactly -> 15 weeks. + # Confirms "above 6.5%" semantics: 0.065 -> 14, 0.0651 -> 15. + al_ui_max_weeks: 15 + +- name: Case 11, UR at 15% (extreme high) - still 20 weeks (statute cap). + period: 2024 + reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_15pct + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 0.15 well above 0.0901 -> bracket 7 (cap) -> 20 weeks. + al_ui_max_weeks: 20 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_maximum_benefit_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_maximum_benefit_amount.yaml new file mode 100644 index 00000000000..7990b89d444 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_maximum_benefit_amount.yaml @@ -0,0 +1,242 @@ +# Tests for Alabama UI Maximum Benefit Amount (MBA) (§ 25-4-74(a)). +# Formula: MBA = min(N x WBA, 0.25 x BPW), rounded to the nearest dollar. +# N = al_ui_max_weeks (14-20 depending on state UR). +# Dual-cap mirrors PA UC, GA UI, MS UI, etc. +# Reference: +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/ + +- name: Case 1, N x WBA is the binding cap (high WBA, modest BPW). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # WBA = (7,150 + 7,150) / 52 = 275 (at cap). + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + # BPW only barely passes the 1.5x test + # (1.5 * 7,150 = 10,725; BPW = 16,500 > 10,725). + # 0.25 * BPW = 4,125; 14 * 275 = 3,850 -> N*WBA binds. + al_ui_base_period_wages: 16_500 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # UR (2024) ~ 2.9% -> max_weeks = 14. + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 275 + # min(14 * 275, 0.25 * 16,500) = min(3,850, 4,125) = 3,850. + al_ui_maximum_benefit_amount: 3_850 + +- name: Case 2, BPW/4 is the binding cap (modest WBA, very high BPW). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # WBA from top-2 quarters of 2,613 each: + # unrounded = 5,226 / 52 = 100.50 -> rounds DOWN to 100. + al_ui_high_quarter_wages: 2_613 + al_ui_second_high_quarter_wages: 2_613 + # Large BPW from other low quarters: 0.25 * 12,000 = 3,000. + # 14 * 100 = 1,400 -> N*WBA binds, BPW cap does NOT bind here. + # Adjust: need BPW cap to bind -> BPW must be < 4 * N * WBA = 5,600. + # But BPW must also pass 1.5x test: BPW >= 1.5 * 2,613 = 3,920. + # Pick BPW = 5,000: 0.25 * 5,000 = 1,250 < 1,400 -> BPW cap binds. + al_ui_base_period_wages: 5_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 100 + # min(14 * 100, 0.25 * 5,000) = min(1,400, 1,250) = 1,250. + al_ui_maximum_benefit_amount: 1_250 + +- name: Case 3, two caps exactly equal - either binds, returns same value. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # Choose so that 14 * WBA = 0.25 * BPW exactly. + # WBA = 100 -> 14 * 100 = 1,400; BPW = 5,600 -> 0.25 * 5,600 = 1,400. + al_ui_high_quarter_wages: 2_613 + al_ui_second_high_quarter_wages: 2_613 + al_ui_base_period_wages: 5_600 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 100 + # min(1,400, 1,400) = 1,400. + al_ui_maximum_benefit_amount: 1_400 + +- name: Case 4, monetarily ineligible (1 quarter) - MBA is $0. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 8_000 + al_ui_second_high_quarter_wages: 0 + al_ui_base_period_wages: 8_000 + al_ui_quarters_with_wages: 1 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Not monetarily eligible -> WBA = 0 -> MBA = min(14 * 0, 2,000) = 0. + al_ui_weekly_benefit_amount: 0 + al_ui_maximum_benefit_amount: 0 + +- name: Case 5, BPW cap with non-integer 0.25 x BPW - rounded per statute. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # WBA = 100 (from 2,613 + 2,613). 14 * 100 = 1,400. + # BPW = 5,003 -> 0.25 * 5,003 = 1,250.75. + # BPW also passes 1.5x test: 5,003 >= 1.5 * 2,613 = 3,919.5. + # min(1,400, 1,250.75) = 1,250.75 -> per § 25-4-74(a), "such total + # amounts of benefits, if not a multiple of one dollar, shall be + # computed to the nearest multiple of one dollar" -> rounds to 1,251. + al_ui_high_quarter_wages: 2_613 + al_ui_second_high_quarter_wages: 2_613 + al_ui_base_period_wages: 5_003 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 100 + # min(1,400, 1,250.75) = 1,250.75; rounded to nearest dollar -> 1,251. + al_ui_maximum_benefit_amount: 1_251 + +- name: Case 6, BPW cap with 0.25 x BPW just below half - rounds DOWN. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # WBA = 100 (from 2,613 + 2,613). 14 * 100 = 1,400. + # BPW = 5,001 -> 0.25 * 5,001 = 1,250.25. + # BPW passes 1.5x test: 5,001 >= 1.5 * 2,613 = 3,919.5. + # min(1,400, 1,250.25) = 1,250.25; nearest dollar -> 1,250. + al_ui_high_quarter_wages: 2_613 + al_ui_second_high_quarter_wages: 2_613 + al_ui_base_period_wages: 5_001 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 100 + # 0.25 * 5,001 = 1,250.25 -> np.round -> 1,250. + al_ui_maximum_benefit_amount: 1_250 + +- name: Case 7, large BPW, low WBA - N x WBA dominates (BPW cap not binding). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # WBA = 50 (unrounded = (1,300 + 1,300) / 52 = 50, no fraction). + # 14 * 50 = 700. + # BPW = 100,000 -> 0.25 * 100,000 = 25,000 (not binding). + al_ui_high_quarter_wages: 1_300 + al_ui_second_high_quarter_wages: 1_300 + al_ui_base_period_wages: 100_000 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 50 + # min(14 * 50, 0.25 * 100,000) = min(700, 25,000) = 700. + al_ui_maximum_benefit_amount: 700 + +- name: Case 8, very small BPW, high WBA - BPW/4 dominates aggressively. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # WBA at 275 cap; BPW just barely passes 1.5x test. + # HQW = 7,150; 1.5 * 7,150 = 10,725; BPW = 10,800 passes. + # 0.25 * 10,800 = 2,700. 14 * 275 = 3,850 -> BPW cap binds. + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 10_800 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 + al_ui_weekly_benefit_amount: 275 + # min(14 * 275, 0.25 * 10,800) = min(3,850, 2,700) = 2,700. + al_ui_maximum_benefit_amount: 2_700 + +- name: Case 9, zero BPW - MBA is $0 (also ineligible). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 0 + al_ui_second_high_quarter_wages: 0 + al_ui_base_period_wages: 0 + al_ui_quarters_with_wages: 0 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Ineligible -> WBA = 0; MBA = min(14 * 0, 0.25 * 0) = 0. + al_ui_monetarily_eligible: false + al_ui_weekly_benefit_amount: 0 + al_ui_maximum_benefit_amount: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_monetarily_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_monetarily_eligible.yaml new file mode 100644 index 00000000000..0f6ca8a0e13 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_monetarily_eligible.yaml @@ -0,0 +1,180 @@ +# Tests for Alabama UI monetary eligibility (§§ 25-4-77(a)(4), 25-4-72(b)(2)). +# A claimant is monetarily eligible only if ALL THREE tests pass: +# 1. Wages paid in at least 2 calendar quarters of the base period +# (§ 25-4-77(a)(4)). +# 2. Total base-period wages >= 1.5 x high-quarter wages +# (§ 25-4-77(a)(4)(a)). +# 3. Unrounded WBA = (HQW + 2HQW) / 52 must exceed $44.50 +# (§ 25-4-72(b)(2)). +# References: +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/ +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ + +- name: Case 1, all three tests pass - fully monetarily eligible. + period: 2024 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 5_000 + al_ui_second_high_quarter_wages: 4_000 + al_ui_base_period_wages: 15_000 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 4 quarters >= 2; BPW 15,000 >= 1.5 * 5,000 = 7,500; + # unrounded WBA = (5,000 + 4,000) / 52 = 173.08 > 44.50. + al_ui_monetarily_eligible: true + +- name: Case 2, fails quarters test - only 1 quarter with wages. + period: 2024 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 8_000 + al_ui_second_high_quarter_wages: 0 + al_ui_base_period_wages: 8_000 + al_ui_quarters_with_wages: 1 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Only 1 quarter < 2 required -> fails § 25-4-77(a)(4). + al_ui_monetarily_eligible: false + +- name: Case 3, fails 1.5x base-wages test. + period: 2024 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 8_000 + al_ui_second_high_quarter_wages: 1_000 + # BPW < 1.5 * 8,000 = 12,000 -> fails test 2. + al_ui_base_period_wages: 10_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # BPW 10,000 < 1.5 * 8,000 = 12,000 -> fails § 25-4-77(a)(4)(a). + al_ui_monetarily_eligible: false + +- name: Case 4, fails unrounded-WBA floor - unrounded WBA below $44.50. + period: 2024 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 1_000 + al_ui_second_high_quarter_wages: 1_000 + al_ui_base_period_wages: 3_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded WBA = (1,000 + 1,000) / 52 = 38.46 < 44.50 -> ineligible. + # (4 qtrs and BPW pass; only the WBA floor fails.) + al_ui_monetarily_eligible: false + +- name: Case 5, boundary - unrounded WBA exactly $44.50 fails (statute uses strictly greater). + period: 2024 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 1_157 + al_ui_second_high_quarter_wages: 1_157 + al_ui_base_period_wages: 4_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded WBA = (1,157 + 1,157) / 52 = 44.50. + # Statute § 25-4-72(b)(2): "not in excess of $44.50" -> ineligible. + al_ui_monetarily_eligible: false + +- name: Case 6, boundary - unrounded WBA just above $44.50 is eligible. + period: 2024 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 1_200 + al_ui_second_high_quarter_wages: 1_200 + al_ui_base_period_wages: 4_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded WBA = (1,200 + 1,200) / 52 = 46.15 > 44.50 -> passes floor. + # 3 qtrs >= 2; BPW 4,000 >= 1.5 * 1,200 = 1,800 -> all 3 tests pass. + al_ui_monetarily_eligible: true + +- name: Case 7, exactly 2 quarters and BPW exactly at 1.5x HQW - all tests pass. + period: 2024 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 4_000 + al_ui_second_high_quarter_wages: 2_000 + # BPW = 1.5 * 4,000 = 6,000 (exactly at threshold; statute uses + # "equal to or exceeding" -> >= passes). + al_ui_base_period_wages: 6_000 + al_ui_quarters_with_wages: 2 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 2 qtrs = 2 required; BPW 6,000 = 1.5 * 4,000; + # unrounded WBA = (4,000 + 2,000) / 52 = 115.38 > 44.50. + al_ui_monetarily_eligible: true + +- name: Case 8, BPW one dollar below 1.5x HQW - fails test 2. + period: 2024 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 4_000 + al_ui_second_high_quarter_wages: 2_000 + # 1.5 * 4,000 = 6,000; BPW = 5,999 just below. + al_ui_base_period_wages: 5_999 + al_ui_quarters_with_wages: 2 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # BPW 5,999 < 1.5 * 4,000 = 6,000 -> fails § 25-4-77(a)(4)(a). + al_ui_monetarily_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.yaml new file mode 100644 index 00000000000..a0c15a3e7eb --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.yaml @@ -0,0 +1,252 @@ +# Tests for Alabama UI partial weekly benefit (§ 25-4-72; Admin Code 480-4-3-.11). +# Formula: partial_weekly_benefit = max(0, WBA - max(0, weekly_earnings - WBA/3)) +# = WBA if weekly_earnings <= WBA/3 +# = WBA - (weekly_earnings - WBA/3) if WBA/3 < weekly_earnings < WBA +# = 0 if weekly_earnings >= WBA +# Equivalently: a week is "partial" if earnings < WBA; the first 1/3 x WBA of +# earnings is disregarded, and earnings above the disregard reduce the WBA +# dollar-for-dollar. +# Reference: +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ +# https://admincode.legislature.state.al.us/administrative-code/480-4-3-.11 + +- name: Case 1, zero earnings - full WBA paid. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # WBA = 275 (at cap). 0 earnings -> full WBA paid. + al_ui_weekly_benefit_amount: 275 + al_ui_partial_weekly_benefit: 275 + +- name: Case 2, earnings below WBA/3 disregard - still full WBA paid. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + # WBA/3 = 91.67; 80 is below the disregard. + al_ui_weekly_earnings: 80 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # earnings 80 <= WBA/3 = 91.67 -> all disregarded -> full WBA. + al_ui_partial_weekly_benefit: 275 + +- name: Case 3, earnings exactly at WBA/3 - still full WBA paid. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + # WBA/3 ≈ 91.67. Use 91.67 to test boundary. + al_ui_weekly_earnings: 91.67 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # earnings = 91.67 ~= WBA/3 -> excess ~= 0 -> full WBA. + al_ui_partial_weekly_benefit: 275 + +- name: Case 4, earnings above WBA/3 - partial benefit reduced dollar-for-dollar. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + # WBA/3 ≈ 91.67; earnings 150 -> excess ~= 58.33. + al_ui_weekly_earnings: 150 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # WBA - (earnings - WBA/3) = 275 - (150 - 91.67) = 275 - 58.33 = 216.67. + al_ui_partial_weekly_benefit: 216.67 + +- name: Case 5, earnings just below WBA - very small partial benefit. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + # Earnings 270 < WBA 275 -> partial. + al_ui_weekly_earnings: 270 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # 275 - (270 - 91.67) = 275 - 178.33 = 96.67. + al_ui_partial_weekly_benefit: 96.67 + +- name: Case 6, earnings exactly at WBA - no partial benefit. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + # Earnings = WBA -> NOT partial (week is "partial" only when earnings + # < WBA per USDOL Tbl 3-8). Returns 0. + al_ui_weekly_earnings: 275 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # earnings >= WBA -> partial benefit = 0. + al_ui_partial_weekly_benefit: 0 + +- name: Case 7, earnings above WBA - no partial benefit. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 500 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # earnings 500 > WBA 275 -> partial benefit = 0. + al_ui_partial_weekly_benefit: 0 + +- name: Case 8, monetarily ineligible - WBA = 0 - partial benefit also $0. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 1_000 + al_ui_second_high_quarter_wages: 1_000 + al_ui_base_period_wages: 3_000 + al_ui_quarters_with_wages: 3 + al_ui_weekly_earnings: 0 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded WBA = 38.46 < 44.50 -> ineligible -> WBA = 0. + al_ui_weekly_benefit_amount: 0 + al_ui_partial_weekly_benefit: 0 + +- name: Case 9, negative weekly earnings (defensive) - treated as full WBA. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + # Defensive case: a negative earnings value should not inflate the + # benefit beyond the WBA. max_(earnings - disregard, 0) -> 0; + # max_(WBA - 0, 0) -> WBA. The where(weekly_earnings < WBA, ...) + # branch is taken (negative < WBA) -> partial_amount returned. + al_ui_weekly_earnings: -50 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # Negative earnings clamped by max_() -> full WBA, not inflated. + al_ui_partial_weekly_benefit: 275 + +- name: Case 10, earnings exactly one cent below WBA - tiny partial. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + # WBA = 275; earnings 274.99 < 275 -> still partial. + al_ui_weekly_earnings: 274.99 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # WBA/3 = 91.6667; 275 - (274.99 - 91.6667) = 275 - 183.3233 = 91.6767. + al_ui_partial_weekly_benefit: 91.68 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_unrounded_wba.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_unrounded_wba.yaml new file mode 100644 index 00000000000..a795f886efd --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_unrounded_wba.yaml @@ -0,0 +1,122 @@ +# Tests for Alabama UI unrounded weekly benefit amount (§ 25-4-72(b)). +# Formula: unrounded_wba = (high_quarter_wages + second_high_quarter_wages) / 52. +# Equivalent: average of top-2 quarters / 26. +# No rounding, no cap, no eligibility gate — used by eligibility and the +# rounding/capping steps downstream. +# Reference: Code of Alabama § 25-4-72(b); +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ + +- name: Case 1, both top quarters equal at $10,000 - unrounded WBA is $384.62. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # (10,000 + 10,000) / 52 = 20,000 / 52 = 384.615... + al_ui_unrounded_wba: 384.62 + +- name: Case 2, unequal top quarters - unrounded WBA averages the two. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 5_200 + al_ui_second_high_quarter_wages: 2_600 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # (5,200 + 2,600) / 52 = 7,800 / 52 = 150. + al_ui_unrounded_wba: 150 + +- name: Case 3, exactly at the $44.50 ineligibility threshold. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # 44.50 * 52 = 2,314, split evenly between top 2 quarters. + al_ui_high_quarter_wages: 1_157 + al_ui_second_high_quarter_wages: 1_157 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # (1,157 + 1,157) / 52 = 2,314 / 52 = 44.50. + al_ui_unrounded_wba: 44.50 + +- name: Case 4, only one quarter of wages - other top quarter is zero. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 8_000 + al_ui_second_high_quarter_wages: 0 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # (8,000 + 0) / 52 = 153.846... + al_ui_unrounded_wba: 153.85 + +- name: Case 5, no wages at all - unrounded WBA is zero. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 0 + al_ui_second_high_quarter_wages: 0 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_unrounded_wba: 0 + +- name: Case 6, well above the WBA cap - formula still returns the raw value. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 25_000 + al_ui_second_high_quarter_wages: 25_000 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # (25,000 + 25,000) / 52 = 961.538... + # No cap applied here; capping happens in al_ui_weekly_benefit_amount. + al_ui_unrounded_wba: 961.54 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_weekly_benefit_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_weekly_benefit_amount.yaml new file mode 100644 index 00000000000..aac22a7b278 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_weekly_benefit_amount.yaml @@ -0,0 +1,284 @@ +# Tests for Alabama UI weekly benefit amount (WBA) (§ 25-4-72(b)). +# Formula: +# 1. Compute unrounded WBA = (HQW + 2HQW) / 52. +# 2. If unrounded WBA <= $44.50 -> WBA = 0 (monetary ineligibility). +# 3. Otherwise apply Alabama rounding: fractional part > $0.50 rounds UP, +# <= $0.50 rounds DOWN (statutory half-down). § 25-4-72(b)(1). +# 4. Cap at the statutory maximum (currently $275). § 25-4-72(b)(5). +# Reference: +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ + +- name: Case 1, unrounded WBA exactly $44.50 - ineligible, returns $0. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 1_157 + al_ui_second_high_quarter_wages: 1_157 + al_ui_base_period_wages: 4_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded = (1,157 + 1,157) / 52 = 44.50 -> "not in excess of $44.50" + # -> WBA = 0 per § 25-4-72(b)(2). + al_ui_weekly_benefit_amount: 0 + +- name: Case 2, unrounded WBA just above $44.50 - rounded to nearest dollar. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 1_200 + al_ui_second_high_quarter_wages: 1_200 + al_ui_base_period_wages: 4_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded = (1,200 + 1,200) / 52 = 46.1538... + # Fractional 0.1538 < 0.50 -> rounds DOWN to 46. + al_ui_weekly_benefit_amount: 46 + +- name: Case 3, WBA exactly at $275 cap - HQW + 2HQW = $14,300. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded = 14,300 / 52 = 275.00 -> rounded 275; cap also 275. + al_ui_weekly_benefit_amount: 275 + +- name: Case 4, WBA above cap - capped at $275. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded = 20,000 / 52 = 384.62 -> rounded 385 -> capped at 275. + al_ui_weekly_benefit_amount: 275 + +- name: Case 5, AL rounding ties round DOWN - unrounded $100.50 -> $100. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # 100.50 * 52 = 5,226; split as 2,613 + 2,613. + al_ui_high_quarter_wages: 2_613 + al_ui_second_high_quarter_wages: 2_613 + al_ui_base_period_wages: 9_000 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded = 5,226 / 52 = 100.50 -> fractional 0.50. + # § 25-4-72(b)(1): "fractional parts ... which are fifty cents ($.50) + # or less shall be dropped to the next lower multiple of one dollar" + # -> WBA = 100 (rounds DOWN at the tie). + al_ui_weekly_benefit_amount: 100 + +- name: Case 6, AL rounding strictly above $0.50 rounds UP - unrounded $100.51 -> $101. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # 100.51 * 52 = 5,226.52; provide quarters that sum to 5,227 so + # unrounded = 5,227 / 52 = 100.5192... > 100.50 -> rounds UP. + al_ui_high_quarter_wages: 2_614 + al_ui_second_high_quarter_wages: 2_613 + al_ui_base_period_wages: 9_000 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded = (2,614 + 2,613) / 52 = 5,227 / 52 = 100.5192. + # Fractional 0.5192 > 0.50 -> rounds UP to 101. + al_ui_weekly_benefit_amount: 101 + +- name: Case 7, monetarily ineligible - only 1 quarter with wages - WBA is $0. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 0 + al_ui_base_period_wages: 10_000 + al_ui_quarters_with_wages: 1 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Although the unrounded WBA (10,000 / 52 = 192.31) exceeds 44.50, + # the claimant fails § 25-4-77(a)(4) (only 1 quarter < 2 required) + # -> not monetarily eligible -> WBA = 0. + al_ui_weekly_benefit_amount: 0 + +- name: Case 8, monetarily ineligible - BPW below 1.5x HQW - WBA is $0. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 8_000 + al_ui_second_high_quarter_wages: 1_000 + # 1.5 * 8,000 = 12,000; BPW = 10,000 fails the 1.5x test. + al_ui_base_period_wages: 10_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Fails § 25-4-77(a)(4)(a) -> monetarily ineligible -> WBA = 0. + al_ui_weekly_benefit_amount: 0 + +- name: Case 9, zero wages everywhere - no inputs - WBA is $0. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 0 + al_ui_second_high_quarter_wages: 0 + al_ui_base_period_wages: 0 + al_ui_quarters_with_wages: 0 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Zero quarters fails quarters test -> ineligible -> WBA = 0. + # Also confirms graceful handling of empty inputs (no crash, no NaN). + al_ui_monetarily_eligible: false + al_ui_weekly_benefit_amount: 0 + +- name: Case 10, unrounded WBA just above $44.50 ($44.5192) - eligible, rounds to $45. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # (1,158 + 1,157) / 52 = 2,315 / 52 = 44.5192 > 44.50 -> eligible. + al_ui_high_quarter_wages: 1_158 + al_ui_second_high_quarter_wages: 1_157 + al_ui_base_period_wages: 4_000 + al_ui_quarters_with_wages: 3 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Fractional 0.5192 > 0.50 -> rounds UP to 45 per § 25-4-72(b)(1). + al_ui_monetarily_eligible: true + al_ui_weekly_benefit_amount: 45 + +- name: Case 11, unrounded WBA just above $275 ($275.48) - capped at $275. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # (7,163 + 7,162) / 52 = 14,325 / 52 = 275.4808. + # Rounding: ceil(275.4808 - 0.5) = ceil(274.9808) = 275. + # Cap at 275 -> still 275 (cap not actually binding here because + # rounding already produced 275). + al_ui_high_quarter_wages: 7_163 + al_ui_second_high_quarter_wages: 7_162 + al_ui_base_period_wages: 28_650 + al_ui_quarters_with_wages: 4 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + +- name: Case 12, AL rounding ties round DOWN at $1.50 -> $1 (sub-threshold ineligible). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # (39 + 39) / 52 = 78 / 52 = 1.50. + al_ui_high_quarter_wages: 39 + al_ui_second_high_quarter_wages: 39 + al_ui_base_period_wages: 200 + al_ui_quarters_with_wages: 2 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Unrounded WBA 1.50 < 44.50 -> ineligible -> WBA = 0. + # Verifies graceful handling of very low wages without crash; the + # half-down rounding rule itself is exercised in Case 5. + al_ui_monetarily_eligible: false + al_ui_weekly_benefit_amount: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml new file mode 100644 index 00000000000..a663250c799 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml @@ -0,0 +1,337 @@ +# Integration tests for Alabama Unemployment Insurance (AL UI). +# End-to-end scenarios verifying al_ui together with all intermediate +# variables. Each case documents the full chain: monetary eligibility -> +# WBA -> max_weeks -> MBA -> partial weekly benefit -> annual benefit. +# References: +# Code of Alabama §§ 25-4-72, 25-4-74, 25-4-77: +# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/ +# Alabama UC Benefit Rights & Responsibilities (BRR) handbook: +# https://labor.alabama.gov/docs/guides/uc_brr.pdf +# USDOL Comparison of State UI Laws 2023: +# https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf + +- name: Case 1, maximum-benefit claimant - full WBA, full duration, no earnings. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 20 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Monetarily eligible: 4 quarters >= 2, BPW 40k >= 1.5 * 10k = 15k, + # unrounded WBA = 20k / 52 = 384.62 > 44.50. + al_ui_monetarily_eligible: true + # Unrounded WBA = 384.62; rounded then capped at 275. + al_ui_unrounded_wba: 384.62 + al_ui_weekly_benefit_amount: 275 + # UR (2024-01) = 0.029 -> max_weeks = 14. + al_ui_max_weeks: 14 + # MBA = min(14 * 275, 0.25 * 40,000) = min(3,850, 10,000) = 3,850. + al_ui_maximum_benefit_amount: 3_850 + # No earnings -> full WBA per week. + al_ui_partial_weekly_benefit: 275 + # payable_weeks = min(max(0, 20 - 1), 14) = 14. + # Annual = 275 * 14 = 3,850 (matches MBA). + al_ui: 3_850 + +- name: Case 2, minimum-benefit claimant - unrounded WBA just above $44.50. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 28 + al_ui_high_quarter_wages: 1_200 + al_ui_second_high_quarter_wages: 1_200 + al_ui_base_period_wages: 4_000 + al_ui_quarters_with_wages: 3 + al_ui_weekly_earnings: 0 + weeks_unemployed: 14 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # 3 quarters >= 2; BPW 4,000 >= 1.5 * 1,200 = 1,800. + # Unrounded WBA = 2,400 / 52 = 46.15 > 44.50. + al_ui_monetarily_eligible: true + al_ui_unrounded_wba: 46.15 + # 46.15 fractional < 0.5 -> rounds DOWN to 46. + al_ui_weekly_benefit_amount: 46 + al_ui_max_weeks: 14 + # MBA = min(14 * 46, 0.25 * 4,000) = min(644, 1,000) = 644. + al_ui_maximum_benefit_amount: 644 + al_ui_partial_weekly_benefit: 46 + # payable_weeks = min(max(0, 14 - 1), 14) = 13. + # Annual = 46 * 13 = 598; MBA 644 not binding. + al_ui: 598 + +- name: Case 3, monetarily ineligible - only 1 quarter with wages. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 0 + al_ui_base_period_wages: 10_000 + al_ui_quarters_with_wages: 1 + al_ui_weekly_earnings: 0 + weeks_unemployed: 14 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Only 1 quarter < 2 required -> ineligible per § 25-4-77(a)(4). + al_ui_monetarily_eligible: false + al_ui_weekly_benefit_amount: 0 + al_ui_maximum_benefit_amount: 0 + al_ui_partial_weekly_benefit: 0 + al_ui: 0 + +- name: Case 4, monetarily ineligible - BPW below 1.5x HQW. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + al_ui_high_quarter_wages: 8_000 + al_ui_second_high_quarter_wages: 1_000 + # 1.5 * 8,000 = 12,000; BPW 10,000 fails. + al_ui_base_period_wages: 10_000 + al_ui_quarters_with_wages: 3 + al_ui_weekly_earnings: 0 + weeks_unemployed: 14 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Fails § 25-4-77(a)(4)(a) -> ineligible. + al_ui_monetarily_eligible: false + al_ui_weekly_benefit_amount: 0 + al_ui: 0 + +- name: Case 5, partial-week claimant - earnings between WBA/3 and WBA. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + al_ui_high_quarter_wages: 7_150 + al_ui_second_high_quarter_wages: 7_150 + al_ui_base_period_wages: 28_600 + al_ui_quarters_with_wages: 4 + # WBA/3 = 91.67; earnings 150 falls in partial range. + al_ui_weekly_earnings: 150 + weeks_unemployed: 15 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_monetarily_eligible: true + al_ui_unrounded_wba: 275 + al_ui_weekly_benefit_amount: 275 + al_ui_max_weeks: 14 + # MBA = min(14 * 275, 0.25 * 28,600) = min(3,850, 7,150) = 3,850. + al_ui_maximum_benefit_amount: 3_850 + # Partial weekly benefit = 275 - (150 - 91.67) = 216.67. + al_ui_partial_weekly_benefit: 216.67 + # payable_weeks = min(max(0, 15 - 1), 14) = 14. + # Annual = 216.67 * 14 = 3,033.33 (below MBA 3,850). + al_ui: 3_033.33 + +- name: Case 6, MBA capped by BPW/4 - low-wage but eligible long-claim. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 38 + # WBA = 100 (unrounded = 100.50 -> half-down -> 100). + al_ui_high_quarter_wages: 2_613 + al_ui_second_high_quarter_wages: 2_613 + # BPW = 5,000 -> 0.25 * 5,000 = 1,250. 14 * 100 = 1,400. + # min(1,400, 1,250) = 1,250 -> BPW cap binds. + al_ui_base_period_wages: 5_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 15 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_monetarily_eligible: true + al_ui_unrounded_wba: 100.50 + al_ui_weekly_benefit_amount: 100 + al_ui_max_weeks: 14 + # MBA = min(14 * 100, 0.25 * 5,000) = min(1,400, 1,250) = 1,250. + al_ui_maximum_benefit_amount: 1_250 + al_ui_partial_weekly_benefit: 100 + # payable_weeks = min(max(0, 15 - 1), 14) = 14. + # Annual = 100 * 14 = 1,400 -> capped at MBA 1,250. + al_ui: 1_250 + +- name: Case 7, short claim - waiting week subtracted, well below MBA cap. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + al_ui_high_quarter_wages: 5_200 + al_ui_second_high_quarter_wages: 5_200 + al_ui_base_period_wages: 18_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 6 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_monetarily_eligible: true + # Unrounded WBA = 10,400 / 52 = 200 exactly. + al_ui_unrounded_wba: 200 + al_ui_weekly_benefit_amount: 200 + al_ui_max_weeks: 14 + # MBA = min(14 * 200, 0.25 * 18,000) = min(2,800, 4,500) = 2,800. + al_ui_maximum_benefit_amount: 2_800 + al_ui_partial_weekly_benefit: 200 + # payable_weeks = min(max(0, 6 - 1), 14) = 5. + # Annual = 200 * 5 = 1,000 (well below MBA 2,800). + al_ui: 1_000 + +- name: Case 8, two AL unemployed earners in one household - both receive al_ui. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 14 + person2: + age: 33 + al_ui_high_quarter_wages: 5_200 + al_ui_second_high_quarter_wages: 5_200 + al_ui_base_period_wages: 18_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 10 + households: + household: + members: [person1, person2] + state_code: AL + output: + people: + person1: + # WBA = 275 capped; max_weeks 14; payable = min(14-1, 14) = 13. + # Annual = 275 * 13 = 3,575; MBA = 3,850 (not binding). + al_ui_weekly_benefit_amount: 275 + al_ui: 3_575 + person2: + # WBA = 200; max_weeks 14; payable = min(10-1, 14) = 9. + # Annual = 200 * 9 = 1,800; MBA = min(2800, 4500) = 2800 (not binding). + al_ui_weekly_benefit_amount: 200 + al_ui: 1_800 + +- name: Case 9, AL person with no UI inputs (all defaults) - $0 al_ui. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + # No al_ui_* inputs supplied. All default to 0. + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # Quarters 0, BPW 0, unrounded WBA 0 -> ineligible -> $0. + al_ui_monetarily_eligible: false + al_ui: 0 + +- name: Case 10, GA person (non-AL) with full inputs - al_ui is $0 (defined_for filter). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + # Inputs supplied but defined_for=StateCode.AL filters them out for + # any non-AL household; al_ui must be 0 regardless of input values. + # Note: al_ui_* input variables are themselves defined_for=AL, so + # they return 0 for non-AL persons. The full chain therefore yields 0. + households: + household: + members: [person1] + state_code: GA + output: + people: + person1: + # Non-AL state -> defined_for filter applies -> al_ui = 0. + al_ui: 0 + +- name: Case 11, federal aggregator includes al_ui correctly. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + al_ui_weekly_earnings: 0 + weeks_unemployed: 14 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + # al_ui = 275 * 13 = 3,575; the federal aggregator + # unemployment_compensation adds al_ui (and ny_ui in the upstream + # version) so the federal total should equal al_ui here. + al_ui: 3_575 + unemployment_compensation: 3_575 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/set_state_unemployment_rate.py b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/set_state_unemployment_rate.py new file mode 100644 index 00000000000..17f29e51c01 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/set_state_unemployment_rate.py @@ -0,0 +1,60 @@ +"""Test reforms that override the Alabama state unemployment rate parameter. + +These reforms exist solely to drive `al_ui_max_weeks.yaml` boundary tests +through each rung of the duration-by-unemployment-rate bracket in +§ 25-4-74(a). Each reform pins the `state_unemployment_rate` parameter to a +fixed value so the bracket lookup deterministically returns the expected +number of weeks regardless of the FRED ALUR time series. +""" + +from policyengine_core.model_api import * +from policyengine_core.periods import instant + + +def _make_reform(value): + """Return a Reform subclass that pins state_unemployment_rate to ``value``.""" + + def modify_parameters(parameters): + parameters.gov.states.al.dol.unemployment_insurance.state_unemployment_rate.update( + start=instant("2020-01-01"), + stop=instant("2030-12-31"), + value=value, + ) + return parameters + + class _R(Reform): + def apply(self): + self.modify_parameters(modify_parameters) + + return _R + + +# Exactly 6.5% — boundary of the first bracket; expect 14 weeks. +set_state_unemployment_rate_to_6_5pct = _make_reform(0.065) + +# 7.0% — boundary of the second bracket; expect 15 weeks. +set_state_unemployment_rate_to_7pct = _make_reform(0.07) + +# 7.5% — boundary of the third bracket; expect 16 weeks. +set_state_unemployment_rate_to_7_5pct = _make_reform(0.075) + +# 8.0% — boundary of the fourth bracket; expect 17 weeks. +set_state_unemployment_rate_to_8pct = _make_reform(0.08) + +# 8.5% — boundary of the fifth bracket; expect 18 weeks. +set_state_unemployment_rate_to_8_5pct = _make_reform(0.085) + +# 9.0% — boundary of the sixth bracket; expect 19 weeks. +set_state_unemployment_rate_to_9pct = _make_reform(0.09) + +# 9.5% — boundary of the seventh (cap) bracket; expect 20 weeks. +set_state_unemployment_rate_to_9_5pct = _make_reform(0.095) + +# 12% — well above the cap; expect 20 weeks. +set_state_unemployment_rate_to_12pct = _make_reform(0.12) + +# 6.51% — just above the 6.5% boundary; expect 15 weeks (statute: > 6.5%). +set_state_unemployment_rate_to_6_51pct = _make_reform(0.0651) + +# 15% — very high (well above 9.5% cap); expect 20 weeks. +set_state_unemployment_rate_to_15pct = _make_reform(0.15) diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py new file mode 100644 index 00000000000..47ce567e2c6 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class al_ui(Variable): + value_type = float + entity = Person + label = "Alabama Unemployment Insurance" + unit = USD + definition_period = YEAR + reference = ( + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/", + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/", + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/", + ) + defined_for = "al_ui_monetarily_eligible" + + def formula(person, period, parameters): + p = parameters(period).gov.states.al.dol.unemployment_insurance + wba = person("al_ui_weekly_benefit_amount", period) + partial_weekly_benefit = person("al_ui_partial_weekly_benefit", period) + weekly_earnings = person("al_ui_weekly_earnings", period) + max_weeks = person("al_ui_max_weeks", period) + mba = person("al_ui_maximum_benefit_amount", period) + weeks_unemployed = person("weeks_unemployed", period) + weeks_paid = clip(weeks_unemployed - p.waiting_weeks, 0, max_weeks) + weekly_amount = where(weekly_earnings < wba, partial_weekly_benefit, wba) + return min_(weeks_paid * weekly_amount, mba) diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_base_period_wages.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_base_period_wages.py new file mode 100644 index 00000000000..f90c5c39a52 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_base_period_wages.py @@ -0,0 +1,12 @@ +from policyengine_us.model_api import * + + +class al_ui_base_period_wages(Variable): + value_type = float + entity = Person + label = "Alabama UI base period wages" + unit = USD + definition_period = YEAR + default_value = 0 + reference = "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/" + defined_for = StateCode.AL diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_high_quarter_wages.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_high_quarter_wages.py new file mode 100644 index 00000000000..897f9b31f2f --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_high_quarter_wages.py @@ -0,0 +1,12 @@ +from policyengine_us.model_api import * + + +class al_ui_high_quarter_wages(Variable): + value_type = float + entity = Person + label = "Alabama UI high quarter wages" + unit = USD + definition_period = YEAR + default_value = 0 + reference = "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/" + defined_for = StateCode.AL diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.py new file mode 100644 index 00000000000..209f909fd95 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class al_ui_max_weeks(Variable): + value_type = int + entity = Person + label = "Alabama UI maximum number of weeks of regular benefits" + unit = "week" + definition_period = YEAR + reference = ( + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/", + "https://alabamaretail.org/news/unemployment-comp-weeks-weekly-benefits/", + ) + defined_for = StateCode.AL + + def formula(person, period, parameters): + # NOTE: state_unemployment_rate is monthly; at YEAR period the + # framework returns the January value of the benefit year. + p = parameters(period).gov.states.al.dol.unemployment_insurance + return p.mba.duration_weeks.calc(p.state_unemployment_rate) diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_maximum_benefit_amount.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_maximum_benefit_amount.py new file mode 100644 index 00000000000..78455c2e0e7 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_maximum_benefit_amount.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class al_ui_maximum_benefit_amount(Variable): + value_type = float + entity = Person + label = "Alabama UI maximum benefit amount" + unit = USD + definition_period = YEAR + reference = ( + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/", + "https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=26", + ) + defined_for = StateCode.AL + + def formula(person, period, parameters): + p = parameters(period).gov.states.al.dol.unemployment_insurance + wba = person("al_ui_weekly_benefit_amount", period) + max_weeks = person("al_ui_max_weeks", period) + base_period_wages = person("al_ui_base_period_wages", period) + weeks_cap = max_weeks * wba + bpw_cap = p.mba.bpw_fraction * base_period_wages + return np.round(min_(weeks_cap, bpw_cap)) diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_monetarily_eligible.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_monetarily_eligible.py new file mode 100644 index 00000000000..f3ee71bc0f1 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_monetarily_eligible.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class al_ui_monetarily_eligible(Variable): + value_type = bool + entity = Person + label = "Monetarily eligible for Alabama Unemployment Insurance" + definition_period = YEAR + reference = ( + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/", + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/", + ) + defined_for = StateCode.AL + + def formula(person, period, parameters): + p = parameters(period).gov.states.al.dol.unemployment_insurance + high_quarter_wages = person("al_ui_high_quarter_wages", period) + base_period_wages = person("al_ui_base_period_wages", period) + quarters_with_wages = person("al_ui_quarters_with_wages", period) + unrounded_wba = person("al_ui_unrounded_wba", period) + + meets_quarters_test = quarters_with_wages >= p.eligibility.quarters_with_wages + meets_wages_test = ( + base_period_wages + >= p.eligibility.bpw_to_hqw_multiplier * high_quarter_wages + ) + meets_unrounded_wba_floor = unrounded_wba > p.wba.min_threshold + return meets_quarters_test & meets_wages_test & meets_unrounded_wba_floor diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py new file mode 100644 index 00000000000..93cb986f235 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class al_ui_partial_weekly_benefit(Variable): + value_type = float + entity = Person + label = "Alabama UI partial weekly benefit amount" + unit = USD + definition_period = YEAR + reference = ( + "https://admincode.legislature.state.al.us/administrative-code/480-4-3-.11", + "https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=20", + "https://www.nelp.org/new-alabama-unemployment-insurance-law-makes-work-pay/", + ) + defined_for = StateCode.AL + + def formula(person, period, parameters): + p = parameters(period).gov.states.al.dol.unemployment_insurance.partial + wba = person("al_ui_weekly_benefit_amount", period) + weekly_earnings = person("al_ui_weekly_earnings", period) + disregard = wba * p.disregard_rate + countable_earnings = max_(weekly_earnings - disregard, 0) + partial_amount = max_(wba - countable_earnings, 0) + return where(weekly_earnings < wba, partial_amount, 0) diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_quarters_with_wages.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_quarters_with_wages.py new file mode 100644 index 00000000000..2fb4822eb88 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_quarters_with_wages.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class al_ui_quarters_with_wages(Variable): + value_type = int + entity = Person + label = "Alabama UI quarters with wages" + definition_period = YEAR + default_value = 0 + reference = "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/" + defined_for = StateCode.AL diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_second_high_quarter_wages.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_second_high_quarter_wages.py new file mode 100644 index 00000000000..21003791269 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_second_high_quarter_wages.py @@ -0,0 +1,12 @@ +from policyengine_us.model_api import * + + +class al_ui_second_high_quarter_wages(Variable): + value_type = float + entity = Person + label = "Alabama UI second high quarter wages" + unit = USD + definition_period = YEAR + default_value = 0 + reference = "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/" + defined_for = StateCode.AL diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_unrounded_wba.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_unrounded_wba.py new file mode 100644 index 00000000000..14fc4247a47 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_unrounded_wba.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class al_ui_unrounded_wba(Variable): + value_type = float + entity = Person + label = "Alabama UI unrounded weekly benefit amount" + unit = USD + definition_period = YEAR + reference = "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/" + defined_for = StateCode.AL + + def formula(person, period, parameters): + high_quarter_wages = person("al_ui_high_quarter_wages", period) + second_high_quarter_wages = person("al_ui_second_high_quarter_wages", period) + return (high_quarter_wages + second_high_quarter_wages) / WEEKS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_benefit_amount.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_benefit_amount.py new file mode 100644 index 00000000000..13605f24bae --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_benefit_amount.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class al_ui_weekly_benefit_amount(Variable): + value_type = float + entity = Person + label = "Alabama UI weekly benefit amount" + unit = USD + definition_period = YEAR + reference = "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/" + defined_for = StateCode.AL + + def formula(person, period, parameters): + p = parameters(period).gov.states.al.dol.unemployment_insurance.wba + unrounded_wba = person("al_ui_unrounded_wba", period) + monetarily_eligible = person("al_ui_monetarily_eligible", period) + # NOTE: § 25-4-72(b)(1) half-down rounding: ties at $0.50 round DOWN. + # np.ceil(x - 0.5): 100.50 -> 100, 100.51 -> 101. + rounded_wba = np.ceil(unrounded_wba - 0.5) + return min_(rounded_wba, p.max) * monetarily_eligible diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_earnings.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_earnings.py new file mode 100644 index 00000000000..52ee885e676 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_earnings.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class al_ui_weekly_earnings(Variable): + value_type = float + entity = Person + label = "Alabama UI gross weekly earnings during a partial unemployment week" + unit = USD + definition_period = YEAR + default_value = 0 + reference = ( + "https://admincode.legislature.state.al.us/administrative-code/480-4-3-.11", + "https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=20", + ) + defined_for = StateCode.AL diff --git a/policyengine_us/variables/gov/states/unemployment_compensation.py b/policyengine_us/variables/gov/states/unemployment_compensation.py index 0857864ab17..d04dad71b99 100644 --- a/policyengine_us/variables/gov/states/unemployment_compensation.py +++ b/policyengine_us/variables/gov/states/unemployment_compensation.py @@ -9,3 +9,4 @@ class unemployment_compensation(Variable): documentation = "Income from unemployment compensation programs." definition_period = YEAR uprating = "calibration.gov.irs.soi.unemployment_compensation" + adds = ["al_ui"] From 6ea71000fe2031a7de0c0a5f72568d15fb4c68f9 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Wed, 13 May 2026 18:05:33 -0400 Subject: [PATCH 3/9] Add changelog fragment for al_ui --- changelog.d/al-ui.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/al-ui.added.md diff --git a/changelog.d/al-ui.added.md b/changelog.d/al-ui.added.md new file mode 100644 index 00000000000..d63188bac85 --- /dev/null +++ b/changelog.d/al-ui.added.md @@ -0,0 +1 @@ +Implement Alabama Unemployment Insurance (al_ui). From d359eccc71d5c393c8483fd5528eff0c9b98deab Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Thu, 14 May 2026 08:57:39 -0400 Subject: [PATCH 4/9] Review-fix round 1: address critical issues from /review-program MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - al_ui.py: fix partial-benefit fallback (was returning full WBA when weekly_earnings >= WBA; should return 0 per "deemed employed" rule in § 25-4-72 / USDOL Tbl 3-8). Now uses al_ui_partial_weekly_benefit directly, which already handles this case correctly. - Fix 5 BRR PDF page anchors (printed page vs file page; off by 3): eligibility/{bpw_to_hqw_multiplier,quarters_with_wages}.yaml, wba/{max,min_threshold}.yaml, waiting_weeks.yaml. - Replace Ala. Admin. Code 480-4-3-.11 citation with statute § 25-4-73 for the 1/3 WBA partial disregard (480-4-3-.11 is procedural). - Add al_ui entry to programs.yaml registry. - Add test case for weekly_earnings >= WBA returning 0 (locks in the al_ui.py fix). - Fix two parameter description verbs ("requires" → "sets"). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../eligibility/bpw_to_hqw_multiplier.yaml | 4 +-- .../eligibility/quarters_with_wages.yaml | 4 +-- .../partial/disregard_rate.yaml | 8 ++--- .../unemployment_insurance/waiting_weeks.yaml | 2 +- .../dol/unemployment_insurance/wba/max.yaml | 2 +- .../wba/min_threshold.yaml | 2 +- policyengine_us/programs.yaml | 11 +++++++ .../al/dol/unemployment_insurance/al_ui.yaml | 29 ++++++++++++++++++- .../al/dol/unemployment_insurance/al_ui.py | 5 +--- .../al_ui_partial_weekly_benefit.py | 2 +- .../al_ui_weekly_earnings.py | 2 +- 11 files changed, 52 insertions(+), 19 deletions(-) diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml index 3ea012cd1e1..372f9c78a0b 100644 --- a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml @@ -1,4 +1,4 @@ -description: Alabama requires total base period wages to be at least this multiple of high quarter wages under the Unemployment Compensation program. +description: Alabama sets this multiple of high quarter wages as the minimum total base period wages required under the Unemployment Compensation program. values: 2020-01-01: 1.5 @@ -11,6 +11,6 @@ metadata: - title: Code of Alabama § 25-4-77(a)(4)(a) href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/ - title: Alabama UC Benefit Rights and Responsibilities Handbook - href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=5 + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=8 - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-2 href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml index b61770e8731..e344d47d9eb 100644 --- a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml @@ -1,4 +1,4 @@ -description: Alabama requires wages to have been paid in at least this many calendar quarters of the base period under the Unemployment Compensation program. +description: Alabama sets this minimum number of calendar quarters of the base period in which wages must have been paid under the Unemployment Compensation program. values: 2020-01-01: 2 @@ -11,6 +11,6 @@ metadata: - title: Code of Alabama § 25-4-77(a)(4) href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/ - title: Alabama UC Benefit Rights and Responsibilities Handbook - href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=5 + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=8 - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-2 href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml index 7b82f7bb7d0..20d3cfa4864 100644 --- a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml @@ -1,17 +1,15 @@ description: Alabama excludes this share of the weekly benefit amount from countable earnings before reducing partial unemployment benefits under the Unemployment Compensation program. values: - 2020-01-01: 0.3333333333 # One-third of WBA per § 25-4-72; Admin Code 480-4-3-.11 + 2020-01-01: 0.3333333333 # One-third of WBA per § 25-4-73 (amended 2015). metadata: unit: /1 period: year label: Alabama UI partial unemployment earnings disregard rate reference: - - title: Code of Alabama § 25-4-72 - href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ - - title: Ala. Admin. Code 480-4-3-.11 — Claims for Partial Unemployment - href: https://admincode.legislature.state.al.us/administrative-code/480-4-3-.11 + - title: Code of Alabama § 25-4-73 + href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-73/ - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-8 href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=20 - title: NELP — New Alabama Unemployment Insurance Law Makes Work Pay diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/waiting_weeks.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/waiting_weeks.yaml index 1a98091cc08..6dc5b98abec 100644 --- a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/waiting_weeks.yaml +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/waiting_weeks.yaml @@ -9,7 +9,7 @@ metadata: label: Alabama UI waiting weeks reference: - title: Alabama UC Benefit Rights and Responsibilities Handbook - href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=6 + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=9 - title: Ala. Admin. Code Chapter 480-4-3 — Benefits href: https://admincode.legislature.state.al.us/administrative-code/480-4-3 - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-7 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/max.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/max.yaml index 532dbb937c6..712a669cb7d 100644 --- a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/max.yaml +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/max.yaml @@ -13,4 +13,4 @@ metadata: - title: Alabama Act 2019-204 summary href: https://alabamaretail.org/news/unemployment-comp-weeks-weekly-benefits/ - title: Alabama UC Benefit Rights and Responsibilities Handbook - href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=5 + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/min_threshold.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/min_threshold.yaml index cfd36bb49d8..51a634a7930 100644 --- a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/min_threshold.yaml +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/wba/min_threshold.yaml @@ -11,4 +11,4 @@ metadata: - title: Code of Alabama § 25-4-72(b)(2) href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-72/ - title: Alabama UC Benefit Rights and Responsibilities Handbook - href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=5 + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=8 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 76b5377b5f3..01164205e7e 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -906,6 +906,17 @@ programs: verified_start_year: 2023 # --- State programs --- + - id: al_ui + name: Alabama UI + full_name: Alabama Unemployment Compensation + category: Benefits + agency: Alabama Department of Workforce + status: complete + coverage: AL + variable: al_ui + parameter_prefix: gov.states.al.dol.unemployment_insurance + verified_years: "2020-2025" + - id: co_oap name: Colorado OAP full_name: Colorado Old Age Pension diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml index 8800fa1eec2..e90f2233025 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml @@ -218,7 +218,34 @@ al_ui_monetarily_eligible: false al_ui: 0 -- name: Case 9, negative weekly earnings (defensive) - full WBA paid each week. +- name: Case 9, weekly earnings >= WBA - deemed employed, $0 annual benefit. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + al_ui_high_quarter_wages: 10_000 + al_ui_second_high_quarter_wages: 10_000 + al_ui_base_period_wages: 40_000 + al_ui_quarters_with_wages: 4 + # Weekly earnings >= WBA -> "deemed employed" per § 25-4-72 / USDOL Tbl 3-8. + al_ui_weekly_earnings: 300 + weeks_unemployed: 11 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_weekly_benefit_amount: 275 + # earnings (300) >= WBA (275) -> partial weekly benefit = 0. + al_ui_partial_weekly_benefit: 0 + # Annual benefit = 0 (no compensable weeks). + al_ui: 0 + +- name: Case 10, negative weekly earnings (defensive) - full WBA paid each week. period: 2024 absolute_error_margin: 0.01 input: diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py index 47ce567e2c6..69a259d34b9 100644 --- a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py @@ -16,12 +16,9 @@ class al_ui(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.al.dol.unemployment_insurance - wba = person("al_ui_weekly_benefit_amount", period) partial_weekly_benefit = person("al_ui_partial_weekly_benefit", period) - weekly_earnings = person("al_ui_weekly_earnings", period) max_weeks = person("al_ui_max_weeks", period) mba = person("al_ui_maximum_benefit_amount", period) weeks_unemployed = person("weeks_unemployed", period) weeks_paid = clip(weeks_unemployed - p.waiting_weeks, 0, max_weeks) - weekly_amount = where(weekly_earnings < wba, partial_weekly_benefit, wba) - return min_(weeks_paid * weekly_amount, mba) + return min_(weeks_paid * partial_weekly_benefit, mba) diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py index 93cb986f235..49259519081 100644 --- a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py @@ -8,7 +8,7 @@ class al_ui_partial_weekly_benefit(Variable): unit = USD definition_period = YEAR reference = ( - "https://admincode.legislature.state.al.us/administrative-code/480-4-3-.11", + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-73/", "https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=20", "https://www.nelp.org/new-alabama-unemployment-insurance-law-makes-work-pay/", ) diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_earnings.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_earnings.py index 52ee885e676..de12d817afb 100644 --- a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_earnings.py +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_weekly_earnings.py @@ -9,7 +9,7 @@ class al_ui_weekly_earnings(Variable): definition_period = YEAR default_value = 0 reference = ( - "https://admincode.legislature.state.al.us/administrative-code/480-4-3-.11", + "https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-73/", "https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=20", ) defined_for = StateCode.AL From b238e62d646c9363e80b9bfba2c26ea9b26f96fd Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Thu, 14 May 2026 09:12:13 -0400 Subject: [PATCH 5/9] Review-fix round 2: tighten BRR page anchor for quarters_with_wages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 2 review identified that the "two quarters of your base period" sentence is on file page 7 of the BRR handbook (printed page 4), not page 8. Page 8 has the related "two highest base period quarters" phrasing, which supports the value but is less direct. Tightening the anchor for precision. Round 2 found 0 critical issues otherwise — all Round 1 fixes verified correct, no regressions. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../unemployment_insurance/eligibility/quarters_with_wages.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml index e344d47d9eb..f03c67c989d 100644 --- a/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml @@ -11,6 +11,6 @@ metadata: - title: Code of Alabama § 25-4-77(a)(4) href: https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-77/ - title: Alabama UC Benefit Rights and Responsibilities Handbook - href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=8 + href: https://labor.alabama.gov/docs/guides/uc_brr.pdf#page=7 - title: U.S. DOL Comparison of State UI Laws 2023, Table 3-2 href: https://oui.doleta.gov/unemploy/pdf/uilawcompar/2023/monetary.pdf#page=5 From e38427927314c7d9eec26fca81692717107a16d7 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Thu, 14 May 2026 09:17:17 -0400 Subject: [PATCH 6/9] Add lessons from Alabama UI implementation --- lessons/agent-lessons.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 lessons/agent-lessons.md diff --git a/lessons/agent-lessons.md b/lessons/agent-lessons.md new file mode 100644 index 00000000000..25ae07fefce --- /dev/null +++ b/lessons/agent-lessons.md @@ -0,0 +1,37 @@ +# Agent Lessons Learned + +Accumulated from /encode-policy-v2 and /backdate-program runs across all contributors. +Loaded by implementation agents on future runs. + +## New Lessons from Alabama UI Implementation (2026-05-14) + +### PARAMETER +- Restrict parameter descriptions to the authorized verb set (limits/provides/sets/excludes/deducts/uses); reject synonyms like "requires" even when grammatically correct. +- Drop explanatory tails from parameter descriptions after the canonical "X provides this Y under the Z program." form; trailing "to determine..." or "based on..." clauses are noise. +- Strip trailing zeros from decimal parameter values (e.g., 44.50 to 44.5) so the YAML matches the canonical numeric form. +- Include the program acronym in parameter labels (e.g., "Alabama UI state unemployment rate" not "Alabama state unemployment rate") so labels are unambiguous when multiple programs share a state. + +### VARIABLE +- Remove inline statute-restatement comments when the class-level `reference` already cites the same authority; keep inline comments only for non-obvious idioms (e.g., half-down rounding) or non-obvious parameter timing. +- Replace hard-coded weeks-per-year integers (52) with framework constants like `WEEKS_IN_YEAR`; the same rule applies to other calendar constants. +- Inline single-use intermediates (e.g., `uncapped_mba = ...; return np.round(uncapped_mba)`) into the return expression when the intermediate name adds no clarity over the operations. + +### FORMULA +- Do not wrap a partial-benefit calculation that already returns 0 at the unemployed boundary in a redundant `where(earnings < WBA, partial, WBA)` selector; the wrapper silently pays full WBA when earnings meet or exceed WBA. +- Test the partial-benefit boundary explicitly at earnings = WBA and earnings > WBA expecting 0; passing tests on earnings < WBA do not exercise the formula's exit branch. + +### REFERENCE +- A procedural Admin Code section (filing mechanics, deadlines, recordkeeping) cannot serve as the citation authority for a substantive amount or rate; cite the substantive statute or rule that establishes the value. +- When an Admin Code rule has not been republished since a later statutory amendment changed the value, follow the statute and cite it; document the staleness so future contributors do not "fix" the model back to the obsolete admin-code figure. +- USDOL Comparison-of-State-UI-Laws "Minimum Wages Needed to Qualify" columns are derived illustrative figures (assumes equal-quarter wage pattern), not statutory floors; do not encode them as parameters. + +### TEST +- Reform-helper Python modules that set parameter-driven enum or bracket values for YAML tests belong alongside the YAML tests in the test directory; document the unusual placement in a header comment so future readers do not move them. +- After fixing a formula bug discovered post-implementation, add the missing boundary test in the same commit so the regression is locked in immediately. + +### WORKFLOW +- When the reference implementation for a new program lives in a sibling PR not yet merged, read it from the parent repo path rather than expecting it on the current worktree base; coordinate this in the implementation spec. +- Clean `__pycache__` directories between worktrees that share a parent path to avoid `conftest.py` collisions that intermittently block test collection. +- When two reviewers conflict on statute-vs-admin-code authority, resolve via the more recent enactment date and the substantive-vs-procedural distinction; record the rationale in the PR so the next contributor inherits the decision. +- Honor user-driven historical-scope simplifications (e.g., "2020 onward only") by collapsing pre-scope dated entries and trimming reference research to the in-scope window; do not silently encode out-of-scope history. + From 688372b4cf855c463241aa530ddc9803527c857b Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Thu, 14 May 2026 10:53:16 -0400 Subject: [PATCH 7/9] Round Alabama UI partial weekly benefits --- .../al_ui_partial_weekly_benefit.yaml | 14 +++++++------- .../al/dol/unemployment_insurance/integration.yaml | 9 +++++---- .../al_ui_partial_weekly_benefit.py | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.yaml index a0c15a3e7eb..95b4c7b571e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.yaml @@ -1,7 +1,7 @@ # Tests for Alabama UI partial weekly benefit (§ 25-4-72; Admin Code 480-4-3-.11). # Formula: partial_weekly_benefit = max(0, WBA - max(0, weekly_earnings - WBA/3)) # = WBA if weekly_earnings <= WBA/3 -# = WBA - (weekly_earnings - WBA/3) if WBA/3 < weekly_earnings < WBA +# = rounded(WBA - (weekly_earnings - WBA/3)) if WBA/3 < weekly_earnings < WBA # = 0 if weekly_earnings >= WBA # Equivalently: a week is "partial" if earnings < WBA; the first 1/3 x WBA of # earnings is disregarded, and earnings above the disregard reduce the WBA @@ -102,8 +102,8 @@ people: person1: al_ui_weekly_benefit_amount: 275 - # WBA - (earnings - WBA/3) = 275 - (150 - 91.67) = 275 - 58.33 = 216.67. - al_ui_partial_weekly_benefit: 216.67 + # WBA - (earnings - WBA/3) = 275 - (150 - 91.67) = 216.67 -> rounded to $217. + al_ui_partial_weekly_benefit: 217 - name: Case 5, earnings just below WBA - very small partial benefit. period: 2024 @@ -126,8 +126,8 @@ people: person1: al_ui_weekly_benefit_amount: 275 - # 275 - (270 - 91.67) = 275 - 178.33 = 96.67. - al_ui_partial_weekly_benefit: 96.67 + # 275 - (270 - 91.67) = 96.67 -> rounded to $97. + al_ui_partial_weekly_benefit: 97 - name: Case 6, earnings exactly at WBA - no partial benefit. period: 2024 @@ -248,5 +248,5 @@ people: person1: al_ui_weekly_benefit_amount: 275 - # WBA/3 = 91.6667; 275 - (274.99 - 91.6667) = 275 - 183.3233 = 91.6767. - al_ui_partial_weekly_benefit: 91.68 + # WBA/3 = 91.6667; 275 - (274.99 - 91.6667) = 91.6767 -> rounded to $92. + al_ui_partial_weekly_benefit: 92 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml index a663250c799..e198344ed7f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml @@ -45,6 +45,7 @@ # payable_weeks = min(max(0, 20 - 1), 14) = 14. # Annual = 275 * 14 = 3,850 (matches MBA). al_ui: 3_850 + unemployment_compensation: 3_850 - name: Case 2, minimum-benefit claimant - unrounded WBA just above $44.50. period: 2024 @@ -160,11 +161,11 @@ al_ui_max_weeks: 14 # MBA = min(14 * 275, 0.25 * 28,600) = min(3,850, 7,150) = 3,850. al_ui_maximum_benefit_amount: 3_850 - # Partial weekly benefit = 275 - (150 - 91.67) = 216.67. - al_ui_partial_weekly_benefit: 216.67 + # Partial weekly benefit = 275 - (150 - 91.67) = 216.67, rounded to $217. + al_ui_partial_weekly_benefit: 217 # payable_weeks = min(max(0, 15 - 1), 14) = 14. - # Annual = 216.67 * 14 = 3,033.33 (below MBA 3,850). - al_ui: 3_033.33 + # Annual = 217 * 14 = 3,038 (below MBA 3,850). + al_ui: 3_038 - name: Case 6, MBA capped by BPW/4 - low-wage but eligible long-claim. period: 2024 diff --git a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py index 49259519081..65ef23f2a01 100644 --- a/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui_partial_weekly_benefit.py @@ -21,4 +21,4 @@ def formula(person, period, parameters): disregard = wba * p.disregard_rate countable_earnings = max_(weekly_earnings - disregard, 0) partial_amount = max_(wba - countable_earnings, 0) - return where(weekly_earnings < wba, partial_amount, 0) + return where(weekly_earnings < wba, np.round(partial_amount), 0) From 3603feae3edab83717b7863a56a249c7731ddd8a Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Thu, 14 May 2026 13:10:02 -0400 Subject: [PATCH 8/9] Trim Alabama UI max weeks tests --- .../al_ui_max_weeks.yaml | 148 ++---------------- .../set_state_unemployment_rate.py | 60 ------- 2 files changed, 12 insertions(+), 196 deletions(-) delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/set_state_unemployment_rate.py diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml index a52e8f06156..09401657ad2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml @@ -1,26 +1,11 @@ -# Tests for Alabama UI maximum benefit weeks (§ 25-4-74(a)). -# The duration is set by a single_amount bracket on the statewide average -# unemployment rate (AAU): -# ≤6.5% -> 14 weeks -# >6.5%-≤7.0% -> 15 -# >7.0%-≤7.5% -> 16 -# >7.5%-≤8.0% -> 17 -# >8.0%-≤8.5% -> 18 -# >8.5%-≤9.0% -> 19 -# ≥9.5% -> 20 -# The bracket is encoded with thresholds at 0, 0.0651, 0.0701, 0.0751, 0.0801, -# 0.0851, 0.0901 -> 14/15/16/17/18/19/20 weeks. Each non-baseline rate is -# pinned via a reform in `set_state_unemployment_rate.py`. The natural -# baseline (2024 FRED ALUR ~ 2.9%) hits bracket 1 and is exercised in -# integration.yaml. -# -# Reference: -# https://law.justia.com/codes/alabama/title-25/chapter-4/article-4/section-25-4-74/ +# Tests for Alabama UI maximum benefit weeks (Code of Alabama § 25-4-74(a)). +# Bracket behavior is covered at the statutory floor, first increment, pre-cap, +# and cap. Other benefit-flow tests exercise the natural 2024 ALUR baseline. -- name: Case 1, UR at 6.5% (boundary of first bracket) - 14 weeks. +- name: Case 1, UR at 6.5 percent gets 14 weeks. period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_6_5pct input: + gov.states.al.dol.unemployment_insurance.state_unemployment_rate: 0.065 people: person1: age: 30 @@ -31,13 +16,12 @@ output: people: person1: - # 0.065 < 0.0651 -> first bracket -> 14 weeks (statute: ≤ 6.5%). al_ui_max_weeks: 14 -- name: Case 2, UR at 7.0% - 15 weeks (top of second bracket). +- name: Case 2, UR just above 6.5 percent gets 15 weeks. period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_7pct input: + gov.states.al.dol.unemployment_insurance.state_unemployment_rate: 0.0651 people: person1: age: 30 @@ -48,13 +32,12 @@ output: people: person1: - # 0.0651 <= 0.07 < 0.0701 -> bracket 2 -> 15 weeks (statute: ≤ 7.0%). al_ui_max_weeks: 15 -- name: Case 3, UR at 7.5% - 16 weeks (top of third bracket). +- name: Case 3, UR at 9.0 percent gets 19 weeks. period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_7_5pct input: + gov.states.al.dol.unemployment_insurance.state_unemployment_rate: 0.09 people: person1: age: 30 @@ -65,64 +48,12 @@ output: people: person1: - # 0.0701 <= 0.075 < 0.0751 -> bracket 3 -> 16 weeks. - al_ui_max_weeks: 16 - -- name: Case 4, UR at 8.0% - 17 weeks. - period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_8pct - input: - people: - person1: - age: 30 - households: - household: - members: [person1] - state_code: AL - output: - people: - person1: - # 0.0751 <= 0.08 < 0.0801 -> bracket 4 -> 17 weeks. - al_ui_max_weeks: 17 - -- name: Case 5, UR at 8.5% - 18 weeks. - period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_8_5pct - input: - people: - person1: - age: 30 - households: - household: - members: [person1] - state_code: AL - output: - people: - person1: - # 0.0801 <= 0.085 < 0.0851 -> bracket 5 -> 18 weeks. - al_ui_max_weeks: 18 - -- name: Case 6, UR at 9.0% - 19 weeks. - period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_9pct - input: - people: - person1: - age: 30 - households: - household: - members: [person1] - state_code: AL - output: - people: - person1: - # 0.0851 <= 0.09 < 0.0901 -> bracket 6 -> 19 weeks. al_ui_max_weeks: 19 -- name: Case 7, UR at 9.5% - 20 weeks (cap). +- name: Case 4, UR at 9.5 percent reaches the 20-week cap. period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_9_5pct input: + gov.states.al.dol.unemployment_insurance.state_unemployment_rate: 0.095 people: person1: age: 30 @@ -133,13 +64,10 @@ output: people: person1: - # 0.095 >= 0.0901 -> bracket 7 -> 20 weeks (statute caps at 20 weeks - # for AAU >= 9.5%). al_ui_max_weeks: 20 -- name: Case 8, UR at 12% (well above 9.5%) - still 20 weeks (capped). +- name: Case 5, natural 2024 ALUR baseline gets 14 weeks. period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_12pct input: people: person1: @@ -151,56 +79,4 @@ output: people: person1: - # Highest bracket: 20 weeks (cap). - al_ui_max_weeks: 20 - -- name: Case 9, natural FRED UR for 2024 (~2.9%) - 14 weeks without reform. - period: 2024 - input: - people: - person1: - age: 30 - households: - household: - members: [person1] - state_code: AL - output: - people: - person1: - # 2024-01 UR = 0.029 from FRED ALUR -> first bracket -> 14 weeks. al_ui_max_weeks: 14 - -- name: Case 10, UR at 6.51% (just above 6.5% boundary) - 15 weeks. - period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_6_51pct - input: - people: - person1: - age: 30 - households: - household: - members: [person1] - state_code: AL - output: - people: - person1: - # 0.0651 hits the second bracket threshold exactly -> 15 weeks. - # Confirms "above 6.5%" semantics: 0.065 -> 14, 0.0651 -> 15. - al_ui_max_weeks: 15 - -- name: Case 11, UR at 15% (extreme high) - still 20 weeks (statute cap). - period: 2024 - reforms: policyengine_us.tests.policy.baseline.gov.states.al.dol.unemployment_insurance.set_state_unemployment_rate.set_state_unemployment_rate_to_15pct - input: - people: - person1: - age: 30 - households: - household: - members: [person1] - state_code: AL - output: - people: - person1: - # 0.15 well above 0.0901 -> bracket 7 (cap) -> 20 weeks. - al_ui_max_weeks: 20 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/set_state_unemployment_rate.py b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/set_state_unemployment_rate.py deleted file mode 100644 index 17f29e51c01..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/set_state_unemployment_rate.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Test reforms that override the Alabama state unemployment rate parameter. - -These reforms exist solely to drive `al_ui_max_weeks.yaml` boundary tests -through each rung of the duration-by-unemployment-rate bracket in -§ 25-4-74(a). Each reform pins the `state_unemployment_rate` parameter to a -fixed value so the bracket lookup deterministically returns the expected -number of weeks regardless of the FRED ALUR time series. -""" - -from policyengine_core.model_api import * -from policyengine_core.periods import instant - - -def _make_reform(value): - """Return a Reform subclass that pins state_unemployment_rate to ``value``.""" - - def modify_parameters(parameters): - parameters.gov.states.al.dol.unemployment_insurance.state_unemployment_rate.update( - start=instant("2020-01-01"), - stop=instant("2030-12-31"), - value=value, - ) - return parameters - - class _R(Reform): - def apply(self): - self.modify_parameters(modify_parameters) - - return _R - - -# Exactly 6.5% — boundary of the first bracket; expect 14 weeks. -set_state_unemployment_rate_to_6_5pct = _make_reform(0.065) - -# 7.0% — boundary of the second bracket; expect 15 weeks. -set_state_unemployment_rate_to_7pct = _make_reform(0.07) - -# 7.5% — boundary of the third bracket; expect 16 weeks. -set_state_unemployment_rate_to_7_5pct = _make_reform(0.075) - -# 8.0% — boundary of the fourth bracket; expect 17 weeks. -set_state_unemployment_rate_to_8pct = _make_reform(0.08) - -# 8.5% — boundary of the fifth bracket; expect 18 weeks. -set_state_unemployment_rate_to_8_5pct = _make_reform(0.085) - -# 9.0% — boundary of the sixth bracket; expect 19 weeks. -set_state_unemployment_rate_to_9pct = _make_reform(0.09) - -# 9.5% — boundary of the seventh (cap) bracket; expect 20 weeks. -set_state_unemployment_rate_to_9_5pct = _make_reform(0.095) - -# 12% — well above the cap; expect 20 weeks. -set_state_unemployment_rate_to_12pct = _make_reform(0.12) - -# 6.51% — just above the 6.5% boundary; expect 15 weeks (statute: > 6.5%). -set_state_unemployment_rate_to_6_51pct = _make_reform(0.0651) - -# 15% — very high (well above 9.5% cap); expect 20 weeks. -set_state_unemployment_rate_to_15pct = _make_reform(0.15) From 413d129386a655f7febff9f68f6d06cd7d85db92 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Thu, 14 May 2026 14:42:59 -0400 Subject: [PATCH 9/9] Avoid backdated Alabama UI aggregation --- .../states/al/dol/unemployment_insurance/integration.yaml | 8 ++------ .../variables/gov/states/unemployment_compensation.py | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml index e198344ed7f..da0a6a802d5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml @@ -45,7 +45,6 @@ # payable_weeks = min(max(0, 20 - 1), 14) = 14. # Annual = 275 * 14 = 3,850 (matches MBA). al_ui: 3_850 - unemployment_compensation: 3_850 - name: Case 2, minimum-benefit claimant - unrounded WBA just above $44.50. period: 2024 @@ -311,7 +310,7 @@ # Non-AL state -> defined_for filter applies -> al_ui = 0. al_ui: 0 -- name: Case 11, federal aggregator includes al_ui correctly. +- name: Case 11, waiting week reduces a 14-week claim. period: 2024 absolute_error_margin: 0.01 input: @@ -331,8 +330,5 @@ output: people: person1: - # al_ui = 275 * 13 = 3,575; the federal aggregator - # unemployment_compensation adds al_ui (and ny_ui in the upstream - # version) so the federal total should equal al_ui here. + # al_ui = 275 * max(0, 14 - 1) = 3,575. al_ui: 3_575 - unemployment_compensation: 3_575 diff --git a/policyengine_us/variables/gov/states/unemployment_compensation.py b/policyengine_us/variables/gov/states/unemployment_compensation.py index d04dad71b99..0857864ab17 100644 --- a/policyengine_us/variables/gov/states/unemployment_compensation.py +++ b/policyengine_us/variables/gov/states/unemployment_compensation.py @@ -9,4 +9,3 @@ class unemployment_compensation(Variable): documentation = "Income from unemployment compensation programs." definition_period = YEAR uprating = "calibration.gov.irs.soi.unemployment_compensation" - adds = ["al_ui"]