Files
cyclone/backend/tests/test_api_line_reconciliation.py
T

113 lines
3.8 KiB
Python

"""SP7 — endpoint test for GET /api/claims/{id}/line-reconciliation.
Spec §5.1.
Architecture note: 837 service lines live in ``Claim.raw_json``, not a
separate ORM table, so the test seeds them via the raw_json blob.
"""
from datetime import date, datetime, timezone
from decimal import Decimal
import json
import uuid
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import select
from cyclone import db
from cyclone.api import app
from cyclone.parsers.models_835 import ClaimPayment, ServicePayment
from cyclone.reconcile import run
from cyclone.store import _persist_835_remit, _remittance_835_row
@pytest.fixture
def client():
return TestClient(app)
def _seed(client):
"""Insert one claim + matching remit + 4 service lines (3 matched, 1 unmatched 837).
Returns the claim id."""
bid = str(uuid.uuid4())
claim_id = "CLM-API-1"
svc_lines_json = []
# 4 837 service lines: 99213, 99214, 99215, 90837 (last one is 837-only — no SVC)
for i, code in enumerate(["99213", "99214", "99215", "90837"], start=1):
svc_lines_json.append({
"line_number": i,
"procedure": {"qualifier": "HC", "code": code, "modifiers": []},
"charge": "100.00",
"unit_type": "UN",
"units": "1",
"place_of_service": "11",
"service_date": f"2026-06-0{i}",
"provider_reference": None,
})
with db.SessionLocal()() as s:
s.add(db.Batch(
id=bid, kind="835", input_filename="x.835",
parsed_at=datetime.now(timezone.utc),
totals_json={"total_claims": 1, "total_paid": "240"},
validation_json={"passed": True, "warnings": [], "errors": []},
raw_result_json={"_": "stub"},
))
s.add(db.Claim(
id=claim_id,
patient_control_number="PCN-API",
charge_amount=Decimal("300.00"),
provider_npi="1234567890",
payer_id="SKCO0",
service_date_from=date(2026, 6, 1),
service_date_to=date(2026, 6, 4),
state="submitted",
batch_id=bid,
raw_json={"service_lines": svc_lines_json},
))
# Remit pays the first 3 lines (line 4 is 837-only)
cp = ClaimPayment(
payer_claim_control_number="PCN-API",
status_code="1",
total_charge=Decimal("300.00"),
total_paid=Decimal("240.00"),
service_payments=[
ServicePayment(
line_number=i, procedure_qualifier="HC",
procedure_code=code, charge=Decimal("100.00"),
payment=Decimal("80.00"),
units=Decimal("1"),
service_date=date(2026, 6, i),
)
for i, code in enumerate(["99213", "99214", "99215"], start=1)
],
)
remit = _remittance_835_row(cp, bid)
s.add(remit)
s.flush()
_persist_835_remit(s, cp, remit.id)
s.commit()
# Run reconcile so matched_remittance_id gets set + LineReconciliation rows persist
run(s, bid)
s.commit()
return claim_id
def test_endpoint_returns_documented_shape(client):
claim_id = _seed(client)
r = client.get(f"/api/claims/{claim_id}/line-reconciliation")
assert r.status_code == 200, r.text
body = r.json()
assert body["claim_id"] == claim_id
assert body["summary"]["total_lines"] == 4
assert body["summary"]["matched_lines"] == 3
statuses = sorted(row["status"] for row in body["lines"])
assert statuses == ["matched", "matched", "matched", "unmatched_837_only"]
def test_endpoint_404_for_unknown_claim(client):
r = client.get("/api/claims/UNKNOWN/line-reconciliation")
assert r.status_code == 404