Update Texas CCS BCY25/BCY26 rates, copay formula, and vectorize payment lookup#7337
Update Texas CCS BCY25/BCY26 rates, copay formula, and vectorize payment lookup#7337hua7450 wants to merge 11 commits intoPolicyEngine:mainfrom
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7337 +/- ##
===========================================
- Coverage 100.00% 97.82% -2.18%
===========================================
Files 4 3 -1
Lines 76 92 +16
Branches 1 2 +1
===========================================
+ Hits 76 90 +14
- Misses 0 1 +1
- Partials 0 1 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Review
Architecture concerns
1. Python __init__.py in parameters directory
parameters/gov/states/tx/twc/ccs/payment/rates_v2/__init__.py puts executable Python code (pandas CSV loading, nested dict construction) inside a parameters folder. Parameters in PolicyEngine are normally YAML files loaded by the framework. This:
- Runs
pd.read_csv()and builds nested dicts eagerly at import time for all BCY years - Adds startup overhead to every import of policyengine_us
- Breaks the convention that parameters/ contains only YAML
Consider moving this to the variables folder or a utility module.
2. @np.vectorize is a Python for-loop, not true vectorization
In tx_ccs_payment_rate.py, _lookup_expanded_rate uses @np.vectorize which is just a convenience wrapper around a Python loop — no NumPy array performance benefit. Combined with decode_to_str() calls on each enum, this could be a microsimulation performance bottleneck.
Data concerns
3. RELATIVE provider type returns $0 in BCY25 — possible regression
The BCY25 CSV only contains LCCC, LCCH, and RCCH provider types. The old rates.yaml included RELATIVE rates. When uses_expanded_age_groups is true (2025-01-01+), only CSV-based rates_v2 is used, so RELATIVE providers now get $0. The test explicitly expects this — but is it correct per TWC policy?
4. Old rates.yaml code path is dead for BCY25+
The uses_expanded_age_groups switch is true from 2025-01-01. The old rates.yaml path (the else branch) is only reachable for pre-2025 periods. Codecov flags 2 uncovered lines there. The old BCY25 data in rates.yaml is now unreachable for its intended period.
Behavioral change
5. Copay formula — linear interpolation changes results vs brackets
The old code used bracket-based lookup (step function). The new code uses a derived linear formula, producing different intermediate results (e.g., ~35% SMI: old = 4.60%, new = ~4.24%). Test expectations are updated and some reference the official calculator, which is good — but this is a behavioral change, not just a parameter update.
What looks good
- Thorough age category tests (15 expanded cases covering boundaries)
- BCY26 payment rate tests cover multiple regions and provider combinations
- Copay tests validated against official calculator values
- Linear copay formula well-documented with derivation comments
- Proper references on all new parameter files
policyengine_us/variables/gov/states/tx/twc/ccs/payment/tx_ccs_payment_rate.py
Outdated
Show resolved
Hide resolved
policyengine_us/parameters/gov/states/tx/twc/ccs/payment/active_bcy_year.yaml
Outdated
Show resolved
Hide resolved
…into update-texas-ccs
Fixes #7336
Fixes #6685
Summary
Updates the Texas CCS copayment (Parent Share of Cost) formula and parameters to match the official TWC PSoC Calculator. Adds BCY25 and BCY26 payment rate parameters with expanded 8-age-group structure. Refactors payment rate lookup from
@np.vectorizedict chains to vectorizedpd.merge().How this fixes #6685
Issue #6685 reported that the BCY2025 PSoC chart's "Add 0.15% for each additional child" text doesn't match the per-family-size tables below it. The "0.15%" is misleading — it's actually the rate increment per 10% SMI bracket step, not a flat rate.
By extracting the formulas from the official TWC PSoC Calculator Excel files (BCY2025, BCY2026), we found the actual calculation:
base_rate + slope × (SMI% - min_SMI%), capped at 7%SMI% × 1.5%per child (not a flat 0.15%)The additional child rate varies by SMI bracket (e.g., 0.30% at 20% SMI, 0.75% at 50% SMI), which matches the per-family-size tables in the chart. Both BCY2025 and BCY2026 use the same formula with the same constants.
Changes
Copayment Parameters
first_child.yaml: Stores two PSoC chart endpoints (1% SMI → 2.00%, 75% SMI → 6.93%); formula derives slope at runtimeadditional_child.yaml: Stores the factor (0.015) directly; rate = SMI% × 0.015 per additional childmaximum.yaml: Unchanged (7% cap from 40 TAC §809.19)Copayment Formula (
tx_ccs_copay.py)np.interp()interpolation with exact linear equations derived from chart endpoint parameterssmi_ratio × factor × num_additional_childrenPayment Rate Architecture (addresses review feedback)
rates_v2/→rates_expanded_age_groups/for clarity_load_long()(melts CSV into long form with columns: region, provider_type, provider_rating, age_group, schedule, rate)@np.vectorize+ 5-level.get()dict chains withpd.merge()— truly vectorized, follows the same pattern as ACA/Medicaid geography lookups@lru_cache— CSVs are only read on first use, not at import timePayment Rate Parameters
rates_expanded_age_groups/bcy25.csv: BCY25 maximum provider payment rates with 8 expanded age groups (28 regions × provider types × ratings)rates_expanded_age_groups/bcy26.csv: BCY26 maximum provider payment rates with 8 expanded age groupsTests
Regulatory Authority