91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
"""SP41 — tests for the `cyclone rebill-from-835` CLI.
|
|
|
|
Three smoke tests:
|
|
1. --help renders cleanly and surfaces --window and --override-filing.
|
|
2. --status (with no other args) renders cleanly and exits 0 — the
|
|
option is recognized even when no prior summary.csv exists.
|
|
3. End-to-end run against a 2-row visits CSV (one in-window, one
|
|
ancient) and a zero-SVC 835 fixture produces a summary.csv with
|
|
the in-window visit classified as REBILLED_B.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
from pathlib import Path
|
|
|
|
from click.testing import CliRunner
|
|
|
|
from cyclone.cli import main
|
|
|
|
|
|
def test_rebill_from_835_help():
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["rebill-from-835", "--help"])
|
|
assert result.exit_code == 0, result.output
|
|
assert "--window" in result.output
|
|
assert "--override-filing" in result.output
|
|
|
|
|
|
def test_rebill_from_835_status_help():
|
|
"""Either bare --status or --status --help should exit 0.
|
|
|
|
The spec note: the help-test ensures the option is recognized, so
|
|
even a "no prior runs" 0-exit path is acceptable. We try both
|
|
invocations because click short-circuits --help ahead of any
|
|
required-option check; the bare --status path is the one that
|
|
proves --status works without --visits / --ingest.
|
|
"""
|
|
runner = CliRunner()
|
|
result_help = runner.invoke(main, ["rebill-from-835", "--status", "--help"])
|
|
assert result_help.exit_code == 0, result_help.output
|
|
|
|
result_bare = runner.invoke(main, ["rebill-from-835", "--status"])
|
|
assert result_bare.exit_code == 0, result_bare.output
|
|
|
|
|
|
def test_rebill_from_835_runs(tmp_path: Path):
|
|
"""End-to-end: 1 in-window visit + 1 ancient visit + zero-SVC 835 → REBILLED_B for the in-window row."""
|
|
visits_path = tmp_path / "visits.csv"
|
|
visits_path.write_text(
|
|
"Visit Date,Member ID,Procedure Code,Billable Amount\n"
|
|
"06/27/2026,J813715,T1019,$2.32\n"
|
|
"01/01/2020,ANCIENT,T1019,$2.32\n"
|
|
)
|
|
|
|
ingest_dir = tmp_path / "ingest"
|
|
ingest_dir.mkdir()
|
|
(ingest_dir / "x.835").write_text("ST*835*0001~SE*0*0001~")
|
|
|
|
out_dir = tmp_path / "out"
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, [
|
|
"rebill-from-835",
|
|
"--visits", str(visits_path),
|
|
"--ingest", str(ingest_dir),
|
|
"--out", str(out_dir),
|
|
"--as-of", "2026-07-07",
|
|
])
|
|
assert result.exit_code == 0, (
|
|
f"CLI exited {result.exit_code}, output={result.output!r}, "
|
|
f"exception={result.exception!r}"
|
|
)
|
|
|
|
summary_path = out_dir / "summary.csv"
|
|
assert summary_path.exists(), summary_path
|
|
|
|
with summary_path.open(newline="") as f:
|
|
rows = list(csv.DictReader(f))
|
|
# Two visits in, two summary rows out (one REBILLED_B, one
|
|
# EXCLUDED_TIMELY_FILING). PAID outcomes would be dropped, but
|
|
# neither of these is PAID — the 835 has no SVCs.
|
|
assert len(rows) == 2, rows
|
|
|
|
by_member = {r["member_id"]: r for r in rows}
|
|
in_window = by_member["J813715"]
|
|
assert in_window["disposition"] == "REBILLED_B", in_window
|
|
assert in_window["dos"] == "2026-06-27", in_window
|
|
|
|
ancient = by_member["ANCIENT"]
|
|
assert ancient["disposition"] == "EXCLUDED_TIMELY_FILING", ancient
|
|
assert ancient["dos"] == "2020-01-01", ancient |