fix(rec): include status-R (Restricted Coverage) in PFS pricer (#340)
Some checks failed
CI / lint (push) Failing after 28s
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 / mc (push) Has been skipped
Infra CI / notebooks (push) Failing after 13s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Successful in 14s
Infra CI / api (push) Successful in 21s
Infra CI / mc (push) Successful in 12s
Deploy / report (push) Successful in 11s
CI / test (push) Has been cancelled
Some checks failed
CI / lint (push) Failing after 28s
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 / mc (push) Has been skipped
Infra CI / notebooks (push) Failing after 13s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Successful in 14s
Infra CI / api (push) Successful in 21s
Infra CI / mc (push) Successful in 12s
Deploy / report (push) Successful in 11s
CI / test (push) Has been cancelled
Investigation: characterized the consistent ~11K-12K ground-truth-only
rows per year (2016-2026) by joining against pfs.rvu. Result: 100% of
the gap is status_code='R'. Zero are A/T (no real calc bugs hidden in
the noise), zero are missing from pfs.rvu entirely.
Status R = Restricted Coverage. CMS applies special coverage rules
(certain settings, modifier requirements, NCD-driven) but the price
formula is identical to A: RVU × GPCI × CF. The pricer was filtering
status_code IN ('A', 'T'), treating R as if it were carrier-priced.
Fix: add 'R' to _ALGORITHMIC_STATUS. One-character change in the
SQL filter, plus updated docstrings.
Verified: live `stack rec pfs` for every year 2016-2026 now reports:
Ground-truth only: 0 (was 9,990 - 12,650)
Status: 100.00% exact
Residual deltas are single-digit ≤1¢ float-rounding noise across
~11M rows. Closes the framework's empirical concordance gap → #340
acceptance criteria fully satisfied.
Test: added 66666 (status R) to the in-memory fixture; updated count
assertions across the existing test class to reflect the new
4-row/3-match baseline; added test_includes_restricted_coverage
regression guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,12 +9,13 @@ Calculated: join ``pfs.rvu`` × ``pfs.gpci`` for the year, override
|
|||||||
non-facility.
|
non-facility.
|
||||||
|
|
||||||
Status-code filter:
|
Status-code filter:
|
||||||
Only status ``A`` (algorithm-priced) and ``T`` (paid under
|
Algorithm-priced statuses ``A``, ``R``, and ``T`` reconcile to the
|
||||||
algorithm when more than one service on same day) are expected
|
cent. ``A`` is active, ``R`` is restricted-coverage (special
|
||||||
to reconcile to the cent. Carrier-priced codes (``C``), bundled
|
coverage rules apply but the price formula is identical to A),
|
||||||
(``B``), and other status indicators are excluded from the
|
and ``T`` is paid-under-algorithm-when-multiple-services. Carrier-
|
||||||
calculated side so they show up as ``ground_truth_only`` rather
|
priced codes (``C``), bundled (``B``), and other status indicators
|
||||||
than being flagged as computation bugs.
|
are excluded from the calculated side so they show up as
|
||||||
|
``ground_truth_only`` rather than being flagged as computation bugs.
|
||||||
|
|
||||||
Gaps tracked:
|
Gaps tracked:
|
||||||
- Limiting charge columns are stack-synthesized in pipe.py
|
- Limiting charge columns are stack-synthesized in pipe.py
|
||||||
@@ -37,7 +38,10 @@ from pfs.rules import RULES
|
|||||||
|
|
||||||
# Status codes whose payment is computed algorithmically and should
|
# Status codes whose payment is computed algorithmically and should
|
||||||
# reconcile to the cent. Everything else is ground-truth only.
|
# reconcile to the cent. Everything else is ground-truth only.
|
||||||
_ALGORITHMIC_STATUS: tuple[str, ...] = ("A", "T")
|
# R = Restricted Coverage — coverage rules differ but pricing formula is
|
||||||
|
# identical to A. Including R closes the entire ~11K/year GT-only gap
|
||||||
|
# observed across CY2016-CY2026 (verified empirically).
|
||||||
|
_ALGORITHMIC_STATUS: tuple[str, ...] = ("A", "R", "T")
|
||||||
|
|
||||||
|
|
||||||
class PfsPricer:
|
class PfsPricer:
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ Provides:
|
|||||||
row C — ground-truth only (carrier row with status_code = 'C'
|
row C — ground-truth only (carrier row with status_code = 'C'
|
||||||
so calc side skips it)
|
so calc side skips it)
|
||||||
row D — calculated only (RVU row with no carrier file entry)
|
row D — calculated only (RVU row with no carrier file entry)
|
||||||
|
row R — exact match for status_code = 'R' (Restricted Coverage):
|
||||||
|
R is algorithm-priced same as A/T, just with coverage
|
||||||
|
restrictions that don't affect the price formula.
|
||||||
|
|
||||||
* ``fake_pricer`` — a hand-rolled Pricer that takes hard-coded polars
|
* ``fake_pricer`` — a hand-rolled Pricer that takes hard-coded polars
|
||||||
frames so engine tests don't need DuckDB.
|
frames so engine tests don't need DuckDB.
|
||||||
@@ -46,6 +49,8 @@ def con() -> duckdb.DuckDBPyConnection:
|
|||||||
# 99214 (A): work=1.50, nfpe=1.56, mp=0.11
|
# 99214 (A): work=1.50, nfpe=1.56, mp=0.11
|
||||||
# 99999 (C): work=1.00, nfpe=1.00, mp=0.00 -- carrier-priced
|
# 99999 (C): work=1.00, nfpe=1.00, mp=0.00 -- carrier-priced
|
||||||
# 88888 (A): work=0.50, nfpe=0.40, mp=0.05 -- no carrier row
|
# 88888 (A): work=0.50, nfpe=0.40, mp=0.05 -- no carrier row
|
||||||
|
# 66666 (R): work=0.80, nfpe=0.90, mp=0.06 -- restricted coverage,
|
||||||
|
# algorithm-priced like A
|
||||||
c.execute(
|
c.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE pfs.rvu (
|
CREATE TABLE pfs.rvu (
|
||||||
@@ -67,7 +72,8 @@ def con() -> duckdb.DuckDBPyConnection:
|
|||||||
({_TEST_YEAR}, '99213', NULL, 'A', 0.97, 1.04, 0.41, 0.07, NULL),
|
({_TEST_YEAR}, '99213', NULL, 'A', 0.97, 1.04, 0.41, 0.07, NULL),
|
||||||
({_TEST_YEAR}, '99214', NULL, 'A', 1.50, 1.56, 0.63, 0.11, NULL),
|
({_TEST_YEAR}, '99214', NULL, 'A', 1.50, 1.56, 0.63, 0.11, NULL),
|
||||||
({_TEST_YEAR}, '99999', NULL, 'C', 1.00, 1.00, 1.00, 0.00, NULL),
|
({_TEST_YEAR}, '99999', NULL, 'C', 1.00, 1.00, 1.00, 0.00, NULL),
|
||||||
({_TEST_YEAR}, '88888', NULL, 'A', 0.50, 0.40, 0.20, 0.05, NULL)
|
({_TEST_YEAR}, '88888', NULL, 'A', 0.50, 0.40, 0.20, 0.05, NULL),
|
||||||
|
({_TEST_YEAR}, '66666', NULL, 'R', 0.80, 0.90, 0.30, 0.06, NULL)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -107,6 +113,10 @@ def con() -> duckdb.DuckDBPyConnection:
|
|||||||
nf_99999 = 55.55
|
nf_99999 = 55.55
|
||||||
fac_99999 = 44.44
|
fac_99999 = 44.44
|
||||||
# Row D (88888): calc side only — no carrier row.
|
# Row D (88888): calc side only — no carrier row.
|
||||||
|
# Row R (66666): status R, must reconcile exactly with the same formula
|
||||||
|
# as A/T after the fix. Seed the carrier value to the calculated value.
|
||||||
|
nf_66666 = round((0.80 * 1.0 + 0.90 * 0.88 + 0.06 * 0.55) * _CF, 2)
|
||||||
|
fac_66666 = round((0.80 * 1.0 + 0.30 * 0.88 + 0.06 * 0.55) * _CF, 2)
|
||||||
|
|
||||||
c.execute(
|
c.execute(
|
||||||
"""
|
"""
|
||||||
@@ -131,7 +141,9 @@ def con() -> duckdb.DuckDBPyConnection:
|
|||||||
({_TEST_YEAR}, '10212', '01', '99214', NULL,
|
({_TEST_YEAR}, '10212', '01', '99214', NULL,
|
||||||
{nf_99214_seeded}, {fac_99214_seeded}, NULL, NULL),
|
{nf_99214_seeded}, {fac_99214_seeded}, NULL, NULL),
|
||||||
({_TEST_YEAR}, '10212', '01', '99999', NULL,
|
({_TEST_YEAR}, '10212', '01', '99999', NULL,
|
||||||
{nf_99999}, {fac_99999}, NULL, NULL)
|
{nf_99999}, {fac_99999}, NULL, NULL),
|
||||||
|
({_TEST_YEAR}, '10212', '01', '66666', NULL,
|
||||||
|
{nf_66666}, {fac_66666}, NULL, NULL)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ class TestGroundTruth:
|
|||||||
def test_row_count(self, con, test_year) -> None:
|
def test_row_count(self, con, test_year) -> None:
|
||||||
p = PfsPricer()
|
p = PfsPricer()
|
||||||
gt = p.ground_truth(con, test_year)
|
gt = p.ground_truth(con, test_year)
|
||||||
# Seeded 3 carrier rows (99213, 99214, 99999)
|
# Seeded 4 carrier rows (99213, 99214, 99999, 66666)
|
||||||
assert gt.height == 3
|
assert gt.height == 4
|
||||||
|
|
||||||
def test_columns(self, con, test_year) -> None:
|
def test_columns(self, con, test_year) -> None:
|
||||||
p = PfsPricer()
|
p = PfsPricer()
|
||||||
@@ -59,6 +59,16 @@ class TestCalculated:
|
|||||||
hcpcs = set(calc["hcpcs"].to_list())
|
hcpcs = set(calc["hcpcs"].to_list())
|
||||||
assert {"99213", "99214", "88888"}.issubset(hcpcs)
|
assert {"99213", "99214", "88888"}.issubset(hcpcs)
|
||||||
|
|
||||||
|
def test_includes_restricted_coverage(self, con, test_year) -> None:
|
||||||
|
"""66666 has status_code='R' (Restricted Coverage). R is
|
||||||
|
algorithm-priced same as A/T — coverage restrictions don't
|
||||||
|
affect the price formula. Was previously excluded; this test
|
||||||
|
guards against regression."""
|
||||||
|
p = PfsPricer()
|
||||||
|
calc = p.calculated(con, test_year)
|
||||||
|
hcpcs = set(calc["hcpcs"].to_list())
|
||||||
|
assert "66666" in hcpcs
|
||||||
|
|
||||||
def test_returns_cents_rounded(self, con, test_year) -> None:
|
def test_returns_cents_rounded(self, con, test_year) -> None:
|
||||||
p = PfsPricer()
|
p = PfsPricer()
|
||||||
calc = p.calculated(con, test_year)
|
calc = p.calculated(con, test_year)
|
||||||
@@ -70,12 +80,12 @@ class TestCalculated:
|
|||||||
class TestReconcilePfs:
|
class TestReconcilePfs:
|
||||||
def test_summary_counts(self, con, test_year) -> None:
|
def test_summary_counts(self, con, test_year) -> None:
|
||||||
r = reconcile(PfsPricer(), con, test_year)
|
r = reconcile(PfsPricer(), con, test_year)
|
||||||
# Ground-truth rows: 99213, 99214, 99999 = 3
|
# Ground-truth rows: 99213, 99214, 99999, 66666 = 4
|
||||||
assert r.ground_truth_rows == 3
|
assert r.ground_truth_rows == 4
|
||||||
# Calculated rows: 99213, 99214, 88888 = 3 (99999 skipped)
|
# Calculated rows: 99213, 99214, 88888, 66666 = 4 (99999 skipped)
|
||||||
assert r.calculated_rows == 3
|
assert r.calculated_rows == 4
|
||||||
# Matched: 99213, 99214 = 2
|
# Matched: 99213, 99214, 66666 = 3
|
||||||
assert r.matched_rows == 2
|
assert r.matched_rows == 3
|
||||||
# gt_only: 99999
|
# gt_only: 99999
|
||||||
assert r.ground_truth_only == 1
|
assert r.ground_truth_only == 1
|
||||||
# calc_only: 88888
|
# calc_only: 88888
|
||||||
@@ -83,19 +93,19 @@ class TestReconcilePfs:
|
|||||||
|
|
||||||
def test_exact_and_near(self, con, test_year) -> None:
|
def test_exact_and_near(self, con, test_year) -> None:
|
||||||
r = reconcile(PfsPricer(), con, test_year)
|
r = reconcile(PfsPricer(), con, test_year)
|
||||||
# 99213 seeded to match exactly; 99214 is 1¢ off on non_fac_fee.
|
# 99213 + 66666 seeded exact; 99214 is 1¢ off on non_fac_fee.
|
||||||
assert r.exact_matches == 1
|
assert r.exact_matches == 2
|
||||||
assert r.near_matches == 2
|
assert r.near_matches == 3
|
||||||
|
|
||||||
def test_tolerance_promotes(self, con, test_year) -> None:
|
def test_tolerance_promotes(self, con, test_year) -> None:
|
||||||
r = reconcile(PfsPricer(), con, test_year, tolerance_cents=1)
|
r = reconcile(PfsPricer(), con, test_year, tolerance_cents=1)
|
||||||
assert r.exact_matches == 2
|
assert r.exact_matches == 3
|
||||||
|
|
||||||
def test_delta_table_contains_hcpcs(self, con, test_year) -> None:
|
def test_delta_table_contains_hcpcs(self, con, test_year) -> None:
|
||||||
r = reconcile(PfsPricer(), con, test_year)
|
r = reconcile(PfsPricer(), con, test_year)
|
||||||
hcpcs = set(r.deltas["hcpcs"].to_list())
|
hcpcs = set(r.deltas["hcpcs"].to_list())
|
||||||
# All four distinct hcpcs from outer join
|
# All five distinct hcpcs from outer join
|
||||||
assert hcpcs == {"99213", "99214", "99999", "88888"}
|
assert hcpcs == {"99213", "99214", "99999", "88888", "66666"}
|
||||||
|
|
||||||
def test_years_available(self, con, test_year) -> None:
|
def test_years_available(self, con, test_year) -> None:
|
||||||
p = PfsPricer()
|
p = PfsPricer()
|
||||||
|
|||||||
Reference in New Issue
Block a user