All checks were successful
CI / lint (push) Successful in 34s
CI / notebooks-smoke (push) Successful in 1m24s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / notebooks (push) Successful in 1m4s
Infra CI / zotero (push) Successful in 14s
Infra CI / docs (push) Successful in 1m25s
Infra CI / api (push) Successful in 1m1s
Infra CI / llm (push) Successful in 1m7s
Infra CI / mc (push) Successful in 16s
Package Supply Chain / pkg-supply-chain (push) Successful in 1m19s
Deploy / report (push) Successful in 13s
CI / test (push) Successful in 14m17s
163 lines
9.0 KiB
Markdown
163 lines
9.0 KiB
Markdown
# CY2027 PFS Proposed Rule (CMS-1848-P) — capture, ingestion, APM analysis
|
||
|
||
**Status:** Designed — repeat of the P36 pattern per standing directive
|
||
**Date:** 2026-08-13
|
||
**Part of:** P37 (CY2027 PFS Proposed Rule)
|
||
**Related:** P36 (#588–#597, merged 53b97b8), #607 (rvu_proposed column alignment — folded in here), spec `2026-08-12-cy2026-pfs-proposed-rule-design.md`
|
||
|
||
## Goal
|
||
|
||
Run the CY2027 PFS proposed rule (CMS-1848-P, docket CMS-2026-2377, FR doc
|
||
`2026-14327`, published 2026-07-16) through the pipeline P36 built: capture
|
||
files, ingest proposed parameters and Addendum B with provenance, farm the
|
||
comments (period is **open** — initial pull now, scheduled re-farm after
|
||
close), index the rule text for RAG, extend the QPP/Advanced-APM registry to
|
||
PY2027, and analyze financial changes in a notebook. Tracked as Gitea
|
||
milestone P37.
|
||
|
||
## Current state (verified 2026-08-13)
|
||
|
||
- CY2027 NPRM published 2026-07-16: FR `2026-14327`, CMS-1848-P, reg.gov
|
||
docket CMS-2026-2377 (per the FR API). Not yet in bib; no comments pulled;
|
||
comment period open (close date to be transcribed from the DATES block).
|
||
- P36 infrastructure exists and is merged: `ProposedRule` model,
|
||
`pfs.nprm` loader + `pfs.rvu_proposed` (keyed by `cms_rule_id` —
|
||
designed for multi-rule), `cms.ingest_log`, `qpp` registry,
|
||
`llm.source.iter_rule_docs` + `rules` pgvector collection,
|
||
`rex.comments.classify.DOCKET_RULES`, notebook pattern.
|
||
- **No CY2027 final rule exists**, so there is no `RULES[2027]` to hang a
|
||
`proposed` block on — and `RuleYear` requires final fields
|
||
(`conversion_factor` is load-bearing for `pfs.pipe`'s RVU-file
|
||
selection). The registry needs a home for proposed-only years.
|
||
- #607 is open: `pfs.rvu_proposed` column names deviate from `pfs.rvu`
|
||
(`modifier`/`mod`, `nonfac_pe_rvu`/`non_fac_pe_rvu`); fixing it before a
|
||
second consumer cycle joins against the table halves the migration cost.
|
||
|
||
## Design deltas vs P36
|
||
|
||
### A. Proposed-only registry home (`pfs.rules`)
|
||
`PROPOSED: dict[int, ProposedRule]` module-level registry for years whose
|
||
final rule hasn't published, plus `proposed_for(year) -> ProposedRule | None`
|
||
returning `RULES[year].proposed` when the year exists in `RULES`, else
|
||
`PROPOSED.get(year)`. When the CY2027 final rule ships, `RULES[2027]` is
|
||
created and the entry migrates onto it (P36 shape); `proposed_for` keeps
|
||
consumers stable across that move. `RuleYear` itself is untouched — no
|
||
optional-final-fields surgery, `pfs.pipe` semantics unchanged.
|
||
|
||
### B. Column alignment first (#607)
|
||
Rename `pfs.rvu_proposed` columns to match `pfs.rvu` (`mod`,
|
||
`non_fac_pe_rvu`, `fac_pe_rvu` — audit the full set against `_RVU_COLUMNS`)
|
||
in `pfs.nprm`, the P36 notebook's SQL, and tests, then reload the table
|
||
(delete-and-reload makes this cheap). Done before any CY2027 consumer exists.
|
||
|
||
### C. Multi-rule NPRM ingest
|
||
`load_rvu_proposed` is parameterized by rule already; generalize discovery:
|
||
`_discover_addendum_b(tag)` takes the supplement tag (`sup:2027_PFS_NPRM`),
|
||
and `dev/scripts/ingest_pfs.py --nprm` loads every configured
|
||
(tag, cms_rule_id) pair — CY2026 and CY2027 partitions coexist keyed by
|
||
`cms_rule_id`, each with its own `ingest_log` row and FR citation from
|
||
`proposed_for(year)`.
|
||
|
||
### D. Open comment period
|
||
Initial farm now (`stack bib fetch-pfs-comments` discovers CMS-1848-P →
|
||
resolves CMS-2026-2377 → pulls comments to date), then backfill/extract/
|
||
index. A **post-close re-farm** (same commands — everything is resumable/
|
||
incremental) is scheduled after the DATES close date; the P37 milestone
|
||
stays open until that re-farm's counts are posted. A local cron reminder
|
||
fires the re-farm; the Gitea issue carries the close date.
|
||
|
||
### E. Everything else is a repeat
|
||
- Rule-text capture: `fetch_fr_attachments.py --key <2027 item>` after
|
||
`discover-pfs-rules` registers/tags the item (`cms-rule:CMS-1848-P`).
|
||
- Addenda: CMS-1848-P detail page → `data/cms/pfs_nprm/2027/`; Addendum B
|
||
attached + tagged `sup:2027_PFS_NPRM`, `module:pfs`, `file:rvu`,
|
||
`year:2027` (fetch script gains a `--year 2027` mode instead of a clone).
|
||
- `DOCKET_RULES["CMS-2026-2377"] = "CMS-1848-P"` + regression test.
|
||
- CY2027 `ProposedRule` values transcribed from the captured text with
|
||
pincites — never memory; comment-close date transcribed from DATES.
|
||
- `qpp`: PY2027 `QppYear` (current law) + `QppProposed` for CMS-1848-P
|
||
changes, both-file citations; re-verify PY2026 values only where the
|
||
CY2027 NPRM restates them.
|
||
- Rule text → `rules` collection (`stack llm index --collection rules
|
||
--key <item>`); comments → `comments` collection scoped to the docket.
|
||
- Notebook `notebooks/cy2027_pfs_proposed_rule.py`: CY2026-final →
|
||
CY2027-proposed CF walk (no final column — labeled "final rule pending");
|
||
RVU deltas `rvu_proposed[CMS-1848-P]` vs CY2026 final `pfs.rvu`;
|
||
Advanced-APM section from `qpp` PY2027; provenance section. Same
|
||
registry-sourced/citation-per-figure rules the P36 review enforced.
|
||
|
||
## Milestones and issues
|
||
|
||
### P37: CY2027 PFS Proposed Rule — capture, ingest, APM analysis
|
||
|
||
1. `pfs: align rvu_proposed column names with pfs.rvu` — existing #607, added to P37 (delta B)
|
||
2. `comments: register CMS-2026-2377 ↔ CMS-1848-P mapping` — delta E
|
||
3. `bib: discover + capture CY2027 rule text (2026-14327) into fr_downloads + bib` — delta E
|
||
4. `pfs: capture CMS-1848-P proposed addenda (sup:2027_PFS_NPRM)` — delta E
|
||
5. `pfs: PROPOSED registry + proposed_for helper, CY2027 NPRM parameters + pincites` — delta A
|
||
6. `pfs: multi-rule NPRM ingest (CY2026 + CY2027 partitions)` — delta C
|
||
7. `qpp: PY2027 registry + CMS-1848-P proposed changes` — delta E
|
||
8. `comments: initial farm of CMS-2026-2377 (fetch/backfill/extract/index)` — delta D
|
||
9. `comments: post-close re-farm + re-index of CMS-2026-2377` — delta D (blocks milestone close; scheduled)
|
||
10. `notebooks: CY2027 proposed-rule financial changes + Advanced APM notebook` — delta E
|
||
|
||
Dependency order: 1 first (schema); 2–4 independent; 5 needs 3; 6 needs
|
||
1+4+5; 7 needs 3; 8 needs 2; 9 after close date; 10 needs 5+6+7.
|
||
|
||
## Testing
|
||
|
||
Same bars as P36: pytest 99% coverage, stdlib mocking; exact-value tests
|
||
pin transcribed CY2027 parameters; multi-rule loader tests cover both
|
||
partitions + the renamed columns; qpp structural tests extend to PY2027;
|
||
notebook through layout tests + marimo check + headless integration; RAG
|
||
spot-checks on the docket and rule-text chunks. Regression: P36 notebook
|
||
still green after the #607 rename.
|
||
|
||
## Out of scope
|
||
|
||
- CY2027 OPPS/other rules; the CY2027 *final* rule (a future milestone
|
||
creates `RULES[2027]` and migrates the PROPOSED entry).
|
||
- Comment classification/sentiment (P30).
|
||
- Cloud LLM usage (llm stays local-only).
|
||
|
||
## P37 build outcomes (2026-08-14)
|
||
|
||
Issues #607–#616 + interstitial #620, milestone P37 (10 commits,
|
||
d726dca..a75b4c7, branch `p37-cy2027-rule` in a worktree — main checkout
|
||
was concurrently owned by #618). #615 and the milestone remain open until
|
||
the scheduled post-close re-farm.
|
||
|
||
- **CY2027 NPRM parameters** (91 FR 43842, item 5ITGVDJV, all values
|
||
byte-verified by two agents): proposed CF 32.8409 non-QP / 33.1693 QP,
|
||
anesthesia 20.2143/20.4165, BN 1.0053, telehealth $32.65, comment close
|
||
**2026-09-14**. Housed in the new `PROPOSED` registry via
|
||
`proposed_for(year)` (no RULES[2027] until the final rule). The NPRM's
|
||
own Summary section mislabels its CY2026 baselines (verbatim carry-over
|
||
of CY2026's *proposed* QP CF); documented in notes, resolved via the RIA.
|
||
- **Multi-rule ingest**: `pfs.rvu_proposed` now partitioned by cms_rule_id
|
||
(CMS-1832-P 14,169 + CMS-1848-P 14,518 rows) with per-rule ingest_log
|
||
provenance; columns aligned with `pfs.rvu` (#607) behind a durable
|
||
schema-migration guard.
|
||
- **QPP correction (#620, adjudicated)**: P36 had mis-keyed the statutory
|
||
threshold schedule (payment years read as performance years) —
|
||
QPP[2023]/[2024] corrected to 50/35 (partial 40/25); QPP[2026] was
|
||
superseded by CAA 2026 (50/35, partial 40/25, **3.1% incentive revived
|
||
for payment year 2028**); [2025]/[2027] confirmed correct at 75/50. Both
|
||
notebooks self-corrected (live registry reads); the P36 notebook's
|
||
narrative and partition filters were fixed in the final-review wave
|
||
(d3750ce). CMS's own drafting error at 414.1430(b)(2)(i)(D) documented.
|
||
- **Comment farm**: initial pull complete — **2,727 comments to date**
|
||
(period open); extraction/indexing follow incrementally; post-close
|
||
re-farm scheduled via host crontab 2026-09-15 03:17 →
|
||
`dev/scripts/refarm_cms_2026_2377.sh` (cron-hardened: PATH, honest
|
||
failure reporting), which posts final counts to #615.
|
||
- **Notebook**: `notebooks/cy2027_pfs_proposed_rule.py` — CF walk
|
||
(final pending), RVU deltas vs CY2026 final, payment-year-labeled APM
|
||
section incl. the CAA-2026 "not a clean sunset" story, provenance;
|
||
headless-validated via staged worktree copy.
|
||
- **Also landed en route**: #617 (FR text normalized at capture — first
|
||
live use: the CY2027 txt arrived clean), #618 (4 tracemalloc-verified
|
||
sqlite leaks fixed + `error::ResourceWarning` gate), #619 filed
|
||
(publish_replica symlink bug, workaround applied), #623 filed (docs
|
||
tag-list drift from controller tag ops).
|