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:
parent
03ff478e4a
commit
1d2dc4a1b9
@ -19,8 +19,21 @@ export const CHART_VIEWS = {
|
||||
reasonRecentVsBase: 'dni-rv-chart-reason-recent-vs-base',
|
||||
expiringAppeals: 'dni-rv-chart-expiring-appeals',
|
||||
sliceRecovery: 'dni-rv-chart-slice-recovery',
|
||||
payerSpeed: 'dni-rv-chart-payer-speed',
|
||||
appealOutcomes: 'dni-rv-chart-appeal-outcomes',
|
||||
} 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 {
|
||||
payer: string;
|
||||
department: string;
|
||||
@ -68,6 +81,21 @@ export interface RecoveryBucket {
|
||||
claims: 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 {
|
||||
weeklyRate: WeekPoint[];
|
||||
@ -76,6 +104,23 @@ export interface BriefingData {
|
||||
reasons: ReasonCompare[];
|
||||
expiring: Expiring[];
|
||||
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);
|
||||
@ -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 sliceFilters = slice
|
||||
const asOf = opts.asOf?.slice(0, 10) || null;
|
||||
const sliceFilters = slice && isDenialKind(opts.kind ?? '')
|
||||
? [
|
||||
...(slice.payer ? [f('payer', slice.payer)] : []),
|
||||
...(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 }),
|
||||
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.reasonRecentVsBase, { 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 {
|
||||
weeklyRate: rate.map(week).sort(byWeek),
|
||||
weeklySlice: sl.map(week).sort(byWeek),
|
||||
asOf,
|
||||
weeklyRate: upTo(rate.map(week).sort(byWeek)),
|
||||
weeklySlice: upTo(foldWeeks(sl.map(week)).sort(byWeek)),
|
||||
payers: payers
|
||||
.map((r) => ({
|
||||
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,
|
||||
}))
|
||||
.sort((a, b) => b.billed - a.billed),
|
||||
recovery: 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)),
|
||||
recovery: sumBy(
|
||||
recovery.map((r) => ({ bucket: str(r.bucket), claims: num(r.claims) ?? 0, billed: num(r.billed) ?? 0 })),
|
||||
(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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
} from 'recharts';
|
||||
import { Loader2, Table2 } from 'lucide-react';
|
||||
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 { 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 c = HUES[theme];
|
||||
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 (
|
||||
<section className="space-y-3">
|
||||
<div className="grid gap-3 lg:grid-cols-2">
|
||||
<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>
|
||||
<div className="grid gap-3 lg:grid-cols-2">{cards}</div>
|
||||
</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 });
|
||||
|
||||
// 1. The spike ----------------------------------------------------------------
|
||||
function SpikeChart({ data, slice, c, title }: { data: BriefingData; slice: Slice | null; c: typeof HUES.light; title: string }) {
|
||||
const pts = data.weeklySlice.length ? data.weeklySlice : data.weeklyRate;
|
||||
const isSlice = data.weeklySlice.length > 0;
|
||||
const spike = slice?.spikeStart ?? null;
|
||||
function SpikeChart({ data, slice, c, title, kind = '' }: { data: BriefingData; slice: Slice | null; c: typeof HUES.light; title: string; kind?: string }) {
|
||||
const isSlice = slice !== null && data.weeklySlice.length > 0;
|
||||
const pts = isSlice ? data.weeklySlice : data.weeklyRate;
|
||||
// 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) }));
|
||||
return (
|
||||
<Frame
|
||||
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}%`]) }}
|
||||
>
|
||||
<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} />
|
||||
<XAxis dataKey="label" {...axis(c)} interval="preserveStartEnd" minTickGap={28} />
|
||||
<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)}
|
||||
stroke={c.ai}
|
||||
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>
|
||||
@ -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}%`]) }}
|
||||
>
|
||||
<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} />
|
||||
<XAxis dataKey="short" {...axis(c)} interval={0} />
|
||||
<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 -------------------------------------------------------------------
|
||||
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', '') }));
|
||||
|
||||
@ -43,7 +43,7 @@ const RECOVERY_COLOR: Record<string, string> = {
|
||||
'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();
|
||||
pres.layout = 'LAYOUT_WIDE'; // 13.33 x 7.5 in
|
||||
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 = [
|
||||
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.',
|
||||
`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)}%.` : '',
|
||||
'Read-only access to Databricks (workspace.rcm). Every query is logged and reproducible from the case.',
|
||||
].filter(Boolean);
|
||||
|
||||
@ -67,9 +67,10 @@ 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 briefing = useQuery(
|
||||
() => loadBriefing(client, slice),
|
||||
[id, slice?.payer, slice?.department, slice?.code],
|
||||
() => loadBriefing(client, slice, { kind, asOf }),
|
||||
[id, slice?.payer, slice?.department, slice?.code, kind, asOf],
|
||||
hasFindings,
|
||||
);
|
||||
|
||||
@ -79,7 +80,7 @@ export function CaseScreen() {
|
||||
try {
|
||||
// pptxgenjs is ~700 KB; nobody pays for it until they ask for slides.
|
||||
const { buildDeck } = await import('../lib/slides');
|
||||
await buildDeck(row, briefing.data, slice);
|
||||
await buildDeck(row, briefing.data, slice, kind);
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
@ -172,7 +173,7 @@ export function CaseScreen() {
|
||||
<>
|
||||
<Verdict row={row} />
|
||||
{briefing.data ? (
|
||||
<Briefing data={briefing.data} slice={slice} />
|
||||
<Briefing data={briefing.data} slice={slice} kind={kind} />
|
||||
) : (
|
||||
<BriefingSkeleton error={briefing.error} />
|
||||
)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user