3946 new tests covering: - cms.table: all 150 SQLTable models (schema, tablename, qualified_name, column_names, nullable fields, construction, uniqueness) - cms.log: JsonlHandler, setup() idempotency, custom attrs, exceptions - ccw.table: all 41 claim tables across 7 types (IP, SNF, Hospice, HHA, HOP, Carrier, DME) with structural validation - ccw.docs: all 308 variable documentation modules (importability, required attrs, type validation)
222 lines
7.6 KiB
Python
222 lines
7.6 KiB
Python
"""Tests for CCW FFS Claims table models.
|
|
|
|
Validates that all 41 auto-generated Pydantic table models across
|
|
7 claim types (IP, SNF, Hospice, HHA, HOP, Carrier, DME) follow
|
|
the SQLTable contract.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import inspect
|
|
from datetime import date
|
|
|
|
import pytest
|
|
|
|
from aco.table.base import SQLTable
|
|
from ccw.table import carrier, dme, hha, hop, hospice, ip, snf
|
|
|
|
|
|
def _all_ccw_classes():
|
|
"""Collect all SQLTable subclasses from all CCW table modules."""
|
|
classes = []
|
|
for mod in [carrier, dme, hha, hop, hospice, ip, snf]:
|
|
for name, cls in inspect.getmembers(mod, inspect.isclass):
|
|
if issubclass(cls, SQLTable) and cls is not SQLTable:
|
|
classes.append(cls)
|
|
return classes
|
|
|
|
|
|
ALL_CCW_CLASSES = _all_ccw_classes()
|
|
ALL_CCW_IDS = [cls.__name__ for cls in ALL_CCW_CLASSES]
|
|
|
|
|
|
# ── Module importability ─────────────────────────────────────────
|
|
|
|
|
|
class TestImports:
|
|
def test_all_seven_modules_import(self) -> None:
|
|
pass
|
|
|
|
def test_total_class_count(self) -> None:
|
|
assert len(ALL_CCW_CLASSES) == 41
|
|
|
|
|
|
# ── Schema metadata (parametrized across all 41 classes) ─────────
|
|
|
|
|
|
class TestSchemaMetadata:
|
|
@pytest.fixture(params=ALL_CCW_CLASSES, ids=ALL_CCW_IDS)
|
|
def table_cls(self, request):
|
|
return request.param
|
|
|
|
def test_schema_is_ccw(self, table_cls) -> None:
|
|
assert table_cls.__schema__ == "ccw"
|
|
|
|
def test_tablename_set(self, table_cls) -> None:
|
|
assert table_cls.__tablename__, f"{table_cls.__name__} empty tablename"
|
|
|
|
def test_qualified_name(self, table_cls) -> None:
|
|
assert table_cls.qualified_name() == f"ccw.{table_cls.__tablename__}"
|
|
|
|
def test_column_names_non_empty(self, table_cls) -> None:
|
|
assert len(table_cls.column_names()) > 0
|
|
|
|
def test_all_fields_nullable(self, table_cls) -> None:
|
|
for field_name, field_info in table_cls.model_fields.items():
|
|
assert field_info.default is None, (
|
|
f"{table_cls.__name__}.{field_name} not nullable"
|
|
)
|
|
|
|
|
|
# ── Claim type structure ─────────────────────────────────────────
|
|
|
|
|
|
class TestClaimTypeStructure:
|
|
"""Institutional claim types (IP, SNF, Hospice, HHA, HOP) have 7
|
|
sub-tables each; professional types (Carrier, DME) have 3 each."""
|
|
|
|
@pytest.mark.parametrize(
|
|
"mod,expected",
|
|
[
|
|
(ip, 7),
|
|
(snf, 7),
|
|
(hospice, 7),
|
|
(hha, 7),
|
|
(hop, 7),
|
|
(carrier, 3),
|
|
(dme, 3),
|
|
],
|
|
)
|
|
def test_subtable_count(self, mod, expected) -> None:
|
|
classes = [
|
|
c
|
|
for _, c in inspect.getmembers(mod, inspect.isclass)
|
|
if issubclass(c, SQLTable) and c is not SQLTable
|
|
]
|
|
assert len(classes) == expected
|
|
|
|
@pytest.mark.parametrize(
|
|
"mod,prefix",
|
|
[
|
|
(ip, "Ip"),
|
|
(snf, "Snf"),
|
|
(hospice, "Hospice"),
|
|
(hha, "Hha"),
|
|
(hop, "Hop"),
|
|
],
|
|
)
|
|
def test_institutional_subtable_names(self, mod, prefix) -> None:
|
|
"""Each institutional claim type has Base, RevenueCenter,
|
|
ConditionCode, OccurrenceCode, SpanCode, ValueCode, Demo."""
|
|
expected = {
|
|
f"{prefix}Base",
|
|
f"{prefix}RevenueCenter",
|
|
f"{prefix}ConditionCode",
|
|
f"{prefix}OccurrenceCode",
|
|
f"{prefix}SpanCode",
|
|
f"{prefix}ValueCode",
|
|
f"{prefix}Demo",
|
|
}
|
|
actual = {
|
|
name
|
|
for name, cls in inspect.getmembers(mod, inspect.isclass)
|
|
if issubclass(cls, SQLTable) and cls is not SQLTable
|
|
}
|
|
assert actual == expected
|
|
|
|
@pytest.mark.parametrize("mod,prefix", [(carrier, "Carrier"), (dme, "Dme")])
|
|
def test_professional_subtable_names(self, mod, prefix) -> None:
|
|
expected = {f"{prefix}Base", f"{prefix}Line", f"{prefix}Demo"}
|
|
actual = {
|
|
name
|
|
for name, cls in inspect.getmembers(mod, inspect.isclass)
|
|
if issubclass(cls, SQLTable) and cls is not SQLTable
|
|
}
|
|
assert actual == expected
|
|
|
|
|
|
# ── Tablename uniqueness ─────────────────────────────────────────
|
|
|
|
|
|
class TestUniqueness:
|
|
def test_no_duplicate_tablenames(self) -> None:
|
|
names = [cls.__tablename__ for cls in ALL_CCW_CLASSES]
|
|
assert len(names) == len(set(names))
|
|
|
|
|
|
# ── Field type correctness ───────────────────────────────────────
|
|
|
|
|
|
class TestFieldTypes:
|
|
"""CCW tables use str, float, and date field types."""
|
|
|
|
def test_field_types_are_expected(self) -> None:
|
|
allowed = {str, float, date}
|
|
for cls in ALL_CCW_CLASSES:
|
|
for field_name, field_info in cls.model_fields.items():
|
|
annotation = field_info.annotation
|
|
# Annotation is `T | None` — extract the base type
|
|
# For union types, __args__ gives (T, NoneType)
|
|
if hasattr(annotation, "__args__"):
|
|
base_types = {t for t in annotation.__args__ if t is not type(None)}
|
|
assert base_types.issubset(allowed), (
|
|
f"{cls.__name__}.{field_name}: {base_types} not in {allowed}"
|
|
)
|
|
|
|
|
|
# ── Construction ─────────────────────────────────────────────────
|
|
|
|
|
|
class TestConstruction:
|
|
def test_empty_construction(self) -> None:
|
|
obj = ip.IpBase()
|
|
assert obj.bene_id is None
|
|
assert obj.clm_id is None
|
|
|
|
def test_construction_with_kwargs(self) -> None:
|
|
obj = carrier.CarrierBase(
|
|
bene_id="BEN123",
|
|
clm_id="CLM456",
|
|
clm_pmt_amt=1234.56,
|
|
)
|
|
assert obj.bene_id == "BEN123"
|
|
assert obj.clm_pmt_amt == 1234.56
|
|
|
|
def test_date_field(self) -> None:
|
|
obj = carrier.CarrierBase(clm_from_dt=date(2024, 3, 15))
|
|
assert obj.clm_from_dt == date(2024, 3, 15)
|
|
|
|
def test_from_attributes_config(self) -> None:
|
|
assert ip.IpBase.model_config.get("from_attributes") is True
|
|
|
|
|
|
# ── Spot checks — largest tables ─────────────────────────────────
|
|
|
|
|
|
class TestSpotChecks:
|
|
def test_ip_base_is_largest(self) -> None:
|
|
assert len(ip.IpBase.column_names()) == 252
|
|
|
|
def test_snf_base_field_count(self) -> None:
|
|
assert len(snf.SnfBase.column_names()) == 180
|
|
|
|
def test_hop_base_field_count(self) -> None:
|
|
assert len(hop.HopBase.column_names()) == 167
|
|
|
|
def test_carrier_line_field_count(self) -> None:
|
|
assert len(carrier.CarrierLine.column_names()) == 89
|
|
|
|
def test_common_key_fields(self) -> None:
|
|
"""All Base tables should have bene_id and clm_id."""
|
|
for cls in ALL_CCW_CLASSES:
|
|
if cls.__name__.endswith("Base"):
|
|
cols = cls.column_names()
|
|
assert "bene_id" in cols, f"{cls.__name__} missing bene_id"
|
|
assert "clm_id" in cols, f"{cls.__name__} missing clm_id"
|
|
|
|
def test_demo_tables_have_demo_fields(self) -> None:
|
|
for cls in ALL_CCW_CLASSES:
|
|
if cls.__name__.endswith("Demo"):
|
|
cols = cls.column_names()
|
|
assert "demo_id_num" in cols, f"{cls.__name__} missing demo_id_num"
|