Files
cyclone/backend/tests/test_rebill_pipeline_b.py

121 lines
5.4 KiB
Python

"""Pipeline B: NOT_IN_835 visits → fresh 837Ps, batched by (member, ISO-week)."""
from datetime import date
from decimal import Decimal
from cyclone.rebill.pipeline_b import build_pipeline_b_batches
from cyclone.rebill.reconcile import VisitRow
def _v(date_, member, proc, amt):
return VisitRow(date=date_, member_id=member, procedure=proc, billed=Decimal(amt))
def test_batches_split_by_member_and_iso_week():
visits = [
_v(date(2026, 6, 23), "J813715", "T1019", "2.32"),
_v(date(2026, 6, 25), "J813715", "T1019", "2.32"), # same week
_v(date(2026, 6, 23), "OTHER", "T1019", "2.32"), # different member
_v(date(2026, 6, 30), "J813715", "T1019", "2.32"), # different week
]
out = build_pipeline_b_batches(visits, as_of=date(2026, 7, 7), override=False)
assert len(out) == 3
sizes = sorted(len(b.visits) for b in out)
assert sizes == [1, 1, 2]
def test_timely_filing_excludes_old_visits():
visits = [
_v(date(2026, 1, 1), "OLD", "T1019", "2.32"), # 187 days old
_v(date(2026, 6, 27), "NEW", "T1019", "2.32"), # 10 days old
]
out = build_pipeline_b_batches(visits, as_of=date(2026, 7, 7), override=False)
members = {b.member_id for b in out}
assert "OLD" not in members
assert "NEW" in members
def test_override_relaxes_timely_filing():
visits = [_v(date(2026, 1, 1), "OLD", "T1019", "2.32")]
out_default = build_pipeline_b_batches(visits, as_of=date(2026, 7, 7), override=False)
out_overridden = build_pipeline_b_batches(visits, as_of=date(2026, 7, 7), override=True)
assert out_default == []
assert len(out_overridden) == 1
def test_override_flag_set_on_past_window_visit_batch():
# Same member "OLD" with one past-window visit (187 days old, ISO
# week 1) and one within-window visit (10 days old, ISO week 26).
# With override=True the past-window visit survives and the batch
# for week 1 must be flagged has_overridden_visits=True because the
# override saved an otherwise-excluded visit.
visits = [
_v(date(2026, 1, 1), "OLD", "T1019", "2.32"), # 187 days old (past window, W01)
_v(date(2026, 6, 27), "OLD", "T1019", "2.32"), # 10 days old (within window, W26)
]
out_overridden = build_pipeline_b_batches(
visits, as_of=date(2026, 7, 7), override=True,
)
# Two batches: one per (member, ISO-week) — the past-window visit and
# the within-window visit land in different weeks.
assert len(out_overridden) == 2
by_week = {b.iso_week: b for b in out_overridden}
assert by_week[1].member_id == "OLD"
assert by_week[1].has_overridden_visits is True # the override saved this batch
assert by_week[26].member_id == "OLD"
assert by_week[26].has_overridden_visits is False # no override needed here
# With override=False the past-window visit is dropped. The surviving
# within-window batch (W26) must NOT carry the override flag, and the
# past-window batch (W01) is absent.
out_default = build_pipeline_b_batches(
visits, as_of=date(2026, 7, 7), override=False,
)
assert len(out_default) == 1
assert out_default[0].member_id == "OLD"
assert out_default[0].iso_week == 26
assert out_default[0].has_overridden_visits is False
def test_serialize_member_week_batch_emits_one_envelope():
"""One 837P envelope per MemberWeekBatch — one CLM + one SV1 + one
DTP*472 service date per visit.
The SP41 plan spec wrote ``DTM*472*`` but the canonical 837P service
date segment is ``DTP*472*`` (per :func:`cyclone.parsers.serialize_837.
_build_dtp_472` and X12 005010X222A1). This test asserts against the
canonical segment name so the batch overload stays consistent with
the existing ``serialize_837`` building blocks.
"""
from cyclone.parsers.serialize_837 import serialize_member_week_batch
visits = [
_v(date(2026, 6, 23), "J813715", "T1019", "2.32"),
_v(date(2026, 6, 25), "J813715", "T1019", "2.32"),
]
batches = build_pipeline_b_batches(visits, as_of=date(2026, 7, 7), override=False)
assert len(batches) == 1
body = serialize_member_week_batch(batches[0])
text = body.decode("utf-8", errors="ignore") if isinstance(body, bytes) else body
# CLM* segment appears twice (once per visit)
assert text.count("CLM*") == 2
# SV1* appears once per visit (one service line per claim)
assert text.count("SV1*") == 2
# DTP*472* service-date segment appears twice (canonical 837P segment name)
assert text.count("DTP*472*") == 2
# Single envelope (single ISA / single IEA), not per-visit envelopes
assert text.count("ISA*") == 1
assert text.count("IEA*") == 1
# Deterministic per-visit claim_id pattern (member_id + date + 1-based idx)
assert "MW-J813715-2026-06-23-01" in text
assert "MW-J813715-2026-06-25-02" in text
def test_serialize_member_week_batch_return_type_is_bytes():
"""Task 14 spec: ``serialize_member_week_batch`` returns ``bytes``
(the existing ``serialize_837`` returns ``str``; this overload
diverges intentionally so callers can write the file directly)."""
from cyclone.parsers.serialize_837 import serialize_member_week_batch
visits = [_v(date(2026, 6, 23), "J813715", "T1019", "2.32")]
batches = build_pipeline_b_batches(visits, as_of=date(2026, 7, 7), override=False)
assert len(batches) == 1
body = serialize_member_week_batch(batches[0])
assert isinstance(body, bytes)