feat(sp7): inbox lanes matched_remittance includes matched/total line counts
This commit is contained in:
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user