Bot-Derpy's picture
PrimaryDraft: SEBI TechSprint prototype (27 deterministic checks, 40 real BSE SME issuers)
baf8a7b verified
Raw History Blame Contribute Delete
9.92 kB
import { useEffect, useState } from "react";
import { ShieldCheck, ArrowRight, CheckCircle, Gavel } from "@phosphor-icons/react";
import { api, ApiError, type Finding, type RuleResult, type RunSummary } from "../lib/api";
import { Empty, ErrorNote, Note, Spinner, TableSkeleton } from "./common";
const ORDER = { blocker: 0, high: 1, medium: 2, info: 3 } as const;
const ACTOR = "Merchant banker";
export function Board({
run, onNext, onRunUpdate,
}: { run: RunSummary; onNext: () => void; onRunUpdate: (r: RunSummary) => void }) {
const [results, setResults] = useState<RuleResult[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [working, setWorking] = useState<string | null>(null);
const [showPasses, setShowPasses] = useState(false);
const [summary, setSummary] = useState<RunSummary>(run);
const check = async () => {
setError(null);
setResults(null);
try {
const r = await api.check(run.id);
setResults(r.rule_results);
setSummary(r);
onRunUpdate(r);
} catch (e) {
setError(e instanceof ApiError ? e.message : String(e));
}
};
useEffect(() => { check(); }, [run.id]);
const resolve = async (f: Finding, resolutionId: string) => {
setWorking(`${f.rule_id}:${resolutionId}`);
setError(null);
try {
const r = await api.resolve(run.id, f.rule_id, resolutionId, ACTOR);
setResults(r.rule_results);
setSummary(r);
onRunUpdate(r);
} catch (e) {
setError(e instanceof ApiError ? e.message : String(e));
} finally {
setWorking(null);
}
};
const findings = (results ?? []).flatMap((r) => r.findings);
const open = findings.filter((f) => !f.resolved).sort((a, b) => ORDER[a.severity] - ORDER[b.severity]);
const done = findings.filter((f) => f.resolved);
const passes = (results ?? []).filter((r) => r.status === "pass");
const nas = (results ?? []).filter((r) => r.status === "not_applicable");
return (
<>
<div className="page-head">
<h1 className="page">Consistency check</h1>
<p className="lede">
{summary.rule_count} deterministic checks over the issuer's real figures and your
proposed terms. This is plain Python: no language model takes part in any verdict here,
and the same result appears whether or not the narrative layer is enabled.
</p>
</div>
{error && <div style={{ marginBottom: 14 }}><ErrorNote message={error} onRetry={check} /></div>}
<div className="stats" style={{ marginBottom: 16 }}>
<div className="stat">
<div className={`stat-v ${summary.severity.blocker ? "stop" : "ok"}`}>
{summary.severity.blocker ?? 0}
</div>
<div className="stat-l">blockers</div>
</div>
<div className="stat">
<div className={`stat-v ${summary.severity.high ? "warn" : ""}`}>
{summary.severity.high ?? 0}
</div>
<div className="stat-l">high</div>
</div>
<div className="stat">
<div className="stat-v">{summary.severity.medium ?? 0}</div>
<div className="stat-l">medium</div>
</div>
<div className="stat">
<div className="stat-v ok">{summary.rules_passed}</div>
<div className="stat-l">checks passed</div>
</div>
<div className="stat">
<div className="stat-v">{summary.rules_na}</div>
<div className="stat-l">not applicable</div>
</div>
<div className="stat">
<div className="stat-v">{done.length}</div>
<div className="stat-l">resolved by you</div>
</div>
</div>
{!results ? (
<div className="panel"><div className="panel-b"><TableSkeleton rows={6} /></div></div>
) : (
<>
{open.length === 0 ? (
<div className="panel" style={{ marginBottom: 14 }}>
<Empty icon={<CheckCircle size={28} weight="fill" />} title="No open findings">
Every deterministic check that applied to this filing passed. {nas.length} checks
did not apply because the underlying figures were not machine-readable, and those
are listed as gaps rather than passes.
</Empty>
</div>
) : (
<section style={{ marginBottom: 16 }}>
{open.map((f) => (
<FindingCard
key={`${f.rule_id}-${f.title}`}
f={f}
working={working}
onResolve={resolve}
/>
))}
</section>
)}
{done.length > 0 && (
<section style={{ marginBottom: 16 }}>
<div className="panel-h" style={{ border: 0, padding: "4px 0 8px" }}>
<h2>Resolved</h2>
<span className="count">{done.length}</span>
</div>
{done.map((f) => (
<div className="finding" key={`${f.rule_id}-${f.title}-done`}>
<div className="finding-h">
<span className="sev sev-resolved">resolved</span>
<div>
<h4>{f.title}</h4>
<p>
{f.resolved_by} 路 {f.resolved_at?.replace("T", " ").replace("+00:00", " UTC")}
</p>
</div>
<span className="finding-rule">{f.rule_id}</span>
</div>
</div>
))}
</section>
)}
<section className="panel">
<div className="panel-h">
<ShieldCheck size={16} />
<h2>All checks</h2>
<span className="count">
{summary.rules_passed} passed 路 {summary.rules_failed} failed 路 {summary.rules_na} n/a
</span>
<button
className="btn btn-ghost btn-sm"
style={{ marginLeft: "auto" }}
onClick={() => setShowPasses((v) => !v)}
>
{showPasses ? "Hide detail" : "Show detail"}
</button>
</div>
{showPasses && (
<div style={{ maxHeight: "42vh", overflowY: "auto" }}>
{results.map((r) => (
<div className="rule-row" key={r.rule_id}>
<span className="rule-id">{r.rule_id}</span>
<span className={`mark mark-${r.status === "not_applicable" ? "na" : r.status}`}>
{r.status === "not_applicable" ? "n/a" : r.status}
</span>
<span className="rule-detail">
{r.detail}
{r.icdr_ref && (
<span className="f-id" style={{ display: "block", marginTop: 2 }}>
{r.icdr_ref}
</span>
)}
</span>
</div>
))}
</div>
)}
</section>
</>
)}
<div style={{ marginTop: 16 }}>
<button className="btn btn-primary" onClick={onNext} disabled={!results}>
Continue to handoff <ArrowRight size={15} />
</button>
</div>
</>
);
}
function FindingCard({
f, working, onResolve,
}: { f: Finding; working: string | null; onResolve: (f: Finding, id: string) => void }) {
return (
<div className="finding">
<div className="finding-h">
<span className={`sev sev-${f.severity}`}>{f.severity}</span>
<div>
<h4>{f.title}</h4>
<p>{f.explanation}</p>
</div>
<span className="finding-rule">{f.rule_id}</span>
</div>
<div className="finding-b">
{f.delta && <div className="delta">difference: {f.delta}</div>}
{f.evidence.length > 0 && (
<div className="ev-grid">
{f.evidence.map((e, i) => (
<div className="ev" key={i}>
<div>
<div className="ev-l">{e.label}</div>
{e.locator && <div className="ev-loc">{e.locator}</div>}
{e.raw_line && <div className="ev-raw">{e.raw_line}</div>}
</div>
<div className="ev-v">{e.display}</div>
</div>
))}
</div>
)}
{f.icdr_ref && (
<div style={{ display: "flex", gap: 6, alignItems: "center", marginBottom: 10 }}>
<Gavel size={13} style={{ color: "var(--text-3)" }} />
<span className="f-id">{f.icdr_ref}</span>
</div>
)}
{f.resolutions.length > 0 ? (
<div className="res-list">
{f.resolutions.map((r) => (
<button
key={r.id}
className={`res ${r.recommended ? "res-rec" : ""}`}
onClick={() => onResolve(f, r.id)}
disabled={working !== null}
>
<div className="res-l">
{working === `${f.rule_id}:${r.id}` && <Spinner />} {r.label}
{r.recommended && (
<span className="tag tag-ok" style={{ marginLeft: 6 }}>recommended</span>
)}
</div>
<div className="res-d">{r.description}</div>
{r.justification && (
<div className="res-d" style={{ fontStyle: "italic", marginTop: 3 }}>
{r.justification}
</div>
)}
</button>
))}
</div>
) : (
<Note tone="quiet">
This finding has no automatic remedy. It must be reconciled against the audited
statements by the merchant banker.
</Note>
)}
</div>
</div>
);
}