Agent skill
policyengine-period-patterns
PolicyEngine period handling - converting between YEAR, MONTH definition periods and testing patterns
Install this agent skill to your Project
npx add-skill https://github.com/PolicyEngine/policyengine-claude/tree/main/skills/technical-patterns/policyengine-period-patterns-skill
SKILL.md
PolicyEngine Period Patterns
Essential patterns for handling different definition periods (YEAR, MONTH) in PolicyEngine.
Quick Reference
| From | To | Method | Example |
|---|---|---|---|
| MONTH formula | YEAR variable | period.this_year |
age = person("age", period.this_year) |
| YEAR formula | MONTH variable | period.first_month |
person("monthly_rent", period.first_month) |
| Any | Year integer | period.start.year |
year = period.start.year |
| Any | Month integer | period.start.month |
month = period.start.month |
| Annual → Monthly | Divide by 12 | / MONTHS_IN_YEAR |
monthly = annual / 12 |
| Monthly → Annual | Multiply by 12 | * MONTHS_IN_YEAR |
annual = monthly * 12 |
1. Definition Periods in PolicyEngine US
Available Periods
- YEAR: Annual values (most common - 2,883 variables)
- MONTH: Monthly values (395 variables)
- ETERNITY: Never changes (1 variable - structural relationships)
Note: QUARTER is NOT used in PolicyEngine US
Examples
from policyengine_us.model_api import *
class annual_income(Variable):
definition_period = YEAR # Annual amount
class monthly_benefit(Variable):
definition_period = MONTH # Monthly amount
class is_head(Variable):
definition_period = ETERNITY # Never changes
2. The Golden Rule
When accessing a variable with a different definition period than your formula, you must specify the target period explicitly.
# ✅ CORRECT - MONTH formula accessing YEAR variable
def formula(person, period, parameters):
age = person("age", period.this_year) # Gets actual age
# ❌ WRONG - Would get age/12
def formula(person, period, parameters):
age = person("age", period) # BAD: gives age divided by 12!
3. Common Patterns
Pattern 1: MONTH Formula Accessing YEAR Variable
Use Case: Monthly benefits need annual demographic data
class monthly_benefit_eligible(Variable):
value_type = bool
entity = Person
definition_period = MONTH # Monthly eligibility
def formula(person, period, parameters):
# Age is YEAR-defined, use period.this_year
age = person("age", period.this_year) # ✅ Gets full age
# is_pregnant is MONTH-defined, just use period
is_pregnant = person("is_pregnant", period) # ✅ Same period
return (age < 18) | is_pregnant
Pattern 2: Accessing Stock Variables (Assets)
Stock variables (point-in-time values like assets) are typically YEAR-defined
class tanf_countable_resources(Variable):
value_type = float
entity = SPMUnit
definition_period = MONTH # Monthly check
def formula(spm_unit, period, parameters):
# Assets are stocks (YEAR-defined)
cash = spm_unit("cash_assets", period.this_year) # ✅
vehicles = spm_unit("vehicles_value", period.this_year) # ✅
p = parameters(period).gov.tanf.resources
return cash + max_(0, vehicles - p.vehicle_exemption)
4. Understanding Auto-Conversion: When to Use period vs period.this_year
The Key Question
When accessing a YEAR variable from a MONTH formula, should the value be divided by 12?
- If YES → Use
period(let auto-conversion happen) - If NO → Use
period.this_year(prevent auto-conversion)
When Auto-Conversion Makes Sense (Use period)
Flow variables where you want the monthly portion:
class monthly_benefit(Variable):
definition_period = MONTH
def formula(person, period, parameters):
# ✅ Use period - want $2,000/month from $24,000/year
monthly_income = person("employment_income", period)
# Compare to monthly threshold
p = parameters(period).gov.program
return monthly_income < p.monthly_threshold
Why: If annual income is $24,000, you want $2,000/month for monthly eligibility checks.
When Auto-Conversion Breaks Things (Use period.this_year)
Stock variables and counts where division by 12 is nonsensical:
1. Age
# ❌ WRONG - gives age/12
age = person("age", period) # 30 years → 2.5 "monthly age" ???
# ✅ CORRECT - gives actual age
age = person("age", period.this_year) # 30 years
2. Assets/Resources (Stocks)
# ❌ WRONG - gives assets/12
assets = spm_unit("spm_unit_assets", period) # $12,000 → $1,000 ???
# ✅ CORRECT - gives point-in-time value
assets = spm_unit("spm_unit_assets", period.this_year) # $12,000
3. Counts (Household Size, Number of Children)
# ❌ WRONG - gives count/12
size = spm_unit("household_size", period) # 4 people → 0.33 people ???
# ✅ CORRECT - gives actual count
size = spm_unit("household_size", period.this_year) # 4 people
4. Boolean/Enum Variables
# ❌ WRONG - weird fractional conversion
status = person("is_disabled", period)
# ✅ CORRECT - actual status
status = person("is_disabled", period.this_year)
Decision Tree
Accessing YEAR variable from MONTH formula?
│
├─ Is it an INCOME or FLOW variable?
│ └─ YES → Use period (auto-convert to monthly) ✅
│ Example: employment_income, self_employment_income
│
└─ Is it AGE, ASSET, COUNT, or BOOLEAN?
└─ YES → Use period.this_year (prevent conversion) ✅
Examples: age, assets, household_size, is_disabled
Complete Example
class monthly_tanf_eligible(Variable):
value_type = bool
entity = Person
definition_period = MONTH
def formula(person, period, parameters):
# Age: Use period.this_year (don't want age/12)
age = person("age", period.this_year) # ✅
# Assets: Use period.this_year (don't want assets/12)
assets = person("assets", period.this_year) # ✅
# Income: Use period (DO want monthly income from annual)
monthly_income = person("employment_income", period) # ✅
p = parameters(period).gov.tanf.eligibility
age_eligible = (age >= 18) & (age <= 64)
asset_eligible = assets <= p.asset_limit
income_eligible = monthly_income <= p.monthly_income_limit
return age_eligible & asset_eligible & income_eligible
Pattern 3: Converting Annual to Monthly
class monthly_income_limit(Variable):
definition_period = MONTH
def formula(household, period, parameters):
# Get annual parameter
annual_limit = parameters(period).gov.program.annual_limit
# Convert to monthly
monthly_limit = annual_limit / MONTHS_IN_YEAR # ✅
return monthly_limit
Pattern 4: Getting Period Components
class federal_poverty_guideline(Variable):
definition_period = MONTH
def formula(entity, period, parameters):
# Get year and month as integers
year = period.start.year # e.g., 2024
month = period.start.month # e.g., 1-12
# FPG updates October 1st
if month >= 10:
instant_str = f"{year}-10-01"
else:
instant_str = f"{year - 1}-10-01"
# Access parameters at specific date
p_fpg = parameters(instant_str).gov.hhs.fpg
return p_fpg.first_person / MONTHS_IN_YEAR
5. Parameter Access
Standard Access
def formula(entity, period, parameters):
# Parameters use current period
p = parameters(period).gov.program.benefit
return p.amount
Specific Date Access
def formula(entity, period, parameters):
# Access parameters at specific instant
p = parameters("2024-10-01").gov.hhs.fpg
return p.amount
Important: Never use parameters(period.this_year) - parameters always use the formula's period
6. Testing with Different Periods
Critical Testing Rules
For MONTH period tests (period: 2025-01):
- Input YEAR variables as annual amounts
- Output YEAR variables show monthly values (÷12)
Test Examples
Example 1: Basic MONTH Test
- name: Monthly income test
period: 2025-01 # MONTH period
input:
people:
person1:
employment_income: 12_000 # Input: Annual
output:
employment_income: 1_000 # Output: Monthly (12_000/12)
Example 2: Mixed Variables
- name: Eligibility with age and income
period: 2024-01 # MONTH period
input:
age: 30 # Age doesn't convert
employment_income: 24_000 # Annual input
output:
age: 30 # Age stays same
employment_income: 2_000 # Monthly output
monthly_eligible: true
Example 3: YEAR Period Test
- name: Annual calculation
period: 2024 # YEAR period
input:
employment_income: 18_000 # Annual
output:
employment_income: 18_000 # Annual output
annual_tax: 2_000
Testing Best Practices
- Always specify period explicitly
- Input YEAR variables as annual amounts
- Expect monthly output for YEAR variables in MONTH tests
- Use underscore separators:
12_000not12000 - Add calculation comments in integration tests
7. Common Mistakes and Solutions
❌ Mistake 1: Not Using period.this_year
# WRONG - From MONTH formula
def formula(person, period, parameters):
age = person("age", period) # Gets age/12!
# CORRECT
def formula(person, period, parameters):
age = person("age", period.this_year) # Gets actual age
❌ Mistake 2: Redundant period.this_year / MONTHS_IN_YEAR for Flow Variables
# WRONG - Double-converting; period already auto-divides by 12
fpg = spm_unit("spm_unit_fpg", period.this_year) / MONTHS_IN_YEAR
# CORRECT - Core auto-converts annual → monthly when you use period
fpg = spm_unit("spm_unit_fpg", period)
This applies to all YEAR-defined flow variables (income, FPG, dollar amounts). Using period from a MONTH formula auto-divides by 12. Using period.this_year / MONTHS_IN_YEAR is equivalent but redundant — just use period.
❌ Mistake 3: Mixing annual and monthly
# WRONG - Comparing different units
monthly_income = person("monthly_income", period)
annual_limit = parameters(period).gov.limit
if monthly_income < annual_limit: # BAD comparison
# CORRECT - Convert to same units
monthly_income = person("monthly_income", period)
annual_limit = parameters(period).gov.limit
monthly_limit = annual_limit / MONTHS_IN_YEAR
if monthly_income < monthly_limit: # Good comparison
❌ Mistake 4: Wrong test expectations
# WRONG - Expecting annual in MONTH test
period: 2024-01
input:
employment_income: 12_000
output:
employment_income: 12_000 # Wrong!
# CORRECT
period: 2024-01
input:
employment_income: 12_000 # Annual input
output:
employment_income: 1_000 # Monthly output
8. Real-World Example
class tanf_income_eligible(Variable):
value_type = bool
entity = SPMUnit
definition_period = MONTH # Monthly eligibility
def formula(spm_unit, period, parameters):
# YEAR variables need period.this_year
household_size = spm_unit("spm_unit_size", period.this_year)
state = spm_unit.household("state_code", period.this_year)
# MONTH variables use period
gross_income = spm_unit("tanf_gross_income", period)
# Parameters use period
p = parameters(period).gov.states[state].tanf
# Convert annual limit to monthly
annual_limit = p.income_limit[household_size]
monthly_limit = annual_limit / MONTHS_IN_YEAR
return gross_income <= monthly_limit
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
policyengine-healthcare
Healthcare program modeling in PolicyEngine-US — Medicaid, ACA marketplace, CHIP, and Medicare. Covers encoding rules, running analyses, and navigating the unique complexity of US healthcare programs. Triggers: "healthcare", "health insurance", "Medicaid", "ACA", "CHIP", "Medicare", "marketplace", "premium tax credit", "APTC", "PTC", "SLCSP", "benchmark plan", "rating area", "age curve", "family tier", "coverage gap", "Medicaid expansion", "MAGI", "medicaid_magi", "aca_magi", "medicaid_income_level", "medicaid_category", "enrollment", "takeup", "take-up", "per capita", "CSR", "cost sharing", "insurance premium", "second lowest silver", "required contribution percentage", "42 CFR", "IRC 36B", "categorical eligibility", "expansion adult", "healthcare reform", "healthcare analysis", "health policy".
policyengine-us
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-US code. Contains the correct API patterns for household calculations and population simulations using the new policyengine package. Covers US federal and state taxes/benefits. Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "earning $", "making $", "calculate benefits", "calculate taxes", "benefit for a", "what would I get", "what is the maximum", "what is the rate", "poverty line", "income limit", "benefit amount", "maximum benefit", "compare states", "TANF", "SNAP", "EITC", "CTC", "SSI", "WIC", "Section 8", "Medicaid", "ACA", "child tax credit", "earned income", "supplemental security", "housing voucher", "microsimulation", "population", "reform", "policy impact", "budgetary", "decile".
policyengine-uk
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-UK code. Contains the correct API patterns for household calculations and population simulations using the new policyengine package (not policyengine_uk directly). Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "with income of", "earning £", "making £", "calculate benefits", "calculate taxes", "benefit for a", "tax for a", "what would I get", "what would they get", "what is the rate", "what is the threshold", "personal allowance", "maximum benefit", "income limit", "benefit amount", "how much is", "Universal Credit", "child benefit", "pension credit", "housing benefit", "council tax", "income tax", "national insurance", "JSA", "ESA", "PIP", "disability living allowance", "working tax credit", "child tax credit", "Scotland", "Wales", "UK", "microsimulation", "population", "reform", "policy impact", "budgetary", "decile".
policyengine-canada
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-Canada code. Contains Canadian federal and provincial tax/benefit rules for household calculations. IMPORTANT: PolicyEngine-Canada does NOT have representative population microdata. Do NOT attempt microsimulation or population-level estimates for Canada. Only provide household-level analysis (single-family impacts, eligibility, benefit amounts). Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "earning $", "making $", "calculate benefits", "calculate taxes", "benefit for a", "what would I get", "what is the maximum", "what is the rate", "income limit", "benefit amount", "maximum benefit", "compare provinces", "CCB", "Canada Child Benefit", "GST credit", "HST credit", "GST/HST", "OAS", "Old Age Security", "GIS", "Guaranteed Income Supplement", "CWB", "Canada Workers Benefit", "EI", "Employment Insurance", "CPP", "Canada Pension Plan", "RRSP", "TFSA", "Ontario Child Benefit", "OCB", "Ontario Trillium Benefit", "OTB", "BC Climate Action", "Alberta Child Benefit", "Quebec", "CRA", "Canada Revenue Agency", "Canadian", "Canada", "Ontario", "British Columbia", "Alberta", "Saskatchewan", "Manitoba", "Nova Scotia", "New Brunswick", "PEI", "Newfoundland", "Yukon", "NWT", "Nunavut", "provincial tax", "federal tax Canada".
policyengine-ui-kit-consumer
This skill should be used when setting up a new project that uses @policyengine/ui-kit, debugging CSS or styling issues in a consumer app, or when Tailwind utility classes are not being generated. Also use when creating globals.css, configuring PostCSS, or troubleshooting "no styles", "no spacing", or "no layout" problems. Triggers: "ui-kit import", "globals.css setup", "Tailwind not working", "styles not applying", "utility classes missing", "setup ui-kit", "PostCSS config", "no styling", "CSS broken", "import ui-kit", "theme.css", "no layout", "no spacing", "@tailwindcss/postcss"
policyengine-tailwind-shadcn
Tailwind CSS v4 + shadcn/ui integration patterns for PolicyEngine frontend projects. Covers @theme namespaces, CSS variable conventions, SVG var() usage, and common mistakes. Triggers: "Tailwind v4", "@theme", "shadcn", "CSS variables", "design tokens CSS", "theme.css", "@theme inline"
Didn't find tool you were looking for?