feat(sp7): inbox lanes matched_remittance includes matched/total line counts

This commit is contained in:
Tyler
2026-06-20 19:43:43 -06:00
parent 6ef59b5d1d
commit b7bda0662f
2 changed files with 234 additions and 8 deletions
+90 -8
View File
@@ -36,7 +36,13 @@ def _isoformat(value) -> str | None:
return str(value)
def _claim_to_row(c: Claim, *, kind: str, score: ScoreBreakdown | None = None) -> dict:
def _claim_to_row(
c: Claim,
*,
kind: str,
score: ScoreBreakdown | None = None,
matched_remittance: dict | None = None,
) -> dict:
return {
"id": c.id,
"kind": kind,
@@ -56,6 +62,7 @@ def _claim_to_row(c: Claim, *, kind: str, score: ScoreBreakdown | None = None) -
"amount": score.amount,
"provider": score.provider,
} if score else None,
"matched_remittance": matched_remittance,
}
@@ -100,13 +107,74 @@ class _RemitScoringShim:
self.rendering_provider_npi = raw.get("rendering_provider_npi")
def _matched_remittance_block(
session: Session,
claim: Claim,
matched_counts: dict,
total_lines_by_claim: dict,
) -> dict | None:
"""Build the ``matched_remittance`` block for a paired claim, or None.
SP7 §5.3: carries the matched remittance id plus per-line counts so
the ``MatchedRemitCard`` can show a ``3/4 matched lines`` badge.
"""
if claim.matched_remittance_id is None:
return None
return {
"id": claim.matched_remittance_id,
"matched_lines": int(matched_counts.get(claim.id, 0)),
"total_lines": int(total_lines_by_claim.get(claim.id, 0)),
}
def _line_count_lookup(session: Session, claims: list[Claim]) -> tuple[dict, dict]:
"""Return ({claim_id: matched_count}, {claim_id: total_lines}) for the given claims.
matched_count = number of LineReconciliation rows with status="matched"
tied to this claim.
total_lines = len(Claim.raw_json["service_lines"]) — 837 service-line
count stored inside the JSON blob.
"""
from cyclone.db import LineReconciliation
claim_ids = [c.id for c in claims]
matched_counts: dict = {}
total_lines_by_claim: dict = {}
if not claim_ids:
return matched_counts, total_lines_by_claim
matched_rows = (
session.query(LineReconciliation.claim_id)
.filter(LineReconciliation.claim_id.in_(claim_ids))
.filter(LineReconciliation.status == "matched")
.all()
)
for (cid,) in matched_rows:
matched_counts[cid] = matched_counts.get(cid, 0) + 1
# total_lines: from raw_json (837 service lines are stored there).
for c in claims:
raw = c.raw_json or {}
total_lines_by_claim[c.id] = len(raw.get("service_lines") or [])
return matched_counts, total_lines_by_claim
def compute_lanes(session: Session, *, dismissed_pairs: Iterable[frozenset]) -> Lanes:
lanes = Lanes()
dismissed = set(dismissed_pairs)
# --- Rejected ---
for c in session.query(Claim).filter(Claim.state == ClaimState.REJECTED).all():
lanes.rejected.append(_claim_to_row(c, kind="claim"))
rejected_claims = (
session.query(Claim).filter(Claim.state == ClaimState.REJECTED).all()
)
matched_counts, total_lines_by_claim = _line_count_lookup(session, rejected_claims)
for c in rejected_claims:
lanes.rejected.append(_claim_to_row(
c, kind="claim",
matched_remittance=_matched_remittance_block(
session, c, matched_counts, total_lines_by_claim,
),
))
# --- Done today ---
cutoff = datetime.now(timezone.utc) - timedelta(hours=24)
@@ -114,11 +182,25 @@ def compute_lanes(session: Session, *, dismissed_pairs: Iterable[frozenset]) ->
ClaimState.PAID, ClaimState.PARTIAL, ClaimState.DENIED,
ClaimState.RECONCILED, ClaimState.REVERSED,
}
for c in session.query(Claim).filter(
Claim.state.in_(terminal_states),
Claim.state_changed_at >= cutoff,
).all():
lanes.done_today.append(_claim_to_row(c, kind="claim"))
done_claims = list(
session.query(Claim).filter(
Claim.state.in_(terminal_states),
Claim.state_changed_at >= cutoff,
).all()
)
# Merge counts from the rejected query — these are different claim
# sets so the dicts can be combined.
if done_claims:
dm, dt = _line_count_lookup(session, done_claims)
matched_counts.update(dm)
total_lines_by_claim.update(dt)
for c in done_claims:
lanes.done_today.append(_claim_to_row(
c, kind="claim",
matched_remittance=_matched_remittance_block(
session, c, matched_counts, total_lines_by_claim,
),
))
# --- Unmatched (claims still submitted, no remittance in flight) ---
for c in session.query(Claim).filter(Claim.state == ClaimState.SUBMITTED).all():