docs(spec): CY2026 PFS proposed rule — capture, ingest, APM analysis (P36)

This commit is contained in:
kert
2026-08-12 10:48:18 -04:00
parent 2e22045ed2
commit 380e107a68

View File

@@ -0,0 +1,182 @@
# CY2026 PFS Proposed Rule (CMS-1832-P) — capture, ingestion, APM analysis
**Status:** Designed — approved in-session 2026-08-12
**Date:** 2026-08-12
**Part of:** P36 (CY2026 PFS Proposed Rule)
**Related:** P30 (comment analysis, #254–#256 still open), P33/P34 (llm module), #514 (lake PFS build-out)
## Goal
Bring the CY2026 PFS proposed rule (CMS-1832-P, docket CMS-2025-0304, FR doc
`2025-13271`, published 2025-07-16) fully into the stack: capture its files,
process them into queryable tables, log calculation-parameter ingestion with
provenance, finish farming its comments, ingest the rule text into the RAG
index, and analyze the financial changes — with particular focus on Advanced
APM requirement changes — in a marimo notebook. Work is tracked as Gitea
milestone P36 and landed via version control like prior milestones.
## Current state (verified 2026-08-12)
- The rule is already discovered in bib (item `2KVJ2HKX`, tags
`cms-rule:CMS-1832-P`, `reg-docket:CMS-2025-0304`); 14,092 comments are
fetched, but only ~2,905 have `.state/comments/.../combined.md` extractions
and none are embedded in pgvector.
- `src/rex/comments/classify.py` hard-codes docket CMS-2025-0304 as the CY2026
**OPPS** rule (CMS-1834-P). Verified against the regulations.gov docket
abstract: CMS-2025-0304 is the **PFS** NPRM (CMS-1832-P). The label is wrong
(skin-substitute payment moved into the PFS rule in CY2026, the likely source
of confusion).
- `pfs.rules.RULES[2026]` holds only **final**-rule parameters (non-APM CF
33.4009, APM CF 33.5675, 90 FR 101174). No proposed-rule parameters exist
anywhere.
- `data/fr_downloads/` lacks `2025-13271.{pdf,txt}`; `llm.source` can index
comments and bib corpus but has no rule-text source; no docket beyond
CMS-2017-0092 is indexed.
- There is no ingest-log table (only JSONL logs + pincites) and no QPP /
MIPS / Advanced-APM model in the repo (`cf_qp` is the only APM-aware field).
## Architecture
```
federalregister.gov ──> data/fr_downloads/2025-13271.{pdf,txt} ──┐
CMS PUFs (NPRM addenda) ──> bib/Zotero (sup:2026_PFS_NPRM) ──┐ │
│ │
pfs.pipe (existing, final files) │ │
pfs.nprm loader (new) <─────────────────┘ │
│ │
v v
aco.duckdb + lake: pfs.rvu_proposed, cms.ingest_log llm.source.iter_rule_docs (new)
│ │
v v
pfs.rules.ProposedRule + src/qpp registry pgvector `rules` collection
│ pgvector `comments` (docket indexed)
v
notebooks/cy2026_pfs_proposed_rule.py (financial deltas + Advanced APM)
```
## Components
### 1. Docket-identity fix (`rex.comments`)
Correct `classify.py`'s CMS-2025-0304 ↔ CMS-1834-P mislabel to CMS-1832-P and
add a `DOCKET_RULES` mapping constant with a regression test asserting it
agrees with bib's `reg-docket:`/`cms-rule:` tag pairs for the dockets we track.
### 2. Rule-text capture (`bib` / fetch script)
Download FR doc `2025-13271` (PDF + full-text TXT) into `data/fr_downloads/`
via `dev/scripts/fetch_fr_attachments.py` and attach both to bib item
`2KVJ2HKX`. Same for the final rule `2025-19787` (needed for
proposed-vs-final deltas; the bib item `NHRGIHGD` exists but the text files
don't).
### 3. NPRM addenda capture (`pfs`)
Download the CMS-1832-P public-use files (proposed Addendum B RVUs; specialty
impact table if published as data) and register them in bib/Zotero with tags
`sup:2026_PFS_NPRM`, `module:pfs`, `file:rvu`, following the existing
`sup:2026_PFS_FR` convention so `pfs.pipe`'s Zotero-driven discovery can see
them without picking them up as final-rule files.
### 4. Proposed-parameter registry (`pfs.rules`)
New `ProposedRule` pydantic model hung off `RuleYear` as
`proposed: ProposedRule | None`: `cms_rule_id`, `fr_document_number`,
`federal_register_citation`, `published`, `comment_close`,
`conversion_factor`, `cf_qp`, `anesthesia_cf`, `anesthesia_cf_qp`,
`budget_neutrality_adjustor`, `telehealth_originating_site_fee`, `notes`.
CY2026 values are transcribed from the captured NPRM text (not from memory)
and cited with a pincite to the bib attachment. Existing final-rule fields
and `pfs.pipe`'s CF-matched RVU-file selection are untouched.
### 5. Proposed Addendum B loader (`pfs.nprm` + ingest script)
A dedicated loader (not `pipe.load_all`) that reads the NPRM Addendum B from
bib storage into DuckDB table `pfs.rvu_proposed` (same column shape as
`pfs.rvu` plus `cms_rule_id`), delete-and-reload per rule id, published to the
replica and the DuckLake `pfs` schema alongside the existing tables, via
`dev/scripts/ingest_pfs.py` (a `--nprm` step) under the `duckdb_batch` lock
preflight.
### 6. QPP / Advanced APM module (`src/qpp`)
New namespace package + `stack[qpp]` extra. Registry-style like `pfs.rules`:
`QppYear` per performance year with QP thresholds (payment-amount %,
patient-count %), partial-QP thresholds, Advanced APM criteria parameters
(CEHRT-use requirement, marginal-risk / total-risk financial standards,
revenue-based nominal-amount standard %, expenditure-based %), and payment-year
consequences (APM incentive % where applicable; CY2026+ QP CF differential by
reference to `pfs.rules`). Each year cites its FR/statutory source; CY2026
carries both current-law values and what CMS-1832-P proposed to change
(`proposed: QppProposed | None`). Pure data + small lookup API + tests; no
service, no ingestion.
### 7. Calculation-ingestion log (`cms.ingest_log`)
DuckDB table `cms.ingest_log` in `aco.duckdb` (and published to the lake):
`run_id, ingested_at, module, table_name, rule_id, source_file, sha256, rows,
fr_citation, pincite_key`. Writer helper in `src/cms/ingest_log.py` used by
`dev/scripts/ingest_pfs.py` (final + NPRM steps). Append-only; JSONL logs
(`src/cms/log.py`) continue unchanged.
### 8. Rule-text RAG ingestion (`llm`)
`iter_rule_docs(store, rule_keys | docket)` in `llm/source.py`: yields FR rule
full text (prefer the TXT attachment; fall back to PDF-extracted text) with
metadata `{doctype: "rule", cms_rule_id, fr_document_number, year, item_key}`.
Indexed through the existing incremental `index_docs` into a new pgvector
collection `rules`; `stack llm index --collection rules` CLI path. The chunker
is reused as-is (markdown/plain-text aware, 2000-char chunks).
### 9. Comment-farm completion + indexing (ops)
Resumable batches, in order: `stack bib backfill-comments` scoped to
CMS-2025-0304 → `stack comments extract` / `extract-ocr` for the remaining
~11k → verify `_index.csv` counts → `stack llm index --collection comments
--docket CMS-2025-0304` and `--collection rules` for the CY2026 rule texts on
the Ollama host pool. Code changes only where gaps surface; progress and
final counts recorded on the Gitea issue.
### 10. Financial-changes notebook (`notebooks/cy2026_pfs_proposed_rule.py`)
Marimo, narrative style of `skin_sub_budget_neutrality.py`, reading
`connect.ducklake()` + the registries:
- CF walk: CY2025 → CY2026 proposed → CY2026 final, including the first-ever
QP / non-QP CF split and anesthesia CFs; BN adjustor decomposition.
- RVU-level deltas: `pfs.rvu_proposed` vs CY2025 `pfs.rvu` (and vs CY2026
final `pfs.rvu`): winners/losers by HCPCS, specialty-level impact.
- **Advanced APM section**: QP threshold and financial-risk-standard changes
proposed in CMS-1832-P vs current law (from `src/qpp`), the QP CF
differential in dollars for example services, and what was finalized.
- Every figure carries its FR citation; charts follow the dataviz guidance.
Registered with the notebook quality gates (nb-watcher + nightly integration).
## Milestones and issues
### P36: CY2026 PFS Proposed Rule — capture, ingest, APM analysis
1. `comments: fix CMS-2025-0304 docket↔rule mapping (OPPS→PFS)` — component 1
2. `bib: capture CY2026 PFS rule text (2025-13271, 2025-19787) into fr_downloads + bib` — component 2
3. `pfs: capture CMS-1832-P proposed addenda into bib/Zotero (sup:2026_PFS_NPRM)` — component 3
4. `pfs: ProposedRule registry with CY2026 NPRM parameters + pincites` — component 4
5. `pfs: NPRM Addendum B loader → pfs.rvu_proposed (duckdb + lake)` — component 5
6. `qpp: module scaffold + QP/Advanced-APM registry incl. CY2026 proposed changes` — component 6
7. `cms: ingest_log provenance table + writer, wired into ingest_pfs` — component 7
8. `llm: rule-text source + rules pgvector collection + CLI` — component 8
9. `comments: complete CMS-2025-0304 farm (backfill/extract/OCR) and index comments + rule text` — component 9
10. `notebooks: CY2026 proposed-rule financial changes + Advanced APM notebook` — component 10
Dependency order: 1–3 independent; 4 needs 2; 5 needs 3+4+7; 6 needs 2;
8 needs 2; 9 needs 1+8; 10 needs 4+5+6.
## Testing
- pytest under the 99% coverage bar, stdlib mocking only: `tests/rex/comments/`
(mapping regression), `tests/pfs/test_rules.py` (ProposedRule), new
`tests/pfs/test_nprm.py`, `tests/qpp/`, `tests/cms/test_ingest_log.py`,
`tests/llm/test_source.py` (rule docs), CLI tests for new flags.
- Cross-registry consistency tests: CY2026 `proposed` vs final CF fields both
present; `qpp` CY2026 references `pfs.rules` CFs without duplication drift.
- Notebook validated headlessly via `dev/scripts/nb_integration.py`; the
ingest scripts run end-to-end on the host before merge (row counts logged to
`cms.ingest_log`).
- RAG spot-check: retrieval from `rules` and `comments` collections returns
CY2026 chunks with correct metadata.
## Out of scope
- The CY2026 OPPS rule (CMS-1834-P) beyond fixing the mislabel.
- Comment classification/sentiment work (remains P30, #254–#256).
- MIPS scoring/measure modeling beyond the QP/Advanced-APM parameters above.
- Cloud LLM usage — the llm module stays local-only per project policy.
- Modeling every NPRM addendum; only Addendum B (RVUs) is tabled.