113 lines
3.8 KiB
Python
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
|