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:41:29 +01:00
parent 8d259eb4d1
commit e2e39f2836
2 changed files with 37 additions and 18 deletions

View file

@ -200,7 +200,9 @@ In Docker it runs as a separate service (`intelligence`) sharing the same image
1. **Queue feeder** — continuously scans `archive.sqlite` for articles that have content, an embedding, and an event assignment. Inserts them into `article_queue` in `intelligence.sqlite`.
2. **Augor worker** — pulls one pending article at a time from the queue. The article is a trigger — the unit of work is the event it belongs to. Fetches all articles in the event, matches them against tracked company embeddings via cosine similarity, calls the LLM once per matched company, writes structured knowledge and predictions, then marks all sibling articles in the event as processed.
3. **Column migrations** — run on startup to safely add new columns to existing databases without data loss.
3. **Consolidation worker** — slow loop (default 60s). For each tracked company, reads all `event_knowledge` rows, builds a flat list of claims, and calls the LLM to normalize and deduplicate them into canonical grouped facts stored in `company_facts`. Preserves `first_seen_at` across cycles. Prunes facts that have only been confirmed once and haven't been seen in 90 days.
4. **Graph worker** — slow loop (default 90s). Reads all `company_facts` rows of type `relationship`, parses the claim format, resolves whether the target entity is a tracked company, and upserts edges into `company_relationships`. Inserts reciprocal edges automatically (supplier ↔ customer, etc.) when both endpoints are tracked.
5. **Column migrations** — run on startup to safely add new columns to existing databases without data loss.
### Output tables (`intelligence.sqlite`)
@ -211,6 +213,8 @@ In Docker it runs as a separate service (`intelligence`) sharing the same image
| `company_embeddings` | Pre-generated embeddings for each company (generated on startup via OpenRouter) |
| `event_knowledge` | Extracted relationships, themes, and factors per event+company |
| `event_predictions` | Forward-looking predictions (market share, stock price, competitive position) with `event_date` from the source articles |
| `company_facts` | Deduplicated, canonical facts per company accumulated across all events. Each fact has a `confirmation_count` and a confidence tier (`low` / `medium` / `high` / `very_high`) |
| `company_relationships` | Cross-company relationship graph derived from `company_facts`. Includes reciprocal edges and `confirmation_count` |
### Company matching
@ -231,10 +235,18 @@ Uses `openRouter.llmModel` via the OpenRouter API. One call per matched company
| `intelligence.similarityThreshold` | Cosine similarity cutoff for company matching (default `0.35`) |
| `workers.augorLoopDelayMs` | Delay between augor iterations when queue is empty (default `1500`) |
| `workers.queueFeederBatchSize` | Articles pulled per feeder batch (default `100`) |
| `workers.consolidationLoopDelayMs` | Delay between consolidation cycles (default `60000`) |
| `workers.graphWorkerLoopDelayMs` | Delay between graph worker cycles (default `90000`) |
### Admin panel
The intelligence data is visible in the admin panel (`/admin`) under the **Intelligence** tab. Predictions can be sorted by event publication date. A **SQL** tab allows raw queries against either database.
The intelligence data is visible in the admin panel (`/admin`) under the **Intelligence** tab. The tab has three views:
- **Knowledge** — raw extracted relationships, themes, and factors per event+company. Filterable by company and type, sortable by ingestion order or event date.
- **Predictions** — forward-looking LLM predictions per event+company, with direction, magnitude, timeframe, and rationale.
- **Graph** — interactive D3 force-directed network diagram of the cross-company relationship graph. Nodes are draggable and zoomable. Edge thickness reflects confirmation count. Hover an edge to see the relationship type and count. Click a tracked company node to see its top facts in a sidebar. Toggle untracked entities on/off with the checkbox in the legend. Use the Expand button to fill the full viewport.
A **SQL** tab allows raw queries against either database. Multiple statements separated by `;` are supported — each runs independently and results render as separate blocks.
## Notes

View file

@ -1427,19 +1427,30 @@ function renderIntelGraph() {
.attr('stroke-opacity', d => 0.2 + (d.count / maxCount) * 0.7)
.attr('marker-end', 'url(#ig-arrow)');
// 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;
// hover label — one floating text element that follows mouse
const hoverLabel = svg.append('text')
.attr('font-size', 11)
.attr('fill', '#94a3b8')
.attr('pointer-events', 'none')
.attr('opacity', 0);
const linkLabels = linkG.selectAll('text')
.data(linksCopy.filter(l => l.count >= medianCount))
.join('text')
.text(d => d.type)
.attr('text-anchor', 'middle')
.attr('font-size', d => 8 + Math.round((d.count / maxCount) * 3))
.attr('fill', '#475569')
.attr('opacity', d => 0.45 + (d.count / maxCount) * 0.45)
.attr('pointer-events', 'none');
linkLines
.on('mouseenter', function(ev, d) {
d3.select(this).attr('stroke', '#60a5fa').attr('stroke-opacity', 1);
hoverLabel.text(`${d.type} ×${d.count}`).attr('opacity', 1);
})
.on('mousemove', function(ev) {
const [mx, my] = d3.pointer(ev, svgEl);
hoverLabel.attr('x', mx + 10).attr('y', my - 6);
})
.on('mouseleave', function(ev, d) {
d3.select(this)
.attr('stroke', '#334155')
.attr('stroke-opacity', 0.2 + (d.count / maxCount) * 0.7);
hoverLabel.attr('opacity', 0);
});
const linkLabels = { attr: () => {} }; // no-op so tick handler doesnt break
// nodes
@ -1535,10 +1546,6 @@ function renderIntelGraph() {
.attr('x2', d => d.target.x)
.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 - 3);
nodeGroups.attr('transform', d => `translate(${d.x},${d.y})`);
});