11from policyengine_us .model_api import *
2+ from policyengine_us .variables .gov .hhs .hhs_smi import smi
3+ from policyengine_us .variables .gov .hhs .tax_unit_fpg import fpg
24
35
46class sc_ccap_copay (Variable ):
@@ -15,22 +17,16 @@ class sc_ccap_copay(Variable):
1517 )
1618
1719 def formula (spm_unit , period , parameters ):
18- from policyengine_us .variables .gov .hhs .hhs_smi import smi
19-
2020 p = parameters (period ).gov .states .sc .dss .ccap .copay
2121 monthly_income = spm_unit ("sc_ccap_countable_income" , period )
2222 tier_count = len (p .smi_tier_ratios .thresholds )
2323
24- # Cap family size at max_family_size for threshold computation.
25- # Pre-2024: 16 (all sizes have paid tiers).
26- # Post-2024: 14 (sizes 15-16 are $0-only).
2724 size = spm_unit ("spm_unit_size" , period .this_year )
2825 max_size = int (p .max_family_size )
29- capped_size = min_ (size , max_size )
30- above_max = size > max_size
26+ fee_scale_size = min_ (size , max_size )
3127
3228 state = spm_unit .household ("state_code_str" , period )
33- monthly_smi = smi ( capped_size , state , period , parameters ) / MONTHS_IN_YEAR
29+ state_group = spm_unit . household ( "state_group_str" , period )
3430
3531 # Family-level copay exemptions (Section 3.4.2, p.108).
3632 # Head Start copay waiver is per-child, handled below.
@@ -46,34 +42,71 @@ def formula(spm_unit, period, parameters):
4642 # Pre-2024-10-01: tiers at fixed SMI ratios (45/55/65/75% mark
4743 # boundaries between 5 tiers).
4844 # Post-2024-10-01: equal-width bands from 150% FPL to 85% SMI.
45+ tier = np .zeros_like (monthly_income , dtype = int )
46+ below_fpl_threshold = np .zeros_like (monthly_income , dtype = bool )
47+ zero_only_row = np .zeros_like (monthly_income , dtype = bool )
4948 if p .fpg_exempt_in_effect :
50- p_fpg = parameters (period ).gov .hhs .fpg
51- state_group = spm_unit .household ("state_group_str" , period )
52- capped_fpg = (
53- p_fpg .first_person [state_group ]
54- + p_fpg .additional_person [state_group ] * (capped_size - 1 )
55- ) / MONTHS_IN_YEAR
56- lower = np .floor (capped_fpg * p .fpg_exempt_rate + 0.5 )
57- below_fpl_threshold = monthly_income <= lower
49+ max_paid_size = int (p .max_paid_family_size )
50+ zero_only_row = fee_scale_size > max_paid_size
51+
52+ # Rows 15-16 are $0-only on the published fee scale. For rows with
53+ # paid tiers, fall back to the largest published paid-tier size
54+ # whose derived 150% FPL and 85% SMI bounds still form a
55+ # nonnegative band.
56+ paid_band_size = np .ones_like (size , dtype = int )
57+ for candidate in range (1 , max_paid_size + 1 ):
58+ lower_candidate = np .floor (
59+ fpg (candidate , state_group , period , parameters )
60+ / MONTHS_IN_YEAR
61+ * p .fpg_exempt_rate
62+ + 0.5
63+ )
64+ upper_candidate = np .floor (
65+ smi (candidate , state , period , parameters )
66+ / MONTHS_IN_YEAR
67+ * p .smi_tier_ratios .calc (tier_count )
68+ + 0.5
69+ )
70+ valid_candidate = (fee_scale_size >= candidate ) & (
71+ lower_candidate <= upper_candidate
72+ )
73+ paid_band_size = where (
74+ valid_candidate ,
75+ candidate ,
76+ paid_band_size ,
77+ )
78+
79+ monthly_fpg = (
80+ fpg (paid_band_size , state_group , period , parameters ) / MONTHS_IN_YEAR
81+ )
82+ monthly_smi = (
83+ smi (paid_band_size , state , period , parameters ) / MONTHS_IN_YEAR
84+ )
85+ lower = np .floor (monthly_fpg * p .fpg_exempt_rate + 0.5 )
5886 upper = np .floor (monthly_smi * p .smi_tier_ratios .calc (tier_count ) + 0.5 )
5987 band_width = np .ceil ((upper - lower ) / tier_count )
60- tier = np . zeros_like ( monthly_income , dtype = int )
88+ below_fpl_threshold = ( ~ zero_only_row ) & ( monthly_income <= lower )
6189 for i in range (1 , tier_count ):
6290 threshold = lower + band_width * i
63- tier = tier + (monthly_income > threshold ).astype (int )
91+ tier = tier + ((~ zero_only_row ) & (monthly_income > threshold )).astype (
92+ int
93+ )
6494 else :
65- below_fpl_threshold = False
66- tier = np .zeros_like (monthly_income , dtype = int )
95+ monthly_smi = (
96+ smi (fee_scale_size , state , period , parameters ) / MONTHS_IN_YEAR
97+ )
6798 for i in range (1 , tier_count ):
6899 ratio = p .smi_tier_ratios .calc (i )
69100 threshold = np .floor (monthly_smi * ratio + 0.5 )
70101 tier = tier + (monthly_income > threshold ).astype (int )
71102
72- exempt = (
73- protective | is_tanf | below_fpl_threshold | has_disabled_child | above_max
74- )
103+ exempt = protective | is_tanf | below_fpl_threshold | has_disabled_child
75104
76- weekly_copay_per_child = p .weekly_amounts .calc (tier + 1 )
105+ weekly_copay_per_child = where (
106+ zero_only_row ,
107+ 0 ,
108+ p .weekly_amounts .calc (tier + 1 ),
109+ )
77110
78111 # Head Start children have no copay (Section 2.15); only count
79112 # non-Head-Start eligible children for the copay calculation.
0 commit comments