refactor admin data queries; consolidate multiple count queries and optimize article fetching
This commit is contained in:
parent
a48b1eef67
commit
653a58b3d8
2 changed files with 98 additions and 21 deletions
|
|
@ -2,22 +2,15 @@
|
||||||
// cards are glance-only; clicking opens a modal with the full breakdown.
|
// cards are glance-only; clicking opens a modal with the full breakdown.
|
||||||
// depends on: app.js, intel-shared.js
|
// depends on: app.js, intel-shared.js
|
||||||
|
|
||||||
|
// full dataset kept in memory so filters/sort never re-fetch
|
||||||
|
let allSignals = [];
|
||||||
|
|
||||||
// local lookup of signal rows keyed by company_id — so the modal can
|
// local lookup of signal rows keyed by company_id — so the modal can
|
||||||
// render without re-fetching. references are still lazy-loaded per open.
|
// render without re-fetching. references are still lazy-loaded per open.
|
||||||
const signalsById = new Map();
|
const signalsById = new Map();
|
||||||
|
|
||||||
|
|
||||||
async function loadSignals() {
|
function renderSignals(data) {
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = await api("/admin/api/intelligence/signals");
|
|
||||||
} catch (_) {
|
|
||||||
data = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
signalsById.clear();
|
|
||||||
(data || []).forEach(s => signalsById.set(s.company_id, s));
|
|
||||||
|
|
||||||
const grid = document.getElementById("intel-signals-grid");
|
const grid = document.getElementById("intel-signals-grid");
|
||||||
const empty = document.getElementById("intel-signals-empty");
|
const empty = document.getElementById("intel-signals-empty");
|
||||||
|
|
||||||
|
|
@ -61,6 +54,55 @@ async function loadSignals() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
const search = (document.getElementById("sf-search").value || "").trim().toLowerCase();
|
||||||
|
const signal = document.getElementById("sf-signal").value;
|
||||||
|
const confidence = document.getElementById("sf-confidence").value;
|
||||||
|
const risk = document.getElementById("sf-risk").value;
|
||||||
|
const sort = document.getElementById("sf-sort").value;
|
||||||
|
|
||||||
|
let results = allSignals.filter(s => {
|
||||||
|
if (search && !s.company_name.toLowerCase().includes(search) && !s.ticker.toLowerCase().includes(search)) return false;
|
||||||
|
if (signal && s.signal !== signal) return false;
|
||||||
|
if (confidence && (s.confidence || "").toLowerCase() !== confidence) return false;
|
||||||
|
if (risk && (s.risk_level || "").toLowerCase() !== risk) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
results.sort((a, b) => {
|
||||||
|
if (sort === "event_desc") {
|
||||||
|
const da = a.latest_event_date || "";
|
||||||
|
const db = b.latest_event_date || "";
|
||||||
|
return da < db ? 1 : da > db ? -1 : 0;
|
||||||
|
}
|
||||||
|
if (sort === "company_asc") return a.company_name.localeCompare(b.company_name);
|
||||||
|
if (sort === "company_desc") return b.company_name.localeCompare(a.company_name);
|
||||||
|
// generated_desc (default)
|
||||||
|
const ga = a.generated_at || "";
|
||||||
|
const gb = b.generated_at || "";
|
||||||
|
return ga < gb ? 1 : ga > gb ? -1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
renderSignals(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function loadSignals() {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await api("/admin/api/intelligence/signals");
|
||||||
|
} catch (_) {
|
||||||
|
data = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
allSignals = data || [];
|
||||||
|
signalsById.clear();
|
||||||
|
allSignals.forEach(s => signalsById.set(s.company_id, s));
|
||||||
|
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let currentSignal = null;
|
let currentSignal = null;
|
||||||
|
|
||||||
function openSignalModal(companyId) {
|
function openSignalModal(companyId) {
|
||||||
|
|
@ -187,16 +229,9 @@ async function regenerateSignal() {
|
||||||
await api(`/admin/api/intelligence/signals/${companyId}`, { method: "DELETE" });
|
await api(`/admin/api/intelligence/signals/${companyId}`, { method: "DELETE" });
|
||||||
toast("Signal cleared — worker will regenerate on next cycle");
|
toast("Signal cleared — worker will regenerate on next cycle");
|
||||||
signalsById.delete(companyId);
|
signalsById.delete(companyId);
|
||||||
|
allSignals = allSignals.filter(s => s.company_id !== companyId);
|
||||||
const card = document.querySelector(`.signal-card[onclick*="(${companyId})"]`);
|
|
||||||
if (card) card.remove();
|
|
||||||
|
|
||||||
const grid = document.getElementById("intel-signals-grid");
|
|
||||||
if (!grid.children.length) {
|
|
||||||
document.getElementById("intel-signals-empty").style.display = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
closeSignalModal();
|
closeSignalModal();
|
||||||
|
applyFilters();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
toast("Failed to clear signal", true);
|
toast("Failed to clear signal", true);
|
||||||
}
|
}
|
||||||
|
|
@ -207,6 +242,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
document.getElementById("signalCloseBtn").onclick = closeSignalModal;
|
document.getElementById("signalCloseBtn").onclick = closeSignalModal;
|
||||||
document.getElementById("signalRegenBtn").onclick = regenerateSignal;
|
document.getElementById("signalRegenBtn").onclick = regenerateSignal;
|
||||||
|
|
||||||
|
document.getElementById("sf-search").addEventListener("input", applyFilters);
|
||||||
|
document.getElementById("sf-signal").addEventListener("change", applyFilters);
|
||||||
|
document.getElementById("sf-confidence").addEventListener("change", applyFilters);
|
||||||
|
document.getElementById("sf-risk").addEventListener("change", applyFilters);
|
||||||
|
document.getElementById("sf-sort").addEventListener("change", applyFilters);
|
||||||
|
|
||||||
// fire both in parallel — signals api returns [] when intelligence
|
// fire both in parallel — signals api returns [] when intelligence
|
||||||
// db is unavailable, so no need to gate on the stats call
|
// db is unavailable, so no need to gate on the stats call
|
||||||
Promise.all([loadIntelStatsRow(), loadSignals()]);
|
Promise.all([loadIntelStatsRow(), loadSignals()]);
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,45 @@
|
||||||
intelligence.sqlite not found — is the intelligence worker running?
|
intelligence.sqlite not found — is the intelligence worker running?
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="filters" id="signal-filters">
|
||||||
|
<label>Search <input type="text" id="sf-search" placeholder="company or ticker…" /></label>
|
||||||
|
<label>Signal
|
||||||
|
<select id="sf-signal">
|
||||||
|
<option value="">All</option>
|
||||||
|
<option value="BUY">BUY</option>
|
||||||
|
<option value="HOLD">HOLD</option>
|
||||||
|
<option value="SELL">SELL</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Confidence
|
||||||
|
<select id="sf-confidence">
|
||||||
|
<option value="">All</option>
|
||||||
|
<option value="high">High</option>
|
||||||
|
<option value="medium">Medium</option>
|
||||||
|
<option value="low">Low</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Risk
|
||||||
|
<select id="sf-risk">
|
||||||
|
<option value="">All</option>
|
||||||
|
<option value="high">High</option>
|
||||||
|
<option value="medium">Medium</option>
|
||||||
|
<option value="low">Low</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Sort
|
||||||
|
<select id="sf-sort">
|
||||||
|
<option value="generated_desc">Newest generated</option>
|
||||||
|
<option value="event_desc">Latest event</option>
|
||||||
|
<option value="company_asc">Company A→Z</option>
|
||||||
|
<option value="company_desc">Company Z→A</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="intel-content">
|
<div id="intel-content">
|
||||||
<div id="intel-signals-grid" class="signal-grid"></div>
|
<div id="intel-signals-grid" class="signal-grid"></div>
|
||||||
<div id="intel-signals-empty" class="signal-empty" style="display:none">No signals generated yet — waiting for the signal worker to run.</div>
|
<div id="intel-signals-empty" class="signal-empty" style="display:none">No signals match the current filters.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue