feat(sp7): inbox lanes matched_remittance includes matched/total line counts

This commit is contained in:
Tyler
2026-06-20 19:43:43 -06:00
parent 6ef59b5d1d
commit b7bda0662f
2 changed files with 234 additions and 8 deletions
+144
View File
@@ -0,0 +1,144 @@
"""SP7 — inbox lanes expose matched_remittance line counts.
For each claim row in the rejected / done_today lanes that has a
matched remittance, the row must include a ``matched_remittance`` block
with ``id``, ``matched_lines``, and ``total_lines``. These power the
``MatchedRemitCard`` badge in T17.
Architecture note: 837 service lines live in ``Claim.raw_json``, so
``total_lines`` is read from the JSON blob.
"""
from datetime import date, datetime, timezone
from decimal import Decimal
import uuid
import pytest
from fastapi.testclient import TestClient
from cyclone import db
from cyclone.api import app
from cyclone.parsers.models_835 import ClaimPayment, ServicePayment
from cyclone.reconcile import run as reconcile_run
from cyclone.store import _persist_835_remit, _remittance_835_row
@pytest.fixture(autouse=True)
def _clear_app_state():
yield
if hasattr(app.state, "dismissed_pairs"):
app.state.dismissed_pairs = set()
@pytest.fixture
def client() -> TestClient:
return TestClient(app)
def _seed_pair_with_two_lines():
"""One 837 claim (2 service lines) + one 835 remit (1 svc composite
matching line 1). After reconcile: 1 matched, 1 unmatched_837_only.
Returns the claim_id (and the remittance_id via matched_remittance).
"""
bid_837 = str(uuid.uuid4())
bid_835 = str(uuid.uuid4())
claim_id = "CLM-INBOX-1"
svc_lines_json = [
{
"line_number": 1,
"procedure": {"qualifier": "HC", "code": "99213", "modifiers": []},
"charge": "100.00",
"unit_type": "UN",
"units": "1",
"place_of_service": "11",
"service_date": "2026-06-01",
"provider_reference": None,
},
{
"line_number": 2,
"procedure": {"qualifier": "HC", "code": "99214", "modifiers": []},
"charge": "100.00",
"unit_type": "UN",
"units": "1",
"place_of_service": "11",
"service_date": "2026-06-01",
"provider_reference": None,
},
]
with db.SessionLocal()() as s:
# 837 batch + claim
s.add(db.Batch(
id=bid_837, kind="837p", input_filename="x.837",
parsed_at=datetime.now(timezone.utc),
totals_json={}, validation_json={}, raw_result_json={},
))
s.add(db.Claim(
id=claim_id,
patient_control_number="PCN-INBOX",
charge_amount=Decimal("200.00"),
provider_npi="1234567890",
payer_id="SKCO0",
service_date_from=date(2026, 6, 1),
service_date_to=date(2026, 6, 1),
state=db.ClaimState.SUBMITTED,
batch_id=bid_837,
raw_json={"service_lines": svc_lines_json},
))
# 835 batch + remit
s.add(db.Batch(
id=bid_835, kind="835", input_filename="x.835",
parsed_at=datetime.now(timezone.utc),
totals_json={}, validation_json={}, raw_result_json={},
))
cp = ClaimPayment(
payer_claim_control_number="PCN-INBOX",
status_code="1",
total_charge=Decimal("100.00"),
total_paid=Decimal("80.00"),
service_payments=[
ServicePayment(
line_number=1, procedure_qualifier="HC",
procedure_code="99213",
charge=Decimal("100.00"),
payment=Decimal("80.00"),
units=Decimal("1"),
service_date=date(2026, 6, 1),
),
],
)
remit = _remittance_835_row(cp, bid_835)
s.add(remit)
s.flush()
_persist_835_remit(s, cp, remit.id)
# Force the claim to a terminal state so it lands in done_today.
# Run reconcile which auto-matches by PCN + sets state to PAID.
reconcile_run(s, bid_835)
s.commit()
return claim_id
def test_done_today_lane_includes_matched_remittance_line_counts(client):
claim_id = _seed_pair_with_two_lines()
# Backfill state_changed_at so the claim lands in the done_today lane
# (reconcile.run() doesn't write it; the API surface is exercised by
# the existing manual-match flow which sets it).
from sqlalchemy.orm import Session
with db.SessionLocal()() as s:
cl = s.get(db.Claim, claim_id)
cl.state_changed_at = datetime.now(timezone.utc)
s.commit()
r = client.get("/api/inbox/lanes")
assert r.status_code == 200
body = r.json()
# The seeded claim should be in done_today (auto-reconciled by run()).
ids = [row["id"] for row in body["done_today"]]
assert claim_id in ids, f"expected claim in done_today, got ids={ids}"
row = next(r for r in body["done_today"] if r["id"] == claim_id)
mr = row.get("matched_remittance")
assert mr is not None, "expected matched_remittance block on done_today claim row"
assert "id" in mr
assert mr["total_lines"] == 2 # two 837 service lines
assert mr["matched_lines"] == 1 # only line 1 has a matching SVC composite