Plan: CMS-64 Expenditure Aggregation (Issue #379)
On this page
Status
| Step | Description | Status |
|---|---|---|
1 |
canopy-medicaid schema. New migration |
Not started |
2 |
canopy-medicaid store + API. New |
Not started |
3 |
canopy-reporting CMS-64 rewrite. Replace the proxy at |
Not started |
4 |
FY26 zero-output disclaimer. Until the upstream claim-adjudication wiring lands (separate plan, not in scope here), |
Not started |
5 |
Tests + docs. Unit tests in |
Not started |
6 |
OpenAPI sync. |
Not started |
Issue: #379
Branch: feat/cms-64-expenditure-aggregation
Labels: type::feature, priority::medium, service::reporting, service::medicaid, program::medicaid, federal-partner::cms, workflow::ready
Context
services/canopy-reporting/src/reporting/medicaid.rs:184-255 is documented as the CMS-64 path but counts enrolled members and labels them as expenditure. The CMS-64 schema demands actual paid amounts grouped by Federal Financial Participation (FFP) rate, waiver code, and category-of-aid. The enrolled-count proxy is structurally wrong; the file annotates this as a known gap, but the report cannot be submitted until real expenditures flow.
Similar to ACF-196 for TANF, the upstream data does not yet exist in canopy. This plan creates the table + ingest endpoint and rewrites the aggregator. The actual claim-adjudication bridge (where rows arrive from MMIS / claims processors) remains a separate concern.
Code references
-
services/canopy-reporting/src/reporting/medicaid.rs:184-255— current CMS-64 proxy. -
services/canopy-medicaid/migrations/— migration directory to extend. -
ADR-001 — expenditures live in canopy-medicaid’s DB; canopy-reporting reads via HTTP.
Scope
In scope:
-
medicaid_expenditurestable + CRUD endpoints in canopy-medicaid. -
CMS-64 rewrite in canopy-reporting using the new data path.
-
Unit tests on aggregation correctness.
Out of scope:
-
Real claim-adjudication wiring (where the rows come from). Separate plan when external claims integration scope arrives.
-
CMS-37 (financial managment standard) — separate report, separate plan.
-
Quarterly amendment/correction workflow.
-
T-MSIS expenditures (already covered separately by canopy-reporting’s existing T-MSIS path).
Dependencies
-
Predecessor
medicaid-federal-reportingplan (already archived). -
No prerequisite plans on disk.
Design
medicaid_expenditures schema:
CREATE TABLE medicaid_expenditures (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
person_id UUID NOT NULL,
claim_id TEXT NOT NULL,
paid_amount_cents BIGINT NOT NULL,
ffp_rate NUMERIC(5,4) NOT NULL,
waiver_code TEXT,
coa_category TEXT NOT NULL,
service_date DATE NOT NULL,
paid_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX medicaid_expenditures_by_service
ON medicaid_expenditures (service_date, coa_category);
CREATE INDEX medicaid_expenditures_by_waiver
ON medicaid_expenditures (waiver_code);
Aggregation API:
#[derive(Serialize, Deserialize, sqlx::FromRow, ToSchema)]
pub struct ExpenditureAggregate {
pub fiscal_quarter: String,
pub ffp_rate: Decimal,
pub waiver_code: Option<String>,
pub coa_category: String,
pub total_cents: i64,
pub claim_count: i64,
}
pub async fn aggregate_by_ffp_and_category(
pool: &PgPool,
fiscal_quarter: &str,
) -> sqlx::Result<Vec<ExpenditureAggregate>>;
CSV generator emits CMS-64 lines in their canonical order. Each row: line number, FFP rate, total federal share, total state share, total computable.
Files Touched
| File | Change |
|---|---|
|
New migration |
|
New store module |
|
New API module |
|
Register routes |
|
Replace lines 184-255 with real aggregation |
|
Replace enrolled-count tests with aggregation tests |
|
Regenerated |
|
Regenerated |
Route + table updates |
|
|
|
Verification
-
cargo nextest run -p canopy-medicaid -p canopy-reporting --lib— unit tests pass. -
cargo xtask api-docs— snapshots regenerate clean. -
cargo xtask dev start && cargo nextest run -p canopy-reporting --test cms64_test --run-ignored only— integration test passes (zero-rows expected pre-claims). -
Manual smoke: POST 3 expenditure rows with different FFP rates, GET
/v1/reporting/cms-64?fy=2026q3, confirm CSV groups correctly. -
cargo xtask validate— full battery green.
Documentation Updates
-
Service Catalog — canopy-medicaid + canopy-reporting routes + tables
-
CHANGELOG.adoc— entry under== Unreleased/=== Changed, noting FY26 zero-expenditure expected -
docs/modules/ROOT/pages/services/canopy-reporting.adoc— CMS-64 path documentation -
docs/modules/ROOT/pages/federal-requirements.adoc— CMS-64 row update -
Plan archive: move to
plans/archive/post-merge