feat(sp7): wire match_service_lines into reconcile.run
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"""SP7 — integration test: reconcile.run() persists LineReconciliation rows.
|
||||
|
||||
Spec §4.4.
|
||||
|
||||
Architecture note: 837 service lines are stored inside
|
||||
``Claim.raw_json["service_lines"]`` keyed by ``line_number`` (1-based).
|
||||
The ``LineReconciliation`` table therefore stores
|
||||
``claim_service_line_number`` (int), not a FK to a separate service_lines
|
||||
table.
|
||||
"""
|
||||
from datetime import date, datetime, timezone
|
||||
from decimal import Decimal
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from cyclone.db import (
|
||||
Batch,
|
||||
Claim,
|
||||
LineReconciliation,
|
||||
SessionLocal,
|
||||
)
|
||||
from cyclone.reconcile import run
|
||||
from cyclone.store import _persist_835_remit, _remittance_835_row
|
||||
from cyclone.parsers.models_835 import ClaimPayment, ServicePayment
|
||||
|
||||
|
||||
def _setup_db() -> tuple:
|
||||
"""Build a minimal 837 claim + 835 remit that share PCN. Returns (claim_id, batch_id)."""
|
||||
bid = str(uuid.uuid4())
|
||||
claim_id = "CLM-T1"
|
||||
service_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 SessionLocal()() as s:
|
||||
# 1. Batch
|
||||
s.add(Batch(
|
||||
id=bid, kind="835", input_filename="x.835",
|
||||
parsed_at=datetime.now(timezone.utc),
|
||||
totals_json={"total_claims": 1, "total_paid": "150"},
|
||||
validation_json={"passed": True, "warnings": [], "errors": []},
|
||||
raw_result_json={"_": "stub"},
|
||||
))
|
||||
# 2. Claim with two service lines (in raw_json)
|
||||
s.add(Claim(
|
||||
id=claim_id,
|
||||
patient_control_number="PCN-1",
|
||||
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="submitted",
|
||||
batch_id=bid,
|
||||
raw_json={"service_lines": service_lines_json},
|
||||
))
|
||||
# 3. Remit with two SVC composites — line 1 matches 99213, line 2
|
||||
# is 90837 (no claim match).
|
||||
cp = ClaimPayment(
|
||||
payer_claim_control_number="PCN-1",
|
||||
status_code="1",
|
||||
total_charge=Decimal("200.00"),
|
||||
total_paid=Decimal("150.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),
|
||||
),
|
||||
ServicePayment(
|
||||
line_number=2, procedure_qualifier="HC",
|
||||
procedure_code="90837",
|
||||
charge=Decimal("100.00"),
|
||||
payment=Decimal("70.00"),
|
||||
units=Decimal("1"),
|
||||
service_date=date(2026, 6, 1),
|
||||
),
|
||||
],
|
||||
)
|
||||
remit = _remittance_835_row(cp, bid)
|
||||
s.add(remit)
|
||||
s.flush()
|
||||
_persist_835_remit(s, cp, remit.id)
|
||||
s.commit()
|
||||
|
||||
return claim_id, bid
|
||||
|
||||
|
||||
def test_run_persists_line_reconciliation_rows():
|
||||
claim_id, bid = _setup_db()
|
||||
with SessionLocal()() as s:
|
||||
result = run(s, bid)
|
||||
s.commit()
|
||||
assert result.matched == 1
|
||||
|
||||
lrs = list(
|
||||
s.execute(
|
||||
select(LineReconciliation).where(LineReconciliation.claim_id == claim_id)
|
||||
).scalars().all()
|
||||
)
|
||||
# Expect 3 rows: 99213 matched, 99214 unmatched_837_only, 90837 unmatched_835_only
|
||||
assert len(lrs) == 3
|
||||
by_status = {lr.status for lr in lrs}
|
||||
assert by_status == {"matched", "unmatched_837_only", "unmatched_835_only"}
|
||||
Reference in New Issue
Block a user