pfs: derive code families from elements and CMS's own "code family" language; hand registry becomes the fixture #687

Closed
opened 2026-09-09 14:30:04 +00:00 by kert · 3 comments
Owner

Spec docs/superpowers/specs/2026-09-09-code-family-longitudinal-design.md (pfs/families.py).

What. pfs.code_family(key, code, role, since, until, item_key, p_id): a family is a set of codes sharing a descriptor stem and differing only in time/actor/population elements, unioned with (a) relation elements (add-on-of, replaces, defined-by-reference-to) and (b) CMS's explicit family phrases in the FR — e.g. "The Chronic Care Management code family will be resurveyed during CY 2020" (YBM4IZUS ¶1578, 85 FR 84639). Roles: base, add-on, predecessor, successor, RHC/FQHC bundle (G0511 averages 99484/99487/99490/99491 + PCM, JE7KYBW3 ¶1100).

API stays. FAMILIES, detect_codes, family_of keep their signatures (P48 chat depends on them); FAMILIES loads from the replica when present and falls back to the current hand list (ACP, CCM, PCM, TCM, APCM — 17 codes) which becomes the regression fixture.

Done when the derived families reproduce the five hand families exactly, add their predecessors (G2058, G2064/G2065, the CY2013–14 CCM G-code) with since/until, and produce at least 20 further families across the 8,572 payable codes without human seeding (spot-check: E/M office visits 99202–99215 + G2211; RPM 99453–99458; audio-only 98008–98016).

Spec docs/superpowers/specs/2026-09-09-code-family-longitudinal-design.md (`pfs/families.py`). **What.** `pfs.code_family(key, code, role, since, until, item_key, p_id)`: a family is a set of codes sharing a descriptor stem and differing only in `time`/`actor`/`population` elements, unioned with (a) `relation` elements (add-on-of, replaces, defined-by-reference-to) and (b) CMS's explicit family phrases in the FR — e.g. "The Chronic Care Management code family will be resurveyed during CY 2020" (`YBM4IZUS` ¶1578, [85 FR 84639](https://www.federalregister.gov/citation/85-FR-84639)). Roles: base, add-on, predecessor, successor, RHC/FQHC bundle (G0511 averages 99484/99487/99490/99491 + PCM, `JE7KYBW3` ¶1100). **API stays.** `FAMILIES`, `detect_codes`, `family_of` keep their signatures (P48 chat depends on them); `FAMILIES` loads from the replica when present and falls back to the current hand list (ACP, CCM, PCM, TCM, APCM — 17 codes) which becomes the regression fixture. **Done when** the derived families reproduce the five hand families exactly, add their predecessors (G2058, G2064/G2065, the CY2013–14 CCM G-code) with `since/until`, and produce at least 20 further families across the 8,572 payable codes without human seeding (spot-check: E/M office visits 99202–99215 + G2211; RPM 99453–99458; audio-only 98008–98016).
kert added this to the P49: Code families as first-class objects — element model, lineage anchors, longitudinal chat milestone 2026-09-09 14:30:04 +00:00
kert added the pfs label 2026-09-09 14:30:04 +00:00
Author
Owner

Implemented on branch worktree-p49-code-family-foundation (Task 8 + final review): src/pfs/families.py keeps the P48 API (FAMILIES, detect_codes, family_of, find_codes); the literal is now HAND_FAMILIES, FAMILIES is refreshed in place by refresh_from(con) from pfs.code_family (hand families are a minimum — never dropped). derive_families = connected components over: addon-of / defined-by-reference-to relation elements, single-target replaces/replaced_by events, and stem-token Jaccard ≥ 0.5 with a non-empty identical activity set (not-with, crosswalk, multi-code lists excluded — they collapsed ACP+CCM+PCM into one component on the first live run). Components spanning two hand families are split by BFS from each hand seed with a warning. Derived keys keep description word order and are made unique on collision; non-code members are rejected. CLI: stack pfs families [--write].

Live run: 17,202 rows / 17,187 keys; the five hand families:

ACP:  99497(add-on) 99498(base)
APCM: G0556 G0557 G0558 (base)
CCM:  99437 99439(predecessor) 99487(add-on) 99489(add-on) 99490 99491 G2058(predecessor) + G0502 G0503
PCM:  99424(predecessor) 99425 99426(add-on) 99427
TCM:  99495 99496

ACP/TCM/APCM/PCM equal the hand lists; CCM = hand list + G2058 as intended, plus G0502/G0503 — a false merge caused by a long background paragraph absorbed as G0502's descriptor (new issue filed; not a family-rule defect). Roles come from events/elements (99424 shows predecessor because two replaced_by-shaped FR events exist for it — membership is right, the role label needs the same earliest-event rule as #686).

Not yet wired into the chat: #699.

Implemented on branch `worktree-p49-code-family-foundation` (Task 8 + final review): `src/pfs/families.py` keeps the P48 API (`FAMILIES`, `detect_codes`, `family_of`, `find_codes`); the literal is now `HAND_FAMILIES`, `FAMILIES` is refreshed in place by `refresh_from(con)` from `pfs.code_family` (hand families are a minimum — never dropped). `derive_families` = connected components over: `addon-of` / `defined-by-reference-to` relation elements, single-target `replaces`/`replaced_by` events, and stem-token Jaccard ≥ 0.5 with a non-empty identical activity set (`not-with`, `crosswalk`, multi-code lists excluded — they collapsed ACP+CCM+PCM into one component on the first live run). Components spanning two hand families are split by BFS from each hand seed with a warning. Derived keys keep description word order and are made unique on collision; non-code members are rejected. CLI: `stack pfs families [--write]`. **Live run:** 17,202 rows / 17,187 keys; the five hand families: ``` ACP: 99497(add-on) 99498(base) APCM: G0556 G0557 G0558 (base) CCM: 99437 99439(predecessor) 99487(add-on) 99489(add-on) 99490 99491 G2058(predecessor) + G0502 G0503 PCM: 99424(predecessor) 99425 99426(add-on) 99427 TCM: 99495 99496 ``` ACP/TCM/APCM/PCM equal the hand lists; CCM = hand list + G2058 as intended, plus G0502/G0503 — a false merge caused by a long background paragraph absorbed as G0502's descriptor (new issue filed; not a family-rule defect). Roles come from events/elements (99424 shows `predecessor` because two `replaced_by`-shaped FR events exist for it — membership is right, the role label needs the same earliest-event rule as #686). Not yet wired into the chat: #699.
Author
Owner

Update 2026-09-09 evening — families now come from the CPT manual's own hierarchy (branch worktree-p49-slice2-anchors, plan docs/superpowers/plans/2026-09-09-cpt-organizing-principles.md, schema review docs/superpowers/specs/2026-09-09-cpt-canonical-schema-design.md).

The AMA CPT editions in Zotero (2019, 2021, 2022, 2024 EPUBs) were imported into bib (stack bib import-zotero), parsed (pfs/cpt_epub.py: sections with code spans, code entries with symbols and required-elements lists, parenthetical instructions, CPT Changes years, Appendix D/M/N/P/T lists) into pfs.cpt_* tables (stack pfs cpt-ingest --all; 12,056 code entries in 2024; guideline reprints and resequenced placeholders resolved into pfs.cpt_code_alt). derive_families now assigns every CPT code to the lowest CPT heading that groups ≥ 2 codes (grouped by heading path, not section id), keyed by the heading title, with the heading path in the new code_family.note; add-on/parent/replacement edges attach only codes without a heading of their own; since/until come from edition presence across all ingested editions.

Live result (stack pfs families --write): 8,518 keys, 816 families with ≥ 2 codes, 856 CPT-named, 5 hand families kept as a minimum, 7,657 singletons (mostly HCPCS codes awaiting element extraction, plus CPT codes deleted since 2019 and unlisted codes alone under a parent heading).

chapter CPT-named families codes
Surgery 573 5,860
Medicine 118 1,122
Radiology 48 657
Evaluation and Management 48 157
Pathology and Laboratory 25 2,031
Category III 19 482
Anesthesia 19 276
Category II 10 565

Every non-hand family maps to exactly one heading (CCM spans the chronic and complex-chronic headings by design). 99490: since 2019, note Evaluation and Management > Care Management Services > Chronic Care Management Services.

Still to land in this slice: CPT Changes years as lineage events and CPT elements as an extraction source (#685/#686), the explainer notebook's CPT sections, then a re-run of stack llm restamp --collection all so chunk families anchors use the new registry (today they only carry the five old families).

**Update 2026-09-09 evening — families now come from the CPT manual's own hierarchy** (branch `worktree-p49-slice2-anchors`, plan `docs/superpowers/plans/2026-09-09-cpt-organizing-principles.md`, schema review `docs/superpowers/specs/2026-09-09-cpt-canonical-schema-design.md`). The AMA CPT editions in Zotero (2019, 2021, 2022, 2024 EPUBs) were imported into bib (`stack bib import-zotero`), parsed (`pfs/cpt_epub.py`: sections with code spans, code entries with symbols and required-elements lists, parenthetical instructions, CPT Changes years, Appendix D/M/N/P/T lists) into `pfs.cpt_*` tables (`stack pfs cpt-ingest --all`; 12,056 code entries in 2024; guideline reprints and resequenced placeholders resolved into `pfs.cpt_code_alt`). `derive_families` now assigns every CPT code to the **lowest CPT heading that groups ≥ 2 codes** (grouped by heading path, not section id), keyed by the heading title, with the heading path in the new `code_family.note`; add-on/parent/replacement edges attach only codes without a heading of their own; `since`/`until` come from edition presence across all ingested editions. **Live result** (`stack pfs families --write`): 8,518 keys, **816 families with ≥ 2 codes**, 856 CPT-named, 5 hand families kept as a minimum, 7,657 singletons (mostly HCPCS codes awaiting element extraction, plus CPT codes deleted since 2019 and unlisted codes alone under a parent heading). | chapter | CPT-named families | codes | |---|---:|---:| | Surgery | 573 | 5,860 | | Medicine | 118 | 1,122 | | Radiology | 48 | 657 | | Evaluation and Management | 48 | 157 | | Pathology and Laboratory | 25 | 2,031 | | Category III | 19 | 482 | | Anesthesia | 19 | 276 | | Category II | 10 | 565 | Every non-hand family maps to exactly one heading (CCM spans the chronic and complex-chronic headings by design). 99490: `since 2019`, note `Evaluation and Management > Care Management Services > Chronic Care Management Services`. Still to land in this slice: CPT Changes years as lineage events and CPT elements as an extraction source (#685/#686), the explainer notebook's CPT sections, then a re-run of `stack llm restamp --collection all` so chunk `families` anchors use the new registry (today they only carry the five old families).
Author
Owner

Delivered in P49 slices 1–2a (edc128e, d016f70): pfs.code_family derived from CPT headings + elements + CMS 'code family' language, hand registry as the fixture (8,489 keys, 805 multi-code, 843 CPT-named); refresh_from feeds the chat (#699). Closing.

Delivered in P49 slices 1–2a (edc128e, d016f70): pfs.code_family derived from CPT headings + elements + CMS 'code family' language, hand registry as the fixture (8,489 keys, 805 multi-code, 843 CPT-named); refresh_from feeds the chat (#699). Closing.
kert closed this issue 2026-09-11 18:55:18 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: homelab/stack#687