Skip to content

Compare the Wisconsin Works CMC infant age limit with the child's age in months - #10259

Open
PavelMakarchuk wants to merge 2 commits into
mainfrom
fix-9815-wi-works-cmc-age
Open

PavelMakarchuk wants to merge 2 commits into
mainfrom
fix-9815-wi-works-cmc-age

Conversation

@PavelMakarchuk

Copy link
Copy Markdown
Collaborator

Summary

wi_works_placement gave the Custodial Parent of an Infant (CMC) placement when a member's monthly_age was at or below cmc_infant_age_limit (2, unit: month). monthly_age is the age in years evaluated for a month, so any child aged 0, 1 or 2 years gave the unit the CMC placement and the $673 grant instead of the $653 Community Service Job (CSJ) grant.

Wis. Stat. § 49.148(1m)(a)1 gives the CMC grant to "a custodial parent of a child 8 weeks old or less", and W-2 Manual 7.4.3 ends the placement 8 weeks (56 days) after the birth. The parameter already approximates 8 weeks as 2 months.

The formula now compares the limit with age * MONTHS_IN_YEAR, the pattern of the state child care age-group variables (AK, KS, KY, AL, DC). With an age in whole years, age 0 still gets CMC (the model cannot tell an infant of 8 weeks or less from an older infant).

Reproduction from the issue (January 2025): child aged 1 or 2 now gives CSJ and $653 (was CMC and $673); age 0 stays CMC and $673; age 3 stays CSJ and $653.

Tests

  • wi_works_placement.yaml: the cases now set age (in years, including 0.125 and 0.25) instead of setting monthly_age directly; new cases for a 3-month-old, a 1-year-old and a 2-year-old (CSJ).
  • integration.yaml: new Case 12, a 1-year-old gives CSJ and $653; corrected the age comments in Cases 2 and 9.
  • 4 new cases fail on main and pass here. policyengine-core test on states/wi/dcf/works: 54 passed.

Fixes #9815

🤖 Generated with Claude Code

wi_works_placement compared the 2-month CMC limit (8 weeks, Wis. Stat.
49.148(1m)(a)1) with monthly_age, which is the age in years. Children aged
1 and 2 got the CMC placement and the $673 grant instead of the $653 CSJ
grant. Use age x 12, as the state child care age-group variables do, and
set age in the placement tests instead of monthly_age.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PavelMakarchuk
PavelMakarchuk marked this pull request as ready for review October 23, 2026 17:03
@codecov

codecov Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (909176a) to head (460fb58).
⚠️ Report is 622 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10259   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         1    -3     
  Lines           76        27   -49     
  Branches         2         0    -2     
=========================================
- Hits            76        27   -49     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Compare the Wisconsin Works CMC infant age limit with the child's age in months

Summary

The PR fixes #9815. Before it, wi_works_placement gave the Custodial Parent of an Infant (CMC) placement when a member's monthly_age was at or below cmc_infant_age_limit (2, unit: month). But monthly_age holds the age in years:

  • age is a float with no unit, so core treats it as a flow.
  • Reading it for a month divides the annual value by 12, and monthly_age multiplies by 12 again.

So every child aged 0, 1 or 2 put the unit in CMC, with $673 instead of the $653 CSJ grant. The PR makes two changes:

  • Formula. It compares the limit with person("age", period.this_year) * MONTHS_IN_YEAR, the same pattern as the AK, KS, KY, AL and DC child care age-group variables.
  • Tests. The placement cases now set age instead of monthly_age.

The fix is right under current law:

  • The law. Wis. Stat. § 49.148(1m)(a)1 gives the $673 grant to "a custodial parent of a child 8 weeks old or less". W-2 Manual 7.4.3.4 ends the placement on the 56th day. The 8-week limit has applied since January 1, 2012 (2011 Wis. Act 32). Before that it was 12 weeks.
  • The comparison. <= matches "8 weeks old or less". A child of 56 days is 1.84 months old, which gives CMC.

Other checks:

  • Tests. I hand-checked 12 expectations, and all are correct. The formula is vectorized, and it adds no state-gated key variable.
  • CI. 29/29 checks pass.
  • Branch. It merges cleanly with main.

One thing needs fixing: the PR body has no axiom: line.

The suggestions are optional:

  • add test cases close to 8 weeks on each side;
  • encode the limit in weeks, with the 12-week limit before 2012, and fix the manual section in the reference title;
  • say in the code that age 0 always gives CMC when ages are whole years;
  • open a follow-up issue for the "other adult" exclusion in § 49.148(1m)(a)1 (pre-existing).

Critical (must fix)

None.

Should address

1. Add the axiom: line to the PR body.

This is a policy fix, because it changes the placement and the grant for children aged 1 and 2. The body has no line starting with axiom in any format, and no axiom text anywhere. Pavel is staff, so the line is required, and axiom: needed isn't available. See "Axiom line assessment" for the recommended queued line.

Suggestions

1. Test close to the 8-week boundary on each side.

The new cases test 0 months, 1.5 months (age: 0.125) and 3 months (age: 0.25). The old "exactly 2 months" case is gone, so the nearest cases are 0.5 and 1 month away from the limit.

An age of exactly 2 months is fragile, because age is stored as float32:

  • age: 0.16666667 × 12 gives 2.0, which is CMC.
  • age: 0.1666667 × 12 gives 2.0000005, which is CSJ.

So use ages a little way from the limit, for example:

- name: Case 4, CMC with an infant about 7.8 weeks old.
  period: 2025-01
  input:
    people:
      person1:
        age: 25
      person2:
        age: 0.15
    households:
      household1:
        members: [person1, person2]
        state_code: WI
  output:
    # 0.15 years x 12 = 1.8 months <= 2 (about 55 days, within 8 weeks)
    wi_works_placement: CMC

- name: Case 5, CSJ with an infant about 8.9 weeks old.
  period: 2025-01
  input:
    people:
      person1:
        age: 25
      person2:
        age: 0.17
    households:
      household1:
        members: [person1, person2]
        state_code: WI
  output:
    # 0.17 years x 12 = 2.04 months > 2 (about 62 days, past 8 weeks)
    wi_works_placement: CSJ

2. Encode the limit in weeks, with its history, and fix the reference title.

Before this PR the parameter never bound, because years were compared with 2. Now it decides the placement, so three pre-existing details in placement/cmc_infant_age_limit.yaml start to matter:

  • Effective date. The file has a single value, 2011-10-01: 2, which also carries back to earlier years.
    • 2011 Wis. Act 32, SECTION 1361s (file page 226), amended (1m)(a)1 to read "A custodial parent of a child 12 8 weeks old or less".
    • SECTION 9408 (1d) (file page 530), "REDUCTION IN CHILD'S AGE FOR CARETAKER OF NEWBORN GRANT", says: "The treatment of section 49.148 (1m) (a) 1. of the statutes takes effect on January 1, 2012."
    • The cut of the CSJ grant from $673 to $653 took effect earlier, on October 1, 2011 (SECTION 9408 (3i), same page).
    • So from October to December 2011, an infant with a fractional age of 61-84 days (about 9 to 12 weeks) now gets CSJ at $653, where the law gives CMC at $673. Before October 2011 both grants were $673, so the placement didn't change the amount.
  • Approximation. 2 months is about 60.9 days, but the placement ends on day 56 (Manual 7.4.3.4: "eight calendar weeks (7 days x 8 weeks = 56 days) ... CMC cannot extend beyond the date the child turns 8 weeks of age"). So ages of 57-60 days still give CMC. For example, age: 0.16 (about 58 days) gives 1.92 months, which is CMC.
  • Title. The second reference's title says "W-2 Manual 7.4.5", but its href and the page itself are 7.4.3.

Suggested parameter:

description: Wisconsin limits the Custodial Parent of an Infant placement to custodial parents of children at or below this age in weeks under the Wisconsin Works program.

values:
  1997-09-01: 12
  2012-01-01: 8

metadata:
  unit: week
  period: month
  label: Wisconsin Works CMC infant age limit
  reference:
    - title: Wisconsin Statutes 49.148(1m)(a)1
      href: https://docs.legis.wisconsin.gov/statutes/statutes/49/iii/148
    - title: "2011 Wisconsin Act 32, sections 1361s and 9408(1d) #page 226,530"
      href: https://docs.legis.wisconsin.gov/2011/related/acts/32.pdf#page=226
    - title: W-2 Manual 7.4.3 - Custodial Parent of an Infant (CMC)
      href: https://dcf.wisconsin.gov/manuals/w-2-manual/Production/07/7.4.3_Custodial_Parent_of_an_Infant_CMC_.htm

The 12 is the text that Act 32 struck. The 1997-09-01 start matches amount.yaml.

In the formula, use age_in_weeks = person("age", period.this_year) * WEEKS_IN_YEAR. WEEKS_IN_YEAR is 52 and is already exported by model_api. Some checks:

  • 56 days → 7.97 weeks, which gives CMC.
  • 57 days → 8.12 weeks, which gives CSJ.
  • 84 days → 11.96 weeks, which gives CMC under the 12-week rule.

Why this is only a suggestion:

  • Whole-year ages, which is all microdata and most household inputs, give the same result either way.
  • programs.yaml doesn't claim 2011 (TANF is verified from 2021).
  • The parameter file and the PR body both document the 2-month approximation.

3. Say in the code that age 0 always gives CMC.

The PR body says that with ages in whole years, age 0 still gets CMC. In the microdata every age-0 child is 0-11 months old, so every Wisconsin Works unit with one gets $673. Only about 8 of every 52 weeks of that year qualify. The code comment doesn't say this. Something like this would help:

# With age in whole years (as in the microdata), every child aged 0
# counts as 8 weeks old or less, so age 0 always gives CMC.

4. Open a follow-up issue for the "other adult" exclusion (pre-existing, outside the diff).

§ 49.148(1m)(a)1 continues: "unless another adult member of the custodial parent's Wisconsin Works group is participating in, or is eligible to participate in, a Wisconsin Works employment position or is employed in unsubsidized employment". Manual 7.4.3.1 adds: "If another adult in the W-2 Group is working in unsubsidized employment or is eligible for W-2 participation, regardless of the W-2 Group's income, the family is not eligible for CMC."

has_infant doesn't check for another adult. Take a two-parent unit like integration Case 8 (one parent earning $1,500), but with a newborn:

  • The head. It gives CMC and $673.
  • The manual. It treats the family as a two-parent family instead (the CSJ grant is $653).

The ARP branch is similar. Para. (a)2 requires "an unmarried woman ... not a custodial parent of a dependent child", but the formula checks only the pregnancy month. Neither gap is changed by this PR.

Verified correct

  • Root cause.
    • How core reads age for a month. age is value_type = float with no unit, so its quantity_type defaults to FLOW. Core's YEAR-to-MONTH branch (calculate_divide) returns the annual value / 12, and monthly_age multiplies by 12, so it returns years.
    • Corroboration. The issue's reproduction shows monthly_age = 1 and 2 for ages 1 and 2 in releases 1.811.2 and 2.25.2. nv_ccdp_age_group.py notes the same: "monthly_age ... returns age in YEARS".
    • Effect. The old formula compared years with a limit in months.
  • The fix. person("age", period.this_year) * MONTHS_IN_YEAR is the pattern in ak_ccap_age_group, ks_ccap_*_age_group, ky_ccap_child_age_category, al_ccsp_age_category and dc_ccsp_age_category, as the body says, and in many other state child care age-group variables.
  • The law and amounts.
    • Statute. § 49.148(1m)(a): "Any of the following may receive a monthly grant of $673: 1. A custodial parent of a child 8 weeks old or less ...". § 49.148(1)(b)1 gives the CSJ grant as "a monthly grant of $653".
    • Manual. W-2 Manual 7.4.3: "A custodial parent of an infant 8 weeks old or younger ... may receive a monthly CMC payment of $673."
    • LFB. LFB Informational Paper 47 (January 2023), file page 17: "a custodial parent of a child who is eight weeks old or younger is eligible to receive a monthly grant of $673". Its table on file page 12 lists "Community Service Job $653" and "Caretaker of Infant 673".
    • The model. amount.yaml has CSJ 653 (from 2011-10-01) and CMC 673.
  • Claimed years. The 8-week limit applies to every year from 2012, including all of TANF's verified years (2021 on). Wis. Stat. 49.148's history note lists only 2011 Acts 32 and 257 and 2013 Acts 20 and 165 since 2009, and the current text still says 8 weeks.
  • Boundary. <= matches "8 weeks old or less" and the manual's "cannot extend beyond the date the child turns 8 weeks of age". In float32, 56 days is 0.15332 years, which × 12 = 1.84 months, so it gives CMC. 61 days gives 2.004 months, which is CSJ (see Suggestion 2 for days 57-60).
  • Vectorization and code.
    • The age product is an array operation, followed by spm_unit.any and select, with no Python branching on values.
    • defined_for = StateCode.WI is unchanged.
    • No new int or enum key variable is added, and the placement default (CSJ) is a key in amount.yaml, so the NH Add New Hampshire state supplementation for 2026 #10156 pattern doesn't apply.
  • Callers.
    • Only wi_works reads wi_works_placement, through p.amount[placement]. monthly_age itself is unchanged.
    • Other monthly_age readers with month-based limits already convert: ME CCAP and WA WCCC multiply by 12, and NV CCDP uses this PR's pattern.
  • Hand-checked expectations (12):
    • wi_works_placement.yaml:
      • Case 1: child 5 → 60 months, not pregnant → CSJ.
      • Case 2: age 0 → 0 ≤ 2 → CMC.
      • Case 3: 0.125 × 12 = 1.5 ≤ 2 → CMC.
      • Case 4: 0.25 × 12 = 3 > 2 → CSJ.
      • Case 5: 1 × 12 = 12 → CSJ.
      • Case 6: 2 × 12 = 24 → CSJ.
      • Case 7: pregnancy month 7 ≥ 7 → ARP.
      • Case 8: month 6 < 7 → CSJ.
      • Case 9: infant aged 0 plus a pregnancy in month 8 → CMC first in select → CMC.
      • Without spm_units, core puts all members in one default SPM unit, so spm_unit.any sees the child.
    • integration.yaml:
      • Case 2 (age 0, $1,000 earnings, $500 cash): eligible, CMC, 673.
      • Case 9 (age 3): 36 months → CSJ, 653.
      • Case 12 (age 1, no income, $500 cash, below the $2,500 limit): eligible, 12 months → CSJ, 653 (CSJ from 2011-10-01).
    • "4 new cases fail on main". On main, monthly_age would be 0.25, 1, 2 and 1, all ≤ 2, giving CMC. So placement Cases 4, 5 and 6 and integration Case 12 fail on main. Cases 3 (0.125) and 2/9 (0) pass on both.
  • PR body.
    • The quotes from § 49.148(1m)(a)1 and the Manual 7.4.3 (56 days) are accurate.
    • "54 passed" was the folder's count at the first commit. The merge of main added 7 wi_works_eligible cases, so there are 61 at the head, and all pass in CI.
  • Changelog. changelog.d/fix-9815-wi-works-cmc-age.fixed.md is a top-level fragment with the fixed type, and its text matches the change.
  • Source-conflict ratchet (Tie source-conflict markers to the register shared with Axiom #10287).
    • New comment. The one new comment in variables/ ("The limit is in months; monthly_age is in years, so convert age.") matches neither CONFLICT_COMMENT_RE nor ATTEMPT_RE.
    • Not scanned. Test YAML isn't scanned.
    • Register. No WI Works file is in KNOWN_UNMARKED, the register or candidates.yaml.
    • Main's changes. Main's only change to the test since the merge base edits unrelated rows.
  • Partner tests. No files under tests/policy/baseline/partners/** are touched, and Household API Partners passes.

Axiom line assessment

The body has no axiom: line (Should 1). encoded-correct isn't available:

Recommended line. File a dispatch-ready pe-parity issue (modeled on rulespec-us#1920 or #1467) and use axiom: TheAxiomFoundation/rulespec-us#<n> queued. The issue should have:

  • Module. Provisionally us-wi/statutes/49/148.yaml, following us-ia/statutes/422/12.yaml, for § 49.148(1m)(a)1 (and (1)(b)1 for the CSJ grant if it is in scope).
  • Corpus citation and blocker. Ingest Wis. Stat. 49.148, 2011 Wis. Act 32 (SECTION 1361s and SECTION 9408 (1d), file pages 226 and 530) and W-2 Manual 7.4.3. Then get approval for the billed encoder run.
  • Law, verbatim. § 49.148(1m)(a)1 (including the "unless another adult member ..." clause), the Act 32 strike-through ("12 8 weeks") and its January 1, 2012 effective date, and Manual 7.4.3.4 ("7 days x 8 weeks = 56 days").
  • Required outputs:
    • wi_w2_cmc_eligible: custodial parent of a child at most 8 weeks old (12 weeks before January 1, 2012), with no other W-2 group adult working or W-2 eligible;
    • the monthly grant: $673 for CMC, against $653 for CSJ (statute (1)(b)1; LFB table).
  • Companion tests (from the statute, the manual and the LFB paper, not PolicyEngine output):
    • Manual 7.4.3.1 Example 4: a 5-week-old baby, and the other adult is on SSDI and not W-2 eligible → CMC, $673.
    • A child 56 days old → CMC. A child 57 days old → not CMC (Manual 7.4.3.4).
    • Manual 7.4.3.1 Example 2: a 3-week-old baby, and the spouse works in unsubsidized employment → not CMC.
    • A 10-week-old in November 2011 → CMC under the 12-week rule. A 10-week-old in February 2012 → not CMC.
    • The CSJ grant is $653 and the CMC grant is $673 (LFB Paper 47, file page 12).

CI status

All 29 checks pass at 460fb5859e (gh pr checks 10259). The workflow run on that SHA was created 2026-10-23 18:44 UTC, after #10287 and #10334. That includes:

  • the Baseline states shards 1-4;
  • the Contrib shards;
  • Household API Partners;
  • PR validation;
  • Microsimulation;
  • the Python Rest shards;
  • Quick Feedback;
  • codecov.

None is pending or failing.

Branch status

The branch is 2 commits ahead of main (the fix on 2026-10-16 and a merge of main on 2026-10-23) and 48 behind. git merge-tree --write-tree PolicyEngine/main 460fb5859e merges cleanly. Since the merge base, main hasn't changed any of these:

  • Wisconsin Works variables, parameters or tests;
  • age or monthly_age.

No other open PR touches wi/dcf/works or monthly_age.

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is right: monthly_age is in years, so 1- and 2-year-olds wrongly got CMC $673, and comparing age x 12 against the 2-month limit matches 49.148(1m)(a)1 ('8 weeks old or less') from 2012. All 12 expectations hand-check. One should-address item: there's no axiom line, so please file a pe-parity issue for us-wi/statutes/49/148 and mark it queued. Four suggestions, including near-boundary tests (exactly 1/6 of a year is fragile in float32) and encoding the limit in weeks. Full report in the comment above.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants