feat(sp7): claim detail includes lineReconciliation slim projection
This commit is contained in:
@@ -1297,6 +1297,78 @@ class CycloneStore:
|
||||
# default ``None`` — the UI shows "no match" rather
|
||||
# than crashing.
|
||||
|
||||
# SP7 §5.2: slim per-line projection so the ServiceLinesTable
|
||||
# can show Paid + Adjustments columns without a second fetch.
|
||||
# The 837 side is keyed by ``claim_service_line_number`` (the
|
||||
# 1-based line number from raw_json) since 837 service lines
|
||||
# are not a separate ORM table.
|
||||
from cyclone.db import (
|
||||
LineReconciliation, ServiceLinePayment, CasAdjustment,
|
||||
)
|
||||
slim_lrs = list(
|
||||
s.query(LineReconciliation)
|
||||
.filter(LineReconciliation.claim_id == claim_id)
|
||||
.all()
|
||||
)
|
||||
svc_ids_for_cas = [
|
||||
lr.service_line_payment_id
|
||||
for lr in slim_lrs
|
||||
if lr.service_line_payment_id is not None
|
||||
]
|
||||
cas_sums_by_svc: dict = {}
|
||||
svc_by_id_slim: dict = {}
|
||||
if svc_ids_for_cas:
|
||||
cas_rows = (
|
||||
s.query(CasAdjustment.service_line_payment_id, CasAdjustment.amount)
|
||||
.filter(CasAdjustment.service_line_payment_id.in_(svc_ids_for_cas))
|
||||
.all()
|
||||
)
|
||||
from collections import defaultdict
|
||||
agg = defaultdict(lambda: Decimal("0"))
|
||||
for svc_id, amount in cas_rows:
|
||||
agg[svc_id] += Decimal(str(amount))
|
||||
cas_sums_by_svc = {k: str(v) for k, v in agg.items()}
|
||||
for svc in (
|
||||
s.query(ServiceLinePayment)
|
||||
.filter(ServiceLinePayment.id.in_(svc_ids_for_cas))
|
||||
.all()
|
||||
):
|
||||
svc_by_id_slim[svc.id] = svc
|
||||
|
||||
slim_by_num: dict = {
|
||||
lr.claim_service_line_number: lr
|
||||
for lr in slim_lrs
|
||||
if lr.claim_service_line_number is not None
|
||||
}
|
||||
line_reconciliation_slim: list = []
|
||||
for sl in detail["serviceLines"]:
|
||||
ln = sl.get("lineNumber")
|
||||
lr = slim_by_num.get(ln)
|
||||
if lr is None:
|
||||
line_reconciliation_slim.append({
|
||||
"lineNumber": ln,
|
||||
"status": "unmatched_837_only",
|
||||
"paid": None,
|
||||
"adjustmentsSum": None,
|
||||
})
|
||||
continue
|
||||
svc = (
|
||||
svc_by_id_slim.get(lr.service_line_payment_id)
|
||||
if lr.service_line_payment_id
|
||||
else None
|
||||
)
|
||||
line_reconciliation_slim.append({
|
||||
"lineNumber": ln,
|
||||
"status": lr.status,
|
||||
"paid": str(Decimal(str(svc.payment))) if svc else None,
|
||||
"adjustmentsSum": (
|
||||
cas_sums_by_svc.get(lr.service_line_payment_id)
|
||||
if lr.service_line_payment_id
|
||||
else None
|
||||
),
|
||||
})
|
||||
detail["lineReconciliation"] = line_reconciliation_slim
|
||||
|
||||
return detail
|
||||
|
||||
def all(self) -> list[BatchRecord]:
|
||||
|
||||
@@ -211,4 +211,26 @@ def test_get_claim_detail_matched_remit_inlines_summary(seeded_store_with_pair):
|
||||
assert mr["status"] in {"received", "reconciled"}
|
||||
# ISO timestamp must end in Z — the spec contract that lets the
|
||||
# UI render the time without a tz suffix shim.
|
||||
assert mr["receivedAt"].endswith("Z"), mr["receivedAt"]
|
||||
assert mr["receivedAt"].endswith("Z"), mr["receivedAt"]
|
||||
|
||||
|
||||
def test_get_claim_detail_includes_line_reconciliation_slim_projection(seeded_store):
|
||||
"""SP7 §5.2: claim detail exposes a `lineReconciliation` slim list
|
||||
parallel to `serviceLines`, indexed by line number. The values are
|
||||
strings (Decimals serialized) or nulls when the line is unmatched."""
|
||||
cid = seeded_store.get("/api/claims", headers=JSON).json()["items"][0]["id"]
|
||||
resp = seeded_store.get(f"/api/claims/{cid}")
|
||||
assert resp.status_code == 200, resp.text
|
||||
body = resp.json()
|
||||
assert "lineReconciliation" in body
|
||||
lr_list = body["lineReconciliation"]
|
||||
assert isinstance(lr_list, list)
|
||||
# No 835 was ingested in this fixture, so every line is unmatched
|
||||
# on the 835 side — the default for an unpaired claim.
|
||||
for row in lr_list:
|
||||
assert "lineNumber" in row
|
||||
assert "status" in row
|
||||
assert "paid" in row
|
||||
assert "adjustmentsSum" in row
|
||||
# One entry per service line.
|
||||
assert len(lr_list) == len(body["serviceLines"])
|
||||
Reference in New Issue
Block a user