Final fix-wave items C1, I1-I4 and B11 (refined), all landing in the
same handful of interconnected files (llm.lineage/llm.rag/llm.evidence
share the collapse/select/label/budget call paths, so they can't be
split into independently-working commits):
- C1 (critical): llm.lineage._store() was one process-global bib.Store
whose sqlite connection is check_same_thread=True — /chat runs each
turn in Starlette's threadpool, so every thread but the first got a
silently inert (or raising) store. Now threading.local(), one Store
opened lazily per thread. rag._docket_year uses the same accessor and
is now fully guarded (never escapes era_of). New
TestConcurrentLineage (8 threads x 40 calls, mirrors
TestConcurrentChats) asserts no cross-thread error and one Store
construction per thread.
- I1 (Ruling B12): _collapse's tie-break is now
(not anchored_fr, is_proposed, p_id, item_key) — a final rule beats a
tied proposed one, and a fully-tied pair is decided by item_key for a
deterministic total order.
- I2 (Ruling B13): rule_label carries "{vol} FR {page}" when the
paragraph resolves (via the cached item/paragraph lookups), making
labels unique across paragraphs that used to share one.
merge_sources dedupes rule-kind rows on (item_key, p_id) instead of
label, since two rule chunks for the same paragraph can now carry
different labels.
- I3 (Important): era_balance picks eras evenly across the range when
there are more distinct eras than top_n, so the round-robin (which
visits newest-first every round) doesn't silently drop the oldest
eras from a wide history question.
- I4 (Important): _SYSTEM's opener, refusal clause and recency
guidance are reworded to match what the prompt actually contains
(excerpts + optional Lineage + optional Valuation), and the Lineage
paragraph now precedes Valuation to match build_messages' order.
- B11 refined (whole-branch review): a hard per-turn code cap
(chat_codes_max=24, explicit codes then family order —
llm.evidence.cap_codes, shared by valuation_evidence and
lineage_evidence so both cap the same question identically),
a valuation-rows cap (valuation_rows_max=24, explicit then newest
vintage), a lineage-rows hard cap (2x lineage_max_rows, priority
rows capped at 4/code), element-diff code lists compacted past 8,
manual sources capped at 2, and build_messages(budget_chars=...)
which drops lineage-source excerpts>4, retrieved>6, cited>8,
manual>1, valuation rows>12, then lineage rows>lineage_max_rows in
that order until the assembled prompt fits — wired into
stream_answer as budget_chars=cfg.chat_num_ctx*3. The
_LINEAGE_SOURCES_HARD_CAP is now enforced in lineage_sources' own
event loop, not only its element-diff tail.
I5: TestLineageEvidenceLive now uses the shared restore_families
fixture (moved to tests/conftest.py) so opening the real replica
doesn't leak thousands of derived families into later tests.
Diagnosing this also turned up a second, pre-existing leak of the same
shape: TestStreamAnswer's control-question test ran the real
lineage_evidence against CFG's default (real, 3GB)
data/replica/aco.ro.duckdb, since llm.lineage.lineage_evidence calls
evidence.warm(cfg) unconditionally before checking for detected codes
— fixed by pointing that one test at a nonexistent replica path
(exactly the "without touching the replica" behavior its own docstring
already claimed).
Verified: uv run pytest tests/llm tests/pfs/test_families.py
tests/pfs/test_lineage.py tests/cli/test_pfs_cli.py tests/dev -q
-p no:cacheprovider -m "not live" — 583 passed. In-process golden run
against the live replica: 3/7 pass (ccm-history, audio-only-em-99441,
g2211-commenters); g2058-replacement/apcm-vs-ccm/99490-telehealth
unchanged documented gaps; g2064-g2065 newly misses one of its three
anchors (JE7KYBW3 p1111) specifically because of the new 4-per-code
lineage-row cap this commit adds (Ruling B11) — an accepted tradeoff
of the budget work, not a bug. Prompt-size check (budget 24,576
chars): "history of CCM coding and payment" 20,670 chars; the 58-code
three-family history question 16,135 chars (58 detected codes capped
to 24) — both under budget.
839 lines
27 KiB
Python
839 lines
27 KiB
Python
"""Shared pytest fixtures for the stack test suite.
|
|
|
|
Fixtures are organized by domain:
|
|
- polars DataFrames for every major input_layer schema shape
|
|
- PFS computation DataFrames (RVU, GPCI, labor, supply, equipment)
|
|
- rex primitives (Sieve, FieldMap, sample raw text)
|
|
- bib.tag helpers
|
|
- temporary file paths
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import shutil
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import polars as pl
|
|
import pytest
|
|
|
|
# ── pfs.families registry fixture (shared: pfs + llm tests) ──────────────────
|
|
# Any test that calls ``refresh_from``/mutates the live ``pfs.families.FAMILIES``
|
|
# registry (directly, or indirectly by opening a real replica — I5) must not
|
|
# leave it clobbered for the rest of the session.
|
|
|
|
|
|
@pytest.fixture
|
|
def restore_families():
|
|
"""Snapshot ``pfs.families.FAMILIES`` and restore it (and the compiled
|
|
phrase/code index built off it) in teardown, even if the test body
|
|
raises."""
|
|
from pfs import families as mod
|
|
|
|
before = dict(mod.FAMILIES)
|
|
try:
|
|
yield mod
|
|
finally:
|
|
mod.FAMILIES.clear()
|
|
mod.FAMILIES.update(before)
|
|
mod.rebuild_index()
|
|
|
|
|
|
# ── Perf hooks — auto-file Gitea issues on test failure/skip in CI ───────────
|
|
# Only active when STACK_FILE_TEST_ISSUES=true (set in CI workflows).
|
|
|
|
try:
|
|
from perf.hooks import pytest_runtest_logreport # noqa: F401
|
|
except ImportError:
|
|
pass
|
|
|
|
# ── Zotero DB fixture (session-scoped for speed) ─────────────────────────────
|
|
# create_db() takes ~10s. By creating once and copying per-test, we cut
|
|
# cumulative DB creation from ~30min to ~10s across 200+ Zotero tests.
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def _zotero_template_db():
|
|
"""Session-scoped: create one Zotero schema DB, reuse everywhere."""
|
|
from zot.schema import create_db
|
|
|
|
with tempfile.TemporaryDirectory() as td:
|
|
path = str(Path(td) / "template.sqlite")
|
|
con = create_db(path)
|
|
con.close()
|
|
yield path
|
|
|
|
|
|
@pytest.fixture
|
|
def zotero_db(_zotero_template_db, tmp_path):
|
|
"""Per-test Zotero DB: fast copy of the session template."""
|
|
dest = str(tmp_path / "z.sqlite")
|
|
shutil.copy2(_zotero_template_db, dest)
|
|
return dest
|
|
|
|
|
|
# ── input_layer fixtures ──────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.fixture
|
|
def medical_claim_df() -> pl.DataFrame:
|
|
"""One-row medical claim matching input_layer.medical_claim schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"claim_id": ["CLM001"],
|
|
"claim_line_number": [1],
|
|
"claim_type": ["professional"],
|
|
"person_id": ["P001"],
|
|
"member_id": ["M001"],
|
|
"patient_id": ["PT001"],
|
|
"payer": ["Medicare"],
|
|
"plan": [None],
|
|
"encounter_id": [None],
|
|
"claim_start_date": [None],
|
|
"claim_end_date": [None],
|
|
"service_unit": [1],
|
|
"hcpcs_code": ["99213"],
|
|
"hcpcs_modifier_1": [None],
|
|
"hcpcs_modifier_2": [None],
|
|
"hcpcs_modifier_3": [None],
|
|
"hcpcs_modifier_4": [None],
|
|
"rendering_npi": ["1234567890"],
|
|
"billing_npi": [None],
|
|
"facility_npi": [None],
|
|
"paid_date": [None],
|
|
"paid_amount": [75.00],
|
|
"allowed_amount": [95.00],
|
|
"charge_amount": [150.00],
|
|
"diagnosis_code_type": ["icd-10-cm"],
|
|
"diagnosis_code_1": ["E11.9"],
|
|
"diagnosis_code_2": [None],
|
|
"diagnosis_poa_1": [None],
|
|
"procedure_code_type": [None],
|
|
"procedure_code_1": [None],
|
|
"procedure_date_1": [None],
|
|
"data_source": ["test"],
|
|
"file_name": ["test.csv"],
|
|
"ingest_datetime": [None],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def eligibility_df() -> pl.DataFrame:
|
|
"""One-row eligibility matching input_layer.eligibility schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"person_id": ["P001"],
|
|
"member_id": ["M001"],
|
|
"subscriber_id": [None],
|
|
"gender": ["M"],
|
|
"race": [None],
|
|
"birth_date": [None],
|
|
"death_date": [None],
|
|
"death_flag": [0],
|
|
"enrollment_start_date": [None],
|
|
"enrollment_end_date": [None],
|
|
"payer": ["Medicare"],
|
|
"payer_type": ["government"],
|
|
"plan": [None],
|
|
"dual_status_code": [None],
|
|
"medicare_status_code": [None],
|
|
"first_name": [None],
|
|
"last_name": [None],
|
|
"state": ["TX"],
|
|
"zip_code": ["78701"],
|
|
"data_source": ["test"],
|
|
"file_name": ["test.csv"],
|
|
"ingest_datetime": [None],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def pharmacy_claim_df() -> pl.DataFrame:
|
|
"""One-row pharmacy claim matching input_layer.pharmacy_claim schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"claim_id": ["RX001"],
|
|
"claim_line_number": [1],
|
|
"person_id": ["P001"],
|
|
"member_id": ["M001"],
|
|
"patient_id": ["PT001"],
|
|
"payer": ["Medicare Part D"],
|
|
"plan": [None],
|
|
"dispensing_provider_id": [None],
|
|
"dispensing_date": [None],
|
|
"ndc_code": ["00071015523"],
|
|
"quantity": [30.0],
|
|
"days_supply": [30],
|
|
"refills": [0],
|
|
"paid_date": [None],
|
|
"paid_amount": [12.50],
|
|
"allowed_amount": [15.00],
|
|
"charge_amount": [25.00],
|
|
"data_source": ["test"],
|
|
"file_name": ["test.csv"],
|
|
"ingest_datetime": [None],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def encounter_df() -> pl.DataFrame:
|
|
"""One-row encounter matching input_layer.encounter schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"encounter_id": ["ENC001"],
|
|
"person_id": ["P001"],
|
|
"patient_id": ["PT001"],
|
|
"encounter_type": ["acute inpatient"],
|
|
"encounter_start_date": [None],
|
|
"encounter_end_date": [None],
|
|
"length_of_stay": [3],
|
|
"admit_source_code": [None],
|
|
"admit_source_description": [None],
|
|
"admit_type_code": [None],
|
|
"admit_type_description": [None],
|
|
"discharge_disposition_code": ["01"],
|
|
"discharge_disposition_description": ["home"],
|
|
"attending_provider_id": [None],
|
|
"attending_provider_name": [None],
|
|
"facility_id": [None],
|
|
"facility_name": [None],
|
|
"primary_diagnosis_code_type": ["icd-10-cm"],
|
|
"primary_diagnosis_code": ["E11.9"],
|
|
"primary_diagnosis_description": [None],
|
|
"drg_code_type": [None],
|
|
"drg_code": [None],
|
|
"drg_description": [None],
|
|
"paid_amount": [5000.00],
|
|
"allowed_amount": [6000.00],
|
|
"charge_amount": [12000.00],
|
|
"data_source": ["test"],
|
|
"file_name": ["test.csv"],
|
|
"ingest_datetime": [None],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def condition_df() -> pl.DataFrame:
|
|
"""One-row condition matching input_layer.condition schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"condition_id": ["COND001"],
|
|
"person_id": ["P001"],
|
|
"patient_id": ["PT001"],
|
|
"encounter_id": ["ENC001"],
|
|
"claim_id": [None],
|
|
"recorded_date": [None],
|
|
"onset_date": [None],
|
|
"resolved_date": [None],
|
|
"status": ["active"],
|
|
"condition_type": ["problem"],
|
|
"source_code_type": ["icd-10-cm"],
|
|
"source_code": ["E119"],
|
|
"source_description": ["Type 2 diabetes mellitus without complications"],
|
|
"normalized_code_type": ["icd-10-cm"],
|
|
"normalized_code": ["E11.9"],
|
|
"normalized_description": [
|
|
"Type 2 diabetes mellitus without complications"
|
|
],
|
|
"condition_rank": [1],
|
|
"present_on_admit_code": [None],
|
|
"present_on_admit_description": [None],
|
|
"data_source": ["test"],
|
|
"file_name": ["test.csv"],
|
|
"ingest_datetime": [None],
|
|
"payer": ["Medicare"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def procedure_df() -> pl.DataFrame:
|
|
"""One-row procedure matching input_layer.procedure schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"procedure_id": ["PROC001"],
|
|
"person_id": ["P001"],
|
|
"member_id": [None],
|
|
"patient_id": ["PT001"],
|
|
"encounter_id": ["ENC001"],
|
|
"claim_id": ["CLM001"],
|
|
"procedure_date": [None],
|
|
"source_code_type": ["hcpcs"],
|
|
"source_code": ["99213"],
|
|
"source_description": ["Office visit, established patient"],
|
|
"normalized_code_type": ["hcpcs"],
|
|
"normalized_code": ["99213"],
|
|
"normalized_description": ["Office visit, established patient"],
|
|
"modifier_1": [None],
|
|
"modifier_2": [None],
|
|
"modifier_3": [None],
|
|
"modifier_4": [None],
|
|
"modifier_5": [None],
|
|
"practitioner_id": [None],
|
|
"data_source": ["test"],
|
|
"file_name": ["test.csv"],
|
|
"ingest_datetime": [None],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def patient_df() -> pl.DataFrame:
|
|
"""One-row patient matching input_layer.patient schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"person_id": ["P001"],
|
|
"patient_id": ["PT001"],
|
|
"name_suffix": [None],
|
|
"first_name": ["Jane"],
|
|
"middle_name": [None],
|
|
"last_name": ["Smith"],
|
|
"sex": ["F"],
|
|
"race": [None],
|
|
"birth_date": [None],
|
|
"death_date": [None],
|
|
"death_flag": [0],
|
|
"social_security_number": [None],
|
|
"address": ["123 Main St"],
|
|
"city": ["Austin"],
|
|
"state": ["TX"],
|
|
"zip_code": ["78701"],
|
|
"county": [None],
|
|
"latitude": [None],
|
|
"longitude": [None],
|
|
"phone": [None],
|
|
"email": [None],
|
|
"ethnicity": [None],
|
|
"data_source": ["test"],
|
|
"file_name": ["test.csv"],
|
|
"ingest_datetime": [None],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def practitioner_df() -> pl.DataFrame:
|
|
"""One-row practitioner matching input_layer.practitioner schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"practitioner_id": ["DR001"],
|
|
"npi": ["1234567890"],
|
|
"first_name": ["Alice"],
|
|
"last_name": ["Johnson"],
|
|
"practice_affiliation": [None],
|
|
"specialty": ["Internal Medicine"],
|
|
"sub_specialty": [None],
|
|
"data_source": ["test"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def location_df() -> pl.DataFrame:
|
|
"""One-row location matching input_layer.location schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"location_id": ["LOC001"],
|
|
"npi": ["9876543210"],
|
|
"name": ["General Hospital"],
|
|
"facility_type": ["hospital"],
|
|
"parent_organization": [None],
|
|
"address": ["456 Oak Ave"],
|
|
"city": ["Austin"],
|
|
"state": ["TX"],
|
|
"zip_code": ["78702"],
|
|
"latitude": [30.2672],
|
|
"longitude": [-97.7431],
|
|
"data_source": ["test"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def medication_df() -> pl.DataFrame:
|
|
"""One-row medication matching input_layer.medication schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"medication_id": ["MED001"],
|
|
"person_id": ["P001"],
|
|
"patient_id": ["PT001"],
|
|
"encounter_id": [None],
|
|
"dispensing_date": [None],
|
|
"prescribing_date": [None],
|
|
"source_code_type": ["ndc"],
|
|
"source_code": ["00071015523"],
|
|
"source_description": ["Lipitor 10mg"],
|
|
"ndc_code": ["00071015523"],
|
|
"ndc_description": ["Atorvastatin 10mg"],
|
|
"rxnorm_code": ["617312"],
|
|
"rxnorm_description": [None],
|
|
"atc_code": [None],
|
|
"atc_description": [None],
|
|
"route": ["oral"],
|
|
"strength": ["10mg"],
|
|
"quantity": [30.0],
|
|
"quantity_unit": ["tablets"],
|
|
"days_supply": [30],
|
|
"practitioner_id": [None],
|
|
"data_source": ["test"],
|
|
"file_name": ["test.csv"],
|
|
"ingest_datetime": [None],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def observation_df() -> pl.DataFrame:
|
|
"""One-row observation matching input_layer.observation schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"observation_id": ["OBS001"],
|
|
"person_id": ["P001"],
|
|
"patient_id": ["PT001"],
|
|
"encounter_id": ["ENC001"],
|
|
"panel_id": [None],
|
|
"observation_date": [None],
|
|
"observation_type": ["vital-sign"],
|
|
"source_code_type": ["loinc"],
|
|
"source_code": ["8480-6"],
|
|
"source_description": ["Systolic blood pressure"],
|
|
"normalized_code_type": ["loinc"],
|
|
"normalized_code": ["8480-6"],
|
|
"normalized_description": ["Systolic blood pressure"],
|
|
"result": ["120"],
|
|
"source_units": ["mm[Hg]"],
|
|
"normalized_units": ["mmHg"],
|
|
"source_reference_range_low": ["90"],
|
|
"source_reference_range_high": ["140"],
|
|
"normalized_reference_range_low": ["90"],
|
|
"normalized_reference_range_high": ["140"],
|
|
"data_source": ["test"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def appointment_df() -> pl.DataFrame:
|
|
"""One-row appointment matching input_layer.appointment schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"appointment_id": ["APT001"],
|
|
"person_id": ["P001"],
|
|
"patient_id": ["PT001"],
|
|
"encounter_id": ["ENC001"],
|
|
"source_appointment_type_code": ["office"],
|
|
"source_appointment_type_description": ["Office Visit"],
|
|
"normalized_appointment_type_code": ["office"],
|
|
"normalized_appointment_type_description": ["Office Visit"],
|
|
"start_datetime": [None],
|
|
"end_datetime": [None],
|
|
"duration": [30],
|
|
"location_id": ["LOC001"],
|
|
"practitioner_id": ["DR001"],
|
|
"source_status": ["completed"],
|
|
"normalized_status": ["completed"],
|
|
"appointment_specialty": ["Internal Medicine"],
|
|
"reason": ["Follow-up"],
|
|
"source_reason_code_type": [None],
|
|
"source_reason_code": [None],
|
|
"source_reason_description": [None],
|
|
"normalized_reason_code_type": [None],
|
|
"normalized_reason_code": [None],
|
|
"normalized_reason_description": [None],
|
|
"cancellation_reason": [None],
|
|
"source_cancellation_reason_code_type": [None],
|
|
"source_cancellation_reason_code": [None],
|
|
"source_cancellation_reason_description": [None],
|
|
"normalized_cancellation_reason_code_type": [None],
|
|
"normalized_cancellation_reason_code": [None],
|
|
"normalized_cancellation_reason_description": [None],
|
|
"data_source": ["test"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def immunization_df() -> pl.DataFrame:
|
|
"""One-row immunization matching input_layer.immunization schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"immunization_id": ["IMM001"],
|
|
"person_id": ["P001"],
|
|
"patient_id": ["PT001"],
|
|
"encounter_id": ["ENC001"],
|
|
"source_code_type": ["cvx"],
|
|
"source_code": ["208"],
|
|
"source_description": ["COVID-19 Pfizer"],
|
|
"normalized_code_type": ["cvx"],
|
|
"normalized_code": ["208"],
|
|
"normalized_description": ["COVID-19 Pfizer-BioNTech"],
|
|
"status": ["completed"],
|
|
"status_reason": [None],
|
|
"occurrence_date": [None],
|
|
"source_dose": ["0.3 mL"],
|
|
"normalized_dose": ["0.3 mL"],
|
|
"lot_number": ["EL9269"],
|
|
"body_site": ["left arm"],
|
|
"route": ["intramuscular"],
|
|
"location_id": ["LOC001"],
|
|
"practitioner_id": ["DR001"],
|
|
"data_source": ["test"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def lab_result_df() -> pl.DataFrame:
|
|
"""One-row lab result matching input_layer.lab_result schema."""
|
|
return pl.DataFrame(
|
|
{
|
|
"lab_result_id": ["LAB001"],
|
|
"person_id": ["P001"],
|
|
"patient_id": ["PT001"],
|
|
"encounter_id": ["ENC001"],
|
|
"accession_number": ["ACC001"],
|
|
"source_order_type": ["loinc"],
|
|
"source_order_code": ["2345-7"],
|
|
"source_order_description": ["Glucose"],
|
|
"source_component_type": ["loinc"],
|
|
"source_component_code": ["2345-7"],
|
|
"source_component_description": ["Glucose [Mass/volume] in Serum"],
|
|
"normalized_order_type": ["loinc"],
|
|
"normalized_order_code": ["2345-7"],
|
|
"normalized_order_description": ["Glucose"],
|
|
"normalized_component_type": ["loinc"],
|
|
"normalized_component_code": ["2345-7"],
|
|
"normalized_component_description": ["Glucose [Mass/volume] in Serum"],
|
|
"status": ["final"],
|
|
"result": ["105"],
|
|
"result_datetime": [None],
|
|
"collection_datetime": [None],
|
|
"source_units": ["mg/dL"],
|
|
"normalized_units": ["mg/dL"],
|
|
"source_reference_range_low": ["70"],
|
|
"source_reference_range_high": ["100"],
|
|
"normalized_reference_range_low": ["70"],
|
|
"normalized_reference_range_high": ["100"],
|
|
"source_abnormal_flag": ["H"],
|
|
"normalized_abnormal_flag": ["high"],
|
|
"specimen": ["serum"],
|
|
"ordering_practitioner_id": ["DR001"],
|
|
"data_source": ["test"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def provider_attribution_df() -> pl.DataFrame:
|
|
"""One-row provider attribution matching input_layer.provider_attribution."""
|
|
return pl.DataFrame(
|
|
{
|
|
"person_id": ["P001"],
|
|
"member_id": ["M001"],
|
|
"practitioner_id": ["DR001"],
|
|
"practitioner_npi": ["1234567890"],
|
|
"attribution_type": ["primary"],
|
|
"attribution_start_date": [None],
|
|
"attribution_end_date": [None],
|
|
"data_source": ["test"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def member_months_df() -> pl.DataFrame:
|
|
"""Simple member-months DataFrame used by claims_preprocessing pipeline."""
|
|
return pl.DataFrame(
|
|
{
|
|
"person_id": ["P001"],
|
|
"member_id": ["M001"],
|
|
"year": [2024],
|
|
"month": [1],
|
|
"payer": ["Medicare"],
|
|
"plan": [None],
|
|
"data_source": ["test"],
|
|
}
|
|
)
|
|
|
|
|
|
# ── PFS computation fixtures ──────────────────────────────────────────────────
|
|
|
|
TEST_CF: float = 32.7442
|
|
"""Conversion factor used by PFS payment() tests (historical CY2023)."""
|
|
|
|
|
|
@pytest.fixture
|
|
def rvu_df() -> pl.DataFrame:
|
|
"""RVU data for PFS payment calculation tests.
|
|
|
|
Note: ``conv_factor`` is no longer a column on pfs.rvu — it's
|
|
passed as the ``cf=`` keyword to ``pfs.calcs.payment.payment()``.
|
|
Use ``tests.conftest.TEST_CF`` when calling.
|
|
"""
|
|
return pl.DataFrame(
|
|
{
|
|
"hcpcs": ["99213", "99214", "99215"],
|
|
"work_rvu": [0.97, 1.50, 2.11],
|
|
"non_fac_pe_rvu": [1.04, 1.56, 2.22],
|
|
"fac_pe_rvu": [0.41, 0.63, 0.95],
|
|
"mp_rvu": [0.07, 0.11, 0.16],
|
|
"mac": ["10212", "10212", "10212"],
|
|
"locality": ["0201", "0201", "0201"],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def gpci_df() -> pl.DataFrame:
|
|
"""GPCI data for PFS payment calculation tests."""
|
|
return pl.DataFrame(
|
|
{
|
|
"mac": ["10212"],
|
|
"locality": ["0201"],
|
|
"work_gpci": [1.0],
|
|
"pe_gpci": [0.998],
|
|
"mp_gpci": [0.633],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def labor_df() -> pl.DataFrame:
|
|
"""Clinical labor inputs for direct PE cost tests."""
|
|
return pl.DataFrame(
|
|
{
|
|
"hcpcs": ["99213", "99213", "99214"],
|
|
"nf_minutes": [14.0, 5.0, 18.0],
|
|
"f_minutes": [9.0, 3.0, 12.0],
|
|
"rate_per_minute": [0.63, 0.49, 0.63],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def supply_df() -> pl.DataFrame:
|
|
"""Medical supply inputs for direct PE cost tests."""
|
|
return pl.DataFrame(
|
|
{
|
|
"hcpcs": ["99213", "99214"],
|
|
"nf_quantity": [2.0, 3.0],
|
|
"f_quantity": [1.0, 2.0],
|
|
"unit_price": [0.12, 0.12],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def equipment_df() -> pl.DataFrame:
|
|
"""Medical equipment inputs for direct PE cost tests."""
|
|
return pl.DataFrame(
|
|
{
|
|
"hcpcs": ["99213", "99214"],
|
|
"nf_minutes": [10.0, 15.0],
|
|
"f_minutes": [6.0, 10.0],
|
|
"unit_price": [1200.0, 1200.0],
|
|
"useful_life": [7.0, 7.0],
|
|
"minutes_per_year": [525600.0, 525600.0],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def equipment_with_maintenance_df() -> pl.DataFrame:
|
|
"""Equipment DataFrame that includes a maintenance_factor column."""
|
|
return pl.DataFrame(
|
|
{
|
|
"hcpcs": ["99213"],
|
|
"nf_minutes": [10.0],
|
|
"f_minutes": [6.0],
|
|
"unit_price": [1200.0],
|
|
"useful_life": [7.0],
|
|
"minutes_per_year": [525600.0],
|
|
"maintenance_factor": [0.05],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def work_time_df() -> pl.DataFrame:
|
|
"""Physician work time for time-intensity ratio tests."""
|
|
return pl.DataFrame(
|
|
{
|
|
"hcpcs": ["99213", "99214", "99215"],
|
|
"work_rvu": [0.97, 1.50, 2.11],
|
|
"total_time": [21.0, 31.0, 46.0],
|
|
"intra_service_time": [15.0, 22.0, 33.0],
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mppr_claims_df() -> pl.DataFrame:
|
|
"""Two procedures in the same session for MPPR reduction tests."""
|
|
return pl.DataFrame(
|
|
{
|
|
"claim_id": ["CLM001", "CLM001"],
|
|
"hcpcs": ["99214", "99213"],
|
|
"session_id": ["SES001", "SES001"],
|
|
"non_fac_pe_rvu": [1.56, 1.04],
|
|
}
|
|
)
|
|
|
|
|
|
# ── rex fixtures ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.fixture
|
|
def sas_listing_text() -> str:
|
|
"""Multiline SAS listing file content for sieve classification tests."""
|
|
return (
|
|
" The SAS System 10:30 AM\n"
|
|
"\n"
|
|
" Obs claim_id paid_amount diag_code\n"
|
|
" 1 CLM001 75.00 E119\n"
|
|
" 2 CLM002 150.00 I10\n"
|
|
" 3 CLM003 225.00 J449\n"
|
|
" Total: 450.00\n"
|
|
"\n"
|
|
"NOTE: The data set WORK.CLAIMS has 3 observations.\n"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def fixed_width_text() -> str:
|
|
"""Fixed-width records for FieldMap positional extraction tests."""
|
|
return (
|
|
"CLM001 P001 20240115000007500\n"
|
|
"CLM002 P002 20240116000015000\n"
|
|
"CLM003 P003 20240117000022500\n"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def pipe_delimited_text() -> str:
|
|
"""Pipe-delimited file for FieldMap delimiter-mode tests."""
|
|
return (
|
|
"claim_id|person_id|service_date|paid_amount\n"
|
|
"CLM001|P001|20240115|75.00\n"
|
|
"CLM002|P002|20240116|150.00\n"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def copybook_text() -> str:
|
|
"""Simple COBOL copybook definition for copybook parser tests."""
|
|
return """
|
|
01 CLAIM-RECORD.
|
|
05 CLAIM-ID PIC X(10).
|
|
05 PERSON-ID PIC X(10).
|
|
05 SERVICE-DATE PIC 9(8).
|
|
05 PAID-AMOUNT PIC S9(7)V99 COMP-3.
|
|
05 FILLER PIC X(5).
|
|
05 DX-CODE PIC X(7).
|
|
"""
|
|
|
|
|
|
@pytest.fixture
|
|
def basic_sieve():
|
|
"""Minimal Sieve with only a data pattern for simple unit tests."""
|
|
from rex.sieve import Sieve
|
|
|
|
return Sieve(
|
|
name="test_basic",
|
|
data=re.compile(r"^\s+\d+\s+\w"),
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def sas_sieve():
|
|
"""Full four-pattern SAS listing Sieve matching the module docstring."""
|
|
from rex.sieve import Sieve
|
|
|
|
return Sieve(
|
|
name="sas_listing",
|
|
head=re.compile(r"^\s*(The SAS System|\x0c)|^\s+\d{2}:\d{2}"),
|
|
rule=re.compile(r"^\s*(Obs\s+\w|---+[\s-]*---+)"),
|
|
data=re.compile(r"^\s+\d+\s+\w"),
|
|
skip=re.compile(r"^\s*(Total|NOTE:|WARNING:|$)"),
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def fixed_width_field_map():
|
|
"""FieldMap in fixed-width positional mode for claim record extraction."""
|
|
from rex.sieve import FieldMap
|
|
|
|
return FieldMap(
|
|
positions={
|
|
"claim_id": (0, 10),
|
|
"person_id": (10, 20),
|
|
"service_date": (20, 28),
|
|
"paid_amount": (28, 37),
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def delimited_field_map():
|
|
"""FieldMap in pipe-delimited mode with index mapping."""
|
|
from rex.sieve import FieldMap
|
|
|
|
return FieldMap(
|
|
delimiter="|",
|
|
indices={
|
|
"claim_id": 0,
|
|
"person_id": 1,
|
|
"service_date": 2,
|
|
"paid_amount": 3,
|
|
},
|
|
)
|
|
|
|
|
|
# ── bib.tag fixtures ──────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.fixture
|
|
def tag_labels() -> list[str]:
|
|
"""Mixed tag label strings for filter_tags tests."""
|
|
return [
|
|
"module:pfs",
|
|
"module:aco",
|
|
"table:pfs.rvu",
|
|
"source:cms-website",
|
|
"year:2026",
|
|
"rule:cms-1807-f",
|
|
"file:rvu",
|
|
]
|
|
|
|
|
|
# ── temp file fixtures ────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.fixture
|
|
def tmp_text_file(tmp_path: Path) -> Path:
|
|
"""Write the SAS listing sample to a temporary .lst file."""
|
|
content = (
|
|
" The SAS System 10:30 AM\n"
|
|
"\n"
|
|
" Obs claim_id paid_amount\n"
|
|
" 1 CLM001 75.00\n"
|
|
" 2 CLM002 150.00\n"
|
|
)
|
|
f = tmp_path / "report.lst"
|
|
f.write_text(content, encoding="utf-8")
|
|
return f
|
|
|
|
|
|
@pytest.fixture
|
|
def tmp_csv_file(tmp_path: Path) -> Path:
|
|
"""Write a simple CSV to a temporary file."""
|
|
content = "claim_id,person_id,paid_amount\nCLM001,P001,75.00\nCLM002,P002,150.00\n"
|
|
f = tmp_path / "claims.csv"
|
|
f.write_text(content, encoding="utf-8")
|
|
return f
|