diff --git a/src/components/AiDecisionPanel.tsx b/src/components/AiDecisionPanel.tsx new file mode 100644 index 0000000..c6e6e45 --- /dev/null +++ b/src/components/AiDecisionPanel.tsx @@ -0,0 +1,274 @@ +import { Bot, Calculator, CheckCircle2, Cpu, ScrollText, User } from 'lucide-react'; +import { Badge, Card, Facts } from './core'; +import type { AuditEntry, Row } from '../api/types'; +import { POLICY } from '../api/config'; +import { dateTime, money, moneyShort, num, pct, ratio, str } from '../format'; + +/** + * Who did what on this file, and what the agent actually saw. + * + * THE CLAIM THIS PANEL HAS TO MAKE, and the reason it exists: on a screen full + * of numbers, a viewer cannot tell which came from a model and which from a + * rule, or which steps a person took. Saying it out loud is weaker than showing + * it — so every row here is attributed, and the attribution is read from the + * workflow's own audit trail rather than asserted by this component. + * + * WHAT IS REAL AND WHAT IS NOT, because that distinction matters more here than + * anywhere else in the app: + * + * * The timeline is the ACTUAL audit trail (`/view/audit`). "Credit + * Assessor AI" appears there because the agent submitted the activity under + * its own JWT and the platform recorded it — not because this file says so. + * * The agent's analysis, citations and deviation flag are the ACTUAL values + * it wrote, read off the instance. + * * The tool figures are the ACTUAL values on the instance, labelled with the + * tool that computes them. The mapping is fixed in 09_agent_tools.sql and + * each tool's SQL was verified to agree with the gate. + * + * NOT shown, and deliberately not faked: the per-step trace — individual LLM + * calls, raw tool payloads, durations, token counts, cost. That lives in + * `aiemployee.tbl_ai_trace_events` and is served by + * `/ai-employee/monitor/instances/:id/trace`, which requires an ORG-SCOPED + * token. The app JWT has no org claim (its claims are user_id / name / email / + * sub / exp / iat), so that endpoint answers 403 "no organization scope on this + * account" for every user of this console. Rendering a plausible-looking + * waterfall from guesses would be the one dishonest thing on a screen whose + * whole job is showing what actually happened. + */ + +/** The roles that belong to an agent rather than a person. */ +const AI_ROLES = ['ai_credit_assessor', 'ai_loan_advisor']; + +function isAgent(e: AuditEntry): boolean { + const roles = e.user_roles ?? []; + return roles.some((r) => AI_ROLES.includes(r)); +} + +/** Audit rows worth showing a human. The commit row for an activity carries the + * state it reached; the rest is engine bookkeeping. */ +function meaningful(entries: AuditEntry[]): AuditEntry[] { + return entries + .filter((e) => { + const st = str(e.execution_state); + // A commit row's execution_state is the state uid it landed on. + return st.startsWith('hdfc-state-'); + }) + .slice() + .reverse(); +} + +export function AiDecisionPanel({ row, audit }: { row: Row; audit: AuditEntry[] }) { + const steps = meaningful(audit); + const agentSteps = steps.filter(isAgent); + + const itr = num(row.itr_declared_income); + const gst = num(row.gst_turnover_12m); + const bank = num(row.bank_credits_12m); + const variance = num(row.income_variance_pct); + const dscr = num(row.dscr); + const maxElig = num(row.max_eligible_amount); + const analysis = str(row.ai_analysis); + const citations = str(row.assessment_citations); + const deviation = str(row.deviation_flags); + const isPersonal = str(row.loan_product_family) === 'personal'; + + return ( +
No completed steps recorded yet.
+ ) : ( +{analysis}
+ {citations && ( ++ The agent wrote the account above and flagged the deviation. It did not compute any + ratio and did not choose the outcome — the figures come from the tools and the + approve / refer / reject decision comes from the rules engine, after it submitted. +
++ Per-step detail — individual model calls, raw tool payloads, durations and cost — is + recorded by the platform against this instance but is not readable with an app sign-in. + It requires an org-scoped token, so it is available from Studio rather than here. +
+
+ {name}
+
+ {purpose}
+