"""SP41-visits: unit tests for ``cyclone.rebill.visits_store``. The visits_store persists the AxisCare visits export to the ``visits`` table. These tests pin the loader and the read-back query helper. """ from __future__ import annotations import csv import tempfile from datetime import date from decimal import Decimal from pathlib import Path import pytest from cyclone import db as db_mod from cyclone.rebill.visits_store import ( _normalize_modifiers, _parse_billed, _parse_dos, load_visits_csv, query_visits, ) @pytest.fixture def visits_csv(tmp_path: Path) -> Path: """Write a small visits CSV in AxisCare's shape.""" csv_path = tmp_path / "visits.csv" with csv_path.open("w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow([ "Client", "Visit Date", "Payer", "Authorized", "Member ID", "Auth Start Date", "Auth End Date", "Authorization #", "ICD-10", "Service", "Procedure Code", "Modifiers", "Client Classes", "Billable Hours", "Billable Amount", "Invoice #", "Claimed", ]) writer.writerow([ "Wilson, Michelle", "01/01/2026", "CO Medicaid", "Yes", "R649327", "01/01/2026", "12/31/2026", "3", "R69", "Homemaker S5150", "S5150", "U8", "DD Waiver", "1", "$212.77", "INV-2026-01-01-R649327", "Yes", ]) writer.writerow([ "O'Keefe , Laura", "05/09/2026", "COHCPF", "yes", "Y477643", "03/10/2026", "08/31/2026", "9", "R69", "IHSS PCP T1019", "T1019", "KX, SC, U2", "IHSS DR", "8.5333", "$237.57", "INV-2026-05-09-Y477643", "Yes", ]) writer.writerow([ "Stale, Old", "12/31/2025", "CO Medicaid", "Yes", "Z999999", "01/01/2025", "12/31/2025", "0", "R69", "Old S5150", "S5150", "U8", "DD Waiver", "1", "$100.00", "INV-2025-12-31-Z999999", "Yes", # OUT-OF-WINDOW ]) return csv_path def test_normalize_modifiers(): """Colon-joined or comma-joined input → colon-joined output.""" assert _normalize_modifiers("KX:SC:U2") == "KX:SC:U2" assert _normalize_modifiers("KX, SC, U2") == "KX:SC:U2" assert _normalize_modifiers("") == "" assert _normalize_modifiers("U8") == "U8" def test_parse_billed(): """$ prefix and thousands separators are stripped.""" assert _parse_billed("$212.77") == Decimal("212.77") assert _parse_billed("1,234.56") == Decimal("1234.56") assert _parse_billed("") == Decimal("0") assert _parse_billed("not a number") == Decimal("0") def test_parse_dos(): """MM/DD/YYYY primary, YYYY-MM-DD fallback.""" assert _parse_dos("01/01/2026") == date(2026, 1, 1) assert _parse_dos("2026-01-01") == date(2026, 1, 1) assert _parse_dos("") is None assert _parse_dos("garbage") is None def test_load_visits_csv_inserts_in_window_only(visits_csv: Path): """Out-of-window rows are dropped, in-window rows are inserted.""" n = load_visits_csv( visits_csv, window_start=date(2026, 1, 1), window_end=date(2026, 6, 27), ) # 2 of 3 rows are in-window (the 12/31/2025 row is dropped) assert n == 2 rows = query_visits(dos_start=date(2026, 1, 1), dos_end=date(2026, 6, 27)) assert len(rows) == 2 member_ids = {r.member_id for r in rows} assert member_ids == {"R649327", "Y477643"} def test_load_visits_csv_dedupes_on_natural_key(visits_csv: Path): """Re-running the loader must not insert duplicate (DOS, member, procedure, mod).""" load_visits_csv(visits_csv, window_start=date(2026, 1, 1), window_end=date(2026, 6, 27)) n2 = load_visits_csv(visits_csv, window_start=date(2026, 1, 1), window_end=date(2026, 6, 27)) assert n2 == 0 # all rows were already there assert len(query_visits()) == 2 def test_query_visits_filters_by_member_and_procedure(visits_csv: Path): load_visits_csv(visits_csv, window_start=date(2026, 1, 1), window_end=date(2026, 6, 27)) rows = query_visits(member_id="R649327") assert len(rows) == 1 assert rows[0].procedure_code == "S5150" assert rows[0].modifiers == "U8" assert rows[0].billed_amount == Decimal("212.77") assert rows[0].icd10 == "R69" assert rows[0].prior_auth == "3" assert rows[0].payer == "CO Medicaid" def test_query_visits_modifiers_normalized_to_colon_joined(visits_csv: Path): """Batch-2 style 'KX, SC, U2' is stored as 'KX:SC:U2'.""" load_visits_csv(visits_csv, window_start=date(2026, 1, 1), window_end=date(2026, 6, 27)) rows = query_visits(member_id="Y477643") assert len(rows) == 1 assert rows[0].modifiers == "KX:SC:U2" assert rows[0].procedure_code == "T1019" def test_visit_table_exists_post_migration(): """Migration 23 created the visits table — schema check.""" db_mod.init_db() from sqlalchemy import inspect, text with db_mod.SessionLocal()() as s: v = s.execute(text("PRAGMA user_version")).scalar() inspector = inspect(db_mod.engine()) assert "visits" in inspector.get_table_names() assert v >= 23