policyengine-variable-patterns
PolicyEngine variable patterns - variable creation, no hard-coding principle, federal/state separation, metadata standards
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
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.