// 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 `
    ${escapeHtml(s.company_name)}
    ${escapeHtml(s.ticker)}
    ${s.signal}
    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 ? `
    Key drivers
      ${driverItems}
    ` : ""} ${riskItems ? `
    Risk factors
      ${riskItems}
    ` : ""}
    Data used

    ${predIds.length} predictions  ·  window: 90 days

    References

    Loading…

    `; }).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()]); });