From 75483f347e0225f98bb41764b467503b974f2b31 Mon Sep 17 00:00:00 2001 From: Yashas Date: Wed, 16 Sep 2026 19:11:02 +0530 Subject: [PATCH] fix: stable claims paging and a deep link that does not lose its date window - claims paging ordered by claim_id (unique) instead of service_date: each page is a separate Spark query, and with thousands of ties the engine repeated rows across pages - the case's anchor date comes from the review's own ceiling or the lakehouse stats view, not from the briefing, which loads seconds later; an expiring-appeals link with no anchor is now suppressed rather than opening every unfiled denial Co-Authored-By: Claude Opus 5 (1M context) --- src/lib/claimsLink.ts | 5 ++++- src/screens/CaseScreen.tsx | 13 ++++++++++--- src/screens/ClaimsScreen.tsx | 7 ++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/lib/claimsLink.ts b/src/lib/claimsLink.ts index 98816c1..6e8a441 100644 --- a/src/lib/claimsLink.ts +++ b/src/lib/claimsLink.ts @@ -27,7 +27,10 @@ export function claimsHref(q: ClaimsQuery): string { /** The rows behind "claims denied" / "affected". */ export function affectedClaimsHref(kind: string, slice: Slice | null, anchor: string | null): string | null { if (kind === 'expiring_appeals') { - return claimsHref({ status: 'denied', appeal: 'not_filed', dl_from: anchor ?? undefined, dl_to: anchor ? addDays(anchor, 14) : undefined }); + // Without the anchor the 14-day window cannot be expressed, and the link + // would open every unfiled denial ever. Show no link instead. + if (!anchor) return null; + return claimsHref({ status: 'denied', appeal: 'not_filed', dl_from: anchor, dl_to: addDays(anchor, 14) }); } if (kind === 'slow_payer') { return slice?.payer ? claimsHref({ payer: slice.payer, status: 'pending' }) : null; diff --git a/src/screens/CaseScreen.tsx b/src/screens/CaseScreen.tsx index f9f8762..e57beee 100644 --- a/src/screens/CaseScreen.tsx +++ b/src/screens/CaseScreen.tsx @@ -17,7 +17,7 @@ import { ActivityForm } from '../components/ActivityForm'; import { AvaLive } from '../components/AvaLive'; import { Disclosure, FullStory, Recheck, Recommendations, SqlEvidence, Verdict } from '../components/Findings'; import { Briefing, BriefingSkeleton } from '../components/Briefing'; -import { anchorDate, loadBriefing, sliceOf } from '../api/charts'; +import { loadBriefing, sliceOf } from '../api/charts'; import type { Action } from '../api/config'; import { dateTime, num, str } from '../format'; @@ -67,7 +67,14 @@ export function CaseScreen() { // Chart rows come from six Databricks views through the record-view API. Loaded // once there are findings to picture; the slice keys decide which weekly line // and which recovery breakdown are drawn. - const asOf = str(row?.review_as_of).slice(0, 10) || null; + const asOf = str(row?.review_to || row?.review_as_of).slice(0, 10) || null; + // The anchor behind "expiring within 14 days". It is the review's own + // ceiling date when the reviewer set one, otherwise the lakehouse's latest + // service date — read from the stats view, NOT from the briefing: the charts + // take seconds to load and a link rendered without the anchor would silently + // drop the deadline window and open thousands of rows instead of hundreds. + const lake = useQuery(() => client.rows('dni-rv-lakehouse-tables', { limit: 10 }), [], !asOf && hasFindings); + const anchor = asOf ?? str(lake.data?.find((r) => str(r.table_name) === 'claims')?.to_date).slice(0, 10) ?? null; const briefing = useQuery( () => loadBriefing(client, slice, { kind, asOf }), [id, slice?.payer, slice?.department, slice?.code, kind, asOf], @@ -171,7 +178,7 @@ export function CaseScreen() { {/* ---------- The findings: what happened, how much, what to approve ---------- */} {hasFindings && ( <> - + {briefing.data ? ( ) : ( diff --git a/src/screens/ClaimsScreen.tsx b/src/screens/ClaimsScreen.tsx index 8e7cccb..6c3cfd0 100644 --- a/src/screens/ClaimsScreen.tsx +++ b/src/screens/ClaimsScreen.tsx @@ -69,8 +69,13 @@ export function ClaimsScreen() { const filtered = filters.length > 0 || !!search; const key = params.toString(); + // ORDER BY A UNIQUE COLUMN. Each page is its own query against Spark, and + // `service_date` has thousands of ties — with a non-unique sort key the + // engine is free to order tied rows differently per execution, so page 2 + // repeated rows from page 1. `claim_id` is unique and is assigned in date + // order, so descending is still newest-first, and every page is disjoint. const q = useQuery( - () => client.recordView('dni-rv-claims', { page, limit: PAGE, filters, search, sortBy: 'service_date', sortDir: 'desc' }), + () => client.recordView('dni-rv-claims', { page, limit: PAGE, filters, search, sortBy: 'claim_id', sortDir: 'desc' }), [key], ); // The platform caps its count at 10,000; the stats view has the exact