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 (
);
})}
);
}
/** 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) => (