Skip to content

Commit ec21243

Browse files
authored
Merge pull request #121 from hua7450/eval-2-skill-improvements
Eval-2: eligibility completeness and childcare subsidy patterns
2 parents b1b58b0 + 37cf50c commit ec21243

File tree

5 files changed

+146
-4
lines changed

5 files changed

+146
-4
lines changed

agents/country-models/rules-engineer.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ Creates parameter YAML files and variable Python files for government benefit pr
4343
2. Read the scope decision (if provided)
4444
3. Search for 3+ similar parameter files AND 3+ variable files from reference implementations
4545
4. Learn their folder structure, naming, description patterns, and code patterns
46+
5. **List every eligibility variable** in the reference implementation's eligibility folder. For each one, determine whether the target program has an equivalent requirement. Common eligibility types to check:
47+
- Income eligibility
48+
- Asset/resource eligibility
49+
- Activity/work requirement eligibility
50+
- Immigration/citizenship eligibility
51+
- Demographic eligibility (age, household composition)
52+
53+
If the reference has an eligibility type that's not in the spec, check the regulation — the spec may be incomplete.
4654

4755
### Step 2: Create Parameters
4856

@@ -104,16 +112,25 @@ Create Python variable files following `policyengine-variable-patterns` and `pol
104112
# But ALWAYS verify with the state's legal code — follow the law, not the pattern.
105113
```
106114

107-
### Step 4: Parameter-to-Variable Mapping (CRITICAL)
115+
### Step 4: Spec-to-Implementation Completeness Check (CRITICAL)
116+
117+
After creating both parameters and variables, perform TWO verification passes:
118+
119+
**Pass 1: Spec coverage** — Go through EVERY requirement in the spec/working_references, line by line:
120+
- [ ] Each requirement has at least one parameter AND one variable implementing it
121+
- [ ] Requirements listed as bullet points or in "other requirements" sections are NOT informational — they need implementation too
122+
- [ ] If the spec mentions employment/work hours → create `{prefix}_activity_eligible` or `{prefix}_work_eligible`
123+
- [ ] If the spec mentions citizenship/immigration → create `{prefix}_immigration_eligible` or use existing federal variable
124+
- [ ] If the spec mentions assets/resources → create `{prefix}_resource_eligible`
108125

109-
After creating both parameters and variables, verify completeness:
126+
**Pass 2: Parameter-to-variable mapping** — List every parameter file you created:
110127
- [ ] Every parameter has at least one variable using it
111128
- [ ] All eligibility parameters have corresponding `_eligible` variables
112129
- [ ] All calculation parameters have corresponding calculation variables
113130
- [ ] Main eligibility variable combines ALL eligibility checks
114131
- [ ] No parameters are orphaned (created but never used)
115132

116-
**RED FLAG:** If you created a `resources/limit` parameter but no `resource_eligible` variable!
133+
**RED FLAG:** If you created a parameter but no variable uses it — e.g., `min_work_hours.yaml` exists but no `work_eligible` variable!
117134

118135
### Step 5: Simplified vs Full TANF
119136

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Eval-2 driven improvements to rules-engineer agent, code-organization skill, and variable-patterns skill

skills/technical-patterns/policyengine-code-organization-skill/SKILL.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Use these standard suffixes with your variable prefix:
6969
{prefix}_eligible # Final eligibility (combines all checks)
7070
{prefix}_income_eligible # Income eligibility
7171
{prefix}_resource_eligible # Resource/asset eligibility
72+
{prefix}_activity_eligible # Work/activity requirement eligibility
7273
{prefix}_categorical_eligible # Categorical eligibility
7374
{prefix}_demographic_eligible # Age, household composition eligibility
7475
{prefix}_immigration_eligible # Immigration status eligibility
@@ -196,6 +197,41 @@ policyengine_us/tests/policy/baseline/gov/states/{state}/{agency}/{program}/
196197

197198
---
198199

200+
## Parameter Folder Organization
201+
202+
Parameter folders follow similar principles to variable folders — group by function.
203+
204+
### Keep folders focused on their purpose
205+
206+
Each subfolder should contain parameters that serve the same function. Don't use a folder as a catch-all.
207+
208+
```
209+
# ❌ BAD — eligibility/ contains rate dimension boundaries that aren't eligibility criteria
210+
ccap/
211+
├── eligibility/
212+
│ ├── income_limit.yaml # ✅ Eligibility
213+
│ ├── child_age_threshold.yaml # ✅ Eligibility
214+
│ ├── center_infant_max_months.yaml # ❌ Rate table age group boundary
215+
│ ├── preschool_max_years.yaml # ❌ Rate table age group boundary
216+
│ └── time_category.yaml # ❌ Authorization concept
217+
└── rates/
218+
219+
# ✅ GOOD — each folder contains parameters that match its purpose
220+
ccap/
221+
├── age_groups/ # Age group boundaries for rate lookups
222+
│ ├── center_infant_max_months.yaml
223+
│ └── preschool_max_years.yaml
224+
├── eligibility/ # Actual eligibility criteria
225+
│ ├── income_limit.yaml
226+
│ └── child_age_threshold.yaml
227+
├── rates/
228+
└── time_category.yaml # At root — doesn't fit neatly in a subfolder
229+
```
230+
231+
**The key question**: "Is this parameter an eligibility rule, a rate-table dimension boundary, a benefit calculation input, or something else?" Put it in the folder that matches its function.
232+
233+
---
234+
199235
## Common Mistakes to Avoid
200236

201237
### Variable Names

skills/technical-patterns/policyengine-variable-patterns-skill/SKILL.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,29 @@ class some_variable(Variable):
207207
reference = "https://example.gov/rules.pdf#page=10" # USE THIS
208208
```
209209

210+
### Scoping `defined_for` to the Right Level
211+
212+
`defined_for` controls which entities run the formula. Use the most specific scope that fits:
213+
214+
```python
215+
# ❌ TOO BROAD — calculates rates for all RI residents (adults, ineligible children)
216+
class ri_ccap_licensed_center_rate(Variable):
217+
entity = Person
218+
defined_for = StateCode.RI
219+
220+
# ✅ CORRECT — only calculates rates for children eligible for CCAP
221+
class ri_ccap_licensed_center_rate(Variable):
222+
entity = Person
223+
defined_for = "ri_ccap_eligible_child"
224+
```
225+
226+
**Guidelines:**
227+
- **SPMUnit-level benefit variables** → `defined_for = "state_program_eligible"` (the main eligibility variable)
228+
- **Person-level variables within a program** (rates, per-child amounts) → `defined_for = "state_program_eligible_child"` or the person-level eligibility variable
229+
- **Eligibility check variables themselves** → `defined_for = StateCode.XX` (they determine eligibility, so they can't depend on it)
230+
231+
This avoids unnecessary computation and makes the variable's scope clear from its definition.
232+
210233
**Reference format:**
211234
```python
212235
# Single reference:
@@ -1127,6 +1150,7 @@ When you create parameters, you MUST create corresponding variables:
11271150
|---------------|---------------------|
11281151
| resources/limit | `state_program_resources_eligible` |
11291152
| income/limit | `state_program_income_eligible` |
1153+
| min_work_hours or activity_requirements | `state_program_activity_eligible` |
11301154
| payment_standard | `state_program_maximum_benefit` |
11311155
| income/disregard | `state_program_countable_earned_income` |
11321156
| categorical/requirements | `state_program_categorically_eligible` |
@@ -1147,8 +1171,41 @@ class state_program_eligible(Variable):
11471171

11481172
**Common Implementation Failures:**
11491173
- ❌ Created resource limit parameter but no resource_eligible variable
1150-
- ❌ Main eligible variable only checks income, ignores resources
1174+
- ❌ Created min_work_hours parameter but no activity_eligible variable
1175+
- ❌ Main eligible variable only checks income, ignores resources/work/immigration
11511176
- ❌ Parameters created but never referenced in any formula
1177+
- ❌ Spec lists requirements (work hours, citizenship) but no variables implement them
1178+
1179+
---
1180+
1181+
## Childcare Subsidy Benefit Calculation
1182+
1183+
Childcare subsidy programs (CCAP, CCFA, etc.) share a common pattern: the benefit is capped at actual childcare expenses, not just the provider reimbursement rate.
1184+
1185+
PolicyEngine has an existing federal variable `pre_subsidy_childcare_expenses` (Person, YEAR) that represents what families actually pay for childcare. **Always use this variable** — don't calculate the benefit from provider rates alone.
1186+
1187+
```python
1188+
# ❌ BAD — subsidy based only on provider rate (ignores actual expenses)
1189+
class ri_ccap(Variable):
1190+
def formula(spm_unit, period, parameters):
1191+
total_weekly_rate = spm_unit.sum(weekly_rate * is_eligible_child)
1192+
return max_(total_weekly_rate - family_share, 0)
1193+
1194+
# ✅ GOOD — subsidy capped at actual expenses (matches MA CCFA, CO CCAP pattern)
1195+
class ri_ccap(Variable):
1196+
def formula(spm_unit, period, parameters):
1197+
actual_expenses = spm_unit(
1198+
"spm_unit_pre_subsidy_childcare_expenses", period.this_year
1199+
)
1200+
copay = spm_unit("ri_ccap_family_share", period)
1201+
max_reimbursement = add(spm_unit, period, ["ri_ccap_weekly_provider_rate"])
1202+
uncapped_benefit = max_(actual_expenses - copay, 0)
1203+
return min_(uncapped_benefit, max_reimbursement)
1204+
```
1205+
1206+
The subsidy is the lesser of:
1207+
1. Actual expenses minus co-payment
1208+
2. Maximum reimbursement (provider rate based on type, quality, age, time)
11521209

11531210
---
11541211

skills/technical-patterns/policyengine-vectorization-skill/SKILL.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,37 @@ else:
7070
)
7171
```
7272

73+
### Pattern 2b: Enum Dispatch → List ALL Conditions
74+
75+
When dispatching on an Enum variable, list all values explicitly in `select()`. Set the `default` to match the Enum's `default_value` so unexpected values get consistent behavior.
76+
77+
```python
78+
# ❌ BAD — hides one option in default, unclear which is the fallback
79+
provider_type = person("ri_ccap_provider_type", period)
80+
types = provider_type.possible_values
81+
return select(
82+
[
83+
provider_type == types.LICENSED_CENTER,
84+
provider_type == types.LICENSED_FAMILY,
85+
],
86+
[center_rate, family_rate],
87+
default=exempt_rate, # Why exempt as fallback?
88+
)
89+
90+
# ✅ GOOD — all options listed, default matches Enum default_value
91+
provider_type = person("ri_ccap_provider_type", period)
92+
types = provider_type.possible_values
93+
return select(
94+
[
95+
provider_type == types.LICENSED_CENTER,
96+
provider_type == types.LICENSED_FAMILY,
97+
provider_type == types.LICENSE_EXEMPT,
98+
],
99+
[center_rate, family_rate, exempt_rate],
100+
default=center_rate, # Matches default_value = LICENSED_CENTER
101+
)
102+
```
103+
73104
### Pattern 3: Boolean Operations
74105

75106
```python

0 commit comments

Comments
 (0)