feat: enrich correlation debug with fit_signal and orphan breakdown
_call_fits_incident now returns (bool, signal_str) so each correlation decision records exactly what evidence fired: unit_overlap, vehicle_overlap, location_proximity, time_fallback, tactical_default, or the corresponding false-return variants (unit_loc_conflict, content_divergence, etc.). - corr_fit_signal and corr_matched_units written to call docs for fast/single and fast/disambig paths - Admin debug endpoint exposes the new fields in calls_detail - Orphan section adds orphans_by_talkgroup summary (count, no-type count, sweep-exhausted count per TGID) and raises orphan limit 100 → 250 - Admin page shows corr_path and fit_signal distribution panels above raw JSON; time_fallback highlighted in yellow as a diagnostic marker No correlation logic changed — diagnostic data only.
This commit is contained in:
@@ -113,7 +113,28 @@ function CorrelationDebugTab() {
|
||||
}
|
||||
|
||||
const json = data ? JSON.stringify(data, null, 2) : null;
|
||||
const meta = data as { incident_count?: number; orphaned_call_count?: number; generated_at?: string } | null;
|
||||
const meta = data as {
|
||||
incident_count?: number;
|
||||
orphaned_call_count?: number;
|
||||
generated_at?: string;
|
||||
incidents?: Array<{ calls_detail?: Array<{ corr_path?: string; corr_fit_signal?: string }> }>;
|
||||
orphans_by_talkgroup?: Array<{ talkgroup_id?: number; talkgroup_name?: string; count: number; no_type_count: number; sweep_exhausted_count: number }>;
|
||||
} | null;
|
||||
|
||||
// Aggregate corr_path and corr_fit_signal counts across all incident calls.
|
||||
const pathCounts: Record<string, number> = {};
|
||||
const signalCounts: Record<string, number> = {};
|
||||
if (meta?.incidents) {
|
||||
for (const inc of meta.incidents) {
|
||||
for (const call of inc.calls_detail ?? []) {
|
||||
const p = call.corr_path ?? "unknown";
|
||||
pathCounts[p] = (pathCounts[p] ?? 0) + 1;
|
||||
if (call.corr_fit_signal) {
|
||||
signalCounts[call.corr_fit_signal] = (signalCounts[call.corr_fit_signal] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -172,6 +193,46 @@ function CorrelationDebugTab() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{meta && Object.keys(pathCounts).length > 0 && (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-xl p-4 space-y-1">
|
||||
<p className="text-xs text-gray-400 font-mono font-semibold mb-2">corr_path distribution</p>
|
||||
{Object.entries(pathCounts).sort((a, b) => b[1] - a[1]).map(([path, n]) => (
|
||||
<div key={path} className="flex justify-between text-xs font-mono">
|
||||
<span className="text-gray-300">{path}</span>
|
||||
<span className="text-indigo-400">{n}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-xl p-4 space-y-1">
|
||||
<p className="text-xs text-gray-400 font-mono font-semibold mb-2">fit_signal distribution</p>
|
||||
{Object.keys(signalCounts).length === 0
|
||||
? <p className="text-xs text-gray-600 font-mono">No signal data yet — deploy correlator update first</p>
|
||||
: Object.entries(signalCounts).sort((a, b) => b[1] - a[1]).map(([sig, n]) => (
|
||||
<div key={sig} className="flex justify-between text-xs font-mono">
|
||||
<span className={sig === "time_fallback" ? "text-yellow-400" : "text-gray-300"}>{sig}</span>
|
||||
<span className="text-indigo-400">{n}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{meta?.orphans_by_talkgroup && meta.orphans_by_talkgroup.length > 0 && (
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-xl p-4 space-y-1">
|
||||
<p className="text-xs text-gray-400 font-mono font-semibold mb-2">orphans by talkgroup</p>
|
||||
{meta.orphans_by_talkgroup.map((tg) => (
|
||||
<div key={String(tg.talkgroup_id)} className="flex justify-between text-xs font-mono">
|
||||
<span className="text-gray-300">{tg.talkgroup_name} <span className="text-gray-600">({tg.talkgroup_id})</span></span>
|
||||
<span className="text-gray-400">
|
||||
{tg.count} total · <span className="text-gray-500">{tg.no_type_count} no-type</span> · <span className="text-red-400">{tg.sweep_exhausted_count} exhausted</span>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{json && (
|
||||
<pre
|
||||
ref={preRef}
|
||||
|
||||
Reference in New Issue
Block a user