@odla-ai/kg 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-T6SYIVST.js +376 -0
- package/dist/chunk-T6SYIVST.js.map +1 -0
- package/dist/index.cjs +410 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +330 -5
- package/dist/index.d.ts +330 -5
- package/dist/index.js +249 -38
- package/dist/index.js.map +1 -1
- package/dist/lenses-C7Q7Gvnx.d.cts +123 -0
- package/dist/lenses-C7Q7Gvnx.d.ts +123 -0
- package/dist/temporal/index.cjs +1 -689
- package/dist/temporal/index.cjs.map +1 -1
- package/dist/temporal/index.d.cts +4 -565
- package/dist/temporal/index.d.ts +4 -565
- package/dist/temporal/index.js +20 -977
- package/dist/temporal/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-V3N2GRYU.js +0 -73
- package/dist/chunk-V3N2GRYU.js.map +0 -1
- package/dist/sources-nJyD0JUP.d.cts +0 -256
- package/dist/sources-nJyD0JUP.d.ts +0 -256
package/dist/temporal/index.cjs
CHANGED
|
@@ -25,48 +25,22 @@ __export(temporal_exports, {
|
|
|
25
25
|
bucketFor: () => bucketFor,
|
|
26
26
|
bucketKey: () => bucketKey,
|
|
27
27
|
bucketRange: () => bucketRange,
|
|
28
|
-
buildNarrationMaterials: () => buildNarrationMaterials,
|
|
29
|
-
buildTemporalReport: () => buildTemporalReport,
|
|
30
|
-
buildTimeline: () => buildTimeline,
|
|
31
|
-
burstScores: () => burstScores,
|
|
32
28
|
byNodeId: () => byNodeId,
|
|
33
29
|
composeTargets: () => composeTargets,
|
|
34
|
-
detectBursts: () => detectBursts,
|
|
35
30
|
directTargets: () => directTargets,
|
|
36
|
-
extractEvents: () => extractEvents,
|
|
37
|
-
flowFrames: () => flowFrames,
|
|
38
|
-
forceLayout: () => forceLayout,
|
|
39
31
|
geoPairs: () => geoPairs,
|
|
40
32
|
hash32: () => hash32,
|
|
41
33
|
indexGraph: () => indexGraph,
|
|
42
34
|
labelPropagation: () => labelPropagation,
|
|
43
|
-
layoutSequence: () => layoutSequence,
|
|
44
|
-
leadScores: () => leadScores,
|
|
45
|
-
lensSnapshots: () => lensSnapshots,
|
|
46
|
-
matchRules: () => matchRules,
|
|
47
35
|
mulberry32: () => mulberry32,
|
|
48
36
|
nodeId: () => nodeId,
|
|
49
|
-
parseAmount: () => parseAmount,
|
|
50
37
|
parseDateMs: () => parseDateMs,
|
|
51
38
|
projectBridges: () => projectBridges,
|
|
52
|
-
rankEnrichmentCandidates: () => rankEnrichmentCandidates,
|
|
53
|
-
rankHotspots: () => rankHotspots,
|
|
54
39
|
reifiedBridgeTargets: () => reifiedBridgeTargets,
|
|
55
40
|
resolvePath: () => resolvePath,
|
|
56
|
-
rollup: () => rollup,
|
|
57
|
-
seriesKeys: () => seriesKeys,
|
|
58
|
-
shouldNarrate: () => shouldNarrate,
|
|
59
41
|
shuffle: () => shuffle,
|
|
60
|
-
snapshotAt: () => snapshotAt,
|
|
61
|
-
snapshotSeries: () => snapshotSeries,
|
|
62
|
-
spatialFindings: () => spatialFindings,
|
|
63
|
-
splitTitleBody: () => splitTitleBody,
|
|
64
|
-
synthesizeBurstSources: () => synthesizeBurstSources,
|
|
65
42
|
timePairs: () => timePairs,
|
|
66
|
-
topBridges: () => topBridges
|
|
67
|
-
topFlows: () => topFlows,
|
|
68
|
-
totals: () => totals,
|
|
69
|
-
trackClusters: () => trackClusters
|
|
43
|
+
topBridges: () => topBridges
|
|
70
44
|
});
|
|
71
45
|
module.exports = __toCommonJS(temporal_exports);
|
|
72
46
|
|
|
@@ -214,219 +188,6 @@ function resolvePath(g, start, labels) {
|
|
|
214
188
|
return frontier.slice().sort(byNodeId);
|
|
215
189
|
}
|
|
216
190
|
|
|
217
|
-
// src/temporal/events.ts
|
|
218
|
-
function parseAmount(v) {
|
|
219
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
|
|
220
|
-
if (typeof v !== "string") return void 0;
|
|
221
|
-
const m = /^\$?\s*([\d,]+(?:\.\d+)?)\s*([kKmMbB])?$/.exec(v.trim());
|
|
222
|
-
if (!m) return void 0;
|
|
223
|
-
const n = Number(m[1].replace(/,/g, ""));
|
|
224
|
-
if (!Number.isFinite(n)) return void 0;
|
|
225
|
-
const suffix = (m[2] ?? "").toLowerCase();
|
|
226
|
-
const mult = suffix === "k" ? 1e3 : suffix === "m" ? 1e6 : suffix === "b" ? 1e9 : 1;
|
|
227
|
-
return n * mult;
|
|
228
|
-
}
|
|
229
|
-
function resolveGeo(g, start, spec) {
|
|
230
|
-
const landing = spec.path?.length ? resolvePath(g, start, spec.path)[0] : start;
|
|
231
|
-
if (!landing) return void 0;
|
|
232
|
-
if (spec.attr) {
|
|
233
|
-
const v = g.node(nodeId(landing))?.attrs[spec.attr];
|
|
234
|
-
return typeof v === "string" && v !== "" ? v : void 0;
|
|
235
|
-
}
|
|
236
|
-
return landing.key;
|
|
237
|
-
}
|
|
238
|
-
function extractEvents(fragment, config) {
|
|
239
|
-
const g = indexGraph(fragment);
|
|
240
|
-
const out = [];
|
|
241
|
-
for (const spec of config.events) {
|
|
242
|
-
for (const n of g.nodesOf(spec.type)) {
|
|
243
|
-
const t = parseDateMs(n.attrs[spec.dateAttr]);
|
|
244
|
-
if (t === null) continue;
|
|
245
|
-
const self = { type: n.type, key: n.key };
|
|
246
|
-
const seen = /* @__PURE__ */ new Set();
|
|
247
|
-
const refs = [];
|
|
248
|
-
for (const path of spec.attach ?? []) {
|
|
249
|
-
for (const ref of resolvePath(g, self, path)) {
|
|
250
|
-
const id = nodeId(ref);
|
|
251
|
-
if (!seen.has(id)) {
|
|
252
|
-
seen.add(id);
|
|
253
|
-
refs.push(ref);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
refs.sort(byNodeId);
|
|
258
|
-
const weight = spec.weightOf?.(n.attrs) ?? (spec.weightAttr ? parseAmount(n.attrs[spec.weightAttr]) : void 0) ?? 1;
|
|
259
|
-
out.push({
|
|
260
|
-
t,
|
|
261
|
-
kind: spec.type,
|
|
262
|
-
node: self,
|
|
263
|
-
refs,
|
|
264
|
-
weight,
|
|
265
|
-
geo: spec.geo ? resolveGeo(g, self, spec.geo) : void 0,
|
|
266
|
-
geoTo: spec.geoTo ? resolveGeo(g, self, spec.geoTo) : void 0
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return out.sort((a, b) => a.t - b.t || byNodeId(a.node, b.node));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/temporal/series.ts
|
|
274
|
-
function seriesKeys(e, by) {
|
|
275
|
-
if (by === "kind") return [e.kind];
|
|
276
|
-
if (by === "geo") return e.geo === void 0 ? [] : [e.geo];
|
|
277
|
-
if (by === "ref") return e.refs.map(nodeId);
|
|
278
|
-
const k = by(e);
|
|
279
|
-
return k === void 0 ? [] : typeof k === "string" ? [k] : k;
|
|
280
|
-
}
|
|
281
|
-
function rollup(events, opts) {
|
|
282
|
-
const { grain, by = "kind", measure = "count" } = opts;
|
|
283
|
-
const from = opts.from ?? events[0]?.t;
|
|
284
|
-
const to = opts.to ?? events[events.length - 1]?.t;
|
|
285
|
-
if (from === void 0 || to === void 0) return { grain, buckets: [], series: [] };
|
|
286
|
-
const buckets = bucketRange(from, to, grain);
|
|
287
|
-
const index = new Map(buckets.map((b, i) => [b.key, i]));
|
|
288
|
-
const rows = /* @__PURE__ */ new Map();
|
|
289
|
-
for (const e of events) {
|
|
290
|
-
const i = index.get(bucketKey(e.t, grain));
|
|
291
|
-
if (i === void 0) continue;
|
|
292
|
-
for (const key of seriesKeys(e, by)) {
|
|
293
|
-
let values = rows.get(key);
|
|
294
|
-
if (!values) {
|
|
295
|
-
values = new Array(buckets.length).fill(0);
|
|
296
|
-
rows.set(key, values);
|
|
297
|
-
}
|
|
298
|
-
values[i] = values[i] + (measure === "count" ? 1 : e.weight);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
const series = [...rows.entries()].map(([key, values]) => ({ key, values })).sort((a, b) => a.key < b.key ? -1 : 1);
|
|
302
|
-
return { grain, buckets, series };
|
|
303
|
-
}
|
|
304
|
-
function totals(set) {
|
|
305
|
-
const values = new Array(set.buckets.length).fill(0);
|
|
306
|
-
for (const s of set.series) {
|
|
307
|
-
s.values.forEach((v, i) => {
|
|
308
|
-
values[i] = values[i] + v;
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
return { key: "total", values };
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// src/temporal/hotspots.ts
|
|
315
|
-
function burstScores(values, opts = {}) {
|
|
316
|
-
const { window = 6, minBaseline = 3, minSigma = 1 } = opts;
|
|
317
|
-
return values.map((x, i) => {
|
|
318
|
-
const from = Math.max(0, i - window);
|
|
319
|
-
const trailing = values.slice(from, i);
|
|
320
|
-
if (trailing.length < minBaseline) return 0;
|
|
321
|
-
const mean = trailing.reduce((a, b) => a + b, 0) / trailing.length;
|
|
322
|
-
const variance = trailing.reduce((a, b) => a + (b - mean) * (b - mean), 0) / trailing.length;
|
|
323
|
-
const sigma = Math.max(Math.sqrt(variance), Math.sqrt(mean), minSigma);
|
|
324
|
-
return (x - mean) / sigma;
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
function detectBursts(set, opts = {}) {
|
|
328
|
-
const { zEnter = 2, zExit = 1 } = opts;
|
|
329
|
-
const out = [];
|
|
330
|
-
for (const series of set.series) {
|
|
331
|
-
const zs = burstScores(series.values, opts);
|
|
332
|
-
let run = null;
|
|
333
|
-
zs.forEach((z, i) => {
|
|
334
|
-
if (run) {
|
|
335
|
-
if (z >= zExit) {
|
|
336
|
-
run.end = i;
|
|
337
|
-
run.peak = Math.max(run.peak, z);
|
|
338
|
-
run.score += Math.max(0, z);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
out.push(run);
|
|
342
|
-
run = null;
|
|
343
|
-
}
|
|
344
|
-
if (z >= zEnter) {
|
|
345
|
-
run = { seriesKey: series.key, start: i, end: i, peak: z, score: z };
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
if (run) out.push(run);
|
|
349
|
-
}
|
|
350
|
-
return out.sort(
|
|
351
|
-
(a, b) => b.score - a.score || a.start - b.start || (a.seriesKey < b.seriesKey ? -1 : 1)
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
function rankHotspots(set, events, opts = {}) {
|
|
355
|
-
const { by = "kind", top = 20, contributorsPer = 5 } = opts;
|
|
356
|
-
const index = new Map(set.buckets.map((b, i) => [b.key, i]));
|
|
357
|
-
return detectBursts(set, opts).slice(0, top).map((burst) => {
|
|
358
|
-
const inRun = events.filter((e) => {
|
|
359
|
-
const i = index.get(bucketKey(e.t, set.grain));
|
|
360
|
-
return i !== void 0 && i >= burst.start && i <= burst.end && seriesKeys(e, by).includes(burst.seriesKey);
|
|
361
|
-
});
|
|
362
|
-
inRun.sort((a, b) => b.weight - a.weight || a.t - b.t);
|
|
363
|
-
return {
|
|
364
|
-
...burst,
|
|
365
|
-
bucketKeys: set.buckets.slice(burst.start, burst.end + 1).map((b) => b.key),
|
|
366
|
-
contributors: inRun.slice(0, contributorsPer)
|
|
367
|
-
};
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// src/temporal/snapshot.ts
|
|
372
|
-
var PAIR_CAP = 12;
|
|
373
|
-
function buildTimeline(events, opts = {}) {
|
|
374
|
-
const mode = opts.mode ?? "entities";
|
|
375
|
-
const firstSeen = /* @__PURE__ */ new Map();
|
|
376
|
-
const edges = [];
|
|
377
|
-
const see = (id, t) => {
|
|
378
|
-
if (!firstSeen.has(id)) firstSeen.set(id, t);
|
|
379
|
-
};
|
|
380
|
-
for (const e of events) {
|
|
381
|
-
const refIds = e.refs.map(nodeId);
|
|
382
|
-
if (mode === "full") {
|
|
383
|
-
const self = nodeId(e.node);
|
|
384
|
-
see(self, e.t);
|
|
385
|
-
for (const ref of refIds) {
|
|
386
|
-
see(ref, e.t);
|
|
387
|
-
const [a, b] = self < ref ? [self, ref] : [ref, self];
|
|
388
|
-
edges.push({ a, b, t: e.t, kind: e.kind, weight: e.weight });
|
|
389
|
-
}
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
for (const ref of refIds) see(ref, e.t);
|
|
393
|
-
if (refIds.length > PAIR_CAP) continue;
|
|
394
|
-
for (let i = 0; i < refIds.length; i++) {
|
|
395
|
-
for (let j = i + 1; j < refIds.length; j++) {
|
|
396
|
-
const [a, b] = refIds[i] < refIds[j] ? [refIds[i], refIds[j]] : [refIds[j], refIds[i]];
|
|
397
|
-
edges.push({ a, b, t: e.t, kind: e.kind, weight: e.weight });
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
edges.sort((x, y) => x.t - y.t || (x.a < y.a ? -1 : x.a > y.a ? 1 : x.b < y.b ? -1 : 1));
|
|
402
|
-
return {
|
|
403
|
-
events: [...events],
|
|
404
|
-
nodes: [...firstSeen.keys()].sort(),
|
|
405
|
-
edges,
|
|
406
|
-
firstSeen
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
function snapshotAt(timeline, t, opts = {}) {
|
|
410
|
-
const since = opts.window === void 0 ? -Infinity : t - opts.window;
|
|
411
|
-
const agg = /* @__PURE__ */ new Map();
|
|
412
|
-
const activity = /* @__PURE__ */ new Map();
|
|
413
|
-
for (const e of timeline.edges) {
|
|
414
|
-
if (e.t > t) break;
|
|
415
|
-
activity.set(e.a, e.t);
|
|
416
|
-
activity.set(e.b, e.t);
|
|
417
|
-
if (e.t <= since) continue;
|
|
418
|
-
const key = `${e.a}|${e.b}`;
|
|
419
|
-
const cell = agg.get(key);
|
|
420
|
-
if (cell) cell.weight += 1;
|
|
421
|
-
else agg.set(key, { a: e.a, b: e.b, weight: 1 });
|
|
422
|
-
}
|
|
423
|
-
const nodes = timeline.nodes.filter((id) => timeline.firstSeen.get(id) <= t);
|
|
424
|
-
return { t, nodes, edges: [...agg.values()], activity };
|
|
425
|
-
}
|
|
426
|
-
function snapshotSeries(timeline, buckets, opts = {}) {
|
|
427
|
-
return buckets.map((b) => snapshotAt(timeline, b.end - 1, opts));
|
|
428
|
-
}
|
|
429
|
-
|
|
430
191
|
// src/temporal/communities.ts
|
|
431
192
|
function labelPropagation(nodes, edges, opts = {}) {
|
|
432
193
|
const { seed = 1, maxRounds = 20 } = opts;
|
|
@@ -484,444 +245,6 @@ function labelPropagation(nodes, edges, opts = {}) {
|
|
|
484
245
|
return new Map(sorted.map((id) => [id, canonical.get(labels.get(id))]));
|
|
485
246
|
}
|
|
486
247
|
|
|
487
|
-
// src/temporal/clusters.ts
|
|
488
|
-
var jaccard = (a, b) => {
|
|
489
|
-
let inter = 0;
|
|
490
|
-
for (const id of b) if (a.has(id)) inter++;
|
|
491
|
-
return inter / (a.size + b.length - inter);
|
|
492
|
-
};
|
|
493
|
-
function trackClusters(frames, opts = {}) {
|
|
494
|
-
const minJaccard = opts.minJaccard ?? 0.3;
|
|
495
|
-
const outFrames = [];
|
|
496
|
-
const changes = [];
|
|
497
|
-
let prev = [];
|
|
498
|
-
for (const frame of frames) {
|
|
499
|
-
const byLabel = /* @__PURE__ */ new Map();
|
|
500
|
-
for (const [id, label] of frame.labels) {
|
|
501
|
-
const list = byLabel.get(label);
|
|
502
|
-
list ? list.push(id) : byLabel.set(label, [id]);
|
|
503
|
-
}
|
|
504
|
-
const current = [...byLabel.values()].map((members) => members.sort());
|
|
505
|
-
current.sort((a, b) => a[0] < b[0] ? -1 : 1);
|
|
506
|
-
const prevSets = prev.map((c) => ({ c, set: new Set(c.members) }));
|
|
507
|
-
const matches = [];
|
|
508
|
-
current.forEach((members, ci) => {
|
|
509
|
-
prevSets.forEach(({ set }, pi) => {
|
|
510
|
-
const j = jaccard(set, members);
|
|
511
|
-
if (j >= minJaccard) {
|
|
512
|
-
let shared = 0;
|
|
513
|
-
for (const id of members) if (set.has(id)) shared++;
|
|
514
|
-
matches.push({ ci, pi, j, shared });
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
});
|
|
518
|
-
matches.sort((a, b) => b.j - a.j || b.shared - a.shared || a.ci - b.ci);
|
|
519
|
-
const idOf = /* @__PURE__ */ new Map();
|
|
520
|
-
const claimedPrev = /* @__PURE__ */ new Map();
|
|
521
|
-
const matchedPrevBy = /* @__PURE__ */ new Map();
|
|
522
|
-
for (const m of matches) {
|
|
523
|
-
const list = matchedPrevBy.get(m.ci);
|
|
524
|
-
list ? list.push(m.pi) : matchedPrevBy.set(m.ci, [m.pi]);
|
|
525
|
-
if (!idOf.has(m.ci) && !claimedPrev.has(m.pi)) {
|
|
526
|
-
idOf.set(m.ci, prev[m.pi].id);
|
|
527
|
-
claimedPrev.set(m.pi, m.ci);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
const usedIds = /* @__PURE__ */ new Set([...idOf.values(), ...prev.map((c) => c.id)]);
|
|
531
|
-
const clusters = current.map((members, ci) => {
|
|
532
|
-
let id = idOf.get(ci);
|
|
533
|
-
if (!id) {
|
|
534
|
-
id = `c:${members[0]}`;
|
|
535
|
-
while (usedIds.has(id)) id += "'";
|
|
536
|
-
usedIds.add(id);
|
|
537
|
-
if (outFrames.length > 0) changes.push({ kind: "birth", t: frame.t, id });
|
|
538
|
-
}
|
|
539
|
-
return { id, members };
|
|
540
|
-
});
|
|
541
|
-
prevSets.forEach(({ c }, pi) => {
|
|
542
|
-
const winners = matches.filter((m) => m.pi === pi).map((m) => m.ci);
|
|
543
|
-
if (winners.length >= 2) {
|
|
544
|
-
changes.push({ kind: "split", t: frame.t, from: c.id, into: winners.map((ci) => clusters[ci].id) });
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
for (const [ci, pis] of matchedPrevBy) {
|
|
548
|
-
if (pis.length >= 2) {
|
|
549
|
-
changes.push({ kind: "merge", t: frame.t, into: clusters[ci].id, from: pis.map((pi) => prev[pi].id) });
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const carried = new Set(clusters.map((c) => c.id));
|
|
553
|
-
for (const c of prev) if (!carried.has(c.id)) changes.push({ kind: "death", t: frame.t, id: c.id });
|
|
554
|
-
clusters.sort((a, b) => a.id < b.id ? -1 : 1);
|
|
555
|
-
outFrames.push({ t: frame.t, clusters });
|
|
556
|
-
prev = clusters;
|
|
557
|
-
}
|
|
558
|
-
return { frames: outFrames, changes };
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// src/temporal/layout.ts
|
|
562
|
-
var initialFor = (id, seed) => {
|
|
563
|
-
const rand = mulberry32(hash32(id) ^ seed >>> 0);
|
|
564
|
-
const theta = 2 * Math.PI * rand();
|
|
565
|
-
const radius = 0.25 + 0.2 * rand();
|
|
566
|
-
return { x: 0.5 + radius * Math.cos(theta), y: 0.5 + radius * Math.sin(theta) };
|
|
567
|
-
};
|
|
568
|
-
function forceLayout(nodes, edges, opts = {}) {
|
|
569
|
-
const { iterations = 300, seed = 1, repulsion = 0.1, gravity = 0.5, positions } = opts;
|
|
570
|
-
const ids = [...nodes].sort();
|
|
571
|
-
const n = ids.length;
|
|
572
|
-
if (n === 0) return /* @__PURE__ */ new Map();
|
|
573
|
-
const k = Math.sqrt(1 / n);
|
|
574
|
-
const xs = new Float64Array(n);
|
|
575
|
-
const ys = new Float64Array(n);
|
|
576
|
-
const index = /* @__PURE__ */ new Map();
|
|
577
|
-
ids.forEach((id, i) => {
|
|
578
|
-
index.set(id, i);
|
|
579
|
-
const p = positions?.get(id) ?? initialFor(id, seed);
|
|
580
|
-
xs[i] = p.x;
|
|
581
|
-
ys[i] = p.y;
|
|
582
|
-
});
|
|
583
|
-
const links = [];
|
|
584
|
-
for (const e of edges) {
|
|
585
|
-
const a = index.get(e.a);
|
|
586
|
-
const b = index.get(e.b);
|
|
587
|
-
if (a === void 0 || b === void 0 || a === b) continue;
|
|
588
|
-
links.push(a < b ? [a, b, Math.min(e.weight ?? 1, 4)] : [b, a, Math.min(e.weight ?? 1, 4)]);
|
|
589
|
-
}
|
|
590
|
-
links.sort((p, q) => p[0] - q[0] || p[1] - q[1] || p[2] - q[2]);
|
|
591
|
-
const dx = new Float64Array(n);
|
|
592
|
-
const dy = new Float64Array(n);
|
|
593
|
-
for (let iter = 0; iter < iterations; iter++) {
|
|
594
|
-
dx.fill(0);
|
|
595
|
-
dy.fill(0);
|
|
596
|
-
for (let i = 0; i < n; i++) {
|
|
597
|
-
for (let j = i + 1; j < n; j++) {
|
|
598
|
-
let vx = xs[i] - xs[j];
|
|
599
|
-
let vy = ys[i] - ys[j];
|
|
600
|
-
let d = Math.hypot(vx, vy);
|
|
601
|
-
if (d < 1e-6) {
|
|
602
|
-
const r = mulberry32(hash32(`${ids[i]}|${ids[j]}`))();
|
|
603
|
-
vx = 1e-4 * Math.cos(r * 7);
|
|
604
|
-
vy = 1e-4 * Math.sin(r * 7);
|
|
605
|
-
d = 1e-4;
|
|
606
|
-
}
|
|
607
|
-
const force = repulsion * k * k / d;
|
|
608
|
-
dx[i] = dx[i] + vx / d * force;
|
|
609
|
-
dy[i] = dy[i] + vy / d * force;
|
|
610
|
-
dx[j] = dx[j] - vx / d * force;
|
|
611
|
-
dy[j] = dy[j] - vy / d * force;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
for (const [a, b, w] of links) {
|
|
615
|
-
const vx = xs[a] - xs[b];
|
|
616
|
-
const vy = ys[a] - ys[b];
|
|
617
|
-
const d = Math.max(Math.hypot(vx, vy), 1e-6);
|
|
618
|
-
const force = d * d / k * w;
|
|
619
|
-
dx[a] = dx[a] - vx / d * force;
|
|
620
|
-
dy[a] = dy[a] - vy / d * force;
|
|
621
|
-
dx[b] = dx[b] + vx / d * force;
|
|
622
|
-
dy[b] = dy[b] + vy / d * force;
|
|
623
|
-
}
|
|
624
|
-
const cap = 0.1 * (1 - iter / iterations) + 2e-3;
|
|
625
|
-
for (let i = 0; i < n; i++) {
|
|
626
|
-
const gx = (0.5 - xs[i]) * gravity;
|
|
627
|
-
const gy = (0.5 - ys[i]) * gravity;
|
|
628
|
-
let mx = dx[i] + gx;
|
|
629
|
-
let my = dy[i] + gy;
|
|
630
|
-
const move = Math.hypot(mx, my);
|
|
631
|
-
if (move > cap) {
|
|
632
|
-
mx = mx / move * cap;
|
|
633
|
-
my = my / move * cap;
|
|
634
|
-
}
|
|
635
|
-
xs[i] = Math.min(1, Math.max(0, xs[i] + mx));
|
|
636
|
-
ys[i] = Math.min(1, Math.max(0, ys[i] + my));
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return new Map(ids.map((id, i) => [id, { x: xs[i], y: ys[i] }]));
|
|
640
|
-
}
|
|
641
|
-
function layoutSequence(snapshots, opts = {}) {
|
|
642
|
-
const { warmIterations = 60, ...base } = opts;
|
|
643
|
-
const out = [];
|
|
644
|
-
let prev = base.positions ? new Map(base.positions) : void 0;
|
|
645
|
-
for (const snap of snapshots) {
|
|
646
|
-
const frame = forceLayout(snap.nodes, snap.edges, {
|
|
647
|
-
...base,
|
|
648
|
-
iterations: prev ? warmIterations : base.iterations,
|
|
649
|
-
positions: prev
|
|
650
|
-
});
|
|
651
|
-
out.push(frame);
|
|
652
|
-
prev = frame;
|
|
653
|
-
}
|
|
654
|
-
return out;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// src/temporal/report.ts
|
|
658
|
-
var DAY2 = 864e5;
|
|
659
|
-
var round2 = (v) => Math.round(v * 100) / 100;
|
|
660
|
-
var isoDay = (t) => new Date(t).toISOString().slice(0, 10);
|
|
661
|
-
function clusterChangeIds(change) {
|
|
662
|
-
if (change.kind === "merge") return [change.into, ...change.from];
|
|
663
|
-
if (change.kind === "split") return [change.from, ...change.into];
|
|
664
|
-
return [change.id];
|
|
665
|
-
}
|
|
666
|
-
function buildTemporalReport(fragment, config, opts = {}) {
|
|
667
|
-
const events = extractEvents(fragment, config);
|
|
668
|
-
const now = opts.now ?? events[events.length - 1]?.t ?? 0;
|
|
669
|
-
const generatedAt = new Date(now).toISOString();
|
|
670
|
-
if (events.length === 0) {
|
|
671
|
-
return {
|
|
672
|
-
generatedAt,
|
|
673
|
-
window: { from: "", to: "", weeks: [] },
|
|
674
|
-
totals: { events: 0, nodes: 0, edges: 0 },
|
|
675
|
-
series: [],
|
|
676
|
-
leads: [],
|
|
677
|
-
clusters: { frames: [], changes: [] }
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
const attrsOf = new Map(fragment.nodes.map((n) => [nodeId(n), n.attrs]));
|
|
681
|
-
const str = (id, attr) => {
|
|
682
|
-
const v = attrsOf.get(id)?.[attr];
|
|
683
|
-
return typeof v === "string" && v !== "" ? v : void 0;
|
|
684
|
-
};
|
|
685
|
-
const windowDays = opts.windowDays ?? 155;
|
|
686
|
-
const from = Math.max(events[0].t, now - windowDays * DAY2);
|
|
687
|
-
const weeks = bucketRange(from, now, "week");
|
|
688
|
-
const weekly = rollup(events, { grain: "week", by: "ref", from, to: now });
|
|
689
|
-
const leads = rankHotspots(weekly, events, {
|
|
690
|
-
by: "ref",
|
|
691
|
-
top: opts.topLeads ?? 8,
|
|
692
|
-
contributorsPer: opts.contributorsPer ?? 3,
|
|
693
|
-
...opts.burst ?? { window: 6 }
|
|
694
|
-
}).map((h) => ({
|
|
695
|
-
key: h.seriesKey,
|
|
696
|
-
type: h.seriesKey.slice(0, Math.max(0, h.seriesKey.indexOf(":"))),
|
|
697
|
-
// ref keys are always "type:key"
|
|
698
|
-
name: str(h.seriesKey, "name") ?? h.seriesKey,
|
|
699
|
-
...str(h.seriesKey, "city") ? { city: str(h.seriesKey, "city") } : {},
|
|
700
|
-
...str(h.seriesKey, "sector") ? { sector: str(h.seriesKey, "sector") } : {},
|
|
701
|
-
score: round2(h.score),
|
|
702
|
-
peak: round2(h.peak),
|
|
703
|
-
window: h.bucketKeys,
|
|
704
|
-
contributors: h.contributors.map((e) => ({
|
|
705
|
-
kind: e.kind,
|
|
706
|
-
key: e.node.key,
|
|
707
|
-
title: str(nodeId(e.node), "title") ?? str(nodeId(e.node), "round") ?? str(nodeId(e.node), "name") ?? e.node.key,
|
|
708
|
-
date: isoDay(e.t)
|
|
709
|
-
}))
|
|
710
|
-
}));
|
|
711
|
-
const timeline = buildTimeline(events);
|
|
712
|
-
const snapshots = snapshotSeries(timeline, weeks);
|
|
713
|
-
const labelFrames = snapshots.map((s) => ({ t: s.t, labels: labelPropagation(s.nodes, s.edges, { seed: 1 }) }));
|
|
714
|
-
const tracked = trackClusters(labelFrames);
|
|
715
|
-
const labelFor = (members) => `${str(members[0], "name") ?? members[0]}${members.length > 1 ? ` +${members.length - 1}` : ""}`;
|
|
716
|
-
const frames = tracked.frames.map((f, i) => ({
|
|
717
|
-
week: weeks[i].key,
|
|
718
|
-
count: f.clusters.length,
|
|
719
|
-
top: [...f.clusters].sort((a, b) => b.members.length - a.members.length).slice(0, 3).map((c) => ({ id: c.id, size: c.members.length, label: labelFor(c.members) }))
|
|
720
|
-
}));
|
|
721
|
-
const changeFloor = weeks[Math.max(0, weeks.length - (opts.changeWeeks ?? 4))].start;
|
|
722
|
-
const weekOf = (t) => weeks.find((w) => t >= w.start && t < w.end).key;
|
|
723
|
-
const nameOfCluster = (frameIdx, id) => {
|
|
724
|
-
for (let i = frameIdx; i >= 0; i--) {
|
|
725
|
-
const c = tracked.frames[i]?.clusters.find((x) => x.id === id);
|
|
726
|
-
if (c) return labelFor(c.members);
|
|
727
|
-
}
|
|
728
|
-
return id;
|
|
729
|
-
};
|
|
730
|
-
const changes = tracked.changes.filter((c) => c.t >= changeFloor).map((c) => {
|
|
731
|
-
const frameIdx = tracked.frames.findIndex((f) => f.t === c.t);
|
|
732
|
-
const ids = clusterChangeIds(c);
|
|
733
|
-
return { week: weekOf(c.t), kind: c.kind, ids, names: ids.map((id) => nameOfCluster(frameIdx, id)) };
|
|
734
|
-
});
|
|
735
|
-
const monthly = rollup(events, {
|
|
736
|
-
grain: "month",
|
|
737
|
-
by: "kind",
|
|
738
|
-
from: now - (opts.monthlyMonths ?? 12) * 30 * DAY2,
|
|
739
|
-
to: now
|
|
740
|
-
});
|
|
741
|
-
const months = monthly.buckets.map((b) => b.key);
|
|
742
|
-
const series = monthly.series.map((s) => ({ kind: s.key, months, values: s.values }));
|
|
743
|
-
return {
|
|
744
|
-
generatedAt,
|
|
745
|
-
window: { from: isoDay(weeks[0].start), to: isoDay(now), weeks: weeks.map((w) => w.key) },
|
|
746
|
-
totals: { events: events.length, nodes: timeline.nodes.length, edges: timeline.edges.length },
|
|
747
|
-
series,
|
|
748
|
-
leads,
|
|
749
|
-
clusters: { frames, changes }
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// src/temporal/narrate.ts
|
|
754
|
-
function shouldNarrate(report) {
|
|
755
|
-
return report.leads.length > 0 || report.clusters.changes.length > 0;
|
|
756
|
-
}
|
|
757
|
-
function buildNarrationMaterials(report, opts = {}) {
|
|
758
|
-
const lines = [];
|
|
759
|
-
const w = report.window;
|
|
760
|
-
lines.push(`Window: ${w.weeks[0] ?? w.from}..${w.weeks[w.weeks.length - 1] ?? w.to} (weekly) \xB7 generated ${report.generatedAt.slice(0, 10)}`);
|
|
761
|
-
lines.push(`Totals: ${report.totals.events} events \xB7 ${report.totals.nodes} nodes \xB7 ${report.totals.edges} edges`);
|
|
762
|
-
const leads = report.leads.slice(0, opts.topLeads ?? 5);
|
|
763
|
-
if (leads.length > 0) {
|
|
764
|
-
lines.push("", `HOTSPOT LEADS (top ${leads.length}):`);
|
|
765
|
-
for (const lead of leads) {
|
|
766
|
-
const where = lead.city ?? lead.sector;
|
|
767
|
-
const evidence = lead.contributors[0];
|
|
768
|
-
lines.push(
|
|
769
|
-
`- ${lead.name} (${lead.type}${where ? `, ${where}` : ""}) \u2014 score ${lead.score}, peak z ${lead.peak}, window ${lead.window[0]}..${lead.window[lead.window.length - 1]}` + (evidence ? `; evidence: "${evidence.title}" (${evidence.date})` : "")
|
|
770
|
-
);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
if (report.clusters.changes.length > 0) {
|
|
774
|
-
lines.push("", `CLUSTER CHANGES (last weeks):`);
|
|
775
|
-
for (const c of report.clusters.changes) {
|
|
776
|
-
const detail = c.kind === "merge" ? `${c.names.slice(1).join(", ")} into ${c.names[0]}` : c.kind === "split" ? `${c.names[0]} into ${c.names.slice(1).join(", ")}` : c.names[0];
|
|
777
|
-
lines.push(`- ${c.week} ${c.kind}: ${detail}`);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (report.series.length > 0) {
|
|
781
|
-
lines.push("", "MONTHLY DENSITY:");
|
|
782
|
-
for (const s of report.series) {
|
|
783
|
-
const span = s.months.length > 0 ? ` (${s.months[0]}..${s.months[s.months.length - 1]})` : "";
|
|
784
|
-
lines.push(`- ${s.kind}: ${s.values.join(" ")}${span}`);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
return lines.join("\n");
|
|
788
|
-
}
|
|
789
|
-
function splitTitleBody(text, fallbackTitle) {
|
|
790
|
-
const trimmed = text.trim();
|
|
791
|
-
const firstLine = trimmed.split("\n", 1)[0] ?? "";
|
|
792
|
-
if (firstLine.startsWith("# ")) {
|
|
793
|
-
return { title: firstLine.slice(2).trim() || fallbackTitle, body: trimmed.slice(firstLine.length).trim() };
|
|
794
|
-
}
|
|
795
|
-
return { title: fallbackTitle, body: trimmed };
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// src/temporal/steer.ts
|
|
799
|
-
function leadScores(report, type = "company") {
|
|
800
|
-
const out = {};
|
|
801
|
-
const prefix = `${type}:`;
|
|
802
|
-
for (const lead of report.leads) {
|
|
803
|
-
if (lead.key.startsWith(prefix)) out[lead.key.slice(prefix.length)] = lead.score;
|
|
804
|
-
}
|
|
805
|
-
return out;
|
|
806
|
-
}
|
|
807
|
-
function rankEnrichmentCandidates(rows, opts) {
|
|
808
|
-
const scores = opts.scores ?? {};
|
|
809
|
-
const isComplete = opts.isComplete ?? ((r) => Boolean(r.website && r.city && r.sector));
|
|
810
|
-
const seen = /* @__PURE__ */ new Set();
|
|
811
|
-
const eligible = [];
|
|
812
|
-
for (const row of rows) {
|
|
813
|
-
const slug = typeof row.slug === "string" ? row.slug : "";
|
|
814
|
-
if (!slug || seen.has(slug)) continue;
|
|
815
|
-
seen.add(slug);
|
|
816
|
-
const score = scores[slug] ?? 0;
|
|
817
|
-
if (opts.cooling?.has(slug)) continue;
|
|
818
|
-
if (score <= 0 && isComplete(row)) continue;
|
|
819
|
-
eligible.push({ slug, name: typeof row.name === "string" ? row.name : slug, score });
|
|
820
|
-
}
|
|
821
|
-
return eligible.sort((a, b) => b.score - a.score).slice(0, Math.max(0, opts.limit));
|
|
822
|
-
}
|
|
823
|
-
var BURST_ORIGIN = "burst";
|
|
824
|
-
var MAX_QUERY_CHARS = 120;
|
|
825
|
-
function synthesizeBurstSources(existing, report, opts = {}) {
|
|
826
|
-
const max = opts.max ?? 4;
|
|
827
|
-
const kept = existing.filter((s) => s.origin !== BURST_ORIGIN);
|
|
828
|
-
const queries = report.leads.filter((lead) => lead.type === "company").slice(0, max).map((lead) => {
|
|
829
|
-
const where = lead.city ?? lead.sector ?? "startup";
|
|
830
|
-
return `"${lead.name}" ${where} funding OR expansion OR hiring news`.slice(0, MAX_QUERY_CHARS);
|
|
831
|
-
});
|
|
832
|
-
if (queries.length === 0) return kept;
|
|
833
|
-
return [
|
|
834
|
-
...kept,
|
|
835
|
-
{
|
|
836
|
-
kind: "web-search",
|
|
837
|
-
name: "Burst sweep (auto)",
|
|
838
|
-
enabled: true,
|
|
839
|
-
cadenceHours: opts.cadenceHours ?? 24,
|
|
840
|
-
watch: "burst",
|
|
841
|
-
origin: BURST_ORIGIN,
|
|
842
|
-
queries
|
|
843
|
-
}
|
|
844
|
-
];
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// src/temporal/flows.ts
|
|
848
|
-
function flowFrames(events, opts) {
|
|
849
|
-
const moves = events.filter((e) => e.geo !== void 0 && e.geoTo !== void 0);
|
|
850
|
-
const from = opts.from ?? moves[0]?.t;
|
|
851
|
-
const to = opts.to ?? moves[moves.length - 1]?.t;
|
|
852
|
-
if (from === void 0 || to === void 0) return [];
|
|
853
|
-
const buckets = bucketRange(from, to, opts.grain);
|
|
854
|
-
const index = new Map(buckets.map((b, i) => [b.key, i]));
|
|
855
|
-
const perBucket = buckets.map(() => /* @__PURE__ */ new Map());
|
|
856
|
-
for (const e of moves) {
|
|
857
|
-
const i = index.get(bucketKey(e.t, opts.grain));
|
|
858
|
-
if (i === void 0) continue;
|
|
859
|
-
const cell = perBucket[i];
|
|
860
|
-
const key = `${e.geo}\u2192${e.geoTo}`;
|
|
861
|
-
const flow = cell.get(key);
|
|
862
|
-
if (flow) {
|
|
863
|
-
flow.weight += e.weight;
|
|
864
|
-
flow.count += 1;
|
|
865
|
-
} else {
|
|
866
|
-
cell.set(key, { from: e.geo, to: e.geoTo, weight: e.weight, count: 1 });
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
const sortFlows = (flows) => flows.sort((a, b) => b.weight - a.weight || (a.from < b.from ? -1 : a.from > b.from ? 1 : a.to < b.to ? -1 : 1));
|
|
870
|
-
if (!opts.cumulative) {
|
|
871
|
-
return buckets.map((bucket, i) => ({
|
|
872
|
-
bucket,
|
|
873
|
-
flows: sortFlows([...perBucket[i].values()])
|
|
874
|
-
}));
|
|
875
|
-
}
|
|
876
|
-
const acc = /* @__PURE__ */ new Map();
|
|
877
|
-
return buckets.map((bucket, i) => {
|
|
878
|
-
for (const [key, flow] of perBucket[i]) {
|
|
879
|
-
const running = acc.get(key);
|
|
880
|
-
if (running) {
|
|
881
|
-
running.weight += flow.weight;
|
|
882
|
-
running.count += flow.count;
|
|
883
|
-
} else {
|
|
884
|
-
acc.set(key, { ...flow });
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
return { bucket, flows: sortFlows([...acc.values()].map((f) => ({ ...f }))) };
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
function topFlows(frame, n) {
|
|
891
|
-
return frame.flows.slice(0, n);
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// src/temporal/spatial.ts
|
|
895
|
-
function matchRules(values, rules) {
|
|
896
|
-
if (!values) return [];
|
|
897
|
-
return rules.filter((r) => {
|
|
898
|
-
const v = values[r.metric];
|
|
899
|
-
if (v === void 0) return false;
|
|
900
|
-
return r.op === ">=" ? v >= r.threshold : v <= r.threshold;
|
|
901
|
-
}).map((r) => r.id);
|
|
902
|
-
}
|
|
903
|
-
function spatialFindings(set, metrics, rules, opts = {}) {
|
|
904
|
-
const { top = 20, requireMatch = false } = opts;
|
|
905
|
-
const best = /* @__PURE__ */ new Map();
|
|
906
|
-
for (const burst of detectBursts(set, opts)) {
|
|
907
|
-
if (!best.has(burst.seriesKey)) best.set(burst.seriesKey, burst);
|
|
908
|
-
}
|
|
909
|
-
const findings = [];
|
|
910
|
-
for (const [region, burst] of best) {
|
|
911
|
-
const values = metrics[region];
|
|
912
|
-
const matched = matchRules(values, rules);
|
|
913
|
-
if (requireMatch && matched.length === 0) continue;
|
|
914
|
-
const boost = rules.filter((r) => matched.includes(r.id)).reduce((a, r) => a + (r.weight ?? 1), 0);
|
|
915
|
-
const evidence = {};
|
|
916
|
-
for (const r of rules) {
|
|
917
|
-
const v = values?.[r.metric];
|
|
918
|
-
if (v !== void 0) evidence[r.metric] = v;
|
|
919
|
-
}
|
|
920
|
-
findings.push({ region, burst, matched, score: burst.score * (1 + boost), metrics: evidence });
|
|
921
|
-
}
|
|
922
|
-
return findings.sort((a, b) => b.score - a.score || (a.region < b.region ? -1 : 1)).slice(0, top);
|
|
923
|
-
}
|
|
924
|
-
|
|
925
248
|
// src/temporal/lenses.ts
|
|
926
249
|
function reifiedBridgeTargets(fragment, opts) {
|
|
927
250
|
const bridgesOf = /* @__PURE__ */ new Map();
|
|
@@ -1036,17 +359,6 @@ function blendPairs(sets) {
|
|
|
1036
359
|
}
|
|
1037
360
|
return [...acc.keys()].sort().map((key) => acc.get(key));
|
|
1038
361
|
}
|
|
1039
|
-
function lensSnapshots(arrivals, pairs, buckets) {
|
|
1040
|
-
const ids = [...arrivals.keys()].sort();
|
|
1041
|
-
return buckets.map((bucket) => {
|
|
1042
|
-
const t = bucket.end - 1;
|
|
1043
|
-
const nodes = ids.filter((id) => arrivals.get(id) <= t);
|
|
1044
|
-
const active = new Set(nodes);
|
|
1045
|
-
const edges = pairs.filter((p) => active.has(p.a) && active.has(p.b)).map((p) => ({ a: p.a, b: p.b, weight: p.w }));
|
|
1046
|
-
const activity = new Map(nodes.map((id) => [id, arrivals.get(id)]));
|
|
1047
|
-
return { t, nodes, edges, activity };
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
362
|
|
|
1051
363
|
// src/temporal/bridges.ts
|
|
1052
364
|
function bridgingScores(edges, labels) {
|