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 16:27:35 +01:00
parent 8aa10d9bc2
commit 8d259eb4d1

View file

@ -1390,15 +1390,30 @@ function renderIntelGraph() {
const zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', ev => g.attr('transform', ev.transform)); const zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', ev => g.attr('transform', ev.transform));
svg.call(zoomBehavior); svg.call(zoomBehavior);
// degree map — nodes with many connections need stronger repulsion
const degree = {};
for (const l of linksCopy) {
degree[l.source] = (degree[l.source] || 0) + 1;
degree[l.target] = (degree[l.target] || 0) + 1;
}
const maxDegree = Math.max(...Object.values(degree), 1);
const simulation = d3.forceSimulation(nodesCopy) const simulation = d3.forceSimulation(nodesCopy)
.force('link', d3.forceLink(linksCopy).id(d => d.key).distance(d => { .force('link', d3.forceLink(linksCopy).id(d => d.key).distance(d => {
// tracked-tracked links spread wider const src = typeof d.source === 'object' ? d.source.key : d.source;
const bothTracked = typeof d.source === 'object' ? d.source.tracked && d.target.tracked : false; const tgt = typeof d.target === 'object' ? d.target.key : d.target;
return bothTracked ? 220 : 160; const deg = Math.max(degree[src] || 1, degree[tgt] || 1);
// busier nodes push their neighbors further away
return 180 + deg * 18;
}))
.force('charge', d3.forceManyBody().strength(d => {
const deg = degree[d.key] || 1;
return -1400 - deg * 200;
})) }))
.force('charge', d3.forceManyBody().strength(-900))
.force('center', d3.forceCenter(width / 2, height / 2)) .force('center', d3.forceCenter(width / 2, height / 2))
.force('collide', d3.forceCollide(d => d.tracked ? 52 : 38)); // collision radius covers circle + name text below it
.force('collide', d3.forceCollide(d => d.tracked ? 72 : 52))
.alphaDecay(0.015);
// links // links
@ -1408,22 +1423,22 @@ function renderIntelGraph() {
.data(linksCopy) .data(linksCopy)
.join('line') .join('line')
.attr('stroke', '#334155') .attr('stroke', '#334155')
.attr('stroke-width', d => { .attr('stroke-width', d => 1 + Math.pow(d.count / maxCount, 0.55) * 6)
// more dramatic: 1px at count=1, up to 7px at maxCount .attr('stroke-opacity', d => 0.2 + (d.count / maxCount) * 0.7)
return 1 + Math.pow(d.count / maxCount, 0.6) * 6;
})
.attr('stroke-opacity', d => 0.25 + (d.count / maxCount) * 0.65)
.attr('marker-end', 'url(#ig-arrow)'); .attr('marker-end', 'url(#ig-arrow)');
// edge labels — size and opacity proportional to count // edge labels — only on edges where count >= median, to avoid hub clutter
const sortedCounts = linksCopy.map(l => l.count).sort((a, b) => a - b);
const medianCount = sortedCounts[Math.floor(sortedCounts.length / 2)] || 1;
const linkLabels = linkG.selectAll('text') const linkLabels = linkG.selectAll('text')
.data(linksCopy) .data(linksCopy.filter(l => l.count >= medianCount))
.join('text') .join('text')
.text(d => d.type) .text(d => d.type)
.attr('text-anchor', 'middle') .attr('text-anchor', 'middle')
.attr('font-size', d => 7 + Math.round((d.count / maxCount) * 4)) .attr('font-size', d => 8 + Math.round((d.count / maxCount) * 3))
.attr('fill', '#475569') .attr('fill', '#475569')
.attr('opacity', d => 0.4 + (d.count / maxCount) * 0.5) .attr('opacity', d => 0.45 + (d.count / maxCount) * 0.45)
.attr('pointer-events', 'none'); .attr('pointer-events', 'none');