Plan: Medicaid COA Phase F — Foster Care, Adoption, Chafee
On this page
Status
| Step | Description | Status |
|---|---|---|
1 |
Add ApplicationContext fields (in_foster_care, has_adoption_assistance, is_chafee_eligible) |
Done (2026-04-13) |
2 |
Extend NonMagiInput with 3 boolean fields |
Done (2026-04-13) |
3 |
Extend NonMagiOutput with 3 boolean fields |
Done (2026-04-13) |
4 |
Add 3 expressions to medicaid-non-magi.json |
Done (2026-04-13) |
5 |
Wire eligible_fn and denial_reason_fn match arms |
Done (2026-04-13) |
6 |
Add 3 COAs to hierarchy ruleset and add unit tests |
Done (2026-04-13) |
Branch: feature/medicaid-coa-phase-f
Context
Foster Care, Adoption Assistance, and Chafee (former foster care youth aging out) are three family non-MAGI COAs that provide Medicaid coverage to children and young adults in the child welfare system.
-
Foster Care (PAMMS 2064): Children in foster care placement are categorically eligible for Medicaid. No income or resource test.
-
Adoption Assistance (PAMMS 2068): Children with adoption assistance agreements (Title IV-E or state-funded) are categorically eligible. No income or resource test.
-
Chafee (42 USC 677): Former foster care youth aged 18-21 who age out of foster care. Named after Senator John Chafee. No income test per the Affordable Care Act (extending to age 26 in many states, but Georgia implements 18-21 per state plan).
All three COAs use the "non_magi" track because they are categorically eligible populations — there is no MAGI income calculation involved. The eligibility determination is a simple boolean gate based on child welfare system status data.
Current state:
-
MedicaidCategory::FosterCare,MedicaidCategory::Adoption, andMedicaidCategory::Chafeeexist in the enum. -
CMD cascade evaluates them in the family non-MAGI block.
-
All three fall through to
_ ⇒ falseineligible_fn. -
The
denial_reason_fnreturns"abd_coa_not_evaluable_without_additional_data"via the catch-all.
Scope
In scope:
-
3 boolean flags on ApplicationContext
-
NonMagiInput / NonMagiOutput extensions (3 fields each)
-
3 expressions in medicaid-non-magi.json
-
eligible_fn / denial_reason_fn wiring (3 match arms each)
-
Hierarchy ruleset additions (3 COAs)
-
2 unit tests
Out of scope:
-
Child welfare system integration (IV-E data comes from external systems — out of scope for eligibility engine)
-
Foster care placement verification workflow
-
Adoption assistance agreement verification
-
Extension of Chafee to age 26 (Georgia implements 18-21; federal option for 26 is a future state plan amendment)
Dependencies
-
No hard dependencies on other Phases (B-E). These are simple boolean COAs.
-
services/canopy-medicaid/src/store/models.rs— MedicaidCategory enum (already has all 3 variants)
Design
ApplicationContext additions
Add to services/canopy-medicaid/src/determine.rs, struct ApplicationContext:
/// Child is in foster care placement (PAMMS 2064).
#[serde(default)]
pub in_foster_care: Option<bool>,
/// Child has adoption assistance agreement (PAMMS 2068).
#[serde(default)]
pub has_adoption_assistance: Option<bool>,
/// Former foster care youth eligible for Chafee (42 USC 677).
/// Set by the orchestrator based on child welfare system data.
#[serde(default)]
pub is_chafee_eligible: Option<bool>,
NonMagiInput additions
Add to services/canopy-medicaid/src/rules_client.rs, struct NonMagiInput:
pub in_foster_care: bool,
pub has_adoption_assistance: bool,
pub is_chafee_eligible: bool,
NonMagiOutput additions
Add to services/canopy-medicaid/src/rules_client.rs, struct NonMagiOutput:
pub foster_care_eligible: bool,
pub adoption_eligible: bool,
pub chafee_eligible: bool,
medicaid-non-magi.json expressions
Foster Care (new expression id ex-foster-care):
in_foster_care
Adoption (new expression id ex-adoption):
has_adoption_assistance
Chafee (new expression id ex-chafee):
is_chafee_eligible and applicant_age >= 18 and applicant_age <= 21
is_chafee_eligible flag is set by the orchestrator based on child welfare data confirming the individual was in foster care at age 18.
eligible_fn additions
MedicaidCategory::FosterCare => non_magi_out.foster_care_eligible,
MedicaidCategory::Adoption => non_magi_out.adoption_eligible,
MedicaidCategory::Chafee => non_magi_out.chafee_eligible,
denial_reason_fn additions
MedicaidCategory::FosterCare => "not_in_foster_care",
MedicaidCategory::Adoption => "no_adoption_assistance_agreement",
MedicaidCategory::Chafee if age < 18 => "age_under_18",
MedicaidCategory::Chafee if age > 21 => "age_over_21",
MedicaidCategory::Chafee => "not_former_foster_care_youth",
Hierarchy ruleset additions
Add to rulesets/georgia/medicaid-eligibility-hierarchy.json:
{"id": "ex-has-foster-care", "key": "has_foster_care", "value": "some(eligible_coas, # == \"foster_care\")"},
{"id": "ex-has-adoption", "key": "has_adoption", "value": "some(eligible_coas, # == \"adoption\")"},
{"id": "ex-has-chafee", "key": "has_chafee", "value": "some(eligible_coas, # == \"chafee\")"}
Position in the hierarchy: in the family non-MAGI block, after Refugee and before FM-MN. This matches the MedicaidCategory enum order (FosterCare, Adoption, Chafee appear between Refugee and Whm).
Update assigned_coa decision table:
has_foster_care | true | "foster_care" | "Foster Care — categorical eligibility (PAMMS 2064)"
has_adoption | true | "adoption" | "Adoption Assistance — categorical eligibility (PAMMS 2068)"
has_chafee | true | "chafee" | "Chafee — former foster care youth 18-21 (42 USC 677)"
Steps
Step 1: ApplicationContext fields
Files: services/canopy-medicaid/src/determine.rs
Add 3 fields to ApplicationContext: in_foster_care: Option<bool>, has_adoption_assistance: Option<bool>, is_chafee_eligible: Option<bool> — all with #[serde(default)]. Unwrap with .unwrap_or(false) in the determine function body.
Step 2: NonMagiInput extension
Files: services/canopy-medicaid/src/rules_client.rs
Add 3 boolean fields to NonMagiInput: in_foster_care, has_adoption_assistance, is_chafee_eligible. Wire in determine.rs where NonMagiInput is constructed, pulling from the unwrapped ApplicationContext values.
Step 3: NonMagiOutput extension
Files: services/canopy-medicaid/src/rules_client.rs
Add 3 boolean fields to NonMagiOutput: foster_care_eligible, adoption_eligible, chafee_eligible.
Step 4: medicaid-non-magi.json expressions
Files: rulesets/georgia/medicaid-non-magi.json
Add 3 expression nodes:
-
ex-foster-carewith keyfoster_care_eligibleand valuein_foster_care -
ex-adoptionwith keyadoption_eligibleand valuehas_adoption_assistance -
ex-chafeewith keychafee_eligibleand valueis_chafee_eligible and applicant_age >= 18 and applicant_age ⇐ 21
Add 3 output keys to the output mapping node.
Step 5: eligible_fn + denial_reason_fn
Files: services/canopy-medicaid/src/determine.rs
Add 3 match arms to eligible_fn mapping FosterCare, Adoption, and Chafee to their NonMagiOutput booleans. These replace the _ ⇒ false catch-all for these COAs.
Add 3 match arms to denial_reason_fn with specific denial reasons. Chafee has age-specific denial reasons (under 18, over 21, or not former foster care).
Step 6: Hierarchy ruleset + unit tests
Files: rulesets/georgia/medicaid-eligibility-hierarchy.json, services/canopy-medicaid/src/determine.rs
Add 3 expression nodes and 3 decision table rows to the hierarchy ruleset, positioned in the family non-MAGI block.
Add 2 unit tests:
-
Foster Care + Adoption eligible: in_foster_care=true → foster_care_eligible=true; has_adoption_assistance=true → adoption_eligible=true. Verify both appear in eligible_coas and hierarchy assigns the higher-priority one.
-
Chafee age boundaries: is_chafee_eligible=true, age=20 → eligible; age=17 → denied (age_under_18); age=22 → denied (age_over_21).
Files Touched
| File | Change |
|---|---|
|
Add 3 ApplicationContext fields, wire eligible_fn (3 arms), wire denial_reason_fn (3 arms) |
|
Extend NonMagiInput (3 boolean fields), NonMagiOutput (3 boolean fields) |
|
Add 3 expression nodes + output keys |
|
Add 3 expression nodes + decision table rows |
Verification
-
cargo nextest run -p canopy-medicaid --lib— unit tests pass (including 2 new tests) -
cargo xtask dev reload— service starts cleanly -
cargo nextest run --workspace— integration tests pass -
cargo xtask e2e— E2E tests pass -
Verify FosterCare, Adoption, and Chafee no longer fall through to
_ ⇒ falsecatch-all -
After all Phases B-F are complete, verify the
_ ⇒ falsecatch-all in eligible_fn is empty (all MedicaidCategory variants have explicit match arms)
Documentation Updates
-
.claude/docs/services.md— update canopy-medicaid feature table (Foster Care, Adoption, Chafee COAs) -
CHANGELOG.adoc— entry under== Unreleased -
.claude/CLAUDE.md— update Phase 3 status to reflect all COA phases complete