@odla-ai/kg 0.2.4 → 0.3.2

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.
@@ -0,0 +1,1086 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/temporal/index.ts
21
+ var temporal_exports = {};
22
+ __export(temporal_exports, {
23
+ blendPairs: () => blendPairs,
24
+ bridgingScores: () => bridgingScores,
25
+ bucketFor: () => bucketFor,
26
+ bucketKey: () => bucketKey,
27
+ bucketRange: () => bucketRange,
28
+ buildNarrationMaterials: () => buildNarrationMaterials,
29
+ buildTemporalReport: () => buildTemporalReport,
30
+ buildTimeline: () => buildTimeline,
31
+ burstScores: () => burstScores,
32
+ byNodeId: () => byNodeId,
33
+ composeTargets: () => composeTargets,
34
+ detectBursts: () => detectBursts,
35
+ directTargets: () => directTargets,
36
+ extractEvents: () => extractEvents,
37
+ flowFrames: () => flowFrames,
38
+ forceLayout: () => forceLayout,
39
+ geoPairs: () => geoPairs,
40
+ hash32: () => hash32,
41
+ indexGraph: () => indexGraph,
42
+ labelPropagation: () => labelPropagation,
43
+ layoutSequence: () => layoutSequence,
44
+ leadScores: () => leadScores,
45
+ lensSnapshots: () => lensSnapshots,
46
+ matchRules: () => matchRules,
47
+ mulberry32: () => mulberry32,
48
+ nodeId: () => nodeId,
49
+ parseAmount: () => parseAmount,
50
+ parseDateMs: () => parseDateMs,
51
+ projectBridges: () => projectBridges,
52
+ rankEnrichmentCandidates: () => rankEnrichmentCandidates,
53
+ rankHotspots: () => rankHotspots,
54
+ reifiedBridgeTargets: () => reifiedBridgeTargets,
55
+ resolvePath: () => resolvePath,
56
+ rollup: () => rollup,
57
+ seriesKeys: () => seriesKeys,
58
+ shouldNarrate: () => shouldNarrate,
59
+ shuffle: () => shuffle,
60
+ snapshotAt: () => snapshotAt,
61
+ snapshotSeries: () => snapshotSeries,
62
+ spatialFindings: () => spatialFindings,
63
+ splitTitleBody: () => splitTitleBody,
64
+ synthesizeBurstSources: () => synthesizeBurstSources,
65
+ timePairs: () => timePairs,
66
+ topBridges: () => topBridges,
67
+ topFlows: () => topFlows,
68
+ totals: () => totals,
69
+ trackClusters: () => trackClusters
70
+ });
71
+ module.exports = __toCommonJS(temporal_exports);
72
+
73
+ // src/temporal/prng.ts
74
+ function hash32(s) {
75
+ let h = 2166136261;
76
+ for (let i = 0; i < s.length; i++) {
77
+ h ^= s.charCodeAt(i);
78
+ h = Math.imul(h, 16777619);
79
+ }
80
+ return h >>> 0;
81
+ }
82
+ function mulberry32(seed) {
83
+ let a = seed >>> 0;
84
+ return () => {
85
+ a = a + 1831565813 >>> 0;
86
+ let t = a;
87
+ t = Math.imul(t ^ t >>> 15, t | 1);
88
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
89
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
90
+ };
91
+ }
92
+ function shuffle(items, rand) {
93
+ const out = items.slice();
94
+ for (let i = out.length - 1; i > 0; i--) {
95
+ const j = Math.floor(rand() * (i + 1));
96
+ const tmp = out[i];
97
+ out[i] = out[j];
98
+ out[j] = tmp;
99
+ }
100
+ return out;
101
+ }
102
+
103
+ // src/temporal/time.ts
104
+ var DAY = 864e5;
105
+ var DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
106
+ function parseDateMs(v) {
107
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
108
+ if (typeof v !== "string") return null;
109
+ const m = DATE_ONLY.exec(v.trim());
110
+ if (m) {
111
+ const t2 = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
112
+ return new Date(t2).toISOString().slice(0, 10) === m[0].trim() ? t2 : null;
113
+ }
114
+ const t = Date.parse(v);
115
+ return Number.isNaN(t) ? null : t;
116
+ }
117
+ function isoWeek(t) {
118
+ const d = new Date(t);
119
+ const midnight = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
120
+ const dow = new Date(midnight).getUTCDay() || 7;
121
+ const thursday = midnight + (4 - dow) * DAY;
122
+ const year = new Date(thursday).getUTCFullYear();
123
+ const week = Math.floor((thursday - Date.UTC(year, 0, 1)) / DAY / 7) + 1;
124
+ return { year, week };
125
+ }
126
+ function isoWeekStart(year, week) {
127
+ const jan4 = Date.UTC(year, 0, 4);
128
+ const dow = new Date(jan4).getUTCDay() || 7;
129
+ return jan4 - (dow - 1) * DAY + (week - 1) * 7 * DAY;
130
+ }
131
+ var pad2 = (n) => String(n).padStart(2, "0");
132
+ function bucketKey(t, grain) {
133
+ const d = new Date(t);
134
+ if (grain === "month") return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}`;
135
+ if (grain === "quarter") return `${d.getUTCFullYear()}-Q${Math.floor(d.getUTCMonth() / 3) + 1}`;
136
+ const { year, week } = isoWeek(t);
137
+ return `${year}-W${pad2(week)}`;
138
+ }
139
+ function bucketFor(key) {
140
+ let m = /^(\d{4})-(\d{2})$/.exec(key);
141
+ if (m) {
142
+ const [y, mo] = [Number(m[1]), Number(m[2])];
143
+ return { key, start: Date.UTC(y, mo - 1, 1), end: Date.UTC(y, mo, 1) };
144
+ }
145
+ m = /^(\d{4})-Q([1-4])$/.exec(key);
146
+ if (m) {
147
+ const [y, q] = [Number(m[1]), Number(m[2])];
148
+ return { key, start: Date.UTC(y, (q - 1) * 3, 1), end: Date.UTC(y, q * 3, 1) };
149
+ }
150
+ m = /^(\d{4})-W(\d{2})$/.exec(key);
151
+ if (m) {
152
+ const start = isoWeekStart(Number(m[1]), Number(m[2]));
153
+ return { key, start, end: start + 7 * DAY };
154
+ }
155
+ throw new Error(`not a bucket key: ${key}`);
156
+ }
157
+ function bucketRange(from, to, grain) {
158
+ const out = [];
159
+ if (from > to) return out;
160
+ let cur = bucketFor(bucketKey(from, grain));
161
+ const last = bucketKey(to, grain);
162
+ for (; ; ) {
163
+ out.push(cur);
164
+ if (cur.key === last) return out;
165
+ cur = bucketFor(bucketKey(cur.end, grain));
166
+ }
167
+ }
168
+
169
+ // src/temporal/adjacency.ts
170
+ function nodeId(ref) {
171
+ return `${ref.type}:${ref.key}`;
172
+ }
173
+ function byNodeId(a, b) {
174
+ const ia = nodeId(a);
175
+ const ib = nodeId(b);
176
+ return ia < ib ? -1 : ia > ib ? 1 : 0;
177
+ }
178
+ function indexGraph(fragment) {
179
+ const nodes = /* @__PURE__ */ new Map();
180
+ const byType = /* @__PURE__ */ new Map();
181
+ for (const n of fragment.nodes) {
182
+ nodes.set(nodeId(n), n);
183
+ const list = byType.get(n.type);
184
+ list ? list.push(n) : byType.set(n.type, [n]);
185
+ }
186
+ const adj = /* @__PURE__ */ new Map();
187
+ for (const e of fragment.edges) {
188
+ const k = `${nodeId(e.from)}|${e.label}`;
189
+ const list = adj.get(k);
190
+ list ? list.push(e.to) : adj.set(k, [e.to]);
191
+ }
192
+ return {
193
+ node: (id) => nodes.get(id),
194
+ out: (id, label) => adj.get(`${id}|${label}`) ?? [],
195
+ nodesOf: (type) => byType.get(type) ?? []
196
+ };
197
+ }
198
+ function resolvePath(g, start, labels) {
199
+ let frontier = [start];
200
+ for (const label of labels) {
201
+ const seen = /* @__PURE__ */ new Set();
202
+ const next = [];
203
+ for (const ref of frontier) {
204
+ for (const target of g.out(nodeId(ref), label)) {
205
+ const id = nodeId(target);
206
+ if (!seen.has(id)) {
207
+ seen.add(id);
208
+ next.push(target);
209
+ }
210
+ }
211
+ }
212
+ frontier = next;
213
+ }
214
+ return frontier.slice().sort(byNodeId);
215
+ }
216
+
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
+ // src/temporal/communities.ts
431
+ function labelPropagation(nodes, edges, opts = {}) {
432
+ const { seed = 1, maxRounds = 20 } = opts;
433
+ const sorted = [...nodes].sort();
434
+ const labels = new Map(sorted.map((id) => [id, id]));
435
+ const neighbors = /* @__PURE__ */ new Map();
436
+ for (const e of edges) {
437
+ if (!labels.has(e.a) || !labels.has(e.b) || e.a === e.b) continue;
438
+ const w = e.weight ?? 1;
439
+ let listA = neighbors.get(e.a);
440
+ if (!listA) neighbors.set(e.a, listA = []);
441
+ listA.push({ id: e.b, weight: w });
442
+ let listB = neighbors.get(e.b);
443
+ if (!listB) neighbors.set(e.b, listB = []);
444
+ listB.push({ id: e.a, weight: w });
445
+ }
446
+ const rand = mulberry32(seed);
447
+ for (let round = 0; round < maxRounds; round++) {
448
+ const order = shuffle(sorted, rand);
449
+ let changed = false;
450
+ for (const id of order) {
451
+ const incident = neighbors.get(id);
452
+ if (!incident || incident.length === 0) continue;
453
+ const votes = /* @__PURE__ */ new Map();
454
+ for (const n of incident) {
455
+ const label = labels.get(n.id);
456
+ votes.set(label, (votes.get(label) ?? 0) + n.weight);
457
+ }
458
+ let best = [];
459
+ let bestWeight = -Infinity;
460
+ for (const [label, weight] of votes) {
461
+ if (weight > bestWeight) {
462
+ bestWeight = weight;
463
+ best = [label];
464
+ } else if (weight === bestWeight) {
465
+ best.push(label);
466
+ }
467
+ }
468
+ const current = labels.get(id);
469
+ if (best.includes(current)) continue;
470
+ best.sort();
471
+ const pick = best.length === 1 ? best[0] : best[Math.floor(rand() * best.length)];
472
+ labels.set(id, pick);
473
+ changed = true;
474
+ }
475
+ if (!changed) break;
476
+ }
477
+ const members = /* @__PURE__ */ new Map();
478
+ for (const [id, label] of labels) {
479
+ const list = members.get(label);
480
+ list ? list.push(id) : members.set(label, [id]);
481
+ }
482
+ const canonical = /* @__PURE__ */ new Map();
483
+ for (const [label, ids] of members) canonical.set(label, ids[0]);
484
+ return new Map(sorted.map((id) => [id, canonical.get(labels.get(id))]));
485
+ }
486
+
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
+ // src/temporal/lenses.ts
926
+ function reifiedBridgeTargets(fragment, opts) {
927
+ const bridgesOf = /* @__PURE__ */ new Map();
928
+ const targetsOf = /* @__PURE__ */ new Map();
929
+ for (const e of fragment.edges) {
930
+ if (e.from.type !== opts.reified) continue;
931
+ const to = `${e.to.type}:${e.to.key}`;
932
+ if (e.label === opts.bridgeLabel) (bridgesOf.get(e.from.key) ?? bridgesOf.set(e.from.key, []).get(e.from.key)).push(to);
933
+ if (e.label === opts.targetLabel) (targetsOf.get(e.from.key) ?? targetsOf.set(e.from.key, []).get(e.from.key)).push(to);
934
+ }
935
+ const out = /* @__PURE__ */ new Map();
936
+ for (const [reifiedKey, bridges] of bridgesOf) {
937
+ for (const bridge of bridges) {
938
+ for (const target of targetsOf.get(reifiedKey) ?? []) {
939
+ (out.get(bridge) ?? out.set(bridge, /* @__PURE__ */ new Set()).get(bridge)).add(target);
940
+ }
941
+ }
942
+ }
943
+ return out;
944
+ }
945
+ function directTargets(fragment, label) {
946
+ const out = /* @__PURE__ */ new Map();
947
+ for (const e of fragment.edges) {
948
+ if (e.label !== label) continue;
949
+ const from = `${e.from.type}:${e.from.key}`;
950
+ (out.get(from) ?? out.set(from, /* @__PURE__ */ new Set()).get(from)).add(`${e.to.type}:${e.to.key}`);
951
+ }
952
+ return out;
953
+ }
954
+ function composeTargets(ab, bc) {
955
+ const out = /* @__PURE__ */ new Map();
956
+ for (const [a, bs] of ab) {
957
+ for (const b of bs) {
958
+ for (const c of bc.get(b) ?? []) {
959
+ (out.get(a) ?? out.set(a, /* @__PURE__ */ new Set()).get(a)).add(c);
960
+ }
961
+ }
962
+ }
963
+ return out;
964
+ }
965
+ function projectBridges(bridges, opts = {}) {
966
+ const { maxPerBridge = 12, viaCap = 4 } = opts;
967
+ const acc = /* @__PURE__ */ new Map();
968
+ for (const [bridge, targetSet] of [...bridges.entries()].sort((x, y) => x[0] < y[0] ? -1 : 1)) {
969
+ const targets = [...targetSet].sort();
970
+ if (targets.length < 2 || targets.length > maxPerBridge) continue;
971
+ for (let i = 0; i < targets.length; i++) {
972
+ for (let j = i + 1; j < targets.length; j++) {
973
+ const key = `${targets[i]}|${targets[j]}`;
974
+ const hit = acc.get(key) ?? { a: targets[i], b: targets[j], w: 0, via: [] };
975
+ hit.w += 1;
976
+ if (hit.via.length < viaCap) hit.via.push(bridge);
977
+ acc.set(key, hit);
978
+ }
979
+ }
980
+ }
981
+ return [...acc.keys()].sort().map((k) => acc.get(k));
982
+ }
983
+ function timePairs(arrivals, opts = {}) {
984
+ const { k = 4, halfLifeDays = 30 } = opts;
985
+ const halfLife = halfLifeDays * 864e5;
986
+ const rows = [...arrivals.entries()].filter(([, t]) => Number.isFinite(t)).sort((x, y) => x[1] - y[1] || (x[0] < y[0] ? -1 : 1));
987
+ const acc = /* @__PURE__ */ new Map();
988
+ for (let i = 0; i < rows.length; i++) {
989
+ for (let j = i + 1; j < Math.min(rows.length, i + 1 + k); j++) {
990
+ const [idA, tA] = rows[i];
991
+ const [idB, tB] = rows[j];
992
+ const w = Math.pow(0.5, Math.abs(tB - tA) / halfLife);
993
+ if (w < 0.01) break;
994
+ const [a, b] = idA < idB ? [idA, idB] : [idB, idA];
995
+ acc.set(`${a}|${b}`, { a, b, w, via: [] });
996
+ }
997
+ }
998
+ return [...acc.keys()].sort().map((key) => acc.get(key));
999
+ }
1000
+ function geoPairs(positions, opts = {}) {
1001
+ const { k = 4, halfDist = 0.04 } = opts;
1002
+ const rows = [...positions.entries()].sort((x, y) => x[0] < y[0] ? -1 : 1);
1003
+ const acc = /* @__PURE__ */ new Map();
1004
+ for (let i = 0; i < rows.length; i++) {
1005
+ const dists = [];
1006
+ for (let j = 0; j < rows.length; j++) {
1007
+ if (i === j) continue;
1008
+ const dx = rows[i][1].x - rows[j][1].x;
1009
+ const dy = rows[i][1].y - rows[j][1].y;
1010
+ dists.push({ j, d: Math.hypot(dx, dy) });
1011
+ }
1012
+ dists.sort((x, y) => x.d - y.d || x.j - y.j);
1013
+ for (const { j, d } of dists.slice(0, k)) {
1014
+ const w = Math.pow(0.5, d / halfDist);
1015
+ if (w < 0.01) continue;
1016
+ const [a, b] = rows[i][0] < rows[j][0] ? [rows[i][0], rows[j][0]] : [rows[j][0], rows[i][0]];
1017
+ const key = `${a}|${b}`;
1018
+ const prev = acc.get(key);
1019
+ if (!prev || prev.w < w) acc.set(key, { a, b, w, via: [] });
1020
+ }
1021
+ }
1022
+ return [...acc.keys()].sort().map((key) => acc.get(key));
1023
+ }
1024
+ function blendPairs(sets) {
1025
+ const acc = /* @__PURE__ */ new Map();
1026
+ for (const { pairs, weight } of sets) {
1027
+ if (weight <= 0 || pairs.length === 0) continue;
1028
+ const max = Math.max(...pairs.map((p) => p.w));
1029
+ for (const p of pairs) {
1030
+ const key = `${p.a}|${p.b}`;
1031
+ const hit = acc.get(key) ?? { a: p.a, b: p.b, w: 0, via: [] };
1032
+ hit.w += p.w / max * weight;
1033
+ for (const v of p.via) if (hit.via.length < 6 && !hit.via.includes(v)) hit.via.push(v);
1034
+ acc.set(key, hit);
1035
+ }
1036
+ }
1037
+ return [...acc.keys()].sort().map((key) => acc.get(key));
1038
+ }
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
+
1051
+ // src/temporal/bridges.ts
1052
+ function bridgingScores(edges, labels) {
1053
+ const cross = /* @__PURE__ */ new Map();
1054
+ const foreign = /* @__PURE__ */ new Map();
1055
+ const touch = (id, otherLabel, w) => {
1056
+ cross.set(id, (cross.get(id) ?? 0) + w);
1057
+ (foreign.get(id) ?? foreign.set(id, /* @__PURE__ */ new Set()).get(id)).add(otherLabel);
1058
+ };
1059
+ for (const e of edges) {
1060
+ const la = labels.get(e.a);
1061
+ const lb = labels.get(e.b);
1062
+ if (la === void 0 || lb === void 0 || la === lb) continue;
1063
+ const w = e.weight ?? 1;
1064
+ touch(e.a, lb, w);
1065
+ touch(e.b, la, w);
1066
+ }
1067
+ let max = 0;
1068
+ const raw = /* @__PURE__ */ new Map();
1069
+ for (const [id, w] of cross) {
1070
+ const r = w * foreign.get(id).size;
1071
+ raw.set(id, r);
1072
+ if (r > max) max = r;
1073
+ }
1074
+ const out = /* @__PURE__ */ new Map();
1075
+ for (const [id] of labels) {
1076
+ const r = raw.get(id) ?? 0;
1077
+ const own = labels.get(id);
1078
+ const groups = r > 0 ? [own, ...[...foreign.get(id)].sort()] : [own];
1079
+ out.set(id, { score: max > 0 ? r / max : 0, groups });
1080
+ }
1081
+ return out;
1082
+ }
1083
+ function topBridges(bridging, n = 5) {
1084
+ return [...bridging.entries()].filter(([, b]) => b.score > 0).sort((x, y) => y[1].score - x[1].score || (x[0] < y[0] ? -1 : 1)).slice(0, n).map(([id, b]) => ({ id, score: Math.round(b.score * 100) / 100, groups: b.groups }));
1085
+ }
1086
+ //# sourceMappingURL=index.cjs.map