add6e982a4
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.
134 lines
4.8 KiB
Python
134 lines
4.8 KiB
Python
"""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 |