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). 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. + 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..372f9c78a0b --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/bpw_to_hqw_multiplier.yaml @@ -0,0 +1,16 @@ +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 + +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=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 new file mode 100644 index 00000000000..f03c67c989d --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/eligibility/quarters_with_wages.yaml @@ -0,0 +1,16 @@ +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 + +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=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 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..20d3cfa4864 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/dol/unemployment_insurance/partial/disregard_rate.yaml @@ -0,0 +1,16 @@ +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-73 (amended 2015). + +metadata: + unit: /1 + period: year + label: Alabama UI partial unemployment earnings disregard rate + reference: + - 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 + 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..6dc5b98abec --- /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=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 + 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..712a669cb7d --- /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=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 new file mode 100644 index 00000000000..51a634a7930 --- /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=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 new file mode 100644 index 00000000000..e90f2233025 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui.yaml @@ -0,0 +1,273 @@ +# 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, 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: + 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..09401657ad2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/al_ui_max_weeks.yaml @@ -0,0 +1,82 @@ +# 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 percent gets 14 weeks. + period: 2024 + input: + gov.states.al.dol.unemployment_insurance.state_unemployment_rate: 0.065 + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 + +- name: Case 2, UR just above 6.5 percent gets 15 weeks. + period: 2024 + input: + gov.states.al.dol.unemployment_insurance.state_unemployment_rate: 0.0651 + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 15 + +- name: Case 3, UR at 9.0 percent gets 19 weeks. + period: 2024 + input: + gov.states.al.dol.unemployment_insurance.state_unemployment_rate: 0.09 + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 19 + +- name: Case 4, UR at 9.5 percent reaches the 20-week cap. + period: 2024 + input: + gov.states.al.dol.unemployment_insurance.state_unemployment_rate: 0.095 + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 20 + +- name: Case 5, natural 2024 ALUR baseline gets 14 weeks. + period: 2024 + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: AL + output: + people: + person1: + al_ui_max_weeks: 14 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..95b4c7b571e --- /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 +# = 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 +# 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) = 216.67 -> rounded to $217. + al_ui_partial_weekly_benefit: 217 + +- 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) = 96.67 -> rounded to $97. + al_ui_partial_weekly_benefit: 97 + +- 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) = 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/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..da0a6a802d5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/dol/unemployment_insurance/integration.yaml @@ -0,0 +1,334 @@ +# 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, rounded to $217. + al_ui_partial_weekly_benefit: 217 + # payable_weeks = min(max(0, 15 - 1), 14) = 14. + # 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 + 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, waiting week reduces a 14-week claim. + 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 * max(0, 14 - 1) = 3,575. + al_ui: 3_575 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..69a259d34b9 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/dol/unemployment_insurance/al_ui.py @@ -0,0 +1,24 @@ +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 + partial_weekly_benefit = person("al_ui_partial_weekly_benefit", 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) + return min_(weeks_paid * partial_weekly_benefit, 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..65ef23f2a01 --- /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://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/", + ) + 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, np.round(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..de12d817afb --- /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://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