Files
stack/tests/aco/test_express_provattr.py

1547 lines
53 KiB
Python

"""Tests for aco.express.provider_attribution — uncovered functions.
Covers int_provider_classification, int_primary_care_claims,
int_current_steps, and int_yearly_steps with polars DataFrames.
"""
from __future__ import annotations
import datetime
from unittest.mock import patch
import polars as pl
import pytest
from aco.express.provider_attribution import (
int_current_steps,
int_primary_care_claims,
int_provider_classification,
int_yearly_steps,
)
# ---- shared fixtures -------------------------------------------------------
D = datetime.date
@pytest.fixture
def provider_df() -> pl.DataFrame:
"""terminology.provider rows — two individuals and one org."""
return pl.DataFrame(
{
"npi": ["NPI_A", "NPI_B", "NPI_ORG"],
"primary_taxonomy_code": ["TAX01", "TAX02", "TAX03"],
"primary_specialty_description": [
"Family Medicine",
"Cardiology",
"Hospital",
],
"entity_type_description": [
"Individual",
"Individual",
"Organization",
],
}
)
@pytest.fixture
def taxonomy_crosswalk_df() -> pl.DataFrame:
"""taxonomy crosswalk: taxonomy_code -> medicare_specialty_code."""
return pl.DataFrame(
{
"provider_taxonomy_code": ["TAX01", "TAX02"],
"medicare_specialty_code": ["8", "6"],
}
)
@pytest.fixture
def specialty_assignment_df() -> pl.DataFrame:
"""provider specialty assignment codes seed."""
return pl.DataFrame(
{
"specialty_code": ["08", "06", "97"],
"primary_care_physician_step1": ["yes", "no", "no"],
"specialist_physician_step_2": ["no", "yes", "no"],
"physician": [1, 1, 0],
}
)
@pytest.fixture
def provider_classification_df() -> pl.DataFrame:
"""Pre-built provider classification for downstream tests."""
return pl.DataFrame(
{
"provider_id": ["NPI_A", "NPI_B", "NPI_C"],
"prov_specialty": [
"Family Medicine",
"Cardiology",
"Nurse Practitioner",
],
"provider_bucket": ["pcp", "specialist", "npp"],
}
)
@pytest.fixture
def calendar_df() -> pl.DataFrame:
"""reference_data.calendar — daily rows for Jan-Dec 2024 and
Jan-Jun 2025, plus one row per month for 2023.
We include enough to span 24-month windows."""
rows = []
for yr in (2023, 2024, 2025):
end_m = 12 if yr <= 2024 else 6
for m in range(1, end_m + 1):
for d in (1, 15):
dt = D(yr, m, d)
ym = int(f"{yr}{m:02d}")
rows.append(
{
"full_date": dt,
"year": yr,
"month": m,
"year_month": f"{yr}{m:02d}",
"first_day_of_month": D(yr, m, 1),
"last_day_of_month": D(yr, m, 28),
"year_month_int": ym,
}
)
return pl.DataFrame(rows)
@pytest.fixture
def member_months_df() -> pl.DataFrame:
"""member_months for 6 persons across 2024."""
rows = []
for pid in ("P1", "P2", "P3", "P4", "P5", "P6"):
for m in range(1, 13):
rows.append(
{
"person_id": pid,
"year_month": f"2024{m:02d}",
}
)
return pl.DataFrame(rows)
@pytest.fixture
def medical_claim_df() -> pl.DataFrame:
"""medical claims touching persons P1-P6 with various
rendering NPIs and HCPCS codes."""
return pl.DataFrame(
{
"claim_id": [
"C01",
"C02",
"C03",
"C04",
"C05",
"C06",
"C07",
"C08",
],
"claim_line_number": [1, 1, 1, 1, 1, 1, 1, 1],
"person_id": [
"P1",
"P2",
"P3",
"P4",
"P5",
"P5",
"P6",
"P6",
],
"claim_start_date": [
D(2024, 3, 1),
D(2024, 4, 1),
D(2024, 5, 1),
D(2024, 6, 1),
D(2024, 7, 1),
D(2024, 8, 1),
D(2024, 9, 1),
D(2024, 10, 1),
],
"claim_end_date": [
D(2024, 3, 15),
D(2024, 4, 15),
D(2024, 5, 15),
D(2024, 6, 15),
D(2024, 7, 15),
D(2024, 8, 15),
D(2024, 9, 15),
D(2024, 10, 15),
],
"allowed_amount": [
100.0,
200.0,
300.0,
400.0,
500.0,
600.0,
700.0,
800.0,
],
"paid_amount": [
80.0,
160.0,
240.0,
320.0,
400.0,
480.0,
560.0,
640.0,
],
"rendering_id": [
"NPI_A",
"NPI_B",
"NPI_C",
"NPI_A",
"NPI_B",
"NPI_C",
"NPI_A",
"NPI_B",
],
"hcpcs_code": [
"99213",
"99213",
"99213",
"99213",
"99213",
"99213",
"99213",
"99213",
],
"data_source": ["t"] * 8,
"encounter_id": [
"E01",
"E02",
"E03",
"E04",
"E05",
"E06",
"E07",
"E08",
],
}
)
@pytest.fixture
def hcpcs_codes_df() -> pl.DataFrame:
"""Primary-care HCPCS codes seed."""
return pl.DataFrame({"hcpcs_code": ["99213", "99214"]})
def _stg_medical_claim(medical_claim_df: pl.DataFrame) -> pl.DataFrame:
"""Apply stg_core__medical_claim transform inline."""
return medical_claim_df.rename({"rendering_id": "rendering_npi"}).select(
"claim_id",
"claim_line_number",
"person_id",
"claim_start_date",
"claim_end_date",
"allowed_amount",
"paid_amount",
"rendering_npi",
"hcpcs_code",
"data_source",
"encounter_id",
)
# ── int_provider_classification ─────────────────────────────────
class TestIntProviderClassification:
"""Joins provider -> crosswalk -> specialty codes, classifies."""
def test_returns_dataframe(
self, provider_df, taxonomy_crosswalk_df, specialty_assignment_df
) -> None:
result = int_provider_classification(
provider_df, taxonomy_crosswalk_df, specialty_assignment_df
)
assert isinstance(result, pl.DataFrame)
def test_output_columns(
self, provider_df, taxonomy_crosswalk_df, specialty_assignment_df
) -> None:
result = int_provider_classification(
provider_df, taxonomy_crosswalk_df, specialty_assignment_df
)
assert set(result.columns) == {
"provider_id",
"prov_specialty",
"provider_bucket",
}
def test_filters_to_individual(
self, provider_df, taxonomy_crosswalk_df, specialty_assignment_df
) -> None:
result = int_provider_classification(
provider_df, taxonomy_crosswalk_df, specialty_assignment_df
)
# NPI_ORG is Organization — should be excluded
ids = result["provider_id"].to_list()
assert "NPI_ORG" not in ids
def test_pcp_classification(
self, provider_df, taxonomy_crosswalk_df, specialty_assignment_df
) -> None:
result = int_provider_classification(
provider_df, taxonomy_crosswalk_df, specialty_assignment_df
)
row_a = result.filter(pl.col("provider_id") == "NPI_A")
assert len(row_a) == 1
assert row_a["provider_bucket"][0] == "pcp"
def test_specialist_classification(
self, provider_df, taxonomy_crosswalk_df, specialty_assignment_df
) -> None:
result = int_provider_classification(
provider_df, taxonomy_crosswalk_df, specialty_assignment_df
)
row_b = result.filter(pl.col("provider_id") == "NPI_B")
assert len(row_b) == 1
assert row_b["provider_bucket"][0] == "specialist"
def test_npp_classification(self) -> None:
"""Non-physician provider (physician == 0)."""
prov = pl.DataFrame(
{
"npi": ["NPI_N"],
"primary_taxonomy_code": ["TAX97"],
"primary_specialty_description": ["Nurse Practitioner"],
"entity_type_description": ["Individual"],
}
)
xwalk = pl.DataFrame(
{
"provider_taxonomy_code": ["TAX97"],
"medicare_specialty_code": ["97"],
}
)
assign = pl.DataFrame(
{
"specialty_code": ["97"],
"primary_care_physician_step1": ["no"],
"specialist_physician_step_2": ["no"],
"physician": [0],
}
)
result = int_provider_classification(prov, xwalk, assign)
assert result["provider_bucket"][0] == "npp"
def test_unknown_classification(self) -> None:
"""physician==1 but neither pcp nor specialist -> unknown."""
prov = pl.DataFrame(
{
"npi": ["NPI_U"],
"primary_taxonomy_code": ["TAX_U"],
"primary_specialty_description": ["Unknown Spec"],
"entity_type_description": ["Individual"],
}
)
xwalk = pl.DataFrame(
{
"provider_taxonomy_code": ["TAX_U"],
"medicare_specialty_code": ["99"],
}
)
assign = pl.DataFrame(
{
"specialty_code": ["99"],
"primary_care_physician_step1": ["no"],
"specialist_physician_step_2": ["no"],
"physician": [1],
}
)
result = int_provider_classification(prov, xwalk, assign)
assert result["provider_bucket"][0] == "unknown"
def test_priority_picks_best_bucket(self) -> None:
"""If a provider maps to two taxonomy codes yielding pcp and
specialist, pcp (priority 1) wins."""
prov = pl.DataFrame(
{
"npi": ["NPI_M", "NPI_M"],
"primary_taxonomy_code": ["TAX_A", "TAX_B"],
"primary_specialty_description": [
"Family Medicine",
"Cardiology",
],
"entity_type_description": [
"Individual",
"Individual",
],
}
)
xwalk = pl.DataFrame(
{
"provider_taxonomy_code": ["TAX_A", "TAX_B"],
"medicare_specialty_code": ["8", "6"],
}
)
assign = pl.DataFrame(
{
"specialty_code": ["08", "06"],
"primary_care_physician_step1": ["yes", "no"],
"specialist_physician_step_2": ["no", "yes"],
"physician": [1, 1],
}
)
result = int_provider_classification(prov, xwalk, assign)
assert len(result) == 1
assert result["provider_bucket"][0] == "pcp"
# ── int_primary_care_claims ──────────────────────────────────────
class TestIntPrimaryClaims:
"""Joins claims with calendar, member months, provider, etc."""
def test_returns_dataframe(
self,
medical_claim_df,
member_months_df,
calendar_df,
provider_classification_df,
hcpcs_codes_df,
) -> None:
mc = _stg_medical_claim(medical_claim_df)
# Provider table with individuals matching our NPIs
prov = pl.DataFrame(
{
"npi": ["NPI_A", "NPI_B", "NPI_C"],
"primary_taxonomy_code": ["T1", "T2", "T3"],
"primary_specialty_description": [
"Family Medicine",
"Cardiology",
"Nurse Practitioner",
],
"entity_type_description": [
"Individual",
"Individual",
"Individual",
],
}
)
result = int_primary_care_claims(
mc,
member_months_df,
calendar_df,
prov,
provider_classification_df,
hcpcs_codes_df,
)
assert isinstance(result, pl.DataFrame)
def test_output_columns(
self,
medical_claim_df,
member_months_df,
calendar_df,
provider_classification_df,
hcpcs_codes_df,
) -> None:
mc = _stg_medical_claim(medical_claim_df)
prov = pl.DataFrame(
{
"npi": ["NPI_A", "NPI_B", "NPI_C"],
"primary_taxonomy_code": ["T1", "T2", "T3"],
"primary_specialty_description": [
"Family Medicine",
"Cardiology",
"Nurse Practitioner",
],
"entity_type_description": [
"Individual",
"Individual",
"Individual",
],
}
)
result = int_primary_care_claims(
mc,
member_months_df,
calendar_df,
prov,
provider_classification_df,
hcpcs_codes_df,
)
expected_cols = {
"person_id",
"provider_id",
"provider_bucket",
"prov_specialty",
"encounter_id",
"claim_id",
"claim_year_month",
"claim_year_month_int",
"claim_year",
"claim_end_date",
"allowed_amount",
}
assert set(result.columns) == expected_cols
def test_fills_null_bucket_with_other_individual(
self,
medical_claim_df,
member_months_df,
calendar_df,
hcpcs_codes_df,
) -> None:
"""Provider not in classification -> bucket = other_individual."""
mc = _stg_medical_claim(medical_claim_df)
prov = pl.DataFrame(
{
"npi": ["NPI_A", "NPI_B", "NPI_C"],
"primary_taxonomy_code": ["T1", "T2", "T3"],
"primary_specialty_description": [
"Family Medicine",
"Cardiology",
"Nurse Practitioner",
],
"entity_type_description": [
"Individual",
"Individual",
"Individual",
],
}
)
# Empty classification — no providers classified
empty_pc = pl.DataFrame(
{
"provider_id": pl.Series([], dtype=pl.Utf8),
"prov_specialty": pl.Series([], dtype=pl.Utf8),
"provider_bucket": pl.Series([], dtype=pl.Utf8),
}
)
result = int_primary_care_claims(
mc,
member_months_df,
calendar_df,
prov,
empty_pc,
hcpcs_codes_df,
)
if len(result) > 0:
buckets = result["provider_bucket"].unique().to_list()
assert buckets == ["other_individual"]
def test_filters_to_primary_care_hcpcs(
self,
medical_claim_df,
member_months_df,
calendar_df,
provider_classification_df,
) -> None:
"""Claims with non-matching HCPCS codes are excluded."""
mc = _stg_medical_claim(medical_claim_df)
prov = pl.DataFrame(
{
"npi": ["NPI_A", "NPI_B", "NPI_C"],
"primary_taxonomy_code": ["T1", "T2", "T3"],
"primary_specialty_description": [
"Family Medicine",
"Cardiology",
"Nurse Practitioner",
],
"entity_type_description": [
"Individual",
"Individual",
"Individual",
],
}
)
# HCPCS codes that do not match any claim
no_match_hcpcs = pl.DataFrame({"hcpcs_code": ["XXXXX"]})
result = int_primary_care_claims(
mc,
member_months_df,
calendar_df,
prov,
provider_classification_df,
no_match_hcpcs,
)
assert len(result) == 0
# ── int_current_steps ────────────────────────────────────────────
def _build_primary_care_claims(
persons: list[str],
providers: list[str],
buckets: list[str],
claim_dates: list[datetime.date],
ym_ints: list[int],
) -> pl.DataFrame:
"""Build a primary-care-claims frame for step tests."""
n = len(persons)
return pl.DataFrame(
{
"person_id": persons,
"provider_id": providers,
"provider_bucket": buckets,
"prov_specialty": ["Spec"] * n,
"encounter_id": [f"E{i}" for i in range(n)],
"claim_id": [f"C{i}" for i in range(n)],
"claim_year_month": [str(ym) for ym in ym_ints],
"claim_year_month_int": ym_ints,
"claim_year": [str(ym)[:4] for ym in ym_ints],
"claim_end_date": claim_dates,
"allowed_amount": [100.0] * n,
}
)
class TestIntCurrentSteps:
"""5-step attribution waterfall for current period."""
@pytest.fixture
def prov_df(self) -> pl.DataFrame:
return pl.DataFrame(
{
"npi": ["NPI_A", "NPI_B", "NPI_C", "NPI_D", "NPI_E"],
"primary_taxonomy_code": ["T"] * 5,
"primary_specialty_description": ["Spec"] * 5,
"entity_type_description": ["Individual"] * 5,
}
)
@pytest.fixture
def pc_df(self) -> pl.DataFrame:
return pl.DataFrame(
{
"provider_id": [
"NPI_A",
"NPI_B",
"NPI_C",
"NPI_D",
"NPI_E",
],
"prov_specialty": ["Spec"] * 5,
"provider_bucket": [
"pcp",
"specialist",
"npp",
"pcp",
"other_individual",
],
}
)
@pytest.fixture
def cal_df(self) -> pl.DataFrame:
"""Calendar rows spanning 2023-01 through 2025-06."""
rows = []
for yr in (2023, 2024, 2025):
end_m = 12 if yr <= 2024 else 6
for m in range(1, end_m + 1):
dt = D(yr, m, 1)
ym = int(f"{yr}{m:02d}")
rows.append(
{
"full_date": dt,
"year": yr,
"month": m,
"year_month": f"{yr}{m:02d}",
"first_day_of_month": dt,
"last_day_of_month": D(yr, m, 28),
"year_month_int": ym,
}
)
# Second day in month to match claim_start_date
dt2 = D(yr, m, 15)
rows.append(
{
"full_date": dt2,
"year": yr,
"month": m,
"year_month": f"{yr}{m:02d}",
"first_day_of_month": dt,
"last_day_of_month": D(yr, m, 28),
"year_month_int": ym,
}
)
return pl.DataFrame(rows)
@pytest.fixture
def mm_df(self) -> pl.DataFrame:
"""Member months for P1-P5 across 2024."""
rows = []
for pid in ("P1", "P2", "P3", "P4", "P5"):
for m in range(1, 13):
rows.append({"person_id": pid, "year_month": f"2024{m:02d}"})
return pl.DataFrame(rows)
@pytest.fixture
def mc_df(self) -> pl.DataFrame:
"""Minimal medical claims for step-5 all-rendering path."""
return pl.DataFrame(
{
"claim_id": ["MC1"],
"claim_line_number": [1],
"person_id": ["P5"],
"claim_start_date": [D(2024, 5, 1)],
"claim_end_date": [D(2024, 5, 15)],
"allowed_amount": [50.0],
"paid_amount": [40.0],
"rendering_npi": ["NPI_E"],
"hcpcs_code": ["99999"],
"data_source": ["t"],
"encounter_id": ["EM1"],
}
)
def test_returns_all_step_columns(
self, pc_df, prov_df, cal_df, mm_df, mc_df
) -> None:
"""Basic run with one person assigned at step 1."""
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc_df, mm_df, cal_df, prov_df)
expected = {
"person_id",
"provider_id",
"provider_bucket",
"prov_specialty",
"step",
"allowed_amount",
"visits",
"step_description",
}
assert set(result.columns) == expected
def test_step1_pcp_npp(self, pc_df, prov_df, cal_df, mm_df, mc_df):
"""Step 1 assigns PCP/NPP claims in 12-month window."""
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc_df, mm_df, cal_df, prov_df)
s1 = result.filter(pl.col("step") == 1)
assert len(s1) >= 1
assert s1["step_description"][0] == ("12-month PCP/NPP primary-care HCPCS")
def test_step2_specialist(self, pc_df, prov_df, cal_df, mm_df, mc_df):
"""Step 2 picks up specialists for persons not assigned in
step 1."""
pcc = _build_primary_care_claims(
persons=["P2"],
providers=["NPI_B"],
buckets=["specialist"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc_df, mm_df, cal_df, prov_df)
s2 = result.filter(pl.col("step") == 2)
assert len(s2) == 1
assert s2["step_description"][0] == ("12-month specialist primary-care HCPCS")
def test_step3_24month_pcp_npp(self, pc_df, prov_df, cal_df, mm_df, mc_df):
"""Step 3 uses 24-month PCP/NPP for unassigned.
We need as_of to be recent so that the older claim falls
outside the 12-month window but inside the 24-month window.
Include a recent dummy claim to push as_of forward.
"""
pcc = _build_primary_care_claims(
# Dummy recent claim for P1 (will land in step1) to
# push as_of to 2024-11-15
# P3's claim at 2023-12-15 is outside 12mo (as_of -
# 335d ~ 2023-12-15) but inside 24mo (as_of - 700d ~
# 2023-01-15)
persons=["P1", "P3"],
providers=["NPI_A", "NPI_C"],
buckets=["pcp", "npp"],
claim_dates=[D(2024, 11, 15), D(2023, 6, 15)],
ym_ints=[202411, 202306],
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc_df, mm_df, cal_df, prov_df)
s3 = result.filter(pl.col("step") == 3)
assert len(s3) == 1
assert s3["step_description"][0] == ("24-month PCP/NPP primary-care HCPCS")
def test_step4_24month_any(self, pc_df, prov_df, cal_df, mm_df, mc_df):
"""Step 4 uses 24-month any classification."""
pcc = _build_primary_care_claims(
persons=["P4"],
providers=["NPI_E"],
buckets=["other_individual"],
claim_dates=[D(2023, 6, 15)],
ym_ints=[202306],
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc_df, mm_df, cal_df, prov_df)
s4 = result.filter(pl.col("step") == 4)
assert len(s4) == 1
assert s4["step_description"][0] == (
"24-month primary-care HCPCS (any classification)"
)
def test_step5_any_rendering(self, pc_df, prov_df, cal_df, mm_df):
"""Step 5 uses all rendering claims for still-unassigned.
We need at least one PCC claim to give a valid as_of date
(otherwise max(claim_end_date) is null). Put a dummy claim
for P1 to anchor as_of, and P5 only has rendering claims.
"""
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 5, 15)],
ym_ints=[202405],
)
mc = pl.DataFrame(
{
"claim_id": ["MC5"],
"claim_line_number": [1],
"person_id": ["P5"],
"claim_start_date": [D(2024, 5, 1)],
"claim_end_date": [D(2024, 5, 15)],
"allowed_amount": [50.0],
"paid_amount": [40.0],
"rendering_npi": ["NPI_E"],
"hcpcs_code": ["99999"],
"data_source": ["t"],
"encounter_id": ["EM5"],
}
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc, mm_df, cal_df, prov_df)
s5 = result.filter(pl.col("step") == 5)
assert len(s5) == 1
assert s5["step_description"][0] == "24-month any rendering NPI"
def test_as_of_date_uses_max_claim_end_when_past(
self, pc_df, prov_df, cal_df, mm_df, mc_df
):
"""When max(claim_end_date) < today, uses max claim date."""
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 3, 15)],
ym_ints=[202403],
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2026, 1, 1)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc_df, mm_df, cal_df, prov_df)
assert isinstance(result, pl.DataFrame)
def test_as_of_date_uses_today_when_future(
self, pc_df, prov_df, cal_df, mm_df, mc_df
):
"""When max(claim_end_date) > today, uses today."""
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2099, 1, 1)],
ym_ints=[209901],
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc_df, mm_df, cal_df, prov_df)
assert isinstance(result, pl.DataFrame)
def test_all_five_steps_populated(self, pc_df, prov_df, cal_df, mm_df):
"""With carefully crafted data, all 5 steps are populated."""
# P1: PCP claim in 12mo -> step1
# P2: specialist claim in 12mo -> step2
# P3: NPP claim in 24mo only -> step3
# P4: other_individual claim in 24mo only -> step4
# P5: no primary care claim, only rendering -> step5
pcc = _build_primary_care_claims(
persons=["P1", "P2", "P3", "P4"],
providers=["NPI_A", "NPI_B", "NPI_C", "NPI_E"],
buckets=["pcp", "specialist", "npp", "other_individual"],
claim_dates=[
D(2024, 6, 15),
D(2024, 6, 15),
D(2023, 6, 15),
D(2023, 6, 15),
],
ym_ints=[202406, 202406, 202306, 202306],
)
mc = pl.DataFrame(
{
"claim_id": ["MCR5"],
"claim_line_number": [1],
"person_id": ["P5"],
"claim_start_date": [D(2024, 5, 1)],
"claim_end_date": [D(2024, 5, 15)],
"allowed_amount": [50.0],
"paid_amount": [40.0],
"rendering_npi": ["NPI_D"],
"hcpcs_code": ["99999"],
"data_source": ["t"],
"encounter_id": ["EM55"],
}
)
with patch("datetime.date") as mock_date:
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
result = int_current_steps(pcc, pc_df, mc, mm_df, cal_df, prov_df)
steps = sorted(result["step"].unique().to_list())
assert steps == [1, 2, 3, 4, 5]
# ── int_yearly_steps ─────────────────────────────────────────────
class TestIntYearlySteps:
"""Per-year 5-step attribution waterfall."""
@pytest.fixture
def person_years_df(self) -> pl.DataFrame:
return pl.DataFrame(
{
"person_id": ["P1", "P2", "P3", "P4", "P5"],
"performance_year": [2024, 2024, 2024, 2024, 2024],
}
).cast({"person_id": pl.Utf8, "performance_year": pl.Int32})
@pytest.fixture
def pc_df(self) -> pl.DataFrame:
return pl.DataFrame(
{
"provider_id": [
"NPI_A",
"NPI_B",
"NPI_C",
"NPI_D",
"NPI_E",
],
"prov_specialty": ["Spec"] * 5,
"provider_bucket": [
"pcp",
"specialist",
"npp",
"pcp",
"other_individual",
],
}
)
@pytest.fixture
def prov_df(self) -> pl.DataFrame:
return pl.DataFrame(
{
"npi": ["NPI_A", "NPI_B", "NPI_C", "NPI_D", "NPI_E"],
"primary_taxonomy_code": ["T"] * 5,
"primary_specialty_description": ["Spec"] * 5,
"entity_type_description": ["Individual"] * 5,
}
)
@pytest.fixture
def cal_df(self) -> pl.DataFrame:
rows = []
for yr in (2023, 2024, 2025):
end_m = 12 if yr <= 2024 else 6
for m in range(1, end_m + 1):
for d in (1, 15):
dt = D(yr, m, d)
ym = int(f"{yr}{m:02d}")
rows.append(
{
"full_date": dt,
"year": yr,
"month": m,
"year_month": f"{yr}{m:02d}",
"first_day_of_month": D(yr, m, 1),
"last_day_of_month": D(yr, m, 28),
"year_month_int": ym,
}
)
return pl.DataFrame(rows)
@pytest.fixture
def mm_df(self) -> pl.DataFrame:
rows = []
for pid in ("P1", "P2", "P3", "P4", "P5"):
for m in range(1, 13):
rows.append({"person_id": pid, "year_month": f"2024{m:02d}"})
return pl.DataFrame(rows)
def test_returns_dataframe(
self, person_years_df, pc_df, prov_df, cal_df, mm_df
) -> None:
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
mc = pl.DataFrame(
{
"claim_id": pl.Series([], dtype=pl.Utf8),
"claim_line_number": pl.Series([], dtype=pl.Int64),
"person_id": pl.Series([], dtype=pl.Utf8),
"claim_start_date": pl.Series([], dtype=pl.Date),
"claim_end_date": pl.Series([], dtype=pl.Date),
"allowed_amount": pl.Series([], dtype=pl.Float64),
"paid_amount": pl.Series([], dtype=pl.Float64),
"rendering_npi": pl.Series([], dtype=pl.Utf8),
"hcpcs_code": pl.Series([], dtype=pl.Utf8),
"data_source": pl.Series([], dtype=pl.Utf8),
"encounter_id": pl.Series([], dtype=pl.Utf8),
}
)
result = int_yearly_steps(
person_years_df, pcc, pc_df, mc, mm_df, cal_df, prov_df
)
assert isinstance(result, pl.DataFrame)
def test_output_columns(
self, person_years_df, pc_df, prov_df, cal_df, mm_df
) -> None:
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
mc = pl.DataFrame(
{
"claim_id": pl.Series([], dtype=pl.Utf8),
"claim_line_number": pl.Series([], dtype=pl.Int64),
"person_id": pl.Series([], dtype=pl.Utf8),
"claim_start_date": pl.Series([], dtype=pl.Date),
"claim_end_date": pl.Series([], dtype=pl.Date),
"allowed_amount": pl.Series([], dtype=pl.Float64),
"paid_amount": pl.Series([], dtype=pl.Float64),
"rendering_npi": pl.Series([], dtype=pl.Utf8),
"hcpcs_code": pl.Series([], dtype=pl.Utf8),
"data_source": pl.Series([], dtype=pl.Utf8),
"encounter_id": pl.Series([], dtype=pl.Utf8),
}
)
result = int_yearly_steps(
person_years_df, pcc, pc_df, mc, mm_df, cal_df, prov_df
)
expected = {
"person_id",
"performance_year",
"provider_id",
"provider_bucket",
"prov_specialty",
"step",
"step_description",
"allowed_amount",
"visits",
}
assert set(result.columns) == expected
def test_step1_yearly_pcp(
self, person_years_df, pc_df, prov_df, cal_df, mm_df
) -> None:
"""Step 1 assigns PCP/NPP in the performance year."""
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
mc = pl.DataFrame(
{
"claim_id": pl.Series([], dtype=pl.Utf8),
"claim_line_number": pl.Series([], dtype=pl.Int64),
"person_id": pl.Series([], dtype=pl.Utf8),
"claim_start_date": pl.Series([], dtype=pl.Date),
"claim_end_date": pl.Series([], dtype=pl.Date),
"allowed_amount": pl.Series([], dtype=pl.Float64),
"paid_amount": pl.Series([], dtype=pl.Float64),
"rendering_npi": pl.Series([], dtype=pl.Utf8),
"hcpcs_code": pl.Series([], dtype=pl.Utf8),
"data_source": pl.Series([], dtype=pl.Utf8),
"encounter_id": pl.Series([], dtype=pl.Utf8),
}
)
result = int_yearly_steps(
person_years_df, pcc, pc_df, mc, mm_df, cal_df, prov_df
)
s1 = result.filter(pl.col("step") == 1)
assert len(s1) >= 1
assert s1["performance_year"][0] == 2024
def test_all_five_steps_yearly(
self, person_years_df, pc_df, prov_df, cal_df, mm_df
) -> None:
"""With crafted data, all 5 steps are populated for a year."""
# P1: PCP 2024 -> step1
# P2: specialist 2024 -> step2
# P3: NPP 2023 (24mo window) -> step3
# P4: other 2023 (24mo window) -> step4
# P5: rendering only -> step5
pcc = _build_primary_care_claims(
persons=["P1", "P2", "P3", "P4"],
providers=["NPI_A", "NPI_B", "NPI_C", "NPI_E"],
buckets=["pcp", "specialist", "npp", "other_individual"],
claim_dates=[
D(2024, 6, 15),
D(2024, 6, 15),
D(2023, 6, 15),
D(2023, 6, 15),
],
ym_ints=[202406, 202406, 202306, 202306],
)
mc = pl.DataFrame(
{
"claim_id": ["MCR5"],
"claim_line_number": [1],
"person_id": ["P5"],
"claim_start_date": [D(2024, 5, 1)],
"claim_end_date": [D(2024, 5, 15)],
"allowed_amount": [50.0],
"paid_amount": [40.0],
"rendering_npi": ["NPI_D"],
"hcpcs_code": ["99999"],
"data_source": ["t"],
"encounter_id": ["EM55"],
}
)
result = int_yearly_steps(
person_years_df, pcc, pc_df, mc, mm_df, cal_df, prov_df
)
steps = sorted(result["step"].unique().to_list())
assert steps == [1, 2, 3, 4, 5]
def test_empty_years_returns_empty(self, pc_df, prov_df, cal_df, mm_df):
"""When person_years is empty, returns empty frame."""
empty_py = pl.DataFrame(
{
"person_id": pl.Series([], dtype=pl.Utf8),
"performance_year": pl.Series([], dtype=pl.Int32),
}
)
pcc = pl.DataFrame(
{
"person_id": pl.Series([], dtype=pl.Utf8),
"provider_id": pl.Series([], dtype=pl.Utf8),
"provider_bucket": pl.Series([], dtype=pl.Utf8),
"prov_specialty": pl.Series([], dtype=pl.Utf8),
"encounter_id": pl.Series([], dtype=pl.Utf8),
"claim_id": pl.Series([], dtype=pl.Utf8),
"claim_year_month": pl.Series([], dtype=pl.Utf8),
"claim_year_month_int": pl.Series([], dtype=pl.Int64),
"claim_year": pl.Series([], dtype=pl.Utf8),
"claim_end_date": pl.Series([], dtype=pl.Date),
"allowed_amount": pl.Series([], dtype=pl.Float64),
}
)
mc = pl.DataFrame(
{
"claim_id": pl.Series([], dtype=pl.Utf8),
"claim_line_number": pl.Series([], dtype=pl.Int64),
"person_id": pl.Series([], dtype=pl.Utf8),
"claim_start_date": pl.Series([], dtype=pl.Date),
"claim_end_date": pl.Series([], dtype=pl.Date),
"allowed_amount": pl.Series([], dtype=pl.Float64),
"paid_amount": pl.Series([], dtype=pl.Float64),
"rendering_npi": pl.Series([], dtype=pl.Utf8),
"hcpcs_code": pl.Series([], dtype=pl.Utf8),
"data_source": pl.Series([], dtype=pl.Utf8),
"encounter_id": pl.Series([], dtype=pl.Utf8),
}
)
result = int_yearly_steps(empty_py, pcc, pc_df, mc, mm_df, cal_df, prov_df)
assert len(result) == 0
def test_step_descriptions(
self, person_years_df, pc_df, prov_df, cal_df, mm_df
) -> None:
"""Verify step_description labels for each step."""
pcc = _build_primary_care_claims(
persons=["P1", "P2", "P3", "P4"],
providers=["NPI_A", "NPI_B", "NPI_C", "NPI_E"],
buckets=["pcp", "specialist", "npp", "other_individual"],
claim_dates=[
D(2024, 6, 15),
D(2024, 6, 15),
D(2023, 6, 15),
D(2023, 6, 15),
],
ym_ints=[202406, 202406, 202306, 202306],
)
mc = pl.DataFrame(
{
"claim_id": ["MCR5"],
"claim_line_number": [1],
"person_id": ["P5"],
"claim_start_date": [D(2024, 5, 1)],
"claim_end_date": [D(2024, 5, 15)],
"allowed_amount": [50.0],
"paid_amount": [40.0],
"rendering_npi": ["NPI_D"],
"hcpcs_code": ["99999"],
"data_source": ["t"],
"encounter_id": ["EM55"],
}
)
result = int_yearly_steps(
person_years_df, pcc, pc_df, mc, mm_df, cal_df, prov_df
)
desc_map = {
1: "12-month PCP/NPP primary-care HCPCS",
2: "12-month specialist primary-care HCPCS",
3: "24-month PCP/NPP primary-care HCPCS",
4: "24-month primary-care HCPCS (any classification)",
5: "24-month any rendering NPI",
}
for step_num, expected_desc in desc_map.items():
rows = result.filter(pl.col("step") == step_num)
if len(rows) > 0:
assert rows["step_description"][0] == expected_desc
# ── backend-agnostic fallback paths ─────────────────────────────
class TestCurrentStepsFallback:
"""Exercise the try/except fallback for .item() in
int_current_steps (lines 263-264)."""
def test_fetchone_fallback(self):
"""When .item() raises AttributeError, falls back to
.fetchone()."""
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
pc = pl.DataFrame(
{
"provider_id": ["NPI_A"],
"prov_specialty": ["Spec"],
"provider_bucket": ["pcp"],
}
)
cal_rows = []
for yr in (2023, 2024, 2025):
end_m = 12 if yr <= 2024 else 6
for m in range(1, end_m + 1):
for d in (1, 15):
dt = D(yr, m, d)
ym = int(f"{yr}{m:02d}")
cal_rows.append(
{
"full_date": dt,
"year": yr,
"month": m,
"year_month": f"{yr}{m:02d}",
"first_day_of_month": D(yr, m, 1),
"last_day_of_month": D(yr, m, 28),
"year_month_int": ym,
}
)
cal = pl.DataFrame(cal_rows)
mm = pl.DataFrame(
[{"person_id": "P1", "year_month": f"2024{m:02d}"} for m in range(1, 13)]
)
mc = pl.DataFrame(
{
"claim_id": pl.Series([], dtype=pl.Utf8),
"claim_line_number": pl.Series([], dtype=pl.Int64),
"person_id": pl.Series([], dtype=pl.Utf8),
"claim_start_date": pl.Series([], dtype=pl.Date),
"claim_end_date": pl.Series([], dtype=pl.Date),
"allowed_amount": pl.Series([], dtype=pl.Float64),
"paid_amount": pl.Series([], dtype=pl.Float64),
"rendering_npi": pl.Series([], dtype=pl.Utf8),
"hcpcs_code": pl.Series([], dtype=pl.Utf8),
"data_source": pl.Series([], dtype=pl.Utf8),
"encounter_id": pl.Series([], dtype=pl.Utf8),
}
)
prov = pl.DataFrame(
{
"npi": ["NPI_A"],
"primary_taxonomy_code": ["T"],
"primary_specialty_description": ["Spec"],
"entity_type_description": ["Individual"],
}
)
_orig_item = pl.DataFrame.item
def _broken_item(self, *a, **kw):
raise AttributeError("no item method")
class _FakeCursor:
"""Mimic a DB cursor that returns a scalar via
fetchone."""
def __init__(self, val):
self._val = val
def fetchone(self):
return (self._val,)
# Patch pl.DataFrame.item so the try block fails, then
# patch to_native to return our cursor-like object
with (
patch.object(pl.DataFrame, "item", _broken_item),
patch("datetime.date") as mock_date,
):
mock_date.today.return_value = D(2025, 1, 15)
mock_date.side_effect = lambda *a, **k: D(*a, **k)
# We also need to_native to return something with
# fetchone. Patch the narwhals DataFrame's to_native.
import narwhals as nw
_orig_to_native = nw.DataFrame.to_native
_first_call = [True]
def _fake_to_native(self_df):
native = _orig_to_native(self_df)
if _first_call[0] and "max_dt" in self_df.columns:
_first_call[0] = False
return _FakeCursor(D(2024, 6, 15))
return native
with patch.object(nw.DataFrame, "to_native", _fake_to_native):
result = int_current_steps(pcc, pc, mc, mm, cal, prov)
assert isinstance(result, pl.DataFrame)
class TestYearlyStepsFallback:
"""Exercise try/except fallback paths in int_yearly_steps
(lines 586-590)."""
def test_fetchall_fallback(self):
"""When ['performance_year'] fails, falls back to
fetchall."""
py_ = pl.DataFrame(
{
"person_id": ["P1"],
"performance_year": pl.Series([2024], dtype=pl.Int32),
}
)
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
pc = pl.DataFrame(
{
"provider_id": ["NPI_A"],
"prov_specialty": ["Spec"],
"provider_bucket": ["pcp"],
}
)
cal_rows = []
for yr in (2023, 2024, 2025):
end_m = 12 if yr <= 2024 else 6
for m in range(1, end_m + 1):
for d in (1, 15):
dt = D(yr, m, d)
ym = int(f"{yr}{m:02d}")
cal_rows.append(
{
"full_date": dt,
"year": yr,
"month": m,
"year_month": f"{yr}{m:02d}",
"first_day_of_month": D(yr, m, 1),
"last_day_of_month": D(yr, m, 28),
"year_month_int": ym,
}
)
cal = pl.DataFrame(cal_rows)
mm = pl.DataFrame(
[{"person_id": "P1", "year_month": f"2024{m:02d}"} for m in range(1, 13)]
)
mc = pl.DataFrame(
{
"claim_id": pl.Series([], dtype=pl.Utf8),
"claim_line_number": pl.Series([], dtype=pl.Int64),
"person_id": pl.Series([], dtype=pl.Utf8),
"claim_start_date": pl.Series([], dtype=pl.Date),
"claim_end_date": pl.Series([], dtype=pl.Date),
"allowed_amount": pl.Series([], dtype=pl.Float64),
"paid_amount": pl.Series([], dtype=pl.Float64),
"rendering_npi": pl.Series([], dtype=pl.Utf8),
"hcpcs_code": pl.Series([], dtype=pl.Utf8),
"data_source": pl.Series([], dtype=pl.Utf8),
"encounter_id": pl.Series([], dtype=pl.Utf8),
}
)
prov = pl.DataFrame(
{
"npi": ["NPI_A"],
"primary_taxonomy_code": ["T"],
"primary_specialty_description": ["Spec"],
"entity_type_description": ["Individual"],
}
)
import narwhals as nw
_orig_to_native = nw.DataFrame.to_native
class _FakeFetchall:
"""Mimic a DB result with fetchall() returning rows."""
def __init__(self, rows):
self._rows = rows
def fetchall(self):
return self._rows
_call_count = [0]
def _fake_to_native(self_df):
native = _orig_to_native(self_df)
_call_count[0] += 1
if list(self_df.columns) == ["performance_year"] and len(self_df) > 0:
return _FakeFetchall([(2024,)])
return native
with patch.object(nw.DataFrame, "to_native", _fake_to_native):
result = int_yearly_steps(py_, pcc, pc, mc, mm, cal, prov)
assert isinstance(result, pl.DataFrame)
assert len(result) >= 1
def test_iter_rows_fallback(self):
"""When both ['col'] and fetchall fail, falls back to
iter_rows."""
py_ = pl.DataFrame(
{
"person_id": ["P1"],
"performance_year": pl.Series([2024], dtype=pl.Int32),
}
)
pcc = _build_primary_care_claims(
persons=["P1"],
providers=["NPI_A"],
buckets=["pcp"],
claim_dates=[D(2024, 6, 15)],
ym_ints=[202406],
)
pc = pl.DataFrame(
{
"provider_id": ["NPI_A"],
"prov_specialty": ["Spec"],
"provider_bucket": ["pcp"],
}
)
cal_rows = []
for yr in (2023, 2024, 2025):
end_m = 12 if yr <= 2024 else 6
for m in range(1, end_m + 1):
for d in (1, 15):
dt = D(yr, m, d)
ym = int(f"{yr}{m:02d}")
cal_rows.append(
{
"full_date": dt,
"year": yr,
"month": m,
"year_month": f"{yr}{m:02d}",
"first_day_of_month": D(yr, m, 1),
"last_day_of_month": D(yr, m, 28),
"year_month_int": ym,
}
)
cal = pl.DataFrame(cal_rows)
mm = pl.DataFrame(
[{"person_id": "P1", "year_month": f"2024{m:02d}"} for m in range(1, 13)]
)
mc = pl.DataFrame(
{
"claim_id": pl.Series([], dtype=pl.Utf8),
"claim_line_number": pl.Series([], dtype=pl.Int64),
"person_id": pl.Series([], dtype=pl.Utf8),
"claim_start_date": pl.Series([], dtype=pl.Date),
"claim_end_date": pl.Series([], dtype=pl.Date),
"allowed_amount": pl.Series([], dtype=pl.Float64),
"paid_amount": pl.Series([], dtype=pl.Float64),
"rendering_npi": pl.Series([], dtype=pl.Utf8),
"hcpcs_code": pl.Series([], dtype=pl.Utf8),
"data_source": pl.Series([], dtype=pl.Utf8),
"encounter_id": pl.Series([], dtype=pl.Utf8),
}
)
prov = pl.DataFrame(
{
"npi": ["NPI_A"],
"primary_taxonomy_code": ["T"],
"primary_specialty_description": ["Spec"],
"entity_type_description": ["Individual"],
}
)
import narwhals as nw
_orig_to_native = nw.DataFrame.to_native
class _FakeIterRows:
"""Mimic a result where ['col'] and fetchall both
fail, but iter_rows works."""
def __init__(self, native):
self._native = native
def __getitem__(self, key):
raise KeyError(key)
def fetchall(self):
raise AttributeError("no fetchall")
def iter_rows(self):
return self._native.iter_rows()
def _fake_to_native(self_df):
native = _orig_to_native(self_df)
if list(self_df.columns) == ["performance_year"] and len(self_df) > 0:
return _FakeIterRows(native)
return native
with patch.object(nw.DataFrame, "to_native", _fake_to_native):
result = int_yearly_steps(py_, pcc, pc, mc, mm, cal, prov)
assert isinstance(result, pl.DataFrame)
assert len(result) >= 1