Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions skills/domain-knowledge/policyengine-us-skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,82 @@ print([c for c in ca_params.children]) # List agencies/programs
- Enter same inputs
- Compare results

## SNAP Deep-Dive: Monthly Eligibility and Cliff Analysis

SNAP benefits are calculated **monthly** (definition_period = MONTH). When sweeping annual income, the annual SNAP value is the sum of 12 monthly calculations. This creates subtle cliff behavior.

### SNAP Eligibility Tests
- **Gross income test**: Monthly gross income ≤ 130% of monthly FPL
- **Net income test**: Monthly net income ≤ 100% of monthly FPL
- **Categorical eligibility**: Can override gross income test in some states

### FPL Fiscal Year Change
The Federal Poverty Level updates in **October** (new fiscal year). This means:
- Jan-Sep uses one FPL threshold, Oct-Dec uses a higher threshold
- A household can fail the gross income test for 9 months but pass for 3 months
- This creates a "partial-year" cliff where annual SNAP drops to ~25% rather than zero

### Example: Missouri 3-Person Household (2025)
```
$33,550/yr → $2,795.83/mo → Eligible all 12 months → $1,956/yr SNAP
$33,600/yr → $2,800.00/mo → 130% FPL = $2,797.17/mo (Jan-Sep)
→ Fails 9 months, passes Oct-Dec → $527/yr SNAP
$34,700/yr → $2,891.67/mo → Exceeds even Oct-Dec threshold → $0/yr SNAP
```

### SNAP Variable Hierarchy for Debugging
```
snap (annual sum of monthly allotments)
├── snap_normal_allotment = max(snap_min_allotment, snap_max_allotment - snap_expected_contribution)
│ ├── snap_max_allotment (household size and region)
│ ├── snap_expected_contribution = floor(snap_net_income) × 0.30
│ │ └── snap_net_income = max(0, snap_gross_income - snap_deductions)
│ │ ├── snap_gross_income = snap_earned_income + snap_unearned_income
│ │ └── snap_deductions = standard + earned_income(20%) + shelter + dependent_care + medical + child_support
│ └── snap_min_allotment (usually only for 1-2 person households)
├── is_snap_eligible
│ ├── meets_snap_gross_income_test (≤ 130% FPL, or categorical)
│ ├── meets_snap_net_income_test (≤ 100% FPL)
│ ├── meets_snap_asset_test
│ └── meets_snap_work_requirements
└── snap_emergency_allotment (COVID-era, now $0)
```

### Using Trace Mode for Monthly SNAP Debugging
```python
sim = Simulation(situation=situation)
sim.trace = True
result = sim.calculate('snap_normal_allotment', '2025-01') # Check specific month!

for node in sim.tracer.trees:
def print_tree(n, indent=0):
val = n.value
val_str = str(val[0]) if hasattr(val, '__len__') and len(val) == 1 else str(val)
print(' ' * indent + f'{n.name} <{n.period}> = {val_str}')
for child in n.children:
print_tree(child, indent + 1)
print_tree(node)
```

### Common Benefit Cliff Causes
| Cliff | Cause | Typical magnitude |
|-------|-------|-------------------|
| SNAP 130% FPL (partial year) | Gross income test fails 9 months, passes Oct-Dec | ~75% of SNAP lost |
| SNAP 130% FPL (full) | Exceeds even Oct-Dec threshold | 100% of SNAP lost |
| School meals (free → reduced) | Free school meals lost at ~130% FPL | ~$250/child/yr |
| School meals (reduced → none) | Reduced-price meals lost at ~185% FPL | ~$990/child/yr |

### Key Gotcha: Simulation vs Microsimulation
```python
# ✅ CORRECT for custom situations
from policyengine_us import Simulation
sim = Simulation(situation=situation)

# ❌ WRONG - Microsimulation expects a dataset, not a situation
from policyengine_us import Microsimulation
sim = Microsimulation(situation=situation) # Raises ValueError
```

## Additional Resources

- **Documentation:** https://policyengine.org/us/docs
Expand Down
157 changes: 157 additions & 0 deletions skills/technical-patterns/policyengine-design-system-skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# PolicyEngine design system

Use this skill when creating or modifying frontend components for PolicyEngine applications. This ensures consistent styling using the shared `@policyengine/design-system` package.

## Package

```bash
npm install @policyengine/design-system
```

## Imports

```typescript
import { colors, typography, spacing } from '@policyengine/design-system/tokens';
```

## Mantine theme setup

PolicyEngine apps use Mantine 8 with a theme built from design tokens:

```typescript
import { createTheme } from '@mantine/core';
import type { MantineColorsTuple } from '@mantine/core';
import { colors, typography } from '@policyengine/design-system/tokens';

const primary: MantineColorsTuple = [
colors.primary[50], colors.primary[100], colors.primary[200],
colors.primary[300], colors.primary[400], colors.primary[500],
colors.primary[600], colors.primary[700], colors.primary[800],
colors.primary[900],
];

export const theme = createTheme({
primaryColor: 'primary',
colors: { primary },
fontFamily: typography.fontFamily.primary,
fontFamilyMonospace: typography.fontFamily.mono,
headings: { fontFamily: typography.fontFamily.primary, fontWeight: '600' },
defaultRadius: 'md',
focusRing: 'auto',
});
```

## Colors

```typescript
// Primary brand - teal
colors.primary[500] // #319795 - main brand color
colors.primary[50] // #E6FFFA - lightest
colors.primary[900] // #1D4044 - darkest

// Gray scale
colors.gray[50] // #F9FAFB
colors.gray[500] // #6B7280
colors.gray[900] // #101828

// Blue accent
colors.blue[500] // #0EA5E9

// Semantic
colors.success // #22C55E
colors.warning // #FEC601
colors.error // #EF4444
colors.info // #1890FF

// Text
colors.text.primary // #000000
colors.text.secondary // #5A5A5A
colors.text.tertiary // #9CA3AF
colors.text.inverse // #FFFFFF

// Background
colors.background.primary // #FFFFFF
colors.background.secondary // #F5F9FF
colors.background.tertiary // #F1F5F9

// Border
colors.border.light // #E2E8F0
colors.border.medium // #CBD5E1
colors.border.dark // #94A3B8
```

## Typography

```typescript
// Fonts
typography.fontFamily.primary // 'Inter, -apple-system, ..., sans-serif'
typography.fontFamily.secondary // 'Public Sans, ..., sans-serif'
typography.fontFamily.body // 'Roboto, ..., sans-serif'
typography.fontFamily.mono // 'JetBrains Mono, "Fira Code", ...'
typography.fontFamily.chart // 'Roboto Serif, Georgia, ...' (for chart axes/labels)

// Font sizes
typography.fontSize.xs // 12px
typography.fontSize.sm // 14px
typography.fontSize.base // 16px
typography.fontSize.lg // 18px
typography.fontSize.xl // 20px
typography.fontSize['2xl'] // 24px

// Font weights
typography.fontWeight.normal // 400
typography.fontWeight.medium // 500
typography.fontWeight.semibold // 600
typography.fontWeight.bold // 700
```

## Plotly chart styling

All Plotly charts should use a consistent layout base:

```typescript
import { colors, typography } from '@policyengine/design-system/tokens';

const chartLayout = {
font: {
family: typography.fontFamily.primary, // Inter (sans-serif)
size: 14,
color: colors.text.primary,
},
paper_bgcolor: colors.background.primary,
plot_bgcolor: colors.background.primary,
margin: { l: 60, r: 40, t: 40, b: 60 },
};

// Semantic chart colors
const chartColors = {
primary: colors.primary[500], // teal - main series
secondary: colors.blue[500], // blue - secondary series
positive: colors.success, // green - positive changes
negative: colors.error, // red - negative changes
oldBaseline: colors.gray[400], // gray dashed - old/prior values
newBaseline: colors.primary[500], // teal solid - new/current values
};
```

## Vitest configuration

If tests fail with ESM module resolution errors for the design system package, add:

```typescript
// vitest.config.ts
test: {
server: {
deps: {
inline: ['@policyengine/design-system'],
},
},
},
```

## Key rules

1. **Never hardcode colors** - always use `colors.*` tokens
2. **Charts use Inter** - set `typography.fontFamily.primary` as chart font (not serif)
3. **Teal is the brand color** - `colors.primary[500]` (#319795)
4. **Sentence case for all UI text** - only capitalize first word and proper nouns
Loading