Files
stack/docs/superpowers/specs/2026-08-12-cy2026-pfs-proposed-rule-design.md

229 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: 13 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/4997949980.
- **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 4926650481** (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.