"""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