Agent skill
policyengine-parameter-patterns
PolicyEngine parameter patterns - YAML structure, naming conventions, metadata requirements, federal/state separation
Install this agent skill to your Project
npx add-skill https://github.com/PolicyEngine/policyengine-claude/tree/main/skills/technical-patterns/policyengine-parameter-patterns-skill
SKILL.md
PolicyEngine Parameter Patterns
Comprehensive patterns for creating PolicyEngine parameter files.
Critical: Required Structure
Every parameter MUST have this exact structure:
description: [One sentence description].
values:
YYYY-MM-DD: value
metadata:
unit: [type] # REQUIRED
period: [period] # REQUIRED
label: [name] # REQUIRED
reference: # REQUIRED
- title: [source]
href: [url]
Missing ANY metadata field = validation error
1. File Naming Conventions
Study Reference Implementations First
Before creating ANY parameter files, read 3-5 files from a similar program in another state. Match their structure — don't invent your own. This is the single most effective way to produce correct parameter files.
Search broadly by program concept (not just program name):
# For a childcare program, search by concept keywords:
Glob 'policyengine_us/parameters/gov/states/*/*/*child*care*/'
Glob 'policyengine_us/parameters/gov/states/*/*/*ccap*/'
Glob 'policyengine_us/parameters/gov/states/*/*/*ccfa*/'
Good references by program type:
- TANF: DC, IL, TX —
/parameters/gov/states/{st}/{agency}/tanf/ - Childcare: MA CCFA, CO CCAP —
/parameters/gov/states/{st}/{agency}/ccfa/orccap/ - LIHEAP: AZ —
/parameters/gov/states/az/{agency}/liheap/
Naming Patterns
Dollar amounts → /amount.yaml
income/deductions/work_expense/amount.yaml # $120
resources/limit/amount.yaml # $6,000
payment_standard/amount.yaml # $320
Percentages/rates → /rate.yaml or /percentage.yaml
income_limit/rate.yaml # 1.85 (185% FPL)
benefit_reduction/rate.yaml # 0.2 (20%)
income/disregard/percentage.yaml # 0.67 (67%)
Thresholds → /threshold.yaml
age_threshold/minor_child.yaml # 18
age_threshold/elderly.yaml # 60
income/threshold.yaml # 30_000
2. Description Field
The ONLY Acceptable Formula
description: [State] [verb] [category] to [this X] under the [Full Program Name] program.
Components:
- [State]: Full state name (Indiana, Texas, California)
- [verb]: ONLY use: limits, provides, sets, excludes, deducts, uses
- [category]: What's being limited/provided (gross income, resources, payment standard)
- [this X]: ALWAYS use generic placeholder
this amount(for currency-USD)this shareorthis percentage(for rates/percentages)this threshold(for age/counts)
- [Full Program Name]: ALWAYS spell out (Temporary Assistance for Needy Families, NOT TANF)
Copy These Exact Templates
For income limits:
description: [State] limits gross income to this amount under the Temporary Assistance for Needy Families program.
For resource limits:
description: [State] limits resources to this amount under the Temporary Assistance for Needy Families program.
For payment standards:
description: [State] provides this amount as the payment standard under the Temporary Assistance for Needy Families program.
For disregards:
description: [State] excludes this share of earnings from countable income under the Temporary Assistance for Needy Families program.
Keep Descriptions Practical
Descriptions should focus on what matters for simulation, not copy regulatory language verbatim. Omit regulatory details that have no practical impact:
# ❌ Too literal — no one simulates a 1-week-old:
description: Rhode Island defines the infant/toddler age range as 1 week up to 3 years under the Child Care Assistance Program.
# ✅ Practical:
description: Rhode Island defines the infant/toddler age range as up to 3 years under the Child Care Assistance Program.
Description Validation Checklist
Run this check on EVERY description:
# Pseudo-code validation
def validate_description(desc):
checks = [
desc.count('.') == 1, # Exactly one sentence
'TANF' not in desc, # No acronyms
'SNAP' not in desc, # No acronyms
'this amount' in desc or 'this share' in desc or 'this percentage' in desc,
'under the' in desc and 'program' in desc,
'by household size' not in desc, # No explanatory text
'based on' not in desc, # No explanatory text
'for eligibility' not in desc, # Redundant
]
return all(checks)
CRITICAL: Always spell out full program names in descriptions!
3. Values Section
Format Rules
values:
2024-01-01: 3_000 # Use underscores
# NOT: 3000
2024-01-01: 0.2 # Remove trailing zeros
# NOT: 0.20 or 0.200
2024-01-01: 2 # No decimals for integers
# NOT: 2.0 or 2.00
Effective Dates
Use exact dates from sources:
# If source says "effective July 1, 2023"
2023-07-01: value
# If source says "as of October 1"
2024-10-01: value
# NOT arbitrary dates:
2000-01-01: value # Shows no research
Date format: YYYY-MM-01 (always use 01 for day)
Effective Date Accuracy
Use the program's effective date, not filing or publication dates:
# Regulation filed June 5, 2003; effective January 1, 2004
❌ 2003-06-05: 500 # Filing date — WRONG
✅ 2004-01-01: 500 # Effective date — CORRECT
Beware backward extrapolation: A parameter's first entry (e.g., 2006-07-01: 20) is returned for ALL prior periods. If the program didn't exist before that date, add an explicit zero:
# ❌ Phantom $20 appears in 2005:
values:
2006-07-01: 20
# ✅ Zero before program start:
values:
2000-01-01: 0
2006-07-01: 20
Don't duplicate unchanged values. If a value is the same across eras, keep one entry at the earliest applicable date.
Before removing "duplicate" date entries, verify ALL sub-categories — what looks identical at the top level may differ in specific combinations (e.g., School Age Part Time rates changed while other age/time categories stayed the same).
Flag unverified historical values in the PR description rather than silently including them.
4. Metadata Fields (ALL REQUIRED)
unit
Common units:
currency-USD- Dollar amounts/1- Rates, percentages (as decimals)month- Number of monthsyear- Age in yearsbool- True/falseperson- Count of people
period
year- Annual valuesmonth- Monthly valuesday- Daily valueseternity- Never changes
Match period to the parameter's semantic meaning. A dimensionless rate or ratio should use period: year, not the period of the quantity it modifies (e.g., don't use period: week for a weekly copay rate — the rate itself is time-invariant).
label
Pattern: [State] [PROGRAM] [description]
label: Montana TANF minor child age threshold
label: Illinois TANF earned income disregard rate
label: California SNAP resource limit
Rules:
- Spell out state name
- Abbreviate program (TANF, SNAP)
- No period at end
reference
Requirements:
- At least one source (prefer two)
- Must contain the actual value
- Title: Include FULL section path (all subsections and sub-subsections)
- PDF links: Add
#page=XXat end of href ONLY (never in title) - Verify page numbers — open the PDF and confirm the page number is correct before using it
Reference Source Hierarchy — prefer online over PDF:
- Online statute/regulation (best): Cornell LII, state legislature sites, public.law
- Example:
https://www.law.cornell.edu/regulations/rhode-island/218-RICR-20-00-4.6
- Example:
- Official government HTML page:
.govpages with section anchors - PDF with verified page number (last resort): Only when no online HTML version exists
- Rate schedules, policy manuals without HTML versions
- Always verify the
#page=XXis correct
Why: Online references are linkable, searchable, and don't require page number verification. PDF page numbers are error-prone (file page vs printed page) and links break when documents are updated.
Title Format - Include ALL subsection levels (NO page numbers):
# ❌ BAD - Too generic:
title: OAR 461-155 # Missing subsections!
title: Section 5 # Which subsection?
title: TEA Manual, page 13 # Page number belongs in href, not title!
# ✅ GOOD - Full section path, no page number:
title: OAR 461-155-0030(2)(a)(B) # All levels included
title: 7 CFR § 273.9(d)(6)(ii)(A) # Federal regulation with all subsections
title: Indiana Admin Code 12-14-2-3.5(b)(1) # State admin code
title: Arkansas TEA Manual Section 5.2.3 # Manual with section (page in href)
PDF Link Format - Always include page in href:
CRITICAL: Use the PDF file page number, NOT the printed page number inside the document.
- The
#page=XXvalue is the page position in the PDF file (1st page = 1, 2nd page = 2, etc.) - This may differ from the page number printed on the document itself
- Why? When users click the link, they must land directly on the page showing the referenced values
# ❌ BAD - No page number:
href: https://state.gov/manual.pdf
# ✅ GOOD - Page anchor in href (file page number):
href: https://humanservices.arkansas.gov/wp-content/uploads/TEA_MANUAL.pdf#page=13
href: https://adminrules.idaho.gov/rules/current/16/160503.pdf#page=8
Complete Examples:
✅ GOOD (page number in href only):
reference:
- title: OAR 461-155-0030(2)(a)(B)
href: https://oregon.public.law/rules/oar_461-155-0030
- title: Oregon DHS TANF Policy Manual Section 4.3.2
href: https://oregon.gov/dhs/tanf-manual.pdf#page=23
✅ GOOD:
reference:
- title: 7 CFR § 273.9(d)(6)(ii)(A)
href: https://www.ecfr.gov/current/title-7/section-273.9#p-273.9(d)(6)(ii)(A)
- title: Arkansas TEA Manual Section 2100
href: https://humanservices.arkansas.gov/wp-content/uploads/TEA_MANUAL.pdf#page=45
❌ BAD (page number in title):
reference:
- title: Arkansas TEA Manual, page 13 # Page belongs in href!
href: https://humanservices.arkansas.gov/wp-content/uploads/TEA_MANUAL.pdf
❌ BAD (missing info):
reference:
- title: Federal LIHEAP regulations # Too generic - no section!
href: https://www.acf.hhs.gov/ocs # No specific page
- title: OAR 461-155 # Missing subsections (2)(a)(B)!
href: https://oregon.gov/manual.pdf # Missing #page=XX
Reference Verification Rules
Distinguish statute from regulation. Cite the statute that establishes a rule and the regulation that implements it separately — they are different legal instruments.
Prefer direct document URLs over landing-page or index-page URLs. Agency websites frequently reorganize navigation pages while direct document links remain stable.
Annotate reference coverage windows. When a source has a known publication cutoff (e.g., last published in 2011), note which values it corroborates — don't cite it for values postdating the cutoff.
Secondary sources (WorkWorld, SSA reports) are simplified. They may omit eligibility requirements, payment categories, or add interpretive language. Always treat the Admin Code/state regulation as authoritative; use secondary sources for cross-verification only.
Verify regulation text is present. When fetching from legal databases (Cornell LII, Westlaw), verify the content contains actual regulation body text, not just a JavaScript shell — many databases render client-side.
When a regulation portal migrates domains, update ALL parameter files referencing the old domain in one pass — broken redirects are systematic.
For state policy choices, cite the state regulation, not the federal CFR that merely sets the ceiling.
5. Federal/State Separation
Federal Parameters
Location: /parameters/gov/{agency}/{program}/
# parameters/gov/hhs/fpg/first_person.yaml
description: HHS sets this amount as the federal poverty guideline for one person.
State Parameters
Location: /parameters/gov/states/{state}/{agency}/{program}/
# parameters/gov/states/ca/dss/tanf/income_limit/rate.yaml
description: California uses this multiplier of the federal poverty guideline for TANF income eligibility.
5.5 Parameter Folder Organization
Core Principles
- Group logically - Parameters that relate to the same aspect should be together
- Don't create subfolder for 1 file - If only 1 parameter for an aspect, keep it at parent level
- Payment standard at root - Main benefit amounts can stay at program root
Common Aspects (adapt to your program)
income/- Income limits, deductions, disregardseligibility/- Age thresholds, citizenship requirementsresources/- Asset/resource limits
Study Existing Implementations
Each program is different. Before organizing, look at similar programs:
ls policyengine_us/parameters/gov/states/{state}/{agency}/
6. Common Parameter Patterns
Income Limits (as FPL multiplier)
# income_limit/rate.yaml
description: State uses this multiplier of the federal poverty guideline for program income limits.
values:
2024-01-01: 1.85 # 185% FPL
metadata:
unit: /1
period: year
label: State PROGRAM income limit multiplier
Benefit Amounts
# payment_standard/amount.yaml
description: State provides this amount as the monthly program benefit.
values:
2024-01-01: 500
metadata:
unit: currency-USD
period: month
label: State PROGRAM payment standard amount
Age Thresholds (Simple)
# age_threshold/minor_child.yaml
description: State defines minor children as under this age for program eligibility.
values:
2024-01-01: 18
metadata:
unit: year
period: eternity
label: State PROGRAM minor child age threshold
Age-Based Eligibility (Bracket Style) - PREFERRED
When eligibility depends on age ranges, use a single bracket-style parameter instead of separate min/max files.
# eligibility/by_age.yaml
description: Massachusetts determines eligibility for the Bay Transportation reduced fare program based on age.
metadata:
threshold_unit: year
amount_unit: bool
period: year
type: single_amount
label: Massachusetts Bay Transportation reduced fare age eligibility
reference:
- title: MBTA Reduced Fare Program
href: https://www.mbta.com/fares/reduced
brackets:
- threshold:
2024-01-01: 0
amount:
2024-01-01: false # Under 18: not eligible
- threshold:
2024-01-01: 18
amount:
2024-01-01: true # Ages 18-64: eligible
- threshold:
2024-01-01: 65
amount:
2024-01-01: false # 65+: not eligible (different program)
Federal example (SNAP student eligibility):
# parameters/gov/usda/snap/student_age_eligibility_threshold.yaml
description: The United States includes students in this age range for SNAP eligibility.
brackets:
- threshold:
2018-01-01: 0
amount:
2018-01-01: true # Under 18: eligible
- threshold:
2018-01-01: 18
amount:
2018-01-01: false # Ages 18-49: not eligible (student restrictions)
- threshold:
2018-01-01: 50
amount:
2018-01-01: true # 50+: eligible
metadata:
type: single_amount
threshold_unit: year
amount_unit: bool
label: SNAP student age eligibility threshold
reference:
- title: 7 U.S. Code § 2015 - Eligibility disqualifications
href: https://www.law.cornell.edu/uscode/text/7/2015
When to use bracket-style:
- ✅ Eligibility varies by age range (eligible for ages X-Y only)
- ✅ Multiple age cutoffs affect the same benefit
- ✅ Boolean eligibility that changes at different thresholds
- ✅ Non-contiguous eligibility (e.g., eligible under 18 AND over 50, but not 18-49)
When NOT to use bracket-style:
- ❌ Single threshold (just use simple
threshold.yaml) - ❌ Non-boolean values that scale with age (use
single_amountbrackets with currency amounts)
Disregard Percentages
# income/disregard/percentage.yaml
description: State excludes this share of earned income from program calculations.
values:
2024-01-01: 0.67 # 67%
metadata:
unit: /1
period: eternity
label: State PROGRAM earned income disregard percentage
Bracket-Based Parameters
CRITICAL: Handling Negative Values
When creating bracket-based parameters (e.g., tax credits based on AGI), the first bracket threshold MUST be -.inf if negative values are possible, NOT 0.
❌ WRONG - Excludes negative AGI:
# threshold.yaml (for single filers)
brackets:
- threshold:
2023-01-01: 0 # ❌ Bug: negative AGI excluded!
amount:
2023-01-01: 300
- threshold:
2023-01-01: 30_000
amount:
2023-01-01: 110
✅ CORRECT - Includes all possible values:
# threshold.yaml (for single filers)
brackets:
- threshold:
2023-01-01: -.inf # ✅ Covers negative AGI
amount:
2023-01-01: 300
- threshold:
2023-01-01: 30_000
amount:
2023-01-01: 110
When to use -.inf:
- Income-based calculations (AGI can be negative)
- Any parameter where negative input values are valid
- Tax credits, deductions, or benefits based on earnings
When 0 is appropriate:
- Age thresholds (always non-negative)
- Count-based parameters (household size, number of dependents)
- Resource limits (assets can't be negative)
Real-world example: Hawaii Food/Excise Tax Credit uses AGI brackets. The first threshold must be -.inf to correctly handle taxpayers with negative AGI (e.g., business losses).
Bracket Boundary: "Above X%" Regulations
CRITICAL: PolicyEngine's single_amount bracket uses "at or above threshold" logic. A value exactly at the threshold gets that bracket's rate, not the previous bracket's. When a regulation says "above X%" (meaning X% itself belongs to the lower bracket), shift the threshold by 0.0001 to match.
Example — co-payment tiers by FPL:
Regulation says:
Level 0: Less than or equal to 100% → $0
Level 1: Above 100% up to and including 125% → 2%
Level 2: Above 125% up to and including 150% → 5%
Level 3: Above 150% up to and including 261% → 7%
❌ WRONG — family at exactly 100% FPL gets 2% instead of 0%:
brackets:
- threshold:
2024-01-01: 0
amount:
2024-01-01: 0
- threshold:
2024-01-01: 1.0 # ❌ At 100% FPL → hits this bracket
amount:
2024-01-01: 0.02
✅ CORRECT — shift by 0.0001 to encode "above X%":
brackets:
- threshold:
2024-01-01: 0
amount:
2024-01-01: 0
- threshold:
2024-01-01: 1.0001 # ✅ "Above 100%" → 100% stays in previous bracket
amount:
2024-01-01: 0.02
- threshold:
2024-01-01: 1.2501 # ✅ "Above 125%"
amount:
2024-01-01: 0.05
- threshold:
2024-01-01: 1.5001 # ✅ "Above 150%"
amount:
2024-01-01: 0.07
BOUNDARY CHECK — do this for every bracket parameter:
- Read the regulation's exact wording for each threshold
- If it says "above X" or "more than X" → shift by 0.0001
- If it says "at or above X" or "X or more" → no shift needed
- Apply consistently to ALL thresholds in the bracket
When to apply the 0.0001 shift:
- Regulation says "above X%" or "more than X%" (exclusive of the boundary)
- Apply consistently to ALL thresholds in the bracket, not just the first
When NOT to shift:
- Regulation says "at or above X%" or "X% or more" (inclusive — matches PolicyEngine's default)
- Regulation says "at least X%" (inclusive)
Multi-Dimensional Rate Tables (Enum Breakdowns)
When a rate table has multiple dimensions (e.g., age group × star rating × authorization level), use Enum breakdowns in a small number of parameter files — NOT one file per combination.
❌ BAD — 45 files (one per age × time level):
rates/licensed_center/infant/full_time.yaml # breakdown: [star_rating]
rates/licensed_center/infant/half_time.yaml
rates/licensed_center/toddler/full_time.yaml
... (16 files for center alone, 45 total)
✅ GOOD — 3 files (one per provider type) with multi-dimensional Enum breakdowns:
rates/licensed_center.yaml # breakdown: [time_category, star_rating, age_group]
rates/licensed_family.yaml # breakdown: [time_category, star_rating, age_group]
rates/license_exempt.yaml # breakdown: [time_category, step_rating, age_group]
Example rate file with 3D Enum breakdown:
# rates/licensed_center.yaml
description: Rhode Island provides these weekly reimbursement rates for licensed child care centers under the Child Care Assistance Program.
metadata:
period: week
unit: currency-USD
label: Rhode Island CCAP licensed center weekly rates
breakdown:
- ri_ccap_time_category
- ri_ccap_star_rating
- ri_ccap_center_age_group
reference:
- title: 218-RICR-20-00-4.12
href: https://www.law.cornell.edu/regulations/rhode-island/218-RICR-20-00-4.12
FULL_TIME:
STAR_1:
INFANT:
2025-07-01: 334
TODDLER:
2025-07-01: 278
PRESCHOOL:
2025-07-01: 236
SCHOOL_AGE:
2025-07-01: 210
STAR_2:
INFANT:
2025-07-01: 341
...
The variable lookup becomes simple Enum indexing:
p.licensed_center[time_category][star_rating][center_age]
When dimensions differ by category (e.g., licensed centers have 4 age groups, family care has 3):
- Use separate Enum variables per category (
ri_ccap_center_age_group,ri_ccap_family_age_group) - Each rate file references the appropriate Enum in its breakdown
- The variable uses
select()on the top-level category (provider type) — each branch uses its own Enums
When quality ratings differ (e.g., star ratings 1-5 for licensed, step ratings 1-4 for exempt):
- Use separate Enum variables (
ri_ccap_star_rating,ri_ccap_step_rating) - Each rate file references the appropriate Enum
Rule of thumb: If you need helper functions in the variable to do the rate lookup, your parameter structure is too granular. Restructure the parameters.
Don't Create Unnecessary Parameters
Don't create parameters for:
- Universal conversion factors — Use framework constants instead.
WEEKS_IN_YEAR / MONTHS_IN_YEARgives weeks-per-month.MONTHS_IN_YEARgives 12. Don't create aweeks_per_month.yamlparameter. - Thresholds with no practical simulation impact — A minimum age of "1 week" for childcare has no simulation value. No one models newborns. Skip it.
- Values derivable from existing parameters — If FPL tables already exist, don't recreate them as program-specific parameters.
Parameter Structure Transitions (Flat → Bracket)
When a parameter changes structure over time (e.g., a flat rate becomes a tiered/marginal rate in a later year), you CANNOT put both structures in a single YAML file. Instead, split into separate files with a boolean toggle.
Problem: A single rate.yaml with marginal brackets would retroactively apply the tiered structure to years that had a flat rate.
Solution: Create a rate/ folder with three files:
rate/
├── flat.yaml # The original flat-rate value
├── incremental.yaml # The new bracket/marginal structure
└── flat_applies.yaml # Boolean toggle: true = use flat, false = use brackets
rate/flat.yaml — The original single-value parameter:
description: Washington taxes long-term capital gains at this rate.
values:
2022-01-01: 0.07
metadata:
unit: /1
period: year
label: Washington flat capital gains tax rate
reference:
- title: RCW 82.87.040(1) Tax imposed—Long-term capital assets
href: https://app.leg.wa.gov/RCW/default.aspx?cite=82.87.040
rate/incremental.yaml — The new bracket structure:
description: Washington taxes long-term capital gains at these marginal rates.
brackets:
- threshold:
2022-01-01: 0
rate:
2022-01-01: 0.07
- threshold:
2025-01-01: 1_000_000
rate:
2025-01-01: 0.099
metadata:
threshold_unit: currency-USD
rate_unit: /1
threshold_period: year
type: marginal_rate
label: Washington marginal capital gains tax rate
reference:
- title: RCW 82.87.040(1)-(2) Tax imposed—Long-term capital assets
href: https://app.leg.wa.gov/RCW/default.aspx?cite=82.87.040
- title: ESSB 5813, Chapter 421, Laws of 2025, Sec. 101
href: https://lawfilesext.leg.wa.gov/biennium/2025-26/Htm/Bills/Session%20Laws/Senate/5813-S.SL.htm
rate/flat_applies.yaml — The boolean toggle:
description: Washington uses this indicator to determine whether the flat capital gains tax rate applies.
values:
2022-01-01: true
2025-01-01: false
metadata:
unit: bool
period: year
label: Washington flat capital gains tax rate applies
reference:
- title: RCW 82.87.040(1) Tax imposed—Long-term capital assets
href: https://app.leg.wa.gov/RCW/default.aspx?cite=82.87.040
- title: ESSB 5813, Chapter 421, Laws of 2025, Sec. 101
href: https://lawfilesext.leg.wa.gov/biennium/2025-26/Htm/Bills/Session%20Laws/Senate/5813-S.SL.htm
When to use this pattern:
- ✅ A flat rate becomes a marginal bracket schedule
- ✅ A single value becomes a lookup table by household size
- ✅ Any parameter whose YAML structure type changes at a specific date
When NOT to use this pattern:
- ❌ Values change but the structure stays the same (just add a new date entry)
- ❌ A new bracket is added to an existing bracket structure (see below)
See variable patterns skill for the corresponding variable-side logic (if p.rate.flat_applies).
Adding New Brackets to Existing Scales
When a new bracket is added to an existing scale in a later year, you CANNOT simply add the bracket with only the new year's date — the bracket would have no defined threshold/amount for prior years, breaking the scale.
Solution: Add the new bracket with its threshold set to .inf (or -.inf for rate brackets starting from the bottom) for the base year. This makes the bracket structurally present for all years but functionally unreachable before the year it takes effect.
Example: Ohio personal exemption phase-out (HB 96)
Ohio's personal exemption had 3 brackets (by AGI). Starting 2025, a 4th bracket phases the exemption to $0 at high incomes ($750k in 2025, $500k in 2026+):
brackets:
- threshold:
2021-01-01: 0
amount:
2021-01-01: 2_400
- threshold:
2021-01-01: 40_001
amount:
2021-01-01: 2_150
- threshold:
2021-01-01: 80_001
amount:
2021-01-01: 1_900
# New bracket: use .inf for base year so pre-2025 is unaffected
- threshold:
2021-01-01: .inf # Pre-2025: unreachable → bracket is inert
2025-01-01: 750_000 # 2025: phase-out at $750k
2026-01-01: 500_000 # 2026+: phase-out drops to $500k
amount:
2021-01-01: 0 # $0 exemption above threshold
Why .inf works:
- For pre-2025 periods, no income can reach
.inf, so the bracket never activates - Starting 2025, the threshold becomes a real value ($750k) and the bracket takes effect
- The scale remains structurally valid across all time periods
Another example: Ohio joint filing credit MAGI cap
brackets:
- threshold:
2021-01-01: 0
amount:
2021-01-01: 0.2
- threshold:
2021-01-01: 25_000
amount:
2021-01-01: 0.15
- threshold:
2021-01-01: 50_000
amount:
2021-01-01: 0.1
- threshold:
2021-01-01: 75_000
amount:
2021-01-01: 0.05
# New bracket: MAGI cap added by HB 96
- threshold:
2021-01-01: .inf
2025-01-01: 750_000
2026-01-01: 500_000
amount:
2021-01-01: 0
When to use .inf for new brackets:
- ✅ A new upper bracket is added in a later year (cap, phase-out, new rate tier)
- ✅ The bracket should not affect calculations for prior years
- ✅ The new bracket sets a value to zero (phase-out) or introduces a new rate
When NOT to use this pattern:
- ❌ The bracket existed in all prior years too (just add it normally with the base date)
- ❌ The parameter structure type itself changes (use the flat→bracket transition pattern above)
Real-world reference: policyengine-us PR #7107 — Ohio 2025 income tax update (HB 96 personal exemption and joint filing credit MAGI caps).
Choosing Between the Three Boolean Toggle Approaches
The flat→bracket transition, .inf new bracket, and in_effect provision gating patterns all handle parameters that change over time, but they solve different problems:
| Flat→Bracket Transition | .inf New Bracket |
in_effect Provision Gating |
|
|---|---|---|---|
| Problem | Structure type changes (flat → brackets) | New bracket added to existing scale | A provision starts or ends at a specific date |
| Parameter side | Split into folder + boolean toggle | Add bracket with .inf threshold |
Single in_effect.yaml boolean |
| Variable side | if p.toggle: to choose access method |
No changes — .calc() works |
if p.in_effect: gates entire logic block |
| Example | WA capital gains: flat 7% → tiered 7%/9.9% | OH exemptions: 3→4 brackets | CT TFA high earnings reduction (new in 2024) |
Provision Gating with in_effect Boolean
When a provision starts (or ends) at a specific date, create a boolean parameter that gates the entire logic block in the variable formula. This is different from flat_applies — it doesn't switch between two parameter access methods, it controls whether a block of logic runs at all.
Use case: A new program feature is added by legislation (e.g., a high-earnings reduction that didn't exist before 2024), or an existing feature is repealed.
in_effect.yaml — Boolean that tracks when the provision is active:
# e.g., payment/high_earnings/in_effect.yaml
description: Connecticut uses this indicator to determine whether the high-earnings benefit reduction applies under the Temporary Family Assistance program.
values:
1997-01-01: false
2024-01-01: true
metadata:
unit: bool
period: month
label: Connecticut TFA high earnings reduction in effect
reference:
- title: State of Connecticut TANF State Plan 2024-2026, High Earnings Provision
href: https://portal.ct.gov/dss/-/media/departments-and-agencies/dss/state-plans-and-federal-reports/tanf-state-plan/ct-tanf-state-plan-2024---2026---41524-amendment.pdf#page=10
- title: Connecticut General Statutes § 17b-112(d)
href: https://cga.ct.gov/current/pub/chap_319s.htm#sec_17b-112
Sibling parameters — The provision's actual values live alongside in_effect.yaml:
payment/high_earnings/
├── in_effect.yaml # false before 2024, true from 2024
├── rate.yaml # FPL multiplier threshold (e.g., 0.75)
└── reduction_rate.yaml # Benefit reduction rate (e.g., 0.25)
See variable patterns skill for the corresponding variable-side logic (if p.high_earnings.in_effect:).
When to use this pattern:
- ✅ A new provision is added by legislation at a specific date
- ✅ An existing provision is repealed at a specific date
- ✅ The provision gates an entire block of logic (not just a parameter access method)
- ✅ The provision has its own sub-parameters (rates, thresholds) that only make sense when active
When NOT to use this pattern:
- ❌ The parameter structure itself changes (use flat→bracket transition)
- ❌ A new bracket is added to an existing scale (use
.infpattern) - ❌ A simple value changes over time (just add a new date entry)
Regional Variation with regional_in_effect Boolean
When a program has regional payment variations that start or end at a specific date, create a boolean that switches between regional lookup and a flat statewide amount.
Use case: A state originally had different payment standards by region, then consolidated to a single statewide amount (or vice versa).
regional_in_effect.yaml — Boolean that tracks when regional variation applies:
# e.g., payment/regional_in_effect.yaml
description: Connecticut uses this indicator to determine whether regional payment standards apply under the Temporary Family Assistance program.
values:
1997-01-01: true
2022-07-01: false
metadata:
unit: bool
period: month
label: Connecticut TFA regional payment standards in effect
reference:
- title: Connecticut General Statutes § 17b-104(c)
href: https://cga.ct.gov/current/pub/chap_319s.htm#sec_17b-104
- title: State of Connecticut TANF State Plan 2021-2023
href: https://portal.ct.gov/dss/-/media/departments-and-agencies/dss/state-plans-and-federal-reports/tanf-state-plan/ct-tanf-plan-2021-2023--draft.pdf#page=57
Folder structure — Regional amounts AND the flat statewide amount coexist:
payment/
├── regional_in_effect.yaml # true before 2022-07, false after
├── regional/
│ ├── region_a/amount.yaml # Regional amounts (by household size)
│ ├── region_b/amount.yaml
│ └── region_c/amount.yaml
├── amount.yaml # Flat statewide amount (used when regional_in_effect is false)
└── max_unit_size.yaml
See variable patterns skill for the corresponding variable-side logic (if p.regional_in_effect:).
When to use this pattern:
- ✅ A program transitions from regional to statewide payment standards (or vice versa)
- ✅ Regional variation is controlled by legislation at a specific date
- ✅ The regional and flat structures are fundamentally different (enum lookup vs simple index)
When NOT to use this pattern:
- ❌ Regional variation always applies (just use the regional parameters directly)
- ❌ The variation is by household characteristic, not geographic region (use
where()in variable)
Real-world reference: Connecticut TFA payment standards — regional (Region A/B/C) before July 2022, flat statewide amount after.
6.5 Bracket parameter path syntax (for reforms and Python access)
CRITICAL: When referencing bracket/scale parameters in reform dicts or Python code, the bracket index goes directly on the scale node, NOT on a .brackets sub-path.
The YAML file defines brackets as a list, but the parameter tree flattens them. The bracket index attaches to the node that contains the brackets list, not to a child called brackets.
Correct syntax
# Tax bracket rates — index on the scale node directly
"gov.states.ca.tax.income.rates.single[8].rate"
"gov.states.ca.tax.income.rates.single[8].threshold"
# UK income tax rates
"gov.hmrc.income_tax.rates.uk[0].rate"
"gov.hmrc.income_tax.rates.uk[1].threshold"
# CTC amount (bracket-based parameter)
"gov.irs.credits.ctc.amount.base[0].amount"
# EITC phase-out thresholds
"gov.irs.credits.eitc.phase_out.start[0].amount"
Wrong syntax (common mistake)
# ❌ WRONG — there is no ".brackets" in the path
"gov.states.ca.tax.income.rates.single.brackets[8].rate"
"gov.irs.credits.ctc.amount.base.brackets[0].amount"
# ❌ WRONG — missing bracket index entirely
"gov.states.ca.tax.income.rates.single.rate"
"gov.irs.credits.ctc.amount.base.amount"
How to determine the correct path
- Find the YAML file in the parameters directory (e.g.,
parameters/gov/states/ca/tax/income/rates/single.yaml) - The parameter path is the directory path with dots, ending at the YAML filename (without
.yaml) - Add the bracket index directly:
path.to.scale_file[N].rateorpath.to.scale_file[N].threshold - Verify in Python:
python
from policyengine_us import CountryTaxBenefitSystem p = CountryTaxBenefitSystem().parameters # Navigate to the node and check: print(p.gov.irs.credits.ctc.amount.base[0].amount("2026-01-01"))
Using bracket paths in Reform.from_dict()
from policyengine_core.reforms import Reform
# ✅ Correct
reform = Reform.from_dict({
'gov.irs.credits.ctc.amount.base[0].amount': {
'2026-01-01.2100-12-31': 3000
},
'gov.states.ca.tax.income.rates.single[8].rate': {
'2026-01-01.2100-12-31': 0.143
},
}, 'policyengine_us')
# ❌ Wrong — will fail or silently not apply
reform = Reform.from_dict({
'gov.irs.credits.ctc.amount.base.brackets[0].amount': {
'2026-01-01.2100-12-31': 3000
},
}, 'policyengine_us')
7. Transcription & Verification
Verify Individual Values
When transcribing rate tables, verify each individual value against the source PDF. Single-digit transpositions (e.g., 305 vs 355) cascade to all derived values.
Don't Assume Arithmetic Ratios
Never assume fractional-time rates are simple fractions of full-time (e.g., half-time ≠ FT/2). Always verify against the official published rate schedule — actual ratios may differ significantly.
Implement All Rows
Always implement ALL rows from the regulatory payment table, not just those in secondary sources. WorkWorld and SSA reports may omit categories that exist in the Admin Code.
Resolve Source Conflicts
- Body text vs data table: When a regulation's body text states one value but the table implies a different one, compute from the table — body text may reflect outdated policy.
- State plan vs operational document: When they appear to conflict, investigate whether they answer different questions (e.g., "max for one child" vs "max for any family"); the operational document is the implementation source of truth.
- "Maximum" rates: When a state plan reports a single maximum, verify whether it applies to all household configurations or only a subset.
Parameter Framework Gotcha
When the parameter framework caches the root parameter tree during microsim init, parameters with start dates after the test year cause ParameterNotFoundError. Use the program's actual policy effective date, not a regulation amendment filing date.
Didn't find tool you were looking for?