feat(sp27): server-aggregate Remittances KPIs (count, paid, adjustments)

The Remittances page's three KPI tiles — REMITS / TOTAL PAID /
ADJUSTMENTS — were computed page-locally via items.reduce(...) over
the merged tail of the current page + live delta. With a 100-row
default limit, a 1,739-row population showed count=100, paid=$16,934,
adjustments=$147 — silently understating reality because the page
hadn't loaded the remaining rows yet.

This change mirrors the silent-incompleteness fix that
/api/dashboard/kpis (commit 59c3275) and /api/remittances (commit
d81b6ed) made for their tiles:

  * CycloneStore.summarize_remittances() iterates the full filtered
    remittance population (no limit) and returns
    {count, total_paid, total_adjustments}. Mirrors iter_remittances
    with limit=_ITER_UNBOUNDED.
  * GET /api/remittances/summary — server endpoint with the same
    filter parameters as /api/remittances. Registered BEFORE the
    /api/remittances/stream handler so FastAPI doesn't treat
    'summary' as a stream sub-path.
  * api.listRemittanceSummary + useRemittanceSummary hook.
  * Remittances.tsx swaps off items.reduce, consumes the server
    summary. Tiles render the server totals so the values reflect
    the entire DB population, not the page-local sample.

Verified live: /api/remittances/summary returns
{count: 1739, total_paid: 227181.58, total_adjustments: 13792.65},
which matches DB ground truth exactly.

Tests: 8 new backend tests in test_api_remittances_summary.py; 1 new
frontend test in Remittances.test.tsx (kpi_tiles_use_server_summary_not
_page_local_reduce) plus the page-level test for the zero-valued mock
default.
This commit is contained in:
Nora
2026-06-29 14:16:24 -06:00
parent d8841834dc
commit 14fcbca5f1
7 changed files with 515 additions and 11 deletions
+23
View File
@@ -651,6 +651,28 @@ async function listRemittances<T = unknown>(
);
}
/**
* Server-aggregated KPI tiles for the Remittances page. Returns the
* full-population ``count``, ``total_paid``, and ``total_adjustments``
* — NOT a page-limited sample — so the KPI tiles can't silently
* understate the true DB population the way a page-local
* ``items.reduce(...)`` would (commits ``59c3275``, ``d81b6ed``).
*/
export interface RemittanceSummary {
count: number;
total_paid: number;
total_adjustments: number;
}
async function listRemittanceSummary(
params: ListRemittancesParams = {}
): Promise<RemittanceSummary> {
if (!isConfigured) throw notConfiguredError();
return authedFetch<RemittanceSummary>(
`/api/remittances/summary${qs(params as Record<string, unknown>)}`
);
}
/**
* Fetch one remittance with its labeled CAS `adjustments` array.
* Throws `ApiError` on 404 so callers can branch on `.status`.
@@ -1029,6 +1051,7 @@ export const api = {
serializeClaim837,
exportBatch837,
listRemittances,
listRemittanceSummary,
getRemittance,
listProviders,
getProvider,