|
| 1 | +--- |
| 2 | +openspp: |
| 3 | + doc_status: draft |
| 4 | +--- |
| 5 | + |
| 6 | +# Eligibility |
| 7 | + |
| 8 | +Eligibility determines who qualifies for a social protection program. It's the set of rules that filter the registry population down to those who should receive benefits. |
| 9 | + |
| 10 | +**For:** All audiences |
| 11 | + |
| 12 | +## What is Eligibility? |
| 13 | + |
| 14 | +Eligibility criteria define the conditions a registrant must meet to participate in a program. These criteria translate policy decisions into concrete rules that OpenSPP can evaluate automatically. |
| 15 | + |
| 16 | +**Examples of eligibility criteria:** |
| 17 | + |
| 18 | +| Program Type | Eligibility Criteria | |
| 19 | +|-------------|---------------------| |
| 20 | +| Old age pension | Age 60 or older | |
| 21 | +| Child grant | Households with children under 5 | |
| 22 | +| Poverty assistance | PMT score below threshold | |
| 23 | +| Disability support | Certified disability status | |
| 24 | +| Geographic targeting | Residence in specific districts | |
| 25 | + |
| 26 | +## Why Eligibility Matters |
| 27 | + |
| 28 | +Proper eligibility determination ensures: |
| 29 | + |
| 30 | +| Goal | How Eligibility Helps | |
| 31 | +|------|----------------------| |
| 32 | +| **Targeting accuracy** | Benefits reach intended populations | |
| 33 | +| **Fairness** | Consistent rules applied to all | |
| 34 | +| **Accountability** | Clear, auditable selection criteria | |
| 35 | +| **Efficiency** | Automated filtering reduces manual work | |
| 36 | +| **Budget control** | Predictable beneficiary counts | |
| 37 | + |
| 38 | +## Types of Eligibility Criteria |
| 39 | + |
| 40 | +### Demographic Criteria |
| 41 | + |
| 42 | +Based on individual or household characteristics: |
| 43 | + |
| 44 | +| Criterion | Example Rule | |
| 45 | +|-----------|-------------| |
| 46 | +| **Age** | `age >= 60` (elderly) or `age < 18` (children) | |
| 47 | +| **Gender** | `gender == 'female'` for maternal programs | |
| 48 | +| **Disability** | `has_disability == true` | |
| 49 | +| **Household size** | `household_size >= 4` (large families) | |
| 50 | +| **Dependency ratio** | `dependents / working_adults > 2` | |
| 51 | + |
| 52 | +### Economic Criteria |
| 53 | + |
| 54 | +Based on income, assets, or poverty indicators: |
| 55 | + |
| 56 | +| Criterion | Example Rule | |
| 57 | +|-----------|-------------| |
| 58 | +| **Income** | `monthly_income < poverty_line` | |
| 59 | +| **PMT score** | `pmt_score < 25` (proxy means test) | |
| 60 | +| **Asset ownership** | `owns_land == false` | |
| 61 | +| **Employment** | `employment_status == 'unemployed'` | |
| 62 | + |
| 63 | +### Geographic Criteria |
| 64 | + |
| 65 | +Based on location: |
| 66 | + |
| 67 | +| Criterion | Example Rule | |
| 68 | +|-----------|-------------| |
| 69 | +| **Administrative area** | `area_id in target_districts` | |
| 70 | +| **Rural/Urban** | `area_type == 'rural'` | |
| 71 | +| **Disaster zone** | `area_id in affected_areas` | |
| 72 | + |
| 73 | +### Categorical Criteria |
| 74 | + |
| 75 | +Based on specific conditions or status: |
| 76 | + |
| 77 | +| Criterion | Example Rule | |
| 78 | +|-----------|-------------| |
| 79 | +| **Orphan status** | `is_orphan == true` | |
| 80 | +| **Refugee status** | `registration_type == 'refugee'` | |
| 81 | +| **School enrollment** | `enrolled_in_school == true` | |
| 82 | +| **Health condition** | `has_chronic_illness == true` | |
| 83 | + |
| 84 | +### Composite Criteria |
| 85 | + |
| 86 | +Combining multiple conditions: |
| 87 | + |
| 88 | +``` |
| 89 | +# Elderly women in rural areas |
| 90 | +age >= 60 AND gender == 'female' AND area_type == 'rural' |
| 91 | +
|
| 92 | +# Large poor households with children |
| 93 | +household_size >= 5 AND pmt_score < 20 AND has_children_under_5 == true |
| 94 | +
|
| 95 | +# Working-age adults without employment |
| 96 | +age >= 18 AND age < 60 AND employment_status == 'unemployed' |
| 97 | +``` |
| 98 | + |
| 99 | +## Eligibility in OpenSPP |
| 100 | + |
| 101 | +### How It Works |
| 102 | + |
| 103 | +```{mermaid} |
| 104 | +graph TD |
| 105 | + R[Registry Population] --> E[Eligibility Manager] |
| 106 | + E --> |Apply criteria| F[Filter registrants] |
| 107 | + F --> |Matching| Q[Qualified registrants] |
| 108 | + F --> |Not matching| NQ[Not qualified] |
| 109 | + Q --> P[Program enrollment] |
| 110 | +
|
| 111 | + style R fill:#e3f2fd |
| 112 | + style E fill:#fff3e0 |
| 113 | + style Q fill:#e8f5e9 |
| 114 | + style NQ fill:#ffebee |
| 115 | +``` |
| 116 | + |
| 117 | +1. **Registry data** provides the population to evaluate |
| 118 | +2. **Eligibility Manager** applies configured criteria |
| 119 | +3. **Matching registrants** become eligible for enrollment |
| 120 | +4. **Non-matching registrants** are marked as not eligible |
| 121 | + |
| 122 | +### Eligibility Manager |
| 123 | + |
| 124 | +The Eligibility Manager is a configurable component that controls how eligibility is determined. OpenSPP supports multiple eligibility approaches: |
| 125 | + |
| 126 | +| Manager Type | Description | Best For | |
| 127 | +|-------------|-------------|----------| |
| 128 | +| **Default (Domain-based)** | Uses Odoo domain filters | Simple criteria | |
| 129 | +| **CEL Expression** | Uses Common Expression Language | Complex rules | |
| 130 | +| **Area-based** | Targets specific administrative areas | Geographic targeting | |
| 131 | +| **Custom** | Developer-defined logic | Specialized requirements | |
| 132 | + |
| 133 | +### Domain-Based Eligibility |
| 134 | + |
| 135 | +The default eligibility manager uses Odoo domain syntax: |
| 136 | + |
| 137 | +```python |
| 138 | +# Households in specific districts |
| 139 | +[('area_id', 'in', [district_1_id, district_2_id])] |
| 140 | + |
| 141 | +# Individuals over 60 |
| 142 | +[('birthdate', '<=', '1964-01-01')] |
| 143 | + |
| 144 | +# Groups with more than 3 members |
| 145 | +[('z_ind_grp_num_individuals', '>=', 3)] |
| 146 | +``` |
| 147 | + |
| 148 | +**Advantages:** |
| 149 | +- Simple to configure |
| 150 | +- No coding required |
| 151 | +- Fast evaluation |
| 152 | + |
| 153 | +**Limitations:** |
| 154 | +- Cannot access related records easily |
| 155 | +- Limited to field comparisons |
| 156 | +- No complex calculations |
| 157 | + |
| 158 | +### CEL-Based Eligibility |
| 159 | + |
| 160 | +For complex criteria, OpenSPP supports CEL (Common Expression Language): |
| 161 | + |
| 162 | +```cel |
| 163 | +# Age-based eligibility |
| 164 | +age_years(me.birthdate) >= 60 |
| 165 | +
|
| 166 | +# Gender and age combined |
| 167 | +me.gender == 'female' and age_years(me.birthdate) >= 18 |
| 168 | +
|
| 169 | +# Household composition check |
| 170 | +members.exists(m, age_years(m.birthdate) < 5) |
| 171 | +
|
| 172 | +# Using computed metrics |
| 173 | +metric('household.pmt_score') < 25 |
| 174 | +
|
| 175 | +# Complex household criteria |
| 176 | +members.filter(m, age_years(m.birthdate) < 18).size() >= 2 |
| 177 | +``` |
| 178 | + |
| 179 | +**CEL Features:** |
| 180 | + |
| 181 | +| Feature | Description | Example | |
| 182 | +|---------|-------------|---------| |
| 183 | +| **`me`** | Current registrant context | `me.gender == 'female'` | |
| 184 | +| **`members`** | Household members (for groups) | `members.size() >= 3` | |
| 185 | +| **`age_years()`** | Calculate age from date | `age_years(me.birthdate) >= 60` | |
| 186 | +| **`metric()`** | Access computed variables | `metric('pmt_score') < 25` | |
| 187 | +| **`exists()`** | Check if any member matches | `members.exists(m, m.has_disability)` | |
| 188 | +| **`filter()`** | Get members matching condition | `members.filter(m, m.age < 18)` | |
| 189 | + |
| 190 | +### Geographic Targeting |
| 191 | + |
| 192 | +The area-based eligibility manager simplifies geographic targeting: |
| 193 | + |
| 194 | +| Configuration | Description | |
| 195 | +|--------------|-------------| |
| 196 | +| **Admin Areas** | Select target districts, villages, etc. | |
| 197 | +| **Area Types** | Target by area classification | |
| 198 | +| **Nested Areas** | Include child areas automatically | |
| 199 | + |
| 200 | +## Eligibility Workflow |
| 201 | + |
| 202 | +### At Program Level |
| 203 | + |
| 204 | +```{mermaid} |
| 205 | +stateDiagram-v2 |
| 206 | + [*] --> Import: Import eligible registrants |
| 207 | + Import --> Draft: Create draft memberships |
| 208 | + Draft --> Verify: Verify eligibility |
| 209 | + Verify --> Enrolled: Passes criteria |
| 210 | + Verify --> NotEligible: Fails criteria |
| 211 | + Enrolled --> [*] |
| 212 | + NotEligible --> Draft: Re-evaluate |
| 213 | +``` |
| 214 | + |
| 215 | +1. **Import** - Find registrants matching criteria from registry |
| 216 | +2. **Draft** - Create draft program memberships |
| 217 | +3. **Verify** - Run eligibility check against current data |
| 218 | +4. **Enrolled/Not Eligible** - Update status based on result |
| 219 | + |
| 220 | +### At Cycle Level |
| 221 | + |
| 222 | +Each cycle can also verify eligibility: |
| 223 | + |
| 224 | +1. **Copy from program** - Bring enrolled members into cycle |
| 225 | +2. **Verify cycle eligibility** - Re-check against current data |
| 226 | +3. **Prepare entitlements** - Only for eligible cycle members |
| 227 | + |
| 228 | +This allows for: |
| 229 | +- Removing members who no longer qualify |
| 230 | +- Adding newly eligible members |
| 231 | +- Updating status based on changed circumstances |
| 232 | + |
| 233 | +## Multiple Eligibility Managers |
| 234 | + |
| 235 | +A program can have multiple eligibility managers that work together: |
| 236 | + |
| 237 | +```{mermaid} |
| 238 | +graph LR |
| 239 | + R[Registrants] --> E1[Manager 1:<br/>Age filter] |
| 240 | + E1 --> E2[Manager 2:<br/>Area filter] |
| 241 | + E2 --> E3[Manager 3:<br/>PMT filter] |
| 242 | + E3 --> Q[Qualified] |
| 243 | +
|
| 244 | + style E1 fill:#e3f2fd |
| 245 | + style E2 fill:#fff3e0 |
| 246 | + style E3 fill:#f3e5f5 |
| 247 | +``` |
| 248 | + |
| 249 | +Managers are applied in sequence—a registrant must pass all managers to qualify. |
| 250 | + |
| 251 | +**Use cases for multiple managers:** |
| 252 | + |
| 253 | +| Scenario | Manager Setup | |
| 254 | +|----------|--------------| |
| 255 | +| **Layered targeting** | Area → PMT score → Household composition | |
| 256 | +| **Phased rollout** | Core criteria → Additional filters per phase | |
| 257 | +| **Complex programs** | Different criteria for different benefit components | |
| 258 | + |
| 259 | +## Eligibility Verification |
| 260 | + |
| 261 | +### When to Verify |
| 262 | + |
| 263 | +| Timing | Purpose | |
| 264 | +|--------|---------| |
| 265 | +| **Initial enrollment** | Determine who qualifies | |
| 266 | +| **Each cycle** | Confirm continued eligibility | |
| 267 | +| **After data updates** | Reflect changed circumstances | |
| 268 | +| **On demand** | Manual re-verification | |
| 269 | + |
| 270 | +### Verification Results |
| 271 | + |
| 272 | +| Result | Meaning | Next Steps | |
| 273 | +|--------|---------|------------| |
| 274 | +| **Passes** | Meets all criteria | Proceed with enrollment/benefits | |
| 275 | +| **Fails** | Doesn't meet criteria | Mark as not eligible | |
| 276 | +| **Pending** | Awaiting data | Request missing information | |
| 277 | + |
| 278 | +## Best Practices |
| 279 | + |
| 280 | +### Designing Eligibility Criteria |
| 281 | + |
| 282 | +1. **Start simple** - Begin with core targeting criteria |
| 283 | +2. **Be specific** - Clear rules are easier to audit |
| 284 | +3. **Consider data availability** - Only use data you have |
| 285 | +4. **Plan for updates** - Circumstances change over time |
| 286 | +5. **Document rationale** - Record why criteria were chosen |
| 287 | + |
| 288 | +### Configuration Tips |
| 289 | + |
| 290 | +| Tip | Reason | |
| 291 | +|-----|--------| |
| 292 | +| **Test with sample data** | Verify criteria work as expected | |
| 293 | +| **Preview counts** | CEL manager shows matching count | |
| 294 | +| **Use progressive refinement** | Add criteria incrementally | |
| 295 | +| **Monitor exclusion rates** | High exclusion may indicate issues | |
| 296 | + |
| 297 | +### Common Pitfalls |
| 298 | + |
| 299 | +| Pitfall | Solution | |
| 300 | +|---------|----------| |
| 301 | +| **Missing data causes exclusion** | Handle null values explicitly | |
| 302 | +| **Overly complex criteria** | Simplify or split into multiple managers | |
| 303 | +| **Criteria not updated** | Schedule periodic reviews | |
| 304 | +| **No audit trail** | Log eligibility decisions | |
| 305 | + |
| 306 | +## Are You Stuck? |
| 307 | + |
| 308 | +### Why are no registrants matching my criteria? |
| 309 | + |
| 310 | +**Check:** |
| 311 | +- Are the field names correct? |
| 312 | +- Does your data contain the expected values? |
| 313 | +- Are you targeting the right type (individual vs. group)? |
| 314 | +- Is the domain syntax valid? |
| 315 | + |
| 316 | +**Debug steps:** |
| 317 | +1. Start with a minimal criterion |
| 318 | +2. Add conditions one at a time |
| 319 | +3. Use CEL preview to see match counts |
| 320 | +4. Check sample registrant data |
| 321 | + |
| 322 | +### How do I target households with specific member characteristics? |
| 323 | + |
| 324 | +Use CEL expressions with the `members` context: |
| 325 | + |
| 326 | +```cel |
| 327 | +# Households with children under 5 |
| 328 | +members.exists(m, age_years(m.birthdate) < 5) |
| 329 | +
|
| 330 | +# Households with 2+ elderly members |
| 331 | +members.filter(m, age_years(m.birthdate) >= 60).size() >= 2 |
| 332 | +
|
| 333 | +# Female-headed households |
| 334 | +members.exists(m, m.is_head == true and m.gender == 'female') |
| 335 | +``` |
| 336 | + |
| 337 | +### Can I combine multiple criteria with OR logic? |
| 338 | + |
| 339 | +Yes, with CEL: |
| 340 | + |
| 341 | +```cel |
| 342 | +# Elderly OR disabled |
| 343 | +age_years(me.birthdate) >= 60 or me.has_disability == true |
| 344 | +``` |
| 345 | + |
| 346 | +With domain syntax, you need to use `|` operator: |
| 347 | + |
| 348 | +```python |
| 349 | +['|', ('age', '>=', 60), ('has_disability', '=', True)] |
| 350 | +``` |
| 351 | + |
| 352 | +### How do I handle missing data? |
| 353 | + |
| 354 | +In CEL, use null checks: |
| 355 | + |
| 356 | +```cel |
| 357 | +# Only check PMT if it exists |
| 358 | +me.pmt_score == null or me.pmt_score < 25 |
| 359 | +``` |
| 360 | + |
| 361 | +In domain syntax: |
| 362 | + |
| 363 | +```python |
| 364 | +['|', ('pmt_score', '=', False), ('pmt_score', '<', 25)] |
| 365 | +``` |
| 366 | + |
| 367 | +## Next Steps |
| 368 | + |
| 369 | +**Learn more about concepts:** |
| 370 | +- {doc}`programs` - How eligibility connects to programs |
| 371 | +- {doc}`cycles` - Cycle-level eligibility verification |
| 372 | +- {doc}`entitlements` - What happens after eligibility is confirmed |
| 373 | + |
| 374 | +**For configuration:** |
| 375 | +- See the Configuration Guide for setting up eligibility managers |
| 376 | + |
| 377 | +**For developers:** |
| 378 | +- See the Developer Guide for creating custom eligibility managers |
0 commit comments