13 KiB
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, tagscms-rule:CMS-1832-P,reg-docket:CMS-2025-0304); 14,092 comments are fetched, but only ~2,905 have.state/comments/.../combined.mdextractions and none are embedded in pgvector. src/rex/comments/classify.pyhard-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/lacks2025-13271.{pdf,txt};llm.sourcecan 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_qpis 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_proposedvs CY2025pfs.rvu(and vs CY2026 finalpfs.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
comments: fix CMS-2025-0304 docket↔rule mapping (OPPS→PFS)— component 1bib: capture CY2026 PFS rule text (2025-13271, 2025-19787) into fr_downloads + bib— component 2pfs: capture CMS-1832-P proposed addenda into bib/Zotero (sup:2026_PFS_NPRM)— component 3pfs: ProposedRule registry with CY2026 NPRM parameters + pincites— component 4pfs: NPRM Addendum B loader → pfs.rvu_proposed (duckdb + lake)— component 5qpp: module scaffold + QP/Advanced-APM registry incl. CY2026 proposed changes— component 6cms: ingest_log provenance table + writer, wired into ingest_pfs— component 7llm: rule-text source + rules pgvector collection + CLI— component 8comments: complete CMS-2025-0304 farm (backfill/extract/OCR) and index comments + rule text— component 9notebooks: 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), newtests/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
proposedvs final CF fields both present;qppCY2026 referencespfs.rulesCFs 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 tocms.ingest_log). - RAG spot-check: retrieval from
rulesandcommentscollections 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_logprovenance (18 rows/2 runs) with sha256, FR citation, pincite. Column names deviate frompfs.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 →
Nonewith 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.