fix: briefing picks charts by finding kind, folds partial slices, honours replay dates

- expiring_appeals / slow_payer / underpayment no longer get the denial-spike
  charts sliced by payer alone (one bar per department x code)
- weekly and recovery rows folded to their chart grain
- replayed mornings show only the trend, cut at the replay date
- new charts: payer speed (slow_payer) and appeal scorecard
- y-axis ticks no longer clipped at two digits

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Yashas 2026-09-16 16:31:43 +05:30
parent 03ff478e4a
commit 1d2dc4a1b9
4 changed files with 208 additions and 31 deletions

View File

@ -19,8 +19,21 @@ export const CHART_VIEWS = {
reasonRecentVsBase: 'dni-rv-chart-reason-recent-vs-base', reasonRecentVsBase: 'dni-rv-chart-reason-recent-vs-base',
expiringAppeals: 'dni-rv-chart-expiring-appeals', expiringAppeals: 'dni-rv-chart-expiring-appeals',
sliceRecovery: 'dni-rv-chart-slice-recovery', sliceRecovery: 'dni-rv-chart-slice-recovery',
payerSpeed: 'dni-rv-chart-payer-speed',
appealOutcomes: 'dni-rv-chart-appeal-outcomes',
} as const; } as const;
/** Finding kinds that describe a DENIAL SPIKE in one payer/department/code
* slice. Only these get the slice-filtered weekly bars and recovery stack;
* the money-timing kinds (expiring appeals, slow payer, underpayment) name a
* payer at most, and slicing the views by payer alone returns one row per
* department x code — hundreds of bars, not a chart. */
export const DENIAL_KINDS = new Set([
'payer_policy', 'front_desk', 'coding', 'provider_coding', 'documentation',
'medical_necessity', 'timely_filing', 'other', '',
]);
export const isDenialKind = (kind: string) => DENIAL_KINDS.has(kind);
export interface Slice { export interface Slice {
payer: string; payer: string;
department: string; department: string;
@ -68,6 +81,21 @@ export interface RecoveryBucket {
claims: number; claims: number;
billed: number; billed: number;
} }
export interface PayerSpeed {
payer: string;
claims: number;
pending: number;
pendingPct: number;
medianDays: number;
pendingBilled: number;
}
export interface AppealScore {
payer: string;
resolved: number;
overturned: number;
overturnPct: number;
recovered: number;
}
export interface BriefingData { export interface BriefingData {
weeklyRate: WeekPoint[]; weeklyRate: WeekPoint[];
@ -76,6 +104,23 @@ export interface BriefingData {
reasons: ReasonCompare[]; reasons: ReasonCompare[];
expiring: Expiring[]; expiring: Expiring[];
recovery: RecoveryBucket[]; recovery: RecoveryBucket[];
speed: PayerSpeed[];
scorecard: AppealScore[];
/** Set when the case replayed a past morning. The views are anchored on the
* lakehouse's latest date, so only the time series can honour it. */
asOf: string | null;
}
/** Views filtered by a PARTIAL slice come back one row per finer key; fold them
* to the grain the chart draws. */
function sumBy<T>(rows: T[], key: (r: T) => string, add: (acc: T, r: T) => T): T[] {
const m = new Map<string, T>();
for (const r of rows) {
const k = key(r);
const prev = m.get(k);
m.set(k, prev ? add(prev, r) : r);
}
return [...m.values()];
} }
const byWeek = (a: { week: string }, b: { week: string }) => a.week.localeCompare(b.week); const byWeek = (a: { week: string }, b: { week: string }) => a.week.localeCompare(b.week);
@ -91,9 +136,14 @@ function week(r: Row): WeekPoint {
}; };
} }
export async function loadBriefing(client: ZinoClient, slice: Slice | null): Promise<BriefingData> { export async function loadBriefing(
client: ZinoClient,
slice: Slice | null,
opts: { kind?: string; asOf?: string | null } = {},
): Promise<BriefingData> {
const f = (field_key: string, value: string) => ({ field_key, value, data_type: 'string' }); const f = (field_key: string, value: string) => ({ field_key, value, data_type: 'string' });
const sliceFilters = slice const asOf = opts.asOf?.slice(0, 10) || null;
const sliceFilters = slice && isDenialKind(opts.kind ?? '')
? [ ? [
...(slice.payer ? [f('payer', slice.payer)] : []), ...(slice.payer ? [f('payer', slice.payer)] : []),
...(slice.department ? [f('department', slice.department)] : []), ...(slice.department ? [f('department', slice.department)] : []),
@ -101,18 +151,26 @@ export async function loadBriefing(client: ZinoClient, slice: Slice | null): Pro
] ]
: []; : [];
const [rate, sl, payers, reasons, expiring, recovery] = await Promise.all([ const [rate, sl, payers, reasons, expiring, recovery, speed, scorecard] = await Promise.all([
client.rows(CHART_VIEWS.weeklyRate, { limit: 200 }), client.rows(CHART_VIEWS.weeklyRate, { limit: 200 }),
sliceFilters.length ? client.rows(CHART_VIEWS.weeklyBySlice, { limit: 200, filters: sliceFilters }) : Promise.resolve([] as Row[]), sliceFilters.length ? client.rows(CHART_VIEWS.weeklyBySlice, { limit: 200, filters: sliceFilters }) : Promise.resolve([] as Row[]),
client.rows(CHART_VIEWS.payerRecentVsBase, { limit: 50 }), client.rows(CHART_VIEWS.payerRecentVsBase, { limit: 50 }),
client.rows(CHART_VIEWS.reasonRecentVsBase, { limit: 50 }), client.rows(CHART_VIEWS.reasonRecentVsBase, { limit: 50 }),
client.rows(CHART_VIEWS.expiringAppeals, { limit: 50 }), client.rows(CHART_VIEWS.expiringAppeals, { limit: 50 }),
sliceFilters.length ? client.rows(CHART_VIEWS.sliceRecovery, { limit: 50, filters: sliceFilters }) : Promise.resolve([] as Row[]), sliceFilters.length ? client.rows(CHART_VIEWS.sliceRecovery, { limit: 200, filters: sliceFilters }) : Promise.resolve([] as Row[]),
client.rows(CHART_VIEWS.payerSpeed, { limit: 50 }).catch(() => [] as Row[]),
client.rows(CHART_VIEWS.appealOutcomes, { limit: 50 }).catch(() => [] as Row[]),
]); ]);
const upTo = (pts: WeekPoint[]) => (asOf ? pts.filter((p) => p.week <= asOf) : pts);
const foldWeeks = (pts: WeekPoint[]) =>
sumBy(pts, (p) => p.week, (a, b) => ({ ...a, claims: a.claims + b.claims, denied: a.denied + b.denied }))
.map((p) => ({ ...p, pct: p.claims ? Math.round((1000 * p.denied) / p.claims) / 10 : 0 }));
return { return {
weeklyRate: rate.map(week).sort(byWeek), asOf,
weeklySlice: sl.map(week).sort(byWeek), weeklyRate: upTo(rate.map(week).sort(byWeek)),
weeklySlice: upTo(foldWeeks(sl.map(week)).sort(byWeek)),
payers: payers payers: payers
.map((r) => ({ .map((r) => ({
payer: str(r.payer), payer: str(r.payer),
@ -139,9 +197,30 @@ export async function loadBriefing(client: ZinoClient, slice: Slice | null): Pro
days: num(r.days_to_first_deadline) ?? 0, days: num(r.days_to_first_deadline) ?? 0,
})) }))
.sort((a, b) => b.billed - a.billed), .sort((a, b) => b.billed - a.billed),
recovery: recovery recovery: sumBy(
.map((r) => ({ bucket: str(r.bucket), claims: num(r.claims) ?? 0, billed: num(r.billed) ?? 0 })) recovery.map((r) => ({ bucket: str(r.bucket), claims: num(r.claims) ?? 0, billed: num(r.billed) ?? 0 })),
.sort((a, b) => RECOVERY_ORDER.indexOf(a.bucket) - RECOVERY_ORDER.indexOf(b.bucket)), (b) => b.bucket,
(a, b) => ({ ...a, claims: a.claims + b.claims, billed: a.billed + b.billed }),
).sort((a, b) => RECOVERY_ORDER.indexOf(a.bucket) - RECOVERY_ORDER.indexOf(b.bucket)),
speed: speed
.map((r) => ({
payer: str(r.payer),
claims: num(r.claims) ?? 0,
pending: num(r.still_pending) ?? 0,
pendingPct: num(r.pending_pct) ?? 0,
medianDays: num(r.median_days_to_remit) ?? 0,
pendingBilled: num(r.pending_billed) ?? 0,
}))
.sort((a, b) => b.pendingPct - a.pendingPct),
scorecard: scorecard
.map((r) => ({
payer: str(r.payer),
resolved: num(r.resolved) ?? 0,
overturned: num(r.overturned) ?? 0,
overturnPct: num(r.overturn_pct) ?? 0,
recovered: num(r.recovered_billed) ?? 0,
}))
.sort((a, b) => b.overturnPct - a.overturnPct),
}; };
} }

View File

@ -14,7 +14,7 @@ import {
} from 'recharts'; } from 'recharts';
import { Loader2, Table2 } from 'lucide-react'; import { Loader2, Table2 } from 'lucide-react';
import type { BriefingData, Slice } from '../api/charts'; import type { BriefingData, Slice } from '../api/charts';
import { RECOVERY_ORDER } from '../api/charts'; import { RECOVERY_ORDER, isDenialKind } from '../api/charts';
import { useTheme } from '../theme'; import { useTheme } from '../theme';
import { money, moneyShort, shortDate } from '../format'; import { money, moneyShort, shortDate } from '../format';
@ -95,19 +95,56 @@ export function BriefingSkeleton({ error }: { error?: string | null }) {
); );
} }
export function Briefing({ data, slice }: { data: BriefingData; slice: Slice | null }) { export function Briefing({ data, slice, kind = '' }: { data: BriefingData; slice: Slice | null; kind?: string }) {
const { theme } = useTheme(); const { theme } = useTheme();
const c = HUES[theme]; const c = HUES[theme];
const title = slice ? [slice.payer, slice.department, slice.code].filter(Boolean).join(' · ') : 'the hospital'; const title = slice ? [slice.payer, slice.department, slice.code].filter(Boolean).join(' · ') : 'the hospital';
const denial = isDenialKind(kind);
// A replayed morning: the lakehouse views are anchored on the data's LATEST
// date, so "recent vs baseline", "expiring within 14 days", appeal status and
// payer speed all describe today, not the morning Ava replayed. Only the
// weekly series can be cut at the replay date honestly — so that is all we
// draw, and we say why.
if (data.asOf) {
return (
<section className="space-y-2">
<SpikeChart data={data} slice={denial ? slice : null} c={c} title={title} kind={kind} />
<p className="text-xs text-faint">
Replayed morning ({shortDate(data.asOf)}): the trend is cut at that date. Comparison charts are
anchored on the latest lakehouse data and are not shown for a replay.
</p>
</section>
);
}
// Which four pictures tell this kind of story.
const cards: React.ReactNode[] = [];
if (kind === 'expiring_appeals') {
if (data.expiring.length) cards.push(<ExpiringChart key="exp" data={data} c={c} />);
if (data.scorecard.length) cards.push(<ScorecardChart key="score" data={data} c={c} />);
cards.push(<PayerChart key="payer" data={data} slice={slice} c={c} />);
cards.push(<SpikeChart key="spike" data={data} slice={null} c={c} title={title} kind={kind} />);
} else if (kind === 'slow_payer') {
if (data.speed.length) cards.push(<SpeedChart key="speed" data={data} slice={slice} c={c} />);
cards.push(<PayerChart key="payer" data={data} slice={slice} c={c} />);
cards.push(<SpikeChart key="spike" data={data} slice={null} c={c} title={title} kind={kind} />);
if (data.expiring.length) cards.push(<ExpiringChart key="exp" data={data} c={c} />);
} else if (kind === 'underpayment') {
cards.push(<PayerChart key="payer" data={data} slice={slice} c={c} />);
cards.push(<SpikeChart key="spike" data={data} slice={null} c={c} title={title} kind={kind} />);
if (data.scorecard.length) cards.push(<ScorecardChart key="score" data={data} c={c} />);
if (data.expiring.length) cards.push(<ExpiringChart key="exp" data={data} c={c} />);
} else {
cards.push(<SpikeChart key="spike" data={data} slice={slice} c={c} title={title} kind={kind} />);
cards.push(<PayerChart key="payer" data={data} slice={slice} c={c} />);
if (data.recovery.length) cards.push(<RecoveryChart key="rec" data={data} c={c} title={title} />);
if (data.expiring.length) cards.push(<ExpiringChart key="exp" data={data} c={c} />);
}
return ( return (
<section className="space-y-3"> <section className="space-y-3">
<div className="grid gap-3 lg:grid-cols-2"> <div className="grid gap-3 lg:grid-cols-2">{cards}</div>
<SpikeChart data={data} slice={slice} c={c} title={title} />
<PayerChart data={data} slice={slice} c={c} />
{data.recovery.length > 0 && <RecoveryChart data={data} c={c} title={title} />}
{data.expiring.length > 0 && <ExpiringChart data={data} c={c} />}
</div>
</section> </section>
); );
} }
@ -186,19 +223,23 @@ const tip = (c: typeof HUES.light) => ({
const axis = (c: typeof HUES.light) => ({ tick: { fill: c.muted, fontSize: 11 }, axisLine: { stroke: c.grid }, tickLine: false as const }); const axis = (c: typeof HUES.light) => ({ tick: { fill: c.muted, fontSize: 11 }, axisLine: { stroke: c.grid }, tickLine: false as const });
// 1. The spike ---------------------------------------------------------------- // 1. The spike ----------------------------------------------------------------
function SpikeChart({ data, slice, c, title }: { data: BriefingData; slice: Slice | null; c: typeof HUES.light; title: string }) { function SpikeChart({ data, slice, c, title, kind = '' }: { data: BriefingData; slice: Slice | null; c: typeof HUES.light; title: string; kind?: string }) {
const pts = data.weeklySlice.length ? data.weeklySlice : data.weeklyRate; const isSlice = slice !== null && data.weeklySlice.length > 0;
const isSlice = data.weeklySlice.length > 0; const pts = isSlice ? data.weeklySlice : data.weeklyRate;
const spike = slice?.spikeStart ?? null; // The dashed marker means different things: a payer bulletin date for a
// policy finding, the week a pattern began otherwise. A money-timing finding
// has no start week on the denial trend, so no marker.
const spike = slice && isDenialKind(kind) ? slice.spikeStart : null;
const marker = kind === 'payer_policy' ? 'policy effective' : 'change begins';
const rows = pts.map((p) => ({ ...p, label: shortDate(p.week) })); const rows = pts.map((p) => ({ ...p, label: shortDate(p.week) }));
return ( return (
<Frame <Frame
title={isSlice ? `Weekly denied claims — ${title}` : 'Weekly denial rate — all claims'} title={isSlice ? `Weekly denied claims — ${title}` : 'Weekly denial rate — all claims'}
caption={spike ? `Dashed line: payer policy effective ${shortDate(spike)}` : 'Service weeks, pending claims excluded'} caption={spike ? `Dashed line: ${marker} ${shortDate(spike)}` : 'Service weeks, pending claims excluded'}
table={{ head: ['Week', isSlice ? 'Denied' : 'Denial %'], rows: rows.map((r) => [r.label, isSlice ? r.denied : `${r.pct}%`]) }} table={{ head: ['Week', isSlice ? 'Denied' : 'Denial %'], rows: rows.map((r) => [r.label, isSlice ? r.denied : `${r.pct}%`]) }}
> >
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<ComposedChart data={rows} margin={{ top: 8, right: 8, bottom: 0, left: -18 }}> <ComposedChart data={rows} margin={{ top: 8, right: 8, bottom: 0, left: -4 }}>
<CartesianGrid vertical={false} stroke={c.grid} /> <CartesianGrid vertical={false} stroke={c.grid} />
<XAxis dataKey="label" {...axis(c)} interval="preserveStartEnd" minTickGap={28} /> <XAxis dataKey="label" {...axis(c)} interval="preserveStartEnd" minTickGap={28} />
<YAxis {...axis(c)} width={44} tickFormatter={(v) => (isSlice ? String(v) : `${v}%`)} /> <YAxis {...axis(c)} width={44} tickFormatter={(v) => (isSlice ? String(v) : `${v}%`)} />
@ -217,7 +258,7 @@ function SpikeChart({ data, slice, c, title }: { data: BriefingData; slice: Slic
x={shortDate(spike)} x={shortDate(spike)}
stroke={c.ai} stroke={c.ai}
strokeDasharray="4 3" strokeDasharray="4 3"
label={{ value: 'policy effective', position: 'insideTopLeft', fill: c.ai, fontSize: 11 }} label={{ value: marker, position: 'insideTopLeft', fill: c.ai, fontSize: 11 }}
/> />
)} )}
</ComposedChart> </ComposedChart>
@ -236,7 +277,7 @@ function PayerChart({ data, slice, c }: { data: BriefingData; slice: Slice | nul
table={{ head: ['Insurer', 'Before', 'Now'], rows: rows.map((r) => [r.payer, `${r.rateBase}%`, `${r.rateRecent}%`]) }} table={{ head: ['Insurer', 'Before', 'Now'], rows: rows.map((r) => [r.payer, `${r.rateBase}%`, `${r.rateRecent}%`]) }}
> >
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<BarChart data={rows} margin={{ top: 8, right: 8, bottom: 0, left: -18 }} barGap={2} barCategoryGap="28%"> <BarChart data={rows} margin={{ top: 8, right: 8, bottom: 0, left: -4 }} barGap={2} barCategoryGap="28%">
<CartesianGrid vertical={false} stroke={c.grid} /> <CartesianGrid vertical={false} stroke={c.grid} />
<XAxis dataKey="short" {...axis(c)} interval={0} /> <XAxis dataKey="short" {...axis(c)} interval={0} />
<YAxis {...axis(c)} width={44} tickFormatter={(v) => `${v}%`} /> <YAxis {...axis(c)} width={44} tickFormatter={(v) => `${v}%`} />
@ -296,6 +337,60 @@ function RecoveryChart({ data, c, title }: { data: BriefingData; c: typeof HUES.
); );
} }
// 5. Speed: who is sitting on our claims ---------------------------------------
function SpeedChart({ data, slice, c }: { data: BriefingData; slice: Slice | null; c: typeof HUES.light }) {
const rows = data.speed.map((p) => ({ ...p, short: p.payer.replace(' Health Plan', '').replace(' Health', '').replace(' Mutual', '') }));
const slow = rows.find((r) => r.payer === slice?.payer) ?? rows[0];
return (
<Frame
title="Claims sent 31–60 days ago, still unanswered"
caption={slow ? `${slow.short}: ${slow.pendingPct}% unanswered, ${moneyShort(slow.pendingBilled)} waiting · others answer in ~${Math.round(rows.filter((r) => r !== slow).reduce((t, r) => t + r.medianDays, 0) / Math.max(rows.length - 1, 1))} days` : 'Share of month-old claims with no payer response'}
table={{ head: ['Insurer', 'Claims', 'Unanswered', 'Median days', 'Waiting'], rows: rows.map((r) => [r.payer, r.claims, `${r.pendingPct}%`, r.medianDays, money(r.pendingBilled)]) }}
>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={rows} layout="vertical" margin={{ top: 4, right: 56, bottom: 0, left: 8 }} barCategoryGap="30%">
<CartesianGrid horizontal={false} stroke={c.grid} />
<XAxis type="number" hide domain={[0, 100]} />
<YAxis type="category" dataKey="short" {...axis(c)} width={70} />
<Tooltip {...tip(c)} formatter={(v: unknown) => [`${Number(v)}% unanswered`, '']} />
<Bar dataKey="pendingPct" radius={[0, 4, 4, 0]} maxBarSize={18} label={{ position: 'right', fill: c.ink, fontSize: 11, formatter: (v: unknown) => `${Number(v)}%` }}>
{rows.map((r) => (
<Cell key={r.payer} fill={r === slow ? c.warn : c.gray} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</Frame>
);
}
// 6. Scorecard: which appeals are worth the staff time -------------------------
function ScorecardChart({ data, c }: { data: BriefingData; c: typeof HUES.light }) {
const rows = data.scorecard.map((p) => ({ ...p, short: p.payer.replace(' Health Plan', '').replace(' Health', '').replace(' Mutual', '') }));
const worst = rows[rows.length - 1];
return (
<Frame
title="Appeals overturned, by insurer"
caption={worst ? `All history · ${worst.short} reverses only ${worst.overturnPct}% — correct and resubmit instead of appealing` : 'Share of resolved appeals the insurer reversed'}
table={{ head: ['Insurer', 'Resolved', 'Overturned', 'Recovered'], rows: rows.map((r) => [r.payer, r.resolved, `${r.overturnPct}%`, money(r.recovered)]) }}
>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={rows} layout="vertical" margin={{ top: 4, right: 56, bottom: 0, left: 8 }} barCategoryGap="30%">
<CartesianGrid horizontal={false} stroke={c.grid} />
<XAxis type="number" hide domain={[0, 100]} />
<YAxis type="category" dataKey="short" {...axis(c)} width={70} />
<Tooltip {...tip(c)} formatter={(v: unknown, _n: unknown, item: { payload?: { resolved?: number } }) => [`${Number(v)}% of ${item?.payload?.resolved ?? ''} resolved`, 'overturned']} />
<Bar dataKey="overturnPct" radius={[0, 4, 4, 0]} maxBarSize={18} label={{ position: 'right', fill: c.ink, fontSize: 11, formatter: (v: unknown) => `${Number(v)}%` }}>
{rows.map((r) => (
<Cell key={r.payer} fill={r.overturnPct < 20 ? c.gray : c.good} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</Frame>
);
}
// 4. Expiring ------------------------------------------------------------------- // 4. Expiring -------------------------------------------------------------------
function ExpiringChart({ data, c }: { data: BriefingData; c: typeof HUES.light }) { function ExpiringChart({ data, c }: { data: BriefingData; c: typeof HUES.light }) {
const rows = data.expiring.map((e) => ({ ...e, short: e.payer.replace(' Health Plan', '').replace(' Health', '').replace(' Mutual', '') })); const rows = data.expiring.map((e) => ({ ...e, short: e.payer.replace(' Health Plan', '').replace(' Health', '').replace(' Mutual', '') }));

View File

@ -43,7 +43,7 @@ const RECOVERY_COLOR: Record<string, string> = {
'window closed': GRAY, 'window closed': GRAY,
}; };
export async function buildDeck(row: Row, data: BriefingData, slice: Slice | null): Promise<void> { export async function buildDeck(row: Row, data: BriefingData, slice: Slice | null, kind = ''): Promise<void> {
const pres = new pptxgen(); const pres = new pptxgen();
pres.layout = 'LAYOUT_WIDE'; // 13.33 x 7.5 in pres.layout = 'LAYOUT_WIDE'; // 13.33 x 7.5 in
pres.author = 'Northlake Health — Denials Desk (AI analyst: Ava)'; pres.author = 'Northlake Health — Denials Desk (AI analyst: Ava)';
@ -223,7 +223,9 @@ export async function buildDeck(row: Row, data: BriefingData, slice: Slice | nul
const bullets = [ const bullets = [
str(row.scanned_summary) || 'Reviewed every payer, department and front desk against an 8-week baseline.', str(row.scanned_summary) || 'Reviewed every payer, department and front desk against an 8-week baseline.',
reconciled ? 'Claims data reconciled against the finance report before any analysis.' : 'Claims data could not be fully reconciled against finance — treat numbers as provisional.', reconciled ? 'Claims data reconciled against the finance report before any analysis.' : 'Claims data could not be fully reconciled against finance — treat numbers as provisional.',
`Cause matched to the payer's own policy bulletin${slice?.spikeStart ? ` effective ${shortDate(slice.spikeStart)}` : ''}.`, kind === 'payer_policy'
? `Cause matched to the payer's own policy bulletin${slice?.spikeStart ? ` effective ${shortDate(slice.spikeStart)}` : ''}.`
: '',
conf != null ? `Stated confidence: ${Math.round(conf * 100)}%.` : '', conf != null ? `Stated confidence: ${Math.round(conf * 100)}%.` : '',
'Read-only access to Databricks (workspace.rcm). Every query is logged and reproducible from the case.', 'Read-only access to Databricks (workspace.rcm). Every query is logged and reproducible from the case.',
].filter(Boolean); ].filter(Boolean);

View File

@ -67,9 +67,10 @@ export function CaseScreen() {
// Chart rows come from six Databricks views through the record-view API. Loaded // 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 // once there are findings to picture; the slice keys decide which weekly line
// and which recovery breakdown are drawn. // and which recovery breakdown are drawn.
const asOf = str(row?.review_as_of).slice(0, 10) || null;
const briefing = useQuery( const briefing = useQuery(
() => loadBriefing(client, slice), () => loadBriefing(client, slice, { kind, asOf }),
[id, slice?.payer, slice?.department, slice?.code], [id, slice?.payer, slice?.department, slice?.code, kind, asOf],
hasFindings, hasFindings,
); );
@ -79,7 +80,7 @@ export function CaseScreen() {
try { try {
// pptxgenjs is ~700 KB; nobody pays for it until they ask for slides. // pptxgenjs is ~700 KB; nobody pays for it until they ask for slides.
const { buildDeck } = await import('../lib/slides'); const { buildDeck } = await import('../lib/slides');
await buildDeck(row, briefing.data, slice); await buildDeck(row, briefing.data, slice, kind);
} finally { } finally {
setExporting(false); setExporting(false);
} }
@ -172,7 +173,7 @@ export function CaseScreen() {
<> <>
<Verdict row={row} /> <Verdict row={row} />
{briefing.data ? ( {briefing.data ? (
<Briefing data={briefing.data} slice={slice} /> <Briefing data={briefing.data} slice={slice} kind={kind} />
) : ( ) : (
<BriefingSkeleton error={briefing.error} /> <BriefingSkeleton error={briefing.error} />
)} )}