Populate receivedAmount from matched Remittance.total_paid
Before this change, every claim — matched or not — came back from `/api/claims` with `receivedAmount: 0.0`. `to_ui_claim_from_orm` hardcoded the value, so the Dashboard's 'Received' KPI and the claim detail drawer's paid amount always read $0 regardless of whether the claim had been paired with a paying remittance. The fix threads the real value through the read path: - `to_ui_claim_from_orm` accepts a new `received_total: float` kwarg (default 0.0 for callers that don't have a remittance in scope). - `iter_claims` bulk-loads `Remittance.total_paid` for every matched claim id in the result set (single SQL roundtrip via `IN` filter — no N+1) and stamps the sum onto each claim dict. - `manual_match` passes `float(remit.total_paid)` from the remit it's already holding. - `manual_unmatch` reads `paired_remit.total_paid` before clearing the FK so the response still reports what was paid pre-unpair. - `add` (write path) and `list_unmatched` (filters matched-remit is NULL) pass `0.0` explicitly for readability. The dev seed CLI now also generates matched Remittance rows for every PAID/PARTIAL claim — `matched_remittance_id` is set on the claim, and the remittance's `total_paid` is derived from the billed amount using the same ratios the frontend's old sample fixtures used (60–100% for PAID, 20–50% for PARTIAL). That gives the Dashboard a non-zero 'Received' KPI on a fresh dev DB. New tests: - tests/test_iter_claims_received.py — 4 cases covering matched, unmatched, orphan FK, and bulk-load paths. Catches regressions if anyone re-introduces the hardcoded 0.0 or breaks the bulk query.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
"""Tests that ``CycloneStore.iter_claims`` populates ``receivedAmount``
|
||||
from the matched ``Remittance.total_paid``.
|
||||
|
||||
Before SP_Auth follow-up: every claim came back with
|
||||
``receivedAmount: 0.0`` regardless of whether it had been paired with
|
||||
a paid remittance, so the Dashboard's "Received" KPI was always $0
|
||||
even when claims had been paid and reconciled.
|
||||
|
||||
The fix: ``iter_claims`` bulk-loads ``Remittance.total_paid`` for every
|
||||
matched claim id in the result set (single SQL, no N+1) and stamps
|
||||
the sum onto each claim dict.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import date, datetime, timezone
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from cyclone import db
|
||||
from cyclone.db import ActivityEvent, Batch, Claim, ClaimState, Remittance
|
||||
from cyclone.store import store as global_store
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _setup(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("CYCLONE_DB_URL", f"sqlite:///{tmp_path}/test.db")
|
||||
db._reset_for_tests()
|
||||
db.init_db()
|
||||
|
||||
|
||||
def _make_batch(s, batch_id: str) -> None:
|
||||
s.add(Batch(
|
||||
id=batch_id,
|
||||
kind="837p",
|
||||
input_filename="seed.edi",
|
||||
parsed_at=datetime(2026, 6, 19, 12, 0, tzinfo=timezone.utc),
|
||||
totals_json={"total_claims": 3},
|
||||
validation_json={"passed": True, "warnings": [], "errors": []},
|
||||
raw_result_json={"_": "stub"},
|
||||
))
|
||||
|
||||
|
||||
def _make_claim(s, claim_id: str, batch_id: str, *, matched_remit_id: str | None = None) -> None:
|
||||
s.add(Claim(
|
||||
id=claim_id,
|
||||
batch_id=batch_id,
|
||||
patient_control_number=claim_id,
|
||||
service_date_from=date(2026, 6, 1),
|
||||
service_date_to=date(2026, 6, 1),
|
||||
charge_amount=Decimal("200.00"),
|
||||
provider_npi="1234567890",
|
||||
payer_id="SKCO0",
|
||||
state=ClaimState.PAID,
|
||||
matched_remittance_id=matched_remit_id,
|
||||
))
|
||||
|
||||
|
||||
def _make_remit(s, remit_id: str, batch_id: str, *, total_paid: Decimal) -> None:
|
||||
s.add(Remittance(
|
||||
id=remit_id,
|
||||
batch_id=batch_id,
|
||||
payer_claim_control_number=remit_id,
|
||||
claim_id=None,
|
||||
status_code="1",
|
||||
total_charge=Decimal("200.00"),
|
||||
total_paid=total_paid,
|
||||
adjustment_amount=Decimal("0"),
|
||||
received_at=datetime(2026, 6, 20, 12, 0, tzinfo=timezone.utc),
|
||||
))
|
||||
|
||||
|
||||
def test_iter_claims_populates_received_amount_from_matched_remittance():
|
||||
"""A matched claim should reflect its remittance's ``total_paid``."""
|
||||
with db.SessionLocal()() as s:
|
||||
_make_batch(s, "b1")
|
||||
_make_remit(s, "r1", "b1", total_paid=Decimal("180.00"))
|
||||
_make_claim(s, "CLM-A", "b1", matched_remit_id="r1")
|
||||
s.commit()
|
||||
|
||||
items = global_store.iter_claims(limit=10)
|
||||
by_id = {c["id"]: c for c in items}
|
||||
assert by_id["CLM-A"]["receivedAmount"] == pytest.approx(180.00)
|
||||
|
||||
|
||||
def test_iter_claims_unmatched_claim_has_zero_received():
|
||||
"""Claims with no matched remittance still get 0.0, not stale data."""
|
||||
with db.SessionLocal()() as s:
|
||||
_make_batch(s, "b1")
|
||||
_make_claim(s, "CLM-U", "b1", matched_remit_id=None)
|
||||
s.commit()
|
||||
|
||||
items = global_store.iter_claims(limit=10)
|
||||
by_id = {c["id"]: c for c in items}
|
||||
assert by_id["CLM-U"]["receivedAmount"] == 0.0
|
||||
|
||||
|
||||
def test_iter_claims_handles_orphan_match_fk():
|
||||
"""A claim with a stale ``matched_remittance_id`` whose remittance row
|
||||
was deleted should default to 0.0 rather than blow up."""
|
||||
with db.SessionLocal()() as s:
|
||||
_make_batch(s, "b1")
|
||||
_make_claim(s, "CLM-ORPHAN", "b1", matched_remit_id="r-deleted")
|
||||
s.commit()
|
||||
|
||||
# No remittance row exists — the FK is dangling, which can happen
|
||||
# if the remittance was deleted between match and now.
|
||||
items = global_store.iter_claims(limit=10)
|
||||
by_id = {c["id"]: c for c in items}
|
||||
assert by_id["CLM-ORPHAN"]["receivedAmount"] == 0.0
|
||||
|
||||
|
||||
def test_iter_claims_bulk_loads_multiple_matches_in_one_pass():
|
||||
"""All matched claims in a page reflect their distinct remittance
|
||||
totals — the bulk load must aggregate per remittance id."""
|
||||
with db.SessionLocal()() as s:
|
||||
_make_batch(s, "b1")
|
||||
_make_remit(s, "r1", "b1", total_paid=Decimal("120.00"))
|
||||
_make_remit(s, "r2", "b1", total_paid=Decimal("175.50"))
|
||||
_make_remit(s, "r3", "b1", total_paid=Decimal("0"))
|
||||
_make_claim(s, "CLM-1", "b1", matched_remit_id="r1")
|
||||
_make_claim(s, "CLM-2", "b1", matched_remit_id="r2")
|
||||
_make_claim(s, "CLM-3", "b1", matched_remit_id="r3")
|
||||
_make_claim(s, "CLM-4", "b1", matched_remit_id=None)
|
||||
s.commit()
|
||||
|
||||
items = global_store.iter_claims(limit=10)
|
||||
by_id = {c["id"]: c for c in items}
|
||||
assert by_id["CLM-1"]["receivedAmount"] == pytest.approx(120.00)
|
||||
assert by_id["CLM-2"]["receivedAmount"] == pytest.approx(175.50)
|
||||
assert by_id["CLM-3"]["receivedAmount"] == pytest.approx(0.0)
|
||||
assert by_id["CLM-4"]["receivedAmount"] == 0.0
|
||||
Reference in New Issue
Block a user