Skip to content

Comments

Update Texas CCS BCY25/BCY26 rates, copay formula, and vectorize payment lookup#7337

Open
hua7450 wants to merge 11 commits intoPolicyEngine:mainfrom
hua7450:update-texas-ccs
Open

Update Texas CCS BCY25/BCY26 rates, copay formula, and vectorize payment lookup#7337
hua7450 wants to merge 11 commits intoPolicyEngine:mainfrom
hua7450:update-texas-ccs

Conversation

@hua7450
Copy link
Collaborator

@hua7450 hua7450 commented Feb 9, 2026

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.vectorize dict chains to vectorized pd.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:

  • First child rate: base_rate + slope × (SMI% - min_SMI%), capped at 7%
  • Additional child rate: SMI% × 1.5% per child (not a flat 0.15%)
  • Total: capped at 7% of income

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 runtime
  • additional_child.yaml: Stores the factor (0.015) directly; rate = SMI% × 0.015 per additional child
  • maximum.yaml: Unchanged (7% cap from 40 TAC §809.19)

Copayment Formula (tx_ccs_copay.py)

  • Replaced bracket-based np.interp() interpolation with exact linear equations derived from chart endpoint parameters
  • First child rate: linear function derived from two stored bracket points
  • Additional child rate: smi_ratio × factor × num_additional_children
  • Total rate capped at 7%, then applied to income

Payment Rate Architecture (addresses review feedback)

  • Renamed rates_v2/rates_expanded_age_groups/ for clarity
  • Replaced nested dict with flat pandas DataFrame via _load_long() (melts CSV into long form with columns: region, provider_type, provider_rating, age_group, schedule, rate)
  • Replaced @np.vectorize + 5-level .get() dict chains with pd.merge() — truly vectorized, follows the same pattern as ACA/Medicaid geography lookups
  • Lazy loading via @lru_cache — CSVs are only read on first use, not at import time

Payment 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 groups
  • Sources: BCY25 PDF, BCY26 PDF

Tests

  • Added copayment test cases validated against official TWC PSoC Estimation Calculator
  • Updated error margin to 0.5 (official calculator rounds to whole dollars)
  • All 103 TX CCS tests pass

Regulatory Authority

@codecov
Copy link

codecov bot commented Feb 9, 2026

Codecov Report

❌ Patch coverage is 95.34884% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.82%. Comparing base (f073b57) to head (464e840).
⚠️ Report is 96 commits behind head on main.

Files with missing lines Patch % Lines
...v/states/tx/twc/ccs/payment/tx_ccs_payment_rate.py 87.50% 1 Missing and 1 partial ⚠️
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     
Flag Coverage Δ
unittests 97.82% <95.34%> (-2.18%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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.

@hua7450 hua7450 marked this pull request as ready for review February 18, 2026 15:30
Copy link
Collaborator

@PavelMakarchuk PavelMakarchuk left a comment

Choose a reason for hiding this comment

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

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

@hua7450 hua7450 changed the title Update Texas CCS parameter values 2026 Update Texas CCS BCY25/BCY26 rates, copay formula, and vectorize payment lookup Feb 18, 2026
Copy link
Collaborator

@PavelMakarchuk PavelMakarchuk left a comment

Choose a reason for hiding this comment

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

minor

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update Texas CCS parameter values 2026 Texas Child Care Services Copay

2 participants