One row per (code, effective date, kind) for becomes-payable / revalued / status-change / ends / telehealth-listed, derived from pfs.rvu (A/R/T payable, left- and right-censored at the file span) and pfs.code_event (revalued, telehealth_list), each anchored to the code's nearest kind-matched FR lineage paragraph. Effective date is 1 January of the rule year unless the event's own year is anchored only by a correction notice (bib.frlink.rule_kind), then that notice's publication date. Rows carry the family key, the year's RVU status and the decile band of non_fac_total among that year's payable codes; control_codes() returns the never-treated set (same status + band, no exposure within a window). stack pfs exposure [--code|--family] [--write] [--controls N --year Y]; notebook section 7c lists a family's exposures with FR jump links and a control-set preview. Live dry run: 6,314 rows / 4,796 codes in 1.3 s; every fixture event in #693 present (99490 2015, 99487/99489 2017, 99439 + G2058 2021, 99424 + G2064 2022, G2211 2024, G0556 2025, 99441 ends 2025).
709 lines
25 KiB
Python
709 lines
25 KiB
Python
"""DDL and writers for the code-family tables in the ``pfs`` schema.
|
|
|
|
``pfs.code_element`` one row per (code, element) with its FR anchor
|
|
``pfs.code_element_review`` element lines the classifier could not place
|
|
``pfs.code_event`` dated lineage events (created, replaced, …)
|
|
``pfs.code_family`` derived family membership with roles
|
|
``pfs.code_exposure`` the exposure calendar (#693): dated payable /
|
|
revalued / status / ends / telehealth events
|
|
|
|
``pfs.cpt_*`` (docs/superpowers/specs/2026-09-09-cpt-canonical-schema-design.md
|
|
§3) hold one CPT codebook edition's own organizing structure —
|
|
``cpt_section``/``cpt_code``/``cpt_instruction``/``cpt_reference``/
|
|
``cpt_crosswalk``/``cpt_list``/``cpt_code_alt`` — written by
|
|
``pfs.cpt_load.ingest`` from ``pfs.cpt_epub.parse_epub``.
|
|
``cpt_section.path_key`` is ``" > ".join(path)`` (C7): the same heading
|
|
title can occur as several ``sec_id``s (2019's running-header repeats),
|
|
so ``cpt_section`` keeps every row and consumers group by ``path_key``
|
|
to treat those repeats as one heading. ``cpt_code_alt`` holds the
|
|
losing entries when a code prints more than once with a real descriptor
|
|
both times (C9 — the parser picks one canonical ``pfs.cpt_code`` row by
|
|
structural score and keeps the rest here with a ``reason``, rather than
|
|
either dropping or duplicating them).
|
|
|
|
Writers are delete-then-insert per code (events, elements) or per
|
|
``edition_year`` (cpt_*), or full replace (families), so a re-run is
|
|
idempotent. Callers own the connection (``conf.connect.duckdb_batch``
|
|
for writes) and republish the replica afterwards; nothing here opens a
|
|
database.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime as _dt
|
|
from dataclasses import astuple, dataclass
|
|
from typing import TYPE_CHECKING, Any, Sequence
|
|
|
|
if TYPE_CHECKING:
|
|
from pfs.cpt_model import CptEdition
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ElementRow:
|
|
code: str
|
|
year: int
|
|
type: str
|
|
value: str
|
|
detail: str
|
|
text: str
|
|
item_key: str
|
|
p_id: int
|
|
page: int
|
|
source: str # "fr" | "cpt" | "hcpcs" | "rvu"
|
|
#: Comma-separated ``source:item_key`` tokens (e.g. ``"cpt:GQGTPGYV"``)
|
|
#: — one per additional source whose own independent transcription
|
|
#: yielded the same ``(type, value, detail)`` as this row (#703). The
|
|
#: winning row (FR over CPT over HCPCS/RVU, unchanged) is the one kept;
|
|
#: this column is the cross-check the dedupe used to discard entirely.
|
|
#: ``""`` when no second source confirmed this element. Last field,
|
|
#: defaulted, so existing callers that build an ``ElementRow`` without
|
|
#: it keep working (Ruling C1 pattern), and a replica read before this
|
|
#: column existed tolerates the short tuple the same way.
|
|
confirmed_by: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ReviewRow:
|
|
code: str
|
|
text: str
|
|
proposed_type: str
|
|
proposed_value: str
|
|
item_key: str
|
|
p_id: int
|
|
#: "fr" | "cpt" | "hcpcs" | "rvu" — which source's pass produced this
|
|
#: unplaced line (#703). Last field, defaulted, so existing callers
|
|
#: that build a ``ReviewRow`` without it keep working, and a replica
|
|
#: read before this column existed tolerates the short tuple.
|
|
source: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class EventRow:
|
|
code: str
|
|
year: int
|
|
kind: str
|
|
from_codes: str # space-joined
|
|
to_codes: str
|
|
item_key: str
|
|
p_id: int
|
|
page: int
|
|
source: str # "fr" | "rvu" | "cpt"
|
|
anchored: bool
|
|
note: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FamilyRow:
|
|
key: str
|
|
name: str
|
|
code: str
|
|
role: str # base | add-on | predecessor | successor | bundle | member
|
|
since: int | None
|
|
until: int | None
|
|
item_key: str
|
|
p_id: int
|
|
#: The code's own CPT heading path (``" > ".join(path)``, C7) — the
|
|
#: heading that actually classified this code, which may differ from
|
|
#: whichever heading named the family (a hand family or a slice-1
|
|
#: edge can pull codes from more than one heading into one family).
|
|
#: ``""`` for a code with no CPT heading (HCPCS, or a rare code alone
|
|
#: under every ancestor heading). Last field, defaulted, so callers
|
|
#: that build a ``FamilyRow`` without it keep working (Ruling C1).
|
|
note: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class GuidanceRow:
|
|
"""One CFR/IOM/MLN reference a family's codes are discussed under
|
|
(``pfs.guidance``, #689) — ``kind`` is ``"cfr"``, ``"iom"`` or
|
|
``"mln"``; ``locator`` is the canonical human-readable cite
|
|
("42 CFR 410.78(a)(3)" | "100-04 ch.12 §30.6.4" | "MLN 907166");
|
|
``item_key`` is the resolved bib item (``""`` when unresolved —
|
|
most CFR/IOM citations have no matching library item on hand);
|
|
``item_key_src``/``p_id_src``/``page_src`` are the citing
|
|
paragraph's own provenance (an FR paragraph, or the CPT edition
|
|
item with ``p_id_src=0``, ``page_src=0``); ``page`` is the 1-based
|
|
page inside the resolved item's PDF where an IOM section heading
|
|
sits (#705 item 2 — ``pfs.guidance.iom_section_page``), ``0`` when
|
|
unlocated or not applicable. Trailing default so positional
|
|
constructors written before it existed keep working."""
|
|
|
|
family: str
|
|
code: str
|
|
kind: str # "cfr" | "iom" | "mln"
|
|
locator: str
|
|
item_key: str
|
|
item_key_src: str
|
|
p_id_src: int
|
|
page_src: int
|
|
page: int = 0
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ReactionRow:
|
|
"""One period's public-reaction counts for a code family
|
|
(``pfs.code_reaction``, #690) — ``period_kind`` is ``"docket"``
|
|
(``period`` is a regulations.gov docket id, ``year`` the docket's
|
|
``comment_end_date`` year) or ``"fr-pairs"`` (``period`` is the FR
|
|
rule ``item_key``, ``year`` its ``rule_year_of``). For a docket row,
|
|
``n_items``/``n_total`` are distinct commenters mentioning the
|
|
family / total distinct commenters in that docket. For an fr-pairs
|
|
row, ``n_items`` is the qualifying Comment:/Response: paragraph
|
|
pairs naming the family and ``n_total`` is the rule's total
|
|
``Comment:`` paragraph count — every one in the rule, family or not
|
|
— the pool ``n_items`` is drawn out of. ``stance_*`` and
|
|
``sample_json`` (``[{"item_key": …, "stance": …}]``) stay zero/
|
|
``"[]"`` unless a stance sample was classified for that docket."""
|
|
|
|
family: str
|
|
period: str
|
|
period_kind: str # "docket" | "fr-pairs"
|
|
year: int
|
|
n_items: int
|
|
n_total: int
|
|
stance_support: int
|
|
stance_oppose: int
|
|
stance_modify: int
|
|
stance_unclear: int
|
|
sample_json: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CptSectionRow:
|
|
edition_year: int
|
|
item_key: str
|
|
sec_id: str
|
|
level: int
|
|
title: str
|
|
path: list[str]
|
|
path_key: str # " > ".join(path) — see module docstring (C7)
|
|
code_lo: str
|
|
code_hi: str
|
|
guideline: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CptCodeRow:
|
|
edition_year: int
|
|
item_key: str
|
|
code: str
|
|
sec_id: str
|
|
category: str # "I" | "II" | "III"
|
|
descriptor: str
|
|
stem: str
|
|
elements: list[str]
|
|
tail: str
|
|
parent: str
|
|
addon: bool
|
|
resequenced: bool
|
|
new: bool
|
|
revised: bool
|
|
telemedicine: bool
|
|
mod51_exempt: bool
|
|
audio_only: bool
|
|
fda_pending: bool
|
|
pla: bool
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CptInstructionRow:
|
|
edition_year: int
|
|
item_key: str
|
|
code: str
|
|
kind: str
|
|
text: str
|
|
targets: list[str]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CptReferenceRow:
|
|
edition_year: int
|
|
item_key: str
|
|
code: str
|
|
kind: str # "cpt-changes" | "cpt-assistant"
|
|
years: list[int]
|
|
text: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CptCrosswalkRow:
|
|
edition_year: int
|
|
item_key: str
|
|
current_code: str
|
|
former_code: str
|
|
year_deleted: str
|
|
citations: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CptListRow:
|
|
edition_year: int
|
|
item_key: str
|
|
appendix: str
|
|
code: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ExposureRow:
|
|
"""One exposure on the calendar (``pfs.code_exposure``, #693): *code*
|
|
is exposed to *kind* (``pfs.exposure.KINDS``) from *effective_date*
|
|
— 1 January of *year* unless the only Federal Register anchor is a
|
|
correction notice, in which case that notice's publication date and
|
|
``correction=True``. ``status``/``prior_status`` are the RVU-file
|
|
status codes on either side of the event (``""`` when the code is
|
|
absent from that year's file); ``rvu_band`` is the decile of
|
|
``non_fac_total`` among that year's payable codes (1 cheapest … 10,
|
|
0 when unranked) and ``non_fac_total`` the value it was ranked on —
|
|
both taken from the last payable year for an ``ends`` row; ``delta_pct``
|
|
is the revaluation fraction (``0.475`` for +47.5%) and ``0.0`` otherwise.
|
|
``item_key``/``p_id``/``page``/``anchor_kind`` carry the lineage row
|
|
the exposure is anchored to (empty/0 when no FR paragraph names the
|
|
code within a year of the event)."""
|
|
|
|
code: str
|
|
family: str
|
|
effective_date: _dt.date
|
|
year: int
|
|
kind: str
|
|
status: str
|
|
prior_status: str
|
|
rvu_band: int
|
|
non_fac_total: float
|
|
delta_pct: float
|
|
item_key: str
|
|
p_id: int
|
|
page: int
|
|
anchor_kind: str
|
|
correction: bool
|
|
note: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CptCodeAltRow:
|
|
edition_year: int
|
|
item_key: str
|
|
code: str
|
|
sec_id: str
|
|
reason: str # "guidelines-reprint" | "lower-score" (C9)
|
|
|
|
|
|
_DDL = """
|
|
CREATE SCHEMA IF NOT EXISTS pfs;
|
|
CREATE TABLE IF NOT EXISTS pfs.code_element (
|
|
code VARCHAR, year INTEGER, type VARCHAR, value VARCHAR, detail VARCHAR,
|
|
text VARCHAR, item_key VARCHAR, p_id INTEGER, page INTEGER, source VARCHAR,
|
|
confirmed_by VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.code_element_review (
|
|
code VARCHAR, text VARCHAR, proposed_type VARCHAR, proposed_value VARCHAR,
|
|
item_key VARCHAR, p_id INTEGER, source VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.code_event (
|
|
code VARCHAR, year INTEGER, kind VARCHAR, from_codes VARCHAR, to_codes VARCHAR,
|
|
item_key VARCHAR, p_id INTEGER, page INTEGER, source VARCHAR, anchored BOOLEAN, note VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.code_family (
|
|
key VARCHAR, name VARCHAR, code VARCHAR, role VARCHAR, since INTEGER, until INTEGER,
|
|
item_key VARCHAR, p_id INTEGER, note VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.code_exposure (
|
|
code VARCHAR, family VARCHAR, effective_date DATE, year INTEGER, kind VARCHAR,
|
|
status VARCHAR, prior_status VARCHAR, rvu_band INTEGER, non_fac_total DOUBLE,
|
|
delta_pct DOUBLE, item_key VARCHAR, p_id INTEGER, page INTEGER, anchor_kind VARCHAR,
|
|
correction BOOLEAN, note VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.code_guidance (
|
|
family VARCHAR, code VARCHAR, kind VARCHAR, locator VARCHAR, item_key VARCHAR,
|
|
item_key_src VARCHAR, p_id_src INTEGER, page_src INTEGER, page INTEGER);
|
|
CREATE TABLE IF NOT EXISTS pfs.code_reaction (
|
|
family VARCHAR, period VARCHAR, period_kind VARCHAR, year INTEGER,
|
|
n_items INTEGER, n_total INTEGER, stance_support INTEGER, stance_oppose INTEGER,
|
|
stance_modify INTEGER, stance_unclear INTEGER, sample_json VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.cpt_section (
|
|
edition_year INTEGER, item_key VARCHAR, sec_id VARCHAR, level INTEGER, title VARCHAR,
|
|
path VARCHAR[], path_key VARCHAR, code_lo VARCHAR, code_hi VARCHAR, guideline VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.cpt_code (
|
|
edition_year INTEGER, item_key VARCHAR, code VARCHAR, sec_id VARCHAR, category VARCHAR,
|
|
descriptor VARCHAR, stem VARCHAR, elements VARCHAR[], tail VARCHAR, parent VARCHAR,
|
|
addon BOOLEAN, resequenced BOOLEAN, new BOOLEAN, revised BOOLEAN, telemedicine BOOLEAN,
|
|
mod51_exempt BOOLEAN, audio_only BOOLEAN, fda_pending BOOLEAN, pla BOOLEAN);
|
|
CREATE TABLE IF NOT EXISTS pfs.cpt_instruction (
|
|
edition_year INTEGER, item_key VARCHAR, code VARCHAR, kind VARCHAR, text VARCHAR,
|
|
targets VARCHAR[]);
|
|
CREATE TABLE IF NOT EXISTS pfs.cpt_reference (
|
|
edition_year INTEGER, item_key VARCHAR, code VARCHAR, kind VARCHAR, years INTEGER[],
|
|
text VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.cpt_crosswalk (
|
|
edition_year INTEGER, item_key VARCHAR, current_code VARCHAR, former_code VARCHAR,
|
|
year_deleted VARCHAR, citations VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.cpt_list (
|
|
edition_year INTEGER, item_key VARCHAR, appendix VARCHAR, code VARCHAR);
|
|
CREATE TABLE IF NOT EXISTS pfs.cpt_code_alt (
|
|
edition_year INTEGER, item_key VARCHAR, code VARCHAR, sec_id VARCHAR, reason VARCHAR);
|
|
"""
|
|
|
|
|
|
def is_missing_table_error(exc: Exception) -> bool:
|
|
"""True for a DuckDB "table/schema does not exist" Catalog error — a
|
|
replica that hasn't had ``cpt-ingest``/``elements``/``lineage
|
|
--write``/``families --write`` run yet is missing
|
|
``pfs.cpt_*``/``pfs.code_element``/``pfs.code_event``/
|
|
``pfs.code_family``, which is not a bug, just nothing derived or
|
|
ingested yet (I4). Shared by ``src/cli/pfs.py``, ``pfs.lineage`` and
|
|
``pfs.extract`` so every read-only CPT-table lookup treats a missing
|
|
table the same way."""
|
|
msg = str(exc)
|
|
return "Catalog" in msg and "does not exist" in msg
|
|
|
|
|
|
def ensure_tables(con: Any) -> None:
|
|
for stmt in _DDL.strip().split(";"):
|
|
if stmt.strip():
|
|
con.execute(stmt)
|
|
# Ruling C1: a pre-existing pfs.code_family table (created before
|
|
# `note` existed) needs the column added — CREATE TABLE IF NOT EXISTS
|
|
# above is a no-op once the table already exists.
|
|
con.execute("ALTER TABLE pfs.code_family ADD COLUMN IF NOT EXISTS note VARCHAR")
|
|
# #703, same pattern: pre-existing pfs.code_element/code_element_review
|
|
# tables (created before confirmed_by/source existed) need the columns
|
|
# added in place.
|
|
con.execute(
|
|
"ALTER TABLE pfs.code_element ADD COLUMN IF NOT EXISTS confirmed_by VARCHAR"
|
|
)
|
|
con.execute(
|
|
"ALTER TABLE pfs.code_element_review ADD COLUMN IF NOT EXISTS source VARCHAR"
|
|
)
|
|
# #705 item 2: the located IOM section page.
|
|
con.execute("ALTER TABLE pfs.code_guidance ADD COLUMN IF NOT EXISTS page INTEGER")
|
|
|
|
|
|
def _insert(con: Any, table: str, rows: Sequence[Any]) -> int:
|
|
if not rows:
|
|
return 0
|
|
width = len(astuple(rows[0]))
|
|
marks = ",".join("?" * width)
|
|
con.executemany(f"INSERT INTO {table} VALUES ({marks})", [astuple(r) for r in rows])
|
|
return len(rows)
|
|
|
|
|
|
def write_elements(
|
|
con: Any, code: str, rows: Sequence[ElementRow], reviews: Sequence[ReviewRow]
|
|
) -> int:
|
|
ensure_tables(con)
|
|
con.execute("DELETE FROM pfs.code_element WHERE code = ?", [code])
|
|
con.execute("DELETE FROM pfs.code_element_review WHERE code = ?", [code])
|
|
_insert(con, "pfs.code_element_review", reviews)
|
|
return _insert(con, "pfs.code_element", rows)
|
|
|
|
|
|
def write_events(con: Any, code: str, rows: Sequence[EventRow]) -> int:
|
|
ensure_tables(con)
|
|
con.execute("DELETE FROM pfs.code_event WHERE code = ?", [code])
|
|
return _insert(con, "pfs.code_event", rows)
|
|
|
|
|
|
def write_families(con: Any, rows: Sequence[FamilyRow]) -> int:
|
|
ensure_tables(con)
|
|
con.execute("DELETE FROM pfs.code_family")
|
|
return _insert(con, "pfs.code_family", rows)
|
|
|
|
|
|
def read_elements(con: Any, code: str) -> list[ElementRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_element WHERE code = ? ORDER BY year, type, value, detail",
|
|
[code],
|
|
).fetchall()
|
|
return [ElementRow(*r) for r in rows]
|
|
|
|
|
|
def read_reviews(con: Any, code: str) -> list[ReviewRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_element_review WHERE code = ? ORDER BY p_id", [code]
|
|
).fetchall()
|
|
return [ReviewRow(*r) for r in rows]
|
|
|
|
|
|
def read_all_reviews(con: Any) -> list[ReviewRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_element_review ORDER BY code, p_id"
|
|
).fetchall()
|
|
return [ReviewRow(*r) for r in rows]
|
|
|
|
|
|
def read_events(con: Any, code: str) -> list[EventRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_event WHERE code = ? ORDER BY year, kind", [code]
|
|
).fetchall()
|
|
return [EventRow(*r) for r in rows]
|
|
|
|
|
|
def read_all_elements(con: Any) -> dict[str, list[ElementRow]]:
|
|
"""Every ``pfs.code_element`` row, grouped by code — one full-table
|
|
scan instead of the ~N per-code queries ``families --write`` used to
|
|
run inside the DuckDB write lock (I9). Rows within each code are
|
|
ordered exactly as ``read_elements`` orders them."""
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_element ORDER BY code, year, type, value, detail"
|
|
).fetchall()
|
|
out: dict[str, list[ElementRow]] = {}
|
|
for r in rows:
|
|
row = ElementRow(*r)
|
|
out.setdefault(row.code, []).append(row)
|
|
return out
|
|
|
|
|
|
def read_all_events(con: Any) -> dict[str, list[EventRow]]:
|
|
"""Every ``pfs.code_event`` row, grouped by code — see
|
|
``read_all_elements``. Rows within each code are ordered exactly as
|
|
``read_events`` orders them."""
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_event ORDER BY code, year, kind"
|
|
).fetchall()
|
|
out: dict[str, list[EventRow]] = {}
|
|
for r in rows:
|
|
row = EventRow(*r)
|
|
out.setdefault(row.code, []).append(row)
|
|
return out
|
|
|
|
|
|
def write_exposures(con: Any, rows: Sequence[ExposureRow]) -> int:
|
|
"""Full replace — the calendar is always rebuilt whole (control sets
|
|
need every code's exposures, not one family's)."""
|
|
ensure_tables(con)
|
|
con.execute("DELETE FROM pfs.code_exposure")
|
|
return _insert(con, "pfs.code_exposure", rows)
|
|
|
|
|
|
def read_exposures(con: Any, *, code: str = "", family: str = "") -> list[ExposureRow]:
|
|
where, params = [], []
|
|
if code:
|
|
where.append("code = ?")
|
|
params.append(code.upper())
|
|
if family:
|
|
where.append("family = ?")
|
|
params.append(family)
|
|
sql = "SELECT * FROM pfs.code_exposure"
|
|
if where:
|
|
sql += " WHERE " + " AND ".join(where)
|
|
rows = con.execute(sql + " ORDER BY code, year, kind", params).fetchall()
|
|
return [ExposureRow(*r) for r in rows]
|
|
|
|
|
|
def read_families(con: Any) -> list[FamilyRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_family ORDER BY key, role, code"
|
|
).fetchall()
|
|
return [FamilyRow(*r) for r in rows]
|
|
|
|
|
|
def write_guidance(con: Any, family: str, rows: Sequence[GuidanceRow]) -> int:
|
|
ensure_tables(con)
|
|
con.execute("DELETE FROM pfs.code_guidance WHERE family = ?", [family])
|
|
return _insert(con, "pfs.code_guidance", rows)
|
|
|
|
|
|
def read_guidance(con: Any, family: str) -> list[GuidanceRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_guidance WHERE family = ? ORDER BY code, kind, locator",
|
|
[family],
|
|
).fetchall()
|
|
# Rows written before ``page`` existed read back NULL for it.
|
|
return [GuidanceRow(*r[:-1], r[-1] or 0) for r in rows]
|
|
|
|
|
|
def write_reaction(con: Any, family: str, rows: Sequence[ReactionRow]) -> int:
|
|
ensure_tables(con)
|
|
con.execute("DELETE FROM pfs.code_reaction WHERE family = ?", [family])
|
|
return _insert(con, "pfs.code_reaction", rows)
|
|
|
|
|
|
def read_reaction(con: Any, family: str) -> list[ReactionRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.code_reaction WHERE family = ? "
|
|
"ORDER BY period_kind, year, period",
|
|
[family],
|
|
).fetchall()
|
|
return [ReactionRow(*r) for r in rows]
|
|
|
|
|
|
_CPT_TABLES = (
|
|
"pfs.cpt_section",
|
|
"pfs.cpt_code",
|
|
"pfs.cpt_instruction",
|
|
"pfs.cpt_reference",
|
|
"pfs.cpt_crosswalk",
|
|
"pfs.cpt_list",
|
|
"pfs.cpt_code_alt",
|
|
)
|
|
|
|
|
|
def write_cpt_edition(
|
|
con: Any, edition: "CptEdition", item_key: str, *, force: bool = False
|
|
) -> dict[str, int]:
|
|
"""Delete-then-insert one CPT codebook edition's parsed rows into
|
|
``pfs.cpt_*``, keyed by ``edition.year``. Returns rows written per
|
|
table. C8: asserts one ``pfs.cpt_code`` row per (edition_year, code)
|
|
before inserting — a duplicate means a resequenced placeholder row
|
|
slipped past the parser's own filter (``pfs.cpt_epub``). C9's
|
|
guideline-reprint/lower-score losers are resolved by the parser
|
|
before this ever runs (``edition.alternates``, written to
|
|
``pfs.cpt_code_alt``), so that assertion should now always pass.
|
|
|
|
F4: refuses (raises ``ValueError``) an edition with zero codes
|
|
unless ``force=True`` — this is delete-then-insert, so writing an
|
|
empty parse would silently wipe whatever real edition data is
|
|
already on file for that year. ``pfs.cpt_epub.parse_epub`` already
|
|
raises before returning an edition this empty in the ordinary case
|
|
(no chapter content found at all); this is the second guard, for an
|
|
edition that parsed some structure but named not one actual code."""
|
|
if not edition.codes and not force:
|
|
raise ValueError(
|
|
f"pfs.cpt_code: edition_year={edition.year} parsed zero codes — "
|
|
"refusing to overwrite any existing edition data; pass force=True "
|
|
"to write it anyway"
|
|
)
|
|
ensure_tables(con)
|
|
year = edition.year
|
|
|
|
seen: dict[str, int] = {}
|
|
for c in edition.codes:
|
|
seen[c.code] = seen.get(c.code, 0) + 1
|
|
dupes = sorted(code for code, n in seen.items() if n > 1)
|
|
if dupes:
|
|
raise ValueError(
|
|
f"pfs.cpt_code: duplicate rows for edition_year={year}, "
|
|
f"codes: {', '.join(dupes)}"
|
|
)
|
|
|
|
section_rows = [
|
|
CptSectionRow(
|
|
edition_year=year,
|
|
item_key=item_key,
|
|
sec_id=s.sec_id,
|
|
level=s.level,
|
|
title=s.title,
|
|
path=list(s.path),
|
|
path_key=" > ".join(s.path),
|
|
code_lo=s.code_lo,
|
|
code_hi=s.code_hi,
|
|
guideline=s.guideline,
|
|
)
|
|
for s in edition.sections
|
|
]
|
|
code_rows = [
|
|
CptCodeRow(
|
|
edition_year=year,
|
|
item_key=item_key,
|
|
code=c.code,
|
|
sec_id=c.sec_id,
|
|
category=c.category,
|
|
descriptor=c.descriptor,
|
|
stem=c.stem,
|
|
elements=list(c.elements),
|
|
tail=c.tail,
|
|
parent=c.parent,
|
|
addon=c.addon,
|
|
resequenced=c.resequenced,
|
|
new=c.new,
|
|
revised=c.revised,
|
|
telemedicine=c.telemedicine,
|
|
mod51_exempt=c.mod51_exempt,
|
|
audio_only=c.audio_only,
|
|
fda_pending=c.fda_pending,
|
|
pla=c.pla,
|
|
)
|
|
for c in edition.codes
|
|
]
|
|
instruction_rows = [
|
|
CptInstructionRow(
|
|
edition_year=year,
|
|
item_key=item_key,
|
|
code=i.code,
|
|
kind=i.kind,
|
|
text=i.text,
|
|
targets=list(i.targets),
|
|
)
|
|
for i in edition.instructions
|
|
]
|
|
reference_rows = [
|
|
CptReferenceRow(
|
|
edition_year=year,
|
|
item_key=item_key,
|
|
code=r.code,
|
|
kind=r.kind,
|
|
years=list(r.years),
|
|
text=r.text,
|
|
)
|
|
for r in edition.references
|
|
]
|
|
crosswalk_rows = [
|
|
CptCrosswalkRow(
|
|
edition_year=year,
|
|
item_key=item_key,
|
|
current_code=x.current_code,
|
|
former_code=x.former_code,
|
|
year_deleted=x.year_deleted,
|
|
citations=x.citations,
|
|
)
|
|
for x in edition.crosswalks
|
|
]
|
|
list_rows = [
|
|
CptListRow(
|
|
edition_year=year, item_key=item_key, appendix=e.appendix, code=e.code
|
|
)
|
|
for e in edition.lists
|
|
]
|
|
alt_rows = [
|
|
CptCodeAltRow(
|
|
edition_year=year,
|
|
item_key=item_key,
|
|
code=a.code,
|
|
sec_id=a.sec_id,
|
|
reason=a.reason,
|
|
)
|
|
for a in edition.alternates
|
|
]
|
|
|
|
for table in _CPT_TABLES:
|
|
con.execute(f"DELETE FROM {table} WHERE edition_year = ?", [year])
|
|
|
|
return {
|
|
"sections": _insert(con, "pfs.cpt_section", section_rows),
|
|
"codes": _insert(con, "pfs.cpt_code", code_rows),
|
|
"instructions": _insert(con, "pfs.cpt_instruction", instruction_rows),
|
|
"references": _insert(con, "pfs.cpt_reference", reference_rows),
|
|
"crosswalks": _insert(con, "pfs.cpt_crosswalk", crosswalk_rows),
|
|
"lists": _insert(con, "pfs.cpt_list", list_rows),
|
|
"alternates": _insert(con, "pfs.cpt_code_alt", alt_rows),
|
|
}
|
|
|
|
|
|
def read_cpt_sections(con: Any, year: int) -> list[CptSectionRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.cpt_section WHERE edition_year = ? ORDER BY sec_id", [year]
|
|
).fetchall()
|
|
return [CptSectionRow(*r) for r in rows]
|
|
|
|
|
|
def read_cpt_codes(con: Any, year: int) -> list[CptCodeRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.cpt_code WHERE edition_year = ? ORDER BY code", [year]
|
|
).fetchall()
|
|
return [CptCodeRow(*r) for r in rows]
|
|
|
|
|
|
def read_cpt_instructions(con: Any, year: int) -> list[CptInstructionRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.cpt_instruction WHERE edition_year = ? ORDER BY code", [year]
|
|
).fetchall()
|
|
return [CptInstructionRow(*r) for r in rows]
|
|
|
|
|
|
def read_cpt_references(con: Any, year: int) -> list[CptReferenceRow]:
|
|
rows = con.execute(
|
|
"SELECT * FROM pfs.cpt_reference WHERE edition_year = ? ORDER BY code", [year]
|
|
).fetchall()
|
|
return [CptReferenceRow(*r) for r in rows]
|
|
|
|
|
|
def cpt_years(con: Any) -> list[int]:
|
|
rows = con.execute(
|
|
"SELECT DISTINCT edition_year FROM pfs.cpt_section ORDER BY edition_year"
|
|
).fetchall()
|
|
return [r[0] for r in rows]
|