Claude
Skills
Sign in
Back

policyengine-variable-patterns

Included with Lifetime
$97 forever

PolicyEngine variable patterns - variable creation, no hard-coding principle, federal/state separation, metadata standards

General

What this skill does


# PolicyEngine Variable Patterns

Essential patterns for creating PolicyEngine variables for government benefit programs.

## FIRST PRINCIPLE: Legal Code is the Source of Truth

**The law defines WHAT to implement. These patterns are just HOW to implement it.**

```
1. READ the legal code/policy manual FIRST
2. UNDERSTAND what the law actually says
3. IMPLEMENT exactly what the law requires
4. USE these patterns as tools to implement correctly
```

**Patterns are tools, not rules to blindly follow:**
- If the legal code says something different from common patterns → **FOLLOW THE LAW**
- If another state does it differently → **Check YOUR state's legal code**
- If a pattern doesn't fit the regulation → **Implement what the law says**

**Every implementation decision should trace back to a specific legal citation.**

---

## Modeling Time-Limited Rules

PolicyEngine supports modeling time-limited disregards. **DO implement these — DO NOT skip them.**

### Not Yet Modeled

Lifetime benefit limits (e.g., federal 60-month TANF limit) are not yet modeled in PolicyEngine. Don't implement these, but don't add comments claiming they're architecturally impossible — they may be added in the future.

### Pattern 1: Calendar Month — Time-Varying Disregard

Use when a disregard rate changes based on the month of the year. Uses `period.start.month` to get the calendar month (January=1, February=2, etc.).

```python
class state_tanf_countable_earned_income(Variable):
    def formula(spm_unit, period, parameters):
        p = parameters(period).gov.states.xx.tanf.income
        earned = spm_unit("tanf_gross_earned_income", period)

        month = period.start.month
        tlp_rate = p.time_limited_percentage.rate.calc(month)
        return earned * (1 - tlp_rate)
```

**Parameter structure** — bracket by month:
```yaml
# .../time_limited_percentage/rate.yaml
brackets:
  - threshold:
      1997-07-01: 1
    amount:
      1997-07-01: 0.5    # Months 1-6: 50%
  - threshold:
      1997-07-01: 7
    amount:
      1997-07-01: 0.35   # Months 7-9: 35%
  - threshold:
      1997-07-01: 10
    amount:
      1997-07-01: 0.25   # Months 10-12: 25%
```

### Pattern 2: `applicable_months` Split

Use when a state splits the year into periods with different disregard rates.

```python
class state_tanf_countable_earned_income(Variable):
    def formula(spm_unit, period, parameters):
        p = parameters(period).gov.states.xx.tanf.income
        # First 6 months: 100% disregard (if eligible)
        # Remaining 6 months: partial disregard
        full_disregard_months = p.full_disregard.applicable_months  # 6
        remaining_months = MONTHS_IN_YEAR - full_disregard_months
        # ... apply appropriate rate based on period
```

---

## Critical Principles

### 1. ZERO Hard-Coded Values
**Every numeric value MUST be parameterized**

```python
❌ FORBIDDEN:
return where(eligible, 1000, 0)     # Hard-coded 1000
age < 15                             # Hard-coded 15
benefit = income * 0.33              # Hard-coded 0.33
month >= 10 and month <= 3           # Hard-coded months

✅ REQUIRED:
return where(eligible, p.maximum_benefit, 0)
age < p.age_threshold.minor_child
benefit = income * p.benefit_rate
month >= p.season.start_month
```

**Acceptable literals:**
- `0`, `1`, `-1` for basic math
- `12` for month conversion (`/ 12`, `* 12`)
- Array indices when structure is known

### 2. No Placeholder Implementations
**Delete the file rather than leave placeholders**

```python
❌ NEVER:
def formula(entity, period, parameters):
    # TODO: Implement
    return 75  # Placeholder

✅ ALWAYS:
# Complete implementation or no file at all
```

### 3. Use `adds` or `add()` - NEVER Manual Addition

**CRITICAL: NEVER manually fetch variables and add them with `+`. Always use `adds` or `add()`.**

#### Rule 1: Pure sum → `adds` attribute (no formula)

```python
❌ WRONG - Writing a formula for simple sum:
class tx_tanf_gross_income(Variable):
    def formula(spm_unit, period, parameters):
        earned = spm_unit("tanf_gross_earned_income", period)
        unearned = spm_unit("tanf_gross_unearned_income", period)
        return earned + unearned  # DON'T DO THIS!

✅ CORRECT - Use adds, no formula needed:
class tx_tanf_gross_income(Variable):
    value_type = float
    entity = SPMUnit
    definition_period = MONTH
    adds = ["tanf_gross_earned_income", "tanf_gross_unearned_income"]
    # NO formula method - adds handles it automatically!
```

#### Rule 2: Sum + other operations → `add()` function

```python
❌ WRONG - Manual fetching and adding:
def formula(spm_unit, period, parameters):
    earned = spm_unit("tanf_gross_earned_income", period)
    unearned = spm_unit("tanf_gross_unearned_income", period)
    gross = earned + unearned  # DON'T manually add!
    return gross * p.rate

✅ CORRECT - Use add() function:
def formula(spm_unit, period, parameters):
    gross = add(spm_unit, period, ["tanf_gross_earned_income", "tanf_gross_unearned_income"])
    return gross * p.rate
```

**Decision rule:**
- Is it ONLY a sum? → `adds = [...]` (no formula)
- Sum + other operations? → `add()` function inside formula

**See policyengine-aggregation-skill for detailed patterns.**

---

## Variable Gotchas

### `age` is a Float
PolicyEngine's `age` variable is a float, not an integer. Age boundary parameters must use precise values (e.g., `1.5` for 18 months, not `2`).

### `monthly_age` Returns Years, Not Months
Despite the name, `monthly_age` returns age in **years**. It reverses PolicyEngine's auto-division by 12 for annual variables accessed in monthly periods. For month-based comparisons, use `person("age", period.this_year) * MONTHS_IN_YEAR`.

### `defined_for` + `entity.sum() / N` Couple Gotcha
When a formula uses `defined_for` on person-level eligibility combined with `marital_unit.sum(variable) / 2` for couples, the ineligible spouse's variable returns 0 (filtered by `defined_for`). The sum includes that zero, so division by 2 halves the eligible spouse's benefit.

### "Eligible Except for Income" Trap
When a program covers people who "would be eligible for [upstream program] except for income," using the upstream `is_xxx_eligible` (which includes income tests) as a gate **excludes the target population**. Create a separate eligibility variable without income tests, or document the gap.

### Verify Boundary Operators
When eligibility depends on income vs a threshold, verify whether the regulation says "less than" (`<`) or "at or below" (`<=`). The wrong operator silently misclassifies people at exactly the threshold.

### Child Status: Use Age, Not Tax Dependency
For benefit eligibility, use an age-based check (`age < 18`), not `is_tax_unit_dependent`. Tax-unit dependents include elderly parents and exclude non-dependent minors.

### Income Exclusion for Exempt Recipients
When a program exempts recipients of another program (e.g., SSI), their **income must also be excluded** from countable income. Exemption of a person implies exemption of that person's income.

### Parameterize FPL Reference Year
When a standard is defined as a percentage of FPL, parameterize the FPL reference year rather than using the current-period FPL. The pinned year may differ from the benefit period.

### Minimum Positive Payment
Check whether the regulation defines a minimum payment for near-threshold recipients (e.g., $1/month). Omitting this floor zeroes out benefits for a narrow but real population.

### Document Unmodeled Pathways
When a program has multiple eligibility pathways, document which are modeled and which are excluded in the variable docstring.

### `is_ssi_eligible` Does NOT Include an Income Test
`is_ssi_eligible` only checks aged/blind/disabled status, resource test, and immigration status. Income is handled separately via `uncapped_ssi` in benefit formulas. To check if someone **actually receives SSI**, use `uncapped_ssi > 0` (positive means income is below the SSI benefit amount). State SSP implementations 

Related in General