add event_date column to event_knowledge and event_predictions tables; update related logic in admin panel and augorWorker

This commit is contained in:
ImBenji 2026-04-23 14:28:24 +01:00
parent 8c20cf3817
commit c31f8b0b16

View file

@ -1277,10 +1277,6 @@ function renderIntelGraph(nodes, links) {
const g = svg.append('g'); const g = svg.append('g');
svg.call(
d3.zoom().scaleExtent([0.15, 5]).on('zoom', ev => g.attr('transform', ev.transform))
);
// arrow marker // arrow marker
svg.append('defs').append('marker') svg.append('defs').append('marker')
.attr('id', 'ig-arrow') .attr('id', 'ig-arrow')
@ -1296,32 +1292,27 @@ function renderIntelGraph(nodes, links) {
const maxCount = Math.max(...links.map(l => l.count), 1); const maxCount = Math.max(...links.map(l => l.count), 1);
const zoomBehavior = d3.zoom().scaleExtent([0.1, 5]).on('zoom', ev => g.attr('transform', ev.transform));
svg.call(zoomBehavior);
const simulation = d3.forceSimulation(nodes) const simulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink(links).id(d => d.key).distance(110)) .force('link', d3.forceLink(links).id(d => d.key).distance(150))
.force('charge', d3.forceManyBody().strength(-320)) .force('charge', d3.forceManyBody().strength(-500))
.force('center', d3.forceCenter(width / 2, height / 2)) .force('center', d3.forceCenter(width / 2, height / 2))
.force('collide', d3.forceCollide(32)); .force('collide', d3.forceCollide(40));
const linkLines = g.append('g') const linkLines = g.append('g')
.selectAll('line') .selectAll('line')
.data(links) .data(links)
.join('line') .join('line')
.attr('stroke', '#1e293b') .attr('stroke', '#334155')
.attr('stroke-width', d => 1 + (d.count / maxCount) * 3.5) .attr('stroke-width', d => 1 + (d.count / maxCount) * 3)
.attr('stroke-opacity', d => 0.35 + (d.count / maxCount) * 0.55) .attr('stroke-opacity', d => 0.3 + (d.count / maxCount) * 0.6)
.attr('marker-end', 'url(#ig-arrow)'); .attr('marker-end', 'url(#ig-arrow)');
// relationship type on hover only via title
const linkLabels = g.append('g') linkLines.append('title').text(d => `${d.type} (×${d.count})`);
.selectAll('text')
.data(links)
.join('text')
.text(d => d.type)
.attr('font-size', 9)
.attr('fill', '#334155')
.attr('text-anchor', 'middle')
.attr('pointer-events', 'none');
const nodeGroups = g.append('g') const nodeGroups = g.append('g')
@ -1346,18 +1337,27 @@ function renderIntelGraph(nodes, links) {
}); });
nodeGroups.append('circle') nodeGroups.append('circle')
.attr('r', d => d.tracked ? 14 : 9) .attr('r', d => d.tracked ? 16 : 11)
.attr('fill', d => d.tracked ? '#1e3a5f' : '#0f172a') .attr('fill', d => d.tracked ? '#1e3a5f' : '#0f172a')
.attr('stroke', d => d.tracked ? '#3b82f6' : '#475569') .attr('stroke', d => d.tracked ? '#3b82f6' : '#475569')
.attr('stroke-width', d => d.tracked ? 2 : 1.5); .attr('stroke-width', d => d.tracked ? 2 : 1.5);
nodeGroups.append('text') // ticker label inside tracked nodes; short name below untracked nodes
.text(d => d.ticker || d.name.slice(0, 7)) nodeGroups.filter(d => d.tracked).append('text')
.text(d => d.ticker || d.name.slice(0, 5))
.attr('text-anchor', 'middle') .attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle') .attr('dominant-baseline', 'middle')
.attr('font-size', d => d.tracked ? 9 : 8) .attr('font-size', 9)
.attr('font-weight', d => d.tracked ? '600' : '400') .attr('font-weight', '600')
.attr('fill', d => d.tracked ? '#93c5fd' : '#64748b') .attr('fill', '#93c5fd')
.attr('pointer-events', 'none');
nodeGroups.filter(d => !d.tracked).append('text')
.text(d => d.name.length > 12 ? d.name.slice(0, 11) + '…' : d.name)
.attr('text-anchor', 'middle')
.attr('y', 20)
.attr('font-size', 9)
.attr('fill', '#475569')
.attr('pointer-events', 'none'); .attr('pointer-events', 'none');
nodeGroups.append('title').text(d => d.name); nodeGroups.append('title').text(d => d.name);
@ -1370,12 +1370,25 @@ function renderIntelGraph(nodes, links) {
.attr('x2', d => d.target.x) .attr('x2', d => d.target.x)
.attr('y2', d => d.target.y); .attr('y2', d => d.target.y);
linkLabels
.attr('x', d => (d.source.x + d.target.x) / 2)
.attr('y', d => (d.source.y + d.target.y) / 2 - 4);
nodeGroups.attr('transform', d => `translate(${d.x},${d.y})`); nodeGroups.attr('transform', d => `translate(${d.x},${d.y})`);
}); });
// fit everything into view once the simulation has cooled enough
simulation.on('end', () => {
const bounds = g.node().getBBox();
if (!bounds.width || !bounds.height) return;
const pad = 40;
const scaleX = width / (bounds.width + pad * 2);
const scaleY = height / (bounds.height + pad * 2);
const scale = Math.min(scaleX, scaleY, 1);
const tx = width / 2 - scale * (bounds.x + bounds.width / 2);
const ty = height / 2 - scale * (bounds.y + bounds.height / 2);
svg.transition().duration(600)
.call(zoomBehavior.transform, d3.zoomIdentity.translate(tx, ty).scale(scale));
});
} }