// Analytics overview view — org-level KPIs, distributions, leaderboards.
function Sparkline({ data, color = "var(--fg)", fill = false, height = 36, width = 120 }) {
const min = Math.min(...data), max = Math.max(...data);
const range = max - min || 1;
const W = width, H = height;
const pts = data.map((v, i) => {
const x = (i / (data.length - 1)) * W;
const y = H - 2 - ((v - min) / range) * (H - 6);
return [x, y];
});
const d = pts.map((p, i) => (i === 0 ? "M" : "L") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" ");
const area = fill ? `${d} L ${W} ${H} L 0 ${H} Z` : null;
return (
);
}
function KPICard({ label, value, delta, sub, spark, sparkColor }) {
const isUp = typeof delta === "number" ? delta > 0 : String(delta).startsWith("+");
const isDown = typeof delta === "number" ? delta < 0 : String(delta).startsWith("−") || String(delta).startsWith("-");
const cls = isUp ? "up" : isDown ? "down" : "flat";
const sign = typeof delta === "number" ? (delta > 0 ? "+" : "") : "";
return (
{isUp && }
{isDown && }
{sign}{delta}
{sub && {sub}}
);
}
function TypologyChart() {
const max = Math.max(...TYPOLOGY_DIST.map(t => t.count));
return (
{TYPOLOGY_DIST.map((t, i) => (
{t.name}
{t.count} · {t.pct}%
))}
);
}
function CorridorTable() {
return (
| Corridor |
Risk |
90d volume |
{CORRIDOR_RISK.map((c, i) => (
|
{c.from}
{c.to}
|
|
{c.vol} |
))}
);
}
function Leaderboard() {
const max = Math.max(...QUEUE_BY_ASSIGNEE.map(a => a.open));
return (
| Analyst |
Workload |
Open |
SLA |
{QUEUE_BY_ASSIGNEE.map(a => (
|
{a.init}
{a.who}
|
|
{a.open} |
0 ? "var(--risk-high)" : "var(--fg-3)" }}>
{a.sla > 0 ? `${a.sla} risk` : "—"}
|
))}
);
}
function HourHeatmap() {
// 7×24 heatmap of alert generation
const heatmapData = [];
for (let d = 0; d < 7; d++) {
const row = [];
for (let h = 0; h < 24; h++) {
// Synthetic but plausible — peaks at business hours, especially Tue/Wed
const business = (h >= 8 && h <= 18) ? 1 : 0.3;
const weekend = (d === 5 || d === 6) ? 0.4 : 1;
const peak = (d === 1 || d === 2 || d === 3) && (h >= 10 && h <= 14) ? 1.4 : 1;
const noise = 0.5 + (Math.sin(d*7 + h*0.7) + 1) * 0.5;
row.push(Math.round(business * weekend * peak * noise * 12));
}
heatmapData.push(row);
}
const max = Math.max(...heatmapData.flat());
const days = ["M","T","W","T","F","S","S"];
return (
{Array.from({ length: 24 }).map((_, h) => (
{h % 4 === 0 ? h.toString().padStart(2, "0") : ""}
))}
{heatmapData.map((row, d) => (
{days[d]}
{row.map((v, h) => (
))}
))}
Less
{[0, 0.25, 0.5, 0.75, 1].map((v, i) => (
))}
More
);
}
function AnalyticsView({ data }) {
const kpis = data?.kpis || window.KPIS || {};
const summary = data?.summary;
const k = (key, fallback) => kpis[key] || fallback;
return (
Operations overview
FIU workload and detection performance · last 30 days
{/* KPI grid */}
{/* Two-col charts */}
Alerts by typology
187 alerts · 8 categories
Corridor risk
top 6 by volume
Alert generation · 7d × 24h
UTC
{/* Detection performance table */}
Detection rules · performance · 30d
| Rule |
Typology |
Fires |
TP rate |
FP rate |
Trend |
Status |
);
}
function RulRow({ id, name, type, fires, tp, fp, trend, status }) {
const sCls = status === "healthy" ? "pill low" : status === "watch" ? "pill med" : "pill high";
return (
|
{id}
{name}
|
{type} |
{fires} |
{tp}% |
50 ? "var(--risk-high)" : fp > 40 ? "var(--risk-med)" : "var(--fg-2)" }}>{fp}% |
|
{status} |
);
}
Object.assign(window, { AnalyticsView, Sparkline });