import { useState } from 'react'; import { Link } from 'react-router-dom'; import { AlertTriangle, CheckCircle2, ChevronDown, ExternalLink, FileSearch } from 'lucide-react'; import type { Slice } from '../api/charts'; import { affectedClaimsHref, recoverableClaimsHref } from '../lib/claimsLink'; import { Badge, Card, Facts } from './core'; import type { AvaDecision, AvaTraceStep, Row } from '../api/types'; import { firstDeadline, headline, money, num, pct, prose, recommendations, shortDate, splitRecommendation, sqlSteps, str } from '../format'; /** * Ava's findings, laid out for a person who has ninety seconds. * * THREE QUESTIONS, THREE SIZES. What happened — one sentence, large. How much * — three numbers you can read from across a table. What to do — the ranked * list, with the decision buttons rendered right under it by the case screen. * Everything a reviewer needs to act is above the fold; everything an analyst * would want to check is behind "the full story" and "her evidence". * * EVERY VALUE IS HERS. Nothing here is computed by the frontend — each field is * read off the instance as she submitted it. Violet marks what an agent * produced; nothing else is violet. */ /** The headline card: one sentence, and the three numbers. */ export function Verdict({ row, slice = null, kind = '', anchor = null, }: { row: Row; slice?: Slice | null; kind?: string; anchor?: string | null; }) { const summary = str(row.summary); if (!summary) return null; const line = prose(headline(summary)); const scanned = prose(row.scanned_summary); const affectedHref = affectedClaimsHref(kind, slice, anchor); const recoverableHref = recoverableClaimsHref(kind, slice, anchor); const atRisk = num(row.amount_at_risk); const recoverable = num(row.recoverable_amount); const affected = num(row.affected_claims); const deadline = firstDeadline(row.recommendations); const confidence = num(row.confidence); const reconciled = row.data_reconciled === true || str(row.data_reconciled) === 'true'; return (

Summary

{line}

{reconciled ? 'Reconciled with finance summary' : 'Not reconciled with finance summary'} {confidence != null && ( Confidence {pct(confidence * 100, 0)} )} AI analysis · Ava
{scanned && (

What Ava reviewed. {scanned}

)}
); } /** The decision list. Rendered directly above the Approve / Send back / Reject buttons. */ export function Recommendations({ row }: { row: Row }) { const recs = recommendations(row.recommendations).map(prose); if (recs.length === 0) return null; return (

Recommended actions

Ranked by financial impact. Each names an owner.

    {recs.map((block, i) => { const { title, body } = splitRecommendation(block); return (
  1. {i + 1}
    {title &&

    {sentenceCase(title)}

    }

    {body}

  2. ); })}
); } /** The paragraph version — for the reader who wants the whole account. Collapsed. */ export function FullStory({ row }: { row: Row }) { const [open, setOpen] = useState(false); const summary = prose(row.summary); const cause = prose(row.root_cause); const secondary = prose(row.secondary_findings); if (!summary) return null; const hasSecondary = secondary && !/^none found/i.test(secondary.trim()); return ( setOpen(!open)} icon={} title="Analysis" hint={hasSecondary ? 'Full analysis, root cause, and a secondary finding' : 'Full analysis and root cause'} tone="ai" >

{summary}

{cause && (

Root cause

{cause}

)} {hasSecondary && (

Secondary finding

{secondary}

)}
); } /** * Every SQL statement she ran. For the sceptic in the room. Collapsed. * * SOURCE OF TRUTH IS THE TRACE, not her sql_log field. The platform records * every statement the agentic tool sent, verbatim, with Databricks' own * SUCCEEDED/FAILED and the row count — so that is what is shown. Her sql_log * is now a one-line-per-step index ("3c: department cut -> Cardiology +13 * pts") and is used only as the note beside each statement. Asking her to * paste 16 full queries into a form field was what blew the tool call past * the output cap and lost a whole investigation. */ export function SqlEvidence({ row, steps, decisions }: { row: Row; steps: AvaTraceStep[]; decisions: AvaDecision[] }) { const [open, setOpen] = useState(false); // Only the attempt that produced the findings. A case can carry an earlier // attempt that failed to submit (its queries are still in the trace); showing // both would double the count and make her look twice as busy as she was. const submitted = decisions.filter((d) => d.status === 'submitted').slice(-1)[0]; const scoped = submitted ? steps.filter((s) => s.decision_id == null || String(s.decision_id) === String(submitted.id)) : steps; const queries = scoped.filter((s) => s.type === 'api' && s.detail); const notes = sqlSteps(row.sql_log); if (queries.length === 0 && notes.length === 0) return null; return ( setOpen(!open)} icon={} title="Query log" hint={ queries.length ? `${queries.length} queries executed against Databricks — reproducible` : `${notes.length} analysis steps` } > {queries.length > 0 ? (
    {queries.map((q, i) => (
  1. Query {i + 1} {q.query_state === 'FAILED' ? ( error — corrected in next query ) : q.rows_returned != null ? ( {q.rows_returned} row{q.rows_returned === 1 ? '' : 's'} ) : null}

    {q.detail}
    {notes[i]?.note &&

    {notes[i].note}

    }
  2. ))}
) : (
    {notes.map((s, i) => (
  1. {s.label &&

    {s.label}

    }

    {s.sql}

    {s.note &&

    {s.note}

    }
  2. ))}
)}
); } /** The re-check. Same shape either way, so "fixed" and "not fixed" read alike. */ export function Recheck({ row }: { row: Row }) { const summary = prose(row.recheck_summary); if (!summary) return null; const current = num(row.current_rate); const baseline = num(row.baseline_rate); const recovered = num(row.recovered_amount); const better = current != null && baseline != null && current - baseline <= 2; return ( {better ? 'Within baseline' : 'Still elevated'}} >

{summary}

); } // --------------------------------------------------------------------------- function Big({ label, value, tone, note, href }: { label: string; value: string; tone?: 'good' | 'bad'; note?: string; href?: string | null }) { const text = tone === 'good' ? 'text-good' : tone === 'bad' ? 'text-bad' : 'text-ink'; const body = ( <>

{label}

{value}

{note &&

{note}

} {href && (

See the claims

)} ); // A number you can click opens the rows behind it in the claims table. return href ? ( {body} ) : (
{body}
); } export function Disclosure({ open, onToggle, icon, title, hint, tone, children, }: { open: boolean; onToggle: () => void; icon: React.ReactNode; title: string; hint?: string; tone?: 'ai'; children: React.ReactNode; }) { return (
{open &&
{children}
}
); } /** "APPEALS — FILE RETRO-AUTH IMMEDIATELY ($1,091,000 …)" → "Appeals — file retro-auth immediately ($1,091,000 …)". * Ava shouts her headings; a manager's screen shouldn't. Acronyms and codes survive. */ function sentenceCase(s: string): string { if (s !== s.toUpperCase()) return s; return s .toLowerCase() .replace(/^\w/, (c) => c.toUpperCase()) .replace(/\b(co-\d+|cpt|blueshield|bluecross|cardiology|northlake|d\. whitfield|dr\.)\b/gi, (m) => m.replace(/\b\w/g, (c) => c.toUpperCase()), ); }