// intelligence → signals (trade signal cards)
// depends on: app.js, intel-shared.js
async function loadSignals() {
let data;
try {
data = await api("/admin/api/intelligence/signals");
} catch (_) {
data = [];
}
const grid = document.getElementById("intel-signals-grid");
const empty = document.getElementById("intel-signals-empty");
if (!data || data.length === 0) {
grid.innerHTML = "";
empty.style.display = "";
return;
}
empty.style.display = "none";
grid.innerHTML = data.map(s => {
const firstSentence = (s.summary || "").split(/\.\s+/)[0].replace(/\.$/, "") + ".";
const eventTs = s.latest_event_date ? s.latest_event_date.slice(0, 10) : null;
let drivers = [];
let risks = [];
let predIds = [];
try { drivers = JSON.parse(s.key_drivers || "[]"); } catch (_) {}
try { risks = JSON.parse(s.risk_factors || "[]"); } catch (_) {}
try { predIds = JSON.parse(s.supporting_prediction_ids || "[]"); } catch (_) {}
const driverItems = drivers.map(d => `
${escapeHtml(d)} `).join("");
const riskItems = risks.map(r => `${escapeHtml(r)} `).join("");
return `
conf: ${escapeHtml(s.confidence)}
risk: ${escapeHtml(s.risk_level)}
${escapeHtml(s.timeframe)}
${escapeHtml(firstSentence)}
${eventTs ? `Latest event ${eventTs}` : "No dated event"}
Summary
${escapeHtml(s.summary || "—")}
${driverItems ? `
` : ""}
${riskItems ? `
` : ""}
Data used
${predIds.length} predictions · window: 90 days
Regenerate
`;
}).join("");
}
function toggleSignalCard(companyId) {
const card = document.getElementById(`signal-card-${companyId}`);
if (!card) return;
card.classList.toggle("expanded");
if (card.classList.contains("expanded")) {
loadSignalReferences(companyId);
}
}
async function loadSignalReferences(companyId) {
const container = document.getElementById(`signal-refs-${companyId}`);
if (!container || container.dataset.loaded === "1") return;
container.dataset.loaded = "1";
let data;
try {
data = await api(`/admin/api/intelligence/signals/${companyId}/references`);
} catch (_) {
container.innerHTML = `
References
Failed to load references
`;
container.dataset.loaded = "0";
return;
}
const events = data?.events || [];
if (!events.length) {
container.innerHTML = `
References
No linked events
`;
return;
}
const blocks = events.map(ev => {
const dateStr = (ev.event_date || ev.created_at || "").slice(0, 10);
const articles = (ev.articles || []).map(a => {
const src = a.source ? `${escapeHtml(a.source)} ` : "";
const pd = a.pub_date ? `${escapeHtml(a.pub_date.slice(0, 10))} ` : "";
return `
${escapeHtml(a.title || a.url)}
${src}${pd}
`;
}).join("");
return `
${escapeHtml(ev.title || `Event #${ev.id}`)}
${dateStr ? `${dateStr} ` : ""}
${articles ? `
` : `
No articles linked
`}
`;
}).join("");
container.innerHTML = `
References
${blocks}
`;
}
async function regenerateSignal(ev, companyId) {
ev.stopPropagation();
try {
await api(`/admin/api/intelligence/signals/${companyId}`, { method: "DELETE" });
toast("Signal cleared — worker will regenerate on next cycle");
const card = document.getElementById(`signal-card-${companyId}`);
if (card) card.remove();
const grid = document.getElementById("intel-signals-grid");
if (!grid.children.length) {
document.getElementById("intel-signals-empty").style.display = "";
}
} catch (_) {
toast("Failed to clear signal", true);
}
}
document.addEventListener("DOMContentLoaded", () => {
// fire both in parallel — signals api returns [] when intelligence
// db is unavailable, so no need to gate on the stats call
Promise.all([loadIntelStatsRow(), loadSignals()]);
});