Files
stack/docs/superpowers/specs/2026-09-09-code-family-longitudinal-design.md
kert 041378b16b fix(cli): pfs families --write prints the summary by default (refs #699)
The per-key dump ("CCM: 99490(base) ...") used to always print — useless
once derived families number in the thousands. --write now prints only
the summary line by default; the full dump is --verbose. refresh_from
and printing moved outside the duckdb_batch block (they don't need the
write lock open) — refresh_from now reads the freshly published
replica, the same pattern stack llm restamp uses.

Also: docs/superpowers/specs/2026-09-09-code-family-longitudinal-design.md
Decision 2 gets one sentence noting lineage's third source (cpt, the
AMA's own change years — effective years) and that a cpt_changed year
anchors an RVU event the way an FR event does.
2026-09-09 21:20:20 -04:00

105 lines
14 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.
# Code families as first-class objects — element model, lineage anchors, longitudinal chat, legal epidemiology
Tracker: milestone P49 (this spec) and milestone P50 (outcome series). Builds on P48 (`pfs/families.py`, `codes` chunk metadata, valuation event), P47 (dockets/seals), P40/P41 (FR + eCFR jump links), P35 (closed-vocabulary RAG tagging).
## Why
When the physician fee schedule decides coding and payment for a service, the unit of decision is a **code** or a **code family**: a bundle of logical elements that say what the service is and is not. CMS says this itself in the Medicare telehealth review process, where Step 3 is literally "Review the elements of the service as described by the HCPCS code" (CY2026 NPRM, 90 FR 32389, item `2KVJ2HKX` ¶398). The corpus already holds every layer of the record for those elements — 84 PFS rules with paragraph anchors back to CY2002, RVU files 20152026, ten comment dockets 20172026, 177 IOM chapters, 699 eCFR sections — but nothing ties the layers together *per code*. The chat can price a code (P48) but cannot answer "where did this code come from, what replaced what, how did the elements change, who objected, and what does the manual say".
The goal of P49 is that the chat, and the notebooks, can build a **longitudinal view of any code or code set payable under the PFS**: creation, predecessors and successors, element changes, valuation history, sub-regulatory guidance, and public reaction, each step anchored to a citable paragraph. P50 then joins that timeline to outcome series (utilization, BLS wages, industry markets) so the fee schedule can be studied as law with effects — legal epidemiology.
## What the corpus already shows (measured 2026-09-09)
**The "elements" construct is explicit in the record.**
- CPT 99490 was adopted for CY2015 in place of a CMS G-code; the FR prints its descriptor as a stem plus enumerated required elements — two or more chronic conditions expected to last ≥12 months; conditions placing the patient at significant risk; comprehensive care plan established/implemented/revised/monitored (CY2015 final rule `DE2VH9PD` ¶12441247, 79 FR 67716). The commenter-vs-CMS argument was about a single element (per calendar month vs per 30 days, ¶12481251).
- HCPCS G0556G0558 (APCM, CY2025) print as a stem plus ~20 elements and `++` sub-elements: consent, initiation visit, 24/7 access, continuity, alternative care delivery, comprehensive care management, electronic care plan, transitions, community coordination, asynchronous communication, population data analysis, risk stratification, performance measurement (CY2025 final rule `JJ6AM5HJ` ¶11631185, 89 FR 9786497865). G0557 is defined *by reference* to G0556's elements plus a population element (¶1186).
- The telehealth process (first printed in the CY2024 rules, `44F3F6VU`/`JJV3R692`, and repeated every year since) is a three-step element test: Step 1 separately payable under the PFS; Step 2 subject to §1834(m), i.e. is the service "in whole or in part, inherently a face-to-face service"; Step 3 each element capable of being furnished via an interactive telecommunications system as defined in § 410.78(a)(3) (`2KVJ2HKX` ¶394, ¶396, ¶398).
**Lineage is recoverable from the record but only by hand today.**
- G-code → CPT 99490 (CY2015 final `DE2VH9PD` ¶12491251).
- G2058 (RVU file 2020 only) → CPT 99439 (2021): "descriptor identical to G2058", temporary crosswalk value (CY2021 final `YBM4IZUS` ¶1578, 85 FR 84639; ¶686, ¶2369).
- G2064/G2065 (20202021) → CPT 99424/99426 (2022), including the knock-on into the RHC/FQHC G0511 rate (CY2022 final `JE7KYBW3` ¶1100/¶1111, 86 FR 65118; CY2023 final `MZ24MX5S` ¶1305).
- 9944199443 deleted by the CPT panel, last in the RVU file 2024, replaced by 9800898015 / 98016 (CY2025 rules `XFGGRBDH`/`JJ6AM5HJ` ¶489/¶765).
- Status changes visible in `pfs.rvu`: 99487/99489 B→A, 99497/99498 I→A, 99091 B→A, G2211 B→A.
- 99490 is discussed in 27 distinct rule documents from CY2015 final to CY2027 proposed; 284 rule chunks already carry `codes ∋ 99490`.
**Public reaction is in the corpus, in two forms.**
- Dockets CMS-2017-0092 … CMS-2026-2377 (10 dockets, 918,484 comment chunks). Abstract-level hits per docket: CCM 5/19/6/11/24/2/5/10/174/27; APCM 19 (CMS-2024-0256, the year it was proposed), 25, 5; G2211 3,353 in CMS-2023-0121 (the year it was activated), then 146 and 190. Telehealth: ~1013k per docket 20202023.
- Before 2017 the only public-comment record is the FR itself: the `Comment:` / `Response:` paragraph pairs (e.g. `DE2VH9PD` ¶1244/¶1249, ¶1255) — a proxy series back to CY2002.
**Sub-regulatory guidance is present but unlinked.** 177 IOM chapters (MCPM ch. 12 `SPJB4FX2` and MBPM ch. 15 `YQFGB6JS` = 988 corpus chunks), 699 eCFR sections (§ 410.78 telehealth `SAJSGG28`, § 410.26 incident-to `Q78GP4Z9`, § 414.22 `T7PXFV4S`), MLN909188 CCM/PCM/APCM booklet (`PV8APQ4A`). None carries a `code:` tag; `comments` and `corpus` chunks carry no `codes` metadata (0 of 1.15 M), only `rules` do (29,595 of 67,340).
**Scale of "any code".** `pfs.rvu` has 18,641 distinct HCPCS over 20152026; 8,572 are status A/R/T in CY2026 (7,087 in 2015). The hand registry in `pfs/families.py` covers 5 families / 17 codes.
## Decisions
1. **Elements are a closed, typed vocabulary**, not free text. Element *types* are fixed in code; element *values* are a curated list that grows by review (P35 pattern: local model proposes from a closed list, humans accept). Types: `actor` (clinical staff directed by / personally by physician or QHP; RHC/FQHC), `time` (threshold minutes, "each additional"), `period` (calendar month, 30 days, per visit, 14/7 days post-discharge), `population` (≥2 chronic conditions ≥12 months; QMB; high risk), `activity` (consent; care plan; 24/7 access; transitions; medication reconciliation; …), `modality` (face-to-face; interactive telecommunications; audio-only; asynchronous), `relation` (add-on to; do-not-report-with; replaces; defined-by-reference-to; crosswalk-valued-to), `setting` (facility/non-facility; place of service), `billing` (one practitioner per month; initiating visit).
2. **Lineage is a table of dated events with anchors**, derived from two independent sources that must agree or be flagged: (a) `pfs.rvu`/`rvu_proposed` (first year, last year, status transitions, descriptor changes, RVU changes beyond a threshold) and (b) FR paragraphs whose text carries the code and an event verb (create/adopt/replace/delete/crosswalk/bundle/telehealth-list). Every event row stores `item_key, p_id, page` so the chat cites a paragraph, never a table. Lineage now has a third source, `cpt` — the AMA CPT Changes book's own "Effective January 1, YYYY" statement per code, ingested as `pfs.cpt_reference`/`pfs.cpt_code` change years, which are effective years rather than paragraph-anchored FR events; a `cpt_changed` year anchors an RVU event (an appearance/status change with no FR paragraph nearby) the same way an FR event does.
3. **Families are derived, not hand-listed.** A family = codes sharing a stem and differing in `time`/`actor`/`population` elements, plus CMS's own "code family" language in the FR ("The Chronic Care Management code family will be resurveyed", `YBM4IZUS` ¶1578) and `relation` elements (add-on/replaces). The five hand families become the regression fixture.
4. **Anchors live in two places, one per consumer.** Items get `code:<hcpcs>` and `family:<key>` tags in bib/Zotero (browsable, syncable); chunks get `codes`, `families`, and `elements` metadata in pgvector (retrievable). `comments` and `corpus` are re-indexed once with `--force` to stamp them, as P48 did for `rules`.
5. **Longitudinal retrieval is era-balanced, not recency-blended.** A "history of X" question retrieves per era (rule year) with a cap per era, so CY2015 and CY2027 both appear; the P34 recency blend stays for ordinary questions.
6. **Chat presents a timeline the way P48 presents a valuation**: a `lineage` SSE event before the first token, rendered as a table (year, event, codes, anchor link), and a prompt block whose rows carry bracketed labels the model must cite.
7. **Self-hosted inference only** for element extraction and stance tagging (llm module rule; qwen2.5 on the GPU pool).
8. **Outcome series (P50) are separate**: they never write into bib; they join on the lineage table's effective dates.
## Architecture
```
FR rules (fr_anchors) ─┐
RVU files (pfs.rvu) ──┼─► element extractor ─► pfs.code_element (code, year, type, value, anchor)
HCPCS/CPT descriptors ─┘ │
family deriver ─► pfs.code_family (key, code, role, since, until, anchor)
rvu first/last/status ─┐ ▼
FR event paragraphs ───┴─► lineage builder ─► pfs.code_event (code, year, kind, from/to, item_key, p_id, page)
┌────────────────────┼─────────────────────┐
▼ ▼ ▼
bib tags code:/family: pgvector metadata notebooks
(rules, comments, IOM, codes/families/elements (timeline, reaction series)
eCFR, MLN) on all 3 collections
└────────────────────┬─────────────────────┘
chat: detect codes → lineage(codes) + era-balanced retrieve → `lineage` SSE + prompt block → cited answer
P50: event calendar ─► BLS OEWS/CES, market series, Part B utilization ─► event-study notebook
```
## Components (P49)
### `pfs/elements.py` — element model
Pure dataclasses + the closed vocabulary (`ElementType` enum; `VOCAB: dict[ElementType, tuple[str, ...]]` seeded from the 99490, 99487/99489, 99491/99437, 99439, 99495/99496, 99497/99498, 9942499427, G0556G0558, 9944199443/9800898016, G2211 descriptors in the corpus). Descriptor parser for the deterministic parts: minutes, "each additional", period words, code references inside parentheticals ("Use in conjunction with 99490", "Do not report … 99487, 99489, 99491").
### `pfs/extract.py` — extractor
Input: a descriptor text plus the FR paragraph run that prints it (stem paragraph followed by element paragraphs, as in `DE2VH9PD` ¶12441247 and `JJ6AM5HJ` ¶11631185). Deterministic parse first; then the local model maps each element paragraph to a vocabulary value or proposes a new value (queued for review, never auto-added). Output rows into DuckDB `pfs.code_element` with the anchor. Runs from `stack pfs elements --code … | --family … | --all-payable`.
### `pfs/lineage.py` — events
Sources: (a) `pfs.rvu`/`rvu_proposed` diffs per code per year (appears, disappears, status change, descriptor change, |ΔRVU| > 10 %); (b) `fr_anchors` paragraphs where `codes ∋ code` and an event pattern matches (`finaliz…creat`, `replace`, `delet`, `crosswalk`, `bundled`, `telehealth services list`). Emits `pfs.code_event`. Cross-check rule: an RVU-file appearance without an FR paragraph within ±1 rule year, or vice versa, is flagged `unanchored` for review. The 5 worked lineages above are the fixture.
### `pfs/families.py` — derive
Keep the API (`FAMILIES`, `detect_codes`, `family_of`) but source `FAMILIES` from `pfs.code_family` at import time when the replica is present, falling back to the hand list. Family key = stem slug; synonyms = descriptor stems + FR family phrases.
### `bib/tag.py` + `bib/sync.py` — anchors on items
New namespaces `code:` and `family:`; tagger walks rules (via `fr_anchors.text`), comments (chunk text via pgvector or abstract), IOM chapters (attachment text), eCFR sections, MLN items. Nightly zotero-sync carries them (P38 tag-merge rules apply; `sup:` tags stay load-bearing).
### `llm/chunk.py` + `llm/index.py` — anchors on chunks
`codes` metadata on all three collections; add `families` (space-joined keys) and `elements` (space-joined `type=value` slugs when a chunk mentions a vocabulary value). One-time `stack llm index --collection all --force`.
### `bib/reaction.py` — reaction series
Per (family, docket): comment count, share of docket, stance (support/oppose/modify — closed vocab, local model, P35 pipeline), top organisations (`org:` tags exist). Pre-2017: `Comment:`/`Response:` pairs from `fr_anchors` where the pair mentions the family's codes. Output table `pfs.code_reaction` and a notebook.
### `llm/evidence.py` + `llm/rag.py` + `llm/web/chat.html` — longitudinal chat
`lineage_evidence(question)` → events + element diffs for the detected codes; `retrieve(..., mode="timeline")` era-balanced; `lineage` SSE event; prompt block; system prompt: cite the event label for every dated claim. UI: timeline table with FR jump links (P40) and eCFR links (P41).
### Evaluation
Golden questions with expected anchors: "history of CCM coding and payment", "what replaced G2058", "how did the APCM elements differ from CCM", "when were audio-only E/M codes payable and why did 9944199443 end", "what did commenters say about G2211 in 2023 vs 2025", "does 99490 pass telehealth Step 3". Pass = every expected `(item_key, p_id)` appears in sources and the prose cites the event labels.
## P50 — legal epidemiology (scoped, not designed)
- `pfs.code_event` supplies the exposure calendar: effective date = 1 January of the rule year (or mid-year for correction rules), exposure = code/family becomes payable, revalued, or ends.
- Outcome series to ingest, each with its own module and provenance in bib: Medicare Physician & Other Practitioners PUF (utilization and allowed amounts per HCPCS, 2013), BLS OEWS (occupation wages: 29-1xxx physicians, 29-2xxx/31-9xxx clinical support), BLS CES (health-care employment), market series (sector ETFs; telehealth/RPM/care-management tickers).
- Event-study notebook: pre/post windows around each event with never-treated code sets as controls; output effect sizes with confidence bands, never causal language in the chat.
## Out of scope (P49)
Locality pricing, OPPS, MA/commercial coding, CPT copyrighted long descriptors beyond what the FR prints, cloud LLM APIs.