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, and MedicaidCategory::Chafee exist in the enum.

  • CMD cascade evaluates them in the family non-MAGI block.

  • All three fall through to _ ⇒ false in eligible_fn.

  • The denial_reason_fn returns "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
NOTE
The age restriction (18-21) is Georgia-specific. The 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-care with key foster_care_eligible and value in_foster_care

  • ex-adoption with key adoption_eligible and value has_adoption_assistance

  • ex-chafee with key chafee_eligible and value is_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:

  1. 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.

  2. 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

services/canopy-medicaid/src/determine.rs

Add 3 ApplicationContext fields, wire eligible_fn (3 arms), wire denial_reason_fn (3 arms)

services/canopy-medicaid/src/rules_client.rs

Extend NonMagiInput (3 boolean fields), NonMagiOutput (3 boolean fields)

rulesets/georgia/medicaid-non-magi.json

Add 3 expression nodes + output keys

rulesets/georgia/medicaid-eligibility-hierarchy.json

Add 3 expression nodes + decision table rows

Verification

  1. cargo nextest run -p canopy-medicaid --lib — unit tests pass (including 2 new tests)

  2. cargo xtask dev reload — service starts cleanly

  3. cargo nextest run --workspace — integration tests pass

  4. cargo xtask e2e — E2E tests pass

  5. Verify FosterCare, Adoption, and Chafee no longer fall through to _ ⇒ false catch-all

  6. After all Phases B-F are complete, verify the _ ⇒ false catch-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

Edit this page · default