229 lines
13 KiB
Markdown
229 lines
13 KiB
Markdown
# 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.
|
||
|
||
## P36 build outcomes (2026-08-13)
|
||
|
||
Issues #588–#597, milestone P36 (17 commits, 4a9be6f..a444d75, branch
|
||
`p36-cy2026-rule`). Deviations and measurements vs the design:
|
||
|
||
- **Comment farm was already complete** — the design's "only ~2,905 of
|
||
14,092 extracted" understated prior work: all 2,905 attachment-bearing
|
||
comments had extractions; the remaining ~11.2k are body-only (abstract
|
||
fallback covers them), and the whole corpus (164k comments, 726,620
|
||
chunks incl. 62,486 for CMS-2025-0304) was already embedded post-P33.
|
||
#596 reduced to rule-text indexing.
|
||
- **Rules collection**: 5,769 chunks (2,254→2,208 NPRM CMS-1832-P,
|
||
3,611→3,561 final CMS-1832-F) after an inline-HTML-strip fix round and
|
||
force reindex; final-rule bib item gained `cms-rule:CMS-1832-F`.
|
||
- **pfs.rvu_proposed**: 14,169 rows (NPRM Addendum B), in aco.duckdb +
|
||
replica + DuckLake; `cms.ingest_log` provenance (18 rows/2 runs) with
|
||
sha256, FR citation, pincite. Column names deviate from `pfs.rvu`
|
||
(`modifier`/`mod`, `nonfac_pe_rvu`/`non_fac_pe_rvu`) — follow-up #607.
|
||
- **CY2026 NPRM parameters** (all source-verified at 90 FR 32352 et seq.):
|
||
proposed CF 33.4209 non-QP / 33.5875 QP, anesthesia 20.5728/20.6754,
|
||
BN adjustor 1.0055, telehealth originating-site fee $31.85, comment
|
||
close 2025-09-12.
|
||
- **qpp module**: QP thresholds 75%/50% (partial 50%/35% — patient-count
|
||
value is a disclosed inference), revenue nominal-risk standard 8%
|
||
(414.1415(c)(3)(i)(A), established 83 FR / 2018-24170); the
|
||
benchmark-based standard (c)(3)(i)(B) is stated nowhere in the corpus
|
||
as a bare percentage → `None` with notes. APM incentive sunset and the
|
||
first-year QP/non-QP CF split (payment year 2026) encoded with
|
||
citations at 90 FR 49928/49979–49980.
|
||
- **Citation correction found by final review**: `RULES[2026]`'s
|
||
final-rule citation had been "90 FR 101174" — that is the *OPPS* final
|
||
rule's citation; the PFS final rule is **90 FR 49266–50481** (CFs at
|
||
49961). Corrected in registry + notebook (a444d75). Design note: the
|
||
spec itself restated the wrong citation as current state; future rule
|
||
cycles should re-verify pre-existing registry citations against newly
|
||
captured text.
|
||
- **Interstitials landed on the branch**: #598 (test poisoned global
|
||
polars.DataFrame → duckdb replacement-scan cache corruption; full
|
||
suite 14,023 green post-fix), #599 (stale .git/hooks copies removed),
|
||
CVE bumps aiohttp 3.14.3 / pyasn1 0.6.4 / cryptography 50.0.0
|
||
(#581–#587 closed with per-CVE exploitability rationale).
|
||
- **Notebook**: `notebooks/cy2026_pfs_proposed_rule.py`, 5 sections, all
|
||
values registry/lake-sourced (thresholds interpolated after review
|
||
fix), 18/18 headless integration green. CMS Table 92 (specialty
|
||
impact) is a scanned graphic → clearly-labeled CPT-range proxy.
|