@odla-ai/kg 0.3.0 → 0.4.0
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 +134 -617
- package/dist/temporal/index.cjs.map +1 -1
- package/dist/temporal/index.d.cts +23 -447
- package/dist/temporal/index.d.ts +23 -447
- package/dist/temporal/index.js +28 -780
- 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
|
@@ -20,41 +20,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/temporal/index.ts
|
|
21
21
|
var temporal_exports = {};
|
|
22
22
|
__export(temporal_exports, {
|
|
23
|
+
blendPairs: () => blendPairs,
|
|
24
|
+
bridgingScores: () => bridgingScores,
|
|
23
25
|
bucketFor: () => bucketFor,
|
|
24
26
|
bucketKey: () => bucketKey,
|
|
25
27
|
bucketRange: () => bucketRange,
|
|
26
|
-
buildNarrationMaterials: () => buildNarrationMaterials,
|
|
27
|
-
buildTemporalReport: () => buildTemporalReport,
|
|
28
|
-
buildTimeline: () => buildTimeline,
|
|
29
|
-
burstScores: () => burstScores,
|
|
30
28
|
byNodeId: () => byNodeId,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
forceLayout: () => forceLayout,
|
|
29
|
+
composeTargets: () => composeTargets,
|
|
30
|
+
directTargets: () => directTargets,
|
|
31
|
+
geoPairs: () => geoPairs,
|
|
35
32
|
hash32: () => hash32,
|
|
36
33
|
indexGraph: () => indexGraph,
|
|
37
34
|
labelPropagation: () => labelPropagation,
|
|
38
|
-
layoutSequence: () => layoutSequence,
|
|
39
|
-
leadScores: () => leadScores,
|
|
40
35
|
mulberry32: () => mulberry32,
|
|
41
36
|
nodeId: () => nodeId,
|
|
42
|
-
parseAmount: () => parseAmount,
|
|
43
37
|
parseDateMs: () => parseDateMs,
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
projectBridges: () => projectBridges,
|
|
39
|
+
reifiedBridgeTargets: () => reifiedBridgeTargets,
|
|
46
40
|
resolvePath: () => resolvePath,
|
|
47
|
-
rollup: () => rollup,
|
|
48
|
-
seriesKeys: () => seriesKeys,
|
|
49
|
-
shouldNarrate: () => shouldNarrate,
|
|
50
41
|
shuffle: () => shuffle,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
splitTitleBody: () => splitTitleBody,
|
|
54
|
-
synthesizeBurstSources: () => synthesizeBurstSources,
|
|
55
|
-
topFlows: () => topFlows,
|
|
56
|
-
totals: () => totals,
|
|
57
|
-
trackClusters: () => trackClusters
|
|
42
|
+
timePairs: () => timePairs,
|
|
43
|
+
topBridges: () => topBridges
|
|
58
44
|
});
|
|
59
45
|
module.exports = __toCommonJS(temporal_exports);
|
|
60
46
|
|
|
@@ -202,219 +188,6 @@ function resolvePath(g, start, labels) {
|
|
|
202
188
|
return frontier.slice().sort(byNodeId);
|
|
203
189
|
}
|
|
204
190
|
|
|
205
|
-
// src/temporal/events.ts
|
|
206
|
-
function parseAmount(v) {
|
|
207
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
|
|
208
|
-
if (typeof v !== "string") return void 0;
|
|
209
|
-
const m = /^\$?\s*([\d,]+(?:\.\d+)?)\s*([kKmMbB])?$/.exec(v.trim());
|
|
210
|
-
if (!m) return void 0;
|
|
211
|
-
const n = Number(m[1].replace(/,/g, ""));
|
|
212
|
-
if (!Number.isFinite(n)) return void 0;
|
|
213
|
-
const suffix = (m[2] ?? "").toLowerCase();
|
|
214
|
-
const mult = suffix === "k" ? 1e3 : suffix === "m" ? 1e6 : suffix === "b" ? 1e9 : 1;
|
|
215
|
-
return n * mult;
|
|
216
|
-
}
|
|
217
|
-
function resolveGeo(g, start, spec) {
|
|
218
|
-
const landing = spec.path?.length ? resolvePath(g, start, spec.path)[0] : start;
|
|
219
|
-
if (!landing) return void 0;
|
|
220
|
-
if (spec.attr) {
|
|
221
|
-
const v = g.node(nodeId(landing))?.attrs[spec.attr];
|
|
222
|
-
return typeof v === "string" && v !== "" ? v : void 0;
|
|
223
|
-
}
|
|
224
|
-
return landing.key;
|
|
225
|
-
}
|
|
226
|
-
function extractEvents(fragment, config) {
|
|
227
|
-
const g = indexGraph(fragment);
|
|
228
|
-
const out = [];
|
|
229
|
-
for (const spec of config.events) {
|
|
230
|
-
for (const n of g.nodesOf(spec.type)) {
|
|
231
|
-
const t = parseDateMs(n.attrs[spec.dateAttr]);
|
|
232
|
-
if (t === null) continue;
|
|
233
|
-
const self = { type: n.type, key: n.key };
|
|
234
|
-
const seen = /* @__PURE__ */ new Set();
|
|
235
|
-
const refs = [];
|
|
236
|
-
for (const path of spec.attach ?? []) {
|
|
237
|
-
for (const ref of resolvePath(g, self, path)) {
|
|
238
|
-
const id = nodeId(ref);
|
|
239
|
-
if (!seen.has(id)) {
|
|
240
|
-
seen.add(id);
|
|
241
|
-
refs.push(ref);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
refs.sort(byNodeId);
|
|
246
|
-
const weight = spec.weightOf?.(n.attrs) ?? (spec.weightAttr ? parseAmount(n.attrs[spec.weightAttr]) : void 0) ?? 1;
|
|
247
|
-
out.push({
|
|
248
|
-
t,
|
|
249
|
-
kind: spec.type,
|
|
250
|
-
node: self,
|
|
251
|
-
refs,
|
|
252
|
-
weight,
|
|
253
|
-
geo: spec.geo ? resolveGeo(g, self, spec.geo) : void 0,
|
|
254
|
-
geoTo: spec.geoTo ? resolveGeo(g, self, spec.geoTo) : void 0
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return out.sort((a, b) => a.t - b.t || byNodeId(a.node, b.node));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// src/temporal/series.ts
|
|
262
|
-
function seriesKeys(e, by) {
|
|
263
|
-
if (by === "kind") return [e.kind];
|
|
264
|
-
if (by === "geo") return e.geo === void 0 ? [] : [e.geo];
|
|
265
|
-
if (by === "ref") return e.refs.map(nodeId);
|
|
266
|
-
const k = by(e);
|
|
267
|
-
return k === void 0 ? [] : typeof k === "string" ? [k] : k;
|
|
268
|
-
}
|
|
269
|
-
function rollup(events, opts) {
|
|
270
|
-
const { grain, by = "kind", measure = "count" } = opts;
|
|
271
|
-
const from = opts.from ?? events[0]?.t;
|
|
272
|
-
const to = opts.to ?? events[events.length - 1]?.t;
|
|
273
|
-
if (from === void 0 || to === void 0) return { grain, buckets: [], series: [] };
|
|
274
|
-
const buckets = bucketRange(from, to, grain);
|
|
275
|
-
const index = new Map(buckets.map((b, i) => [b.key, i]));
|
|
276
|
-
const rows = /* @__PURE__ */ new Map();
|
|
277
|
-
for (const e of events) {
|
|
278
|
-
const i = index.get(bucketKey(e.t, grain));
|
|
279
|
-
if (i === void 0) continue;
|
|
280
|
-
for (const key of seriesKeys(e, by)) {
|
|
281
|
-
let values = rows.get(key);
|
|
282
|
-
if (!values) {
|
|
283
|
-
values = new Array(buckets.length).fill(0);
|
|
284
|
-
rows.set(key, values);
|
|
285
|
-
}
|
|
286
|
-
values[i] = values[i] + (measure === "count" ? 1 : e.weight);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
const series = [...rows.entries()].map(([key, values]) => ({ key, values })).sort((a, b) => a.key < b.key ? -1 : 1);
|
|
290
|
-
return { grain, buckets, series };
|
|
291
|
-
}
|
|
292
|
-
function totals(set) {
|
|
293
|
-
const values = new Array(set.buckets.length).fill(0);
|
|
294
|
-
for (const s of set.series) {
|
|
295
|
-
s.values.forEach((v, i) => {
|
|
296
|
-
values[i] = values[i] + v;
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
return { key: "total", values };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// src/temporal/hotspots.ts
|
|
303
|
-
function burstScores(values, opts = {}) {
|
|
304
|
-
const { window = 6, minBaseline = 3, minSigma = 1 } = opts;
|
|
305
|
-
return values.map((x, i) => {
|
|
306
|
-
const from = Math.max(0, i - window);
|
|
307
|
-
const trailing = values.slice(from, i);
|
|
308
|
-
if (trailing.length < minBaseline) return 0;
|
|
309
|
-
const mean = trailing.reduce((a, b) => a + b, 0) / trailing.length;
|
|
310
|
-
const variance = trailing.reduce((a, b) => a + (b - mean) * (b - mean), 0) / trailing.length;
|
|
311
|
-
const sigma = Math.max(Math.sqrt(variance), Math.sqrt(mean), minSigma);
|
|
312
|
-
return (x - mean) / sigma;
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
function detectBursts(set, opts = {}) {
|
|
316
|
-
const { zEnter = 2, zExit = 1 } = opts;
|
|
317
|
-
const out = [];
|
|
318
|
-
for (const series of set.series) {
|
|
319
|
-
const zs = burstScores(series.values, opts);
|
|
320
|
-
let run = null;
|
|
321
|
-
zs.forEach((z, i) => {
|
|
322
|
-
if (run) {
|
|
323
|
-
if (z >= zExit) {
|
|
324
|
-
run.end = i;
|
|
325
|
-
run.peak = Math.max(run.peak, z);
|
|
326
|
-
run.score += Math.max(0, z);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
out.push(run);
|
|
330
|
-
run = null;
|
|
331
|
-
}
|
|
332
|
-
if (z >= zEnter) {
|
|
333
|
-
run = { seriesKey: series.key, start: i, end: i, peak: z, score: z };
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
if (run) out.push(run);
|
|
337
|
-
}
|
|
338
|
-
return out.sort(
|
|
339
|
-
(a, b) => b.score - a.score || a.start - b.start || (a.seriesKey < b.seriesKey ? -1 : 1)
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
function rankHotspots(set, events, opts = {}) {
|
|
343
|
-
const { by = "kind", top = 20, contributorsPer = 5 } = opts;
|
|
344
|
-
const index = new Map(set.buckets.map((b, i) => [b.key, i]));
|
|
345
|
-
return detectBursts(set, opts).slice(0, top).map((burst) => {
|
|
346
|
-
const inRun = events.filter((e) => {
|
|
347
|
-
const i = index.get(bucketKey(e.t, set.grain));
|
|
348
|
-
return i !== void 0 && i >= burst.start && i <= burst.end && seriesKeys(e, by).includes(burst.seriesKey);
|
|
349
|
-
});
|
|
350
|
-
inRun.sort((a, b) => b.weight - a.weight || a.t - b.t);
|
|
351
|
-
return {
|
|
352
|
-
...burst,
|
|
353
|
-
bucketKeys: set.buckets.slice(burst.start, burst.end + 1).map((b) => b.key),
|
|
354
|
-
contributors: inRun.slice(0, contributorsPer)
|
|
355
|
-
};
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// src/temporal/snapshot.ts
|
|
360
|
-
var PAIR_CAP = 12;
|
|
361
|
-
function buildTimeline(events, opts = {}) {
|
|
362
|
-
const mode = opts.mode ?? "entities";
|
|
363
|
-
const firstSeen = /* @__PURE__ */ new Map();
|
|
364
|
-
const edges = [];
|
|
365
|
-
const see = (id, t) => {
|
|
366
|
-
if (!firstSeen.has(id)) firstSeen.set(id, t);
|
|
367
|
-
};
|
|
368
|
-
for (const e of events) {
|
|
369
|
-
const refIds = e.refs.map(nodeId);
|
|
370
|
-
if (mode === "full") {
|
|
371
|
-
const self = nodeId(e.node);
|
|
372
|
-
see(self, e.t);
|
|
373
|
-
for (const ref of refIds) {
|
|
374
|
-
see(ref, e.t);
|
|
375
|
-
const [a, b] = self < ref ? [self, ref] : [ref, self];
|
|
376
|
-
edges.push({ a, b, t: e.t, kind: e.kind, weight: e.weight });
|
|
377
|
-
}
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
for (const ref of refIds) see(ref, e.t);
|
|
381
|
-
if (refIds.length > PAIR_CAP) continue;
|
|
382
|
-
for (let i = 0; i < refIds.length; i++) {
|
|
383
|
-
for (let j = i + 1; j < refIds.length; j++) {
|
|
384
|
-
const [a, b] = refIds[i] < refIds[j] ? [refIds[i], refIds[j]] : [refIds[j], refIds[i]];
|
|
385
|
-
edges.push({ a, b, t: e.t, kind: e.kind, weight: e.weight });
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
edges.sort((x, y) => x.t - y.t || (x.a < y.a ? -1 : x.a > y.a ? 1 : x.b < y.b ? -1 : 1));
|
|
390
|
-
return {
|
|
391
|
-
events: [...events],
|
|
392
|
-
nodes: [...firstSeen.keys()].sort(),
|
|
393
|
-
edges,
|
|
394
|
-
firstSeen
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
function snapshotAt(timeline, t, opts = {}) {
|
|
398
|
-
const since = opts.window === void 0 ? -Infinity : t - opts.window;
|
|
399
|
-
const agg = /* @__PURE__ */ new Map();
|
|
400
|
-
const activity = /* @__PURE__ */ new Map();
|
|
401
|
-
for (const e of timeline.edges) {
|
|
402
|
-
if (e.t > t) break;
|
|
403
|
-
activity.set(e.a, e.t);
|
|
404
|
-
activity.set(e.b, e.t);
|
|
405
|
-
if (e.t <= since) continue;
|
|
406
|
-
const key = `${e.a}|${e.b}`;
|
|
407
|
-
const cell = agg.get(key);
|
|
408
|
-
if (cell) cell.weight += 1;
|
|
409
|
-
else agg.set(key, { a: e.a, b: e.b, weight: 1 });
|
|
410
|
-
}
|
|
411
|
-
const nodes = timeline.nodes.filter((id) => timeline.firstSeen.get(id) <= t);
|
|
412
|
-
return { t, nodes, edges: [...agg.values()], activity };
|
|
413
|
-
}
|
|
414
|
-
function snapshotSeries(timeline, buckets, opts = {}) {
|
|
415
|
-
return buckets.map((b) => snapshotAt(timeline, b.end - 1, opts));
|
|
416
|
-
}
|
|
417
|
-
|
|
418
191
|
// src/temporal/communities.ts
|
|
419
192
|
function labelPropagation(nodes, edges, opts = {}) {
|
|
420
193
|
const { seed = 1, maxRounds = 20 } = opts;
|
|
@@ -472,410 +245,154 @@ function labelPropagation(nodes, edges, opts = {}) {
|
|
|
472
245
|
return new Map(sorted.map((id) => [id, canonical.get(labels.get(id))]));
|
|
473
246
|
}
|
|
474
247
|
|
|
475
|
-
// src/temporal/
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
for (const
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
list ? list.push(id) : byLabel.set(label, [id]);
|
|
491
|
-
}
|
|
492
|
-
const current = [...byLabel.values()].map((members) => members.sort());
|
|
493
|
-
current.sort((a, b) => a[0] < b[0] ? -1 : 1);
|
|
494
|
-
const prevSets = prev.map((c) => ({ c, set: new Set(c.members) }));
|
|
495
|
-
const matches = [];
|
|
496
|
-
current.forEach((members, ci) => {
|
|
497
|
-
prevSets.forEach(({ set }, pi) => {
|
|
498
|
-
const j = jaccard(set, members);
|
|
499
|
-
if (j >= minJaccard) {
|
|
500
|
-
let shared = 0;
|
|
501
|
-
for (const id of members) if (set.has(id)) shared++;
|
|
502
|
-
matches.push({ ci, pi, j, shared });
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
matches.sort((a, b) => b.j - a.j || b.shared - a.shared || a.ci - b.ci);
|
|
507
|
-
const idOf = /* @__PURE__ */ new Map();
|
|
508
|
-
const claimedPrev = /* @__PURE__ */ new Map();
|
|
509
|
-
const matchedPrevBy = /* @__PURE__ */ new Map();
|
|
510
|
-
for (const m of matches) {
|
|
511
|
-
const list = matchedPrevBy.get(m.ci);
|
|
512
|
-
list ? list.push(m.pi) : matchedPrevBy.set(m.ci, [m.pi]);
|
|
513
|
-
if (!idOf.has(m.ci) && !claimedPrev.has(m.pi)) {
|
|
514
|
-
idOf.set(m.ci, prev[m.pi].id);
|
|
515
|
-
claimedPrev.set(m.pi, m.ci);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
const usedIds = /* @__PURE__ */ new Set([...idOf.values(), ...prev.map((c) => c.id)]);
|
|
519
|
-
const clusters = current.map((members, ci) => {
|
|
520
|
-
let id = idOf.get(ci);
|
|
521
|
-
if (!id) {
|
|
522
|
-
id = `c:${members[0]}`;
|
|
523
|
-
while (usedIds.has(id)) id += "'";
|
|
524
|
-
usedIds.add(id);
|
|
525
|
-
if (outFrames.length > 0) changes.push({ kind: "birth", t: frame.t, id });
|
|
526
|
-
}
|
|
527
|
-
return { id, members };
|
|
528
|
-
});
|
|
529
|
-
prevSets.forEach(({ c }, pi) => {
|
|
530
|
-
const winners = matches.filter((m) => m.pi === pi).map((m) => m.ci);
|
|
531
|
-
if (winners.length >= 2) {
|
|
532
|
-
changes.push({ kind: "split", t: frame.t, from: c.id, into: winners.map((ci) => clusters[ci].id) });
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
for (const [ci, pis] of matchedPrevBy) {
|
|
536
|
-
if (pis.length >= 2) {
|
|
537
|
-
changes.push({ kind: "merge", t: frame.t, into: clusters[ci].id, from: pis.map((pi) => prev[pi].id) });
|
|
248
|
+
// src/temporal/lenses.ts
|
|
249
|
+
function reifiedBridgeTargets(fragment, opts) {
|
|
250
|
+
const bridgesOf = /* @__PURE__ */ new Map();
|
|
251
|
+
const targetsOf = /* @__PURE__ */ new Map();
|
|
252
|
+
for (const e of fragment.edges) {
|
|
253
|
+
if (e.from.type !== opts.reified) continue;
|
|
254
|
+
const to = `${e.to.type}:${e.to.key}`;
|
|
255
|
+
if (e.label === opts.bridgeLabel) (bridgesOf.get(e.from.key) ?? bridgesOf.set(e.from.key, []).get(e.from.key)).push(to);
|
|
256
|
+
if (e.label === opts.targetLabel) (targetsOf.get(e.from.key) ?? targetsOf.set(e.from.key, []).get(e.from.key)).push(to);
|
|
257
|
+
}
|
|
258
|
+
const out = /* @__PURE__ */ new Map();
|
|
259
|
+
for (const [reifiedKey, bridges] of bridgesOf) {
|
|
260
|
+
for (const bridge of bridges) {
|
|
261
|
+
for (const target of targetsOf.get(reifiedKey) ?? []) {
|
|
262
|
+
(out.get(bridge) ?? out.set(bridge, /* @__PURE__ */ new Set()).get(bridge)).add(target);
|
|
538
263
|
}
|
|
539
264
|
}
|
|
540
|
-
const carried = new Set(clusters.map((c) => c.id));
|
|
541
|
-
for (const c of prev) if (!carried.has(c.id)) changes.push({ kind: "death", t: frame.t, id: c.id });
|
|
542
|
-
clusters.sort((a, b) => a.id < b.id ? -1 : 1);
|
|
543
|
-
outFrames.push({ t: frame.t, clusters });
|
|
544
|
-
prev = clusters;
|
|
545
265
|
}
|
|
546
|
-
return
|
|
266
|
+
return out;
|
|
547
267
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return { x: 0.5 + radius * Math.cos(theta), y: 0.5 + radius * Math.sin(theta) };
|
|
555
|
-
};
|
|
556
|
-
function forceLayout(nodes, edges, opts = {}) {
|
|
557
|
-
const { iterations = 300, seed = 1, repulsion = 0.1, gravity = 0.5, positions } = opts;
|
|
558
|
-
const ids = [...nodes].sort();
|
|
559
|
-
const n = ids.length;
|
|
560
|
-
if (n === 0) return /* @__PURE__ */ new Map();
|
|
561
|
-
const k = Math.sqrt(1 / n);
|
|
562
|
-
const xs = new Float64Array(n);
|
|
563
|
-
const ys = new Float64Array(n);
|
|
564
|
-
const index = /* @__PURE__ */ new Map();
|
|
565
|
-
ids.forEach((id, i) => {
|
|
566
|
-
index.set(id, i);
|
|
567
|
-
const p = positions?.get(id) ?? initialFor(id, seed);
|
|
568
|
-
xs[i] = p.x;
|
|
569
|
-
ys[i] = p.y;
|
|
570
|
-
});
|
|
571
|
-
const links = [];
|
|
572
|
-
for (const e of edges) {
|
|
573
|
-
const a = index.get(e.a);
|
|
574
|
-
const b = index.get(e.b);
|
|
575
|
-
if (a === void 0 || b === void 0 || a === b) continue;
|
|
576
|
-
links.push(a < b ? [a, b, Math.min(e.weight ?? 1, 4)] : [b, a, Math.min(e.weight ?? 1, 4)]);
|
|
268
|
+
function directTargets(fragment, label) {
|
|
269
|
+
const out = /* @__PURE__ */ new Map();
|
|
270
|
+
for (const e of fragment.edges) {
|
|
271
|
+
if (e.label !== label) continue;
|
|
272
|
+
const from = `${e.from.type}:${e.from.key}`;
|
|
273
|
+
(out.get(from) ?? out.set(from, /* @__PURE__ */ new Set()).get(from)).add(`${e.to.type}:${e.to.key}`);
|
|
577
274
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
let vx = xs[i] - xs[j];
|
|
587
|
-
let vy = ys[i] - ys[j];
|
|
588
|
-
let d = Math.hypot(vx, vy);
|
|
589
|
-
if (d < 1e-6) {
|
|
590
|
-
const r = mulberry32(hash32(`${ids[i]}|${ids[j]}`))();
|
|
591
|
-
vx = 1e-4 * Math.cos(r * 7);
|
|
592
|
-
vy = 1e-4 * Math.sin(r * 7);
|
|
593
|
-
d = 1e-4;
|
|
594
|
-
}
|
|
595
|
-
const force = repulsion * k * k / d;
|
|
596
|
-
dx[i] = dx[i] + vx / d * force;
|
|
597
|
-
dy[i] = dy[i] + vy / d * force;
|
|
598
|
-
dx[j] = dx[j] - vx / d * force;
|
|
599
|
-
dy[j] = dy[j] - vy / d * force;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
for (const [a, b, w] of links) {
|
|
603
|
-
const vx = xs[a] - xs[b];
|
|
604
|
-
const vy = ys[a] - ys[b];
|
|
605
|
-
const d = Math.max(Math.hypot(vx, vy), 1e-6);
|
|
606
|
-
const force = d * d / k * w;
|
|
607
|
-
dx[a] = dx[a] - vx / d * force;
|
|
608
|
-
dy[a] = dy[a] - vy / d * force;
|
|
609
|
-
dx[b] = dx[b] + vx / d * force;
|
|
610
|
-
dy[b] = dy[b] + vy / d * force;
|
|
611
|
-
}
|
|
612
|
-
const cap = 0.1 * (1 - iter / iterations) + 2e-3;
|
|
613
|
-
for (let i = 0; i < n; i++) {
|
|
614
|
-
const gx = (0.5 - xs[i]) * gravity;
|
|
615
|
-
const gy = (0.5 - ys[i]) * gravity;
|
|
616
|
-
let mx = dx[i] + gx;
|
|
617
|
-
let my = dy[i] + gy;
|
|
618
|
-
const move = Math.hypot(mx, my);
|
|
619
|
-
if (move > cap) {
|
|
620
|
-
mx = mx / move * cap;
|
|
621
|
-
my = my / move * cap;
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
function composeTargets(ab, bc) {
|
|
278
|
+
const out = /* @__PURE__ */ new Map();
|
|
279
|
+
for (const [a, bs] of ab) {
|
|
280
|
+
for (const b of bs) {
|
|
281
|
+
for (const c of bc.get(b) ?? []) {
|
|
282
|
+
(out.get(a) ?? out.set(a, /* @__PURE__ */ new Set()).get(a)).add(c);
|
|
622
283
|
}
|
|
623
|
-
xs[i] = Math.min(1, Math.max(0, xs[i] + mx));
|
|
624
|
-
ys[i] = Math.min(1, Math.max(0, ys[i] + my));
|
|
625
284
|
}
|
|
626
285
|
}
|
|
627
|
-
return new Map(ids.map((id, i) => [id, { x: xs[i], y: ys[i] }]));
|
|
628
|
-
}
|
|
629
|
-
function layoutSequence(snapshots, opts = {}) {
|
|
630
|
-
const { warmIterations = 60, ...base } = opts;
|
|
631
|
-
const out = [];
|
|
632
|
-
let prev = base.positions ? new Map(base.positions) : void 0;
|
|
633
|
-
for (const snap of snapshots) {
|
|
634
|
-
const frame = forceLayout(snap.nodes, snap.edges, {
|
|
635
|
-
...base,
|
|
636
|
-
iterations: prev ? warmIterations : base.iterations,
|
|
637
|
-
positions: prev
|
|
638
|
-
});
|
|
639
|
-
out.push(frame);
|
|
640
|
-
prev = frame;
|
|
641
|
-
}
|
|
642
286
|
return out;
|
|
643
287
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if (events.length === 0) {
|
|
659
|
-
return {
|
|
660
|
-
generatedAt,
|
|
661
|
-
window: { from: "", to: "", weeks: [] },
|
|
662
|
-
totals: { events: 0, nodes: 0, edges: 0 },
|
|
663
|
-
series: [],
|
|
664
|
-
leads: [],
|
|
665
|
-
clusters: { frames: [], changes: [] }
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
const attrsOf = new Map(fragment.nodes.map((n) => [nodeId(n), n.attrs]));
|
|
669
|
-
const str = (id, attr) => {
|
|
670
|
-
const v = attrsOf.get(id)?.[attr];
|
|
671
|
-
return typeof v === "string" && v !== "" ? v : void 0;
|
|
672
|
-
};
|
|
673
|
-
const windowDays = opts.windowDays ?? 155;
|
|
674
|
-
const from = Math.max(events[0].t, now - windowDays * DAY2);
|
|
675
|
-
const weeks = bucketRange(from, now, "week");
|
|
676
|
-
const weekly = rollup(events, { grain: "week", by: "ref", from, to: now });
|
|
677
|
-
const leads = rankHotspots(weekly, events, {
|
|
678
|
-
by: "ref",
|
|
679
|
-
top: opts.topLeads ?? 8,
|
|
680
|
-
contributorsPer: opts.contributorsPer ?? 3,
|
|
681
|
-
...opts.burst ?? { window: 6 }
|
|
682
|
-
}).map((h) => ({
|
|
683
|
-
key: h.seriesKey,
|
|
684
|
-
type: h.seriesKey.slice(0, Math.max(0, h.seriesKey.indexOf(":"))),
|
|
685
|
-
// ref keys are always "type:key"
|
|
686
|
-
name: str(h.seriesKey, "name") ?? h.seriesKey,
|
|
687
|
-
...str(h.seriesKey, "city") ? { city: str(h.seriesKey, "city") } : {},
|
|
688
|
-
...str(h.seriesKey, "sector") ? { sector: str(h.seriesKey, "sector") } : {},
|
|
689
|
-
score: round2(h.score),
|
|
690
|
-
peak: round2(h.peak),
|
|
691
|
-
window: h.bucketKeys,
|
|
692
|
-
contributors: h.contributors.map((e) => ({
|
|
693
|
-
kind: e.kind,
|
|
694
|
-
key: e.node.key,
|
|
695
|
-
title: str(nodeId(e.node), "title") ?? str(nodeId(e.node), "round") ?? str(nodeId(e.node), "name") ?? e.node.key,
|
|
696
|
-
date: isoDay(e.t)
|
|
697
|
-
}))
|
|
698
|
-
}));
|
|
699
|
-
const timeline = buildTimeline(events);
|
|
700
|
-
const snapshots = snapshotSeries(timeline, weeks);
|
|
701
|
-
const labelFrames = snapshots.map((s) => ({ t: s.t, labels: labelPropagation(s.nodes, s.edges, { seed: 1 }) }));
|
|
702
|
-
const tracked = trackClusters(labelFrames);
|
|
703
|
-
const labelFor = (members) => `${str(members[0], "name") ?? members[0]}${members.length > 1 ? ` +${members.length - 1}` : ""}`;
|
|
704
|
-
const frames = tracked.frames.map((f, i) => ({
|
|
705
|
-
week: weeks[i].key,
|
|
706
|
-
count: f.clusters.length,
|
|
707
|
-
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) }))
|
|
708
|
-
}));
|
|
709
|
-
const changeFloor = weeks[Math.max(0, weeks.length - (opts.changeWeeks ?? 4))].start;
|
|
710
|
-
const weekOf = (t) => weeks.find((w) => t >= w.start && t < w.end).key;
|
|
711
|
-
const nameOfCluster = (frameIdx, id) => {
|
|
712
|
-
for (let i = frameIdx; i >= 0; i--) {
|
|
713
|
-
const c = tracked.frames[i]?.clusters.find((x) => x.id === id);
|
|
714
|
-
if (c) return labelFor(c.members);
|
|
288
|
+
function projectBridges(bridges, opts = {}) {
|
|
289
|
+
const { maxPerBridge = 12, viaCap = 4 } = opts;
|
|
290
|
+
const acc = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const [bridge, targetSet] of [...bridges.entries()].sort((x, y) => x[0] < y[0] ? -1 : 1)) {
|
|
292
|
+
const targets = [...targetSet].sort();
|
|
293
|
+
if (targets.length < 2 || targets.length > maxPerBridge) continue;
|
|
294
|
+
for (let i = 0; i < targets.length; i++) {
|
|
295
|
+
for (let j = i + 1; j < targets.length; j++) {
|
|
296
|
+
const key = `${targets[i]}|${targets[j]}`;
|
|
297
|
+
const hit = acc.get(key) ?? { a: targets[i], b: targets[j], w: 0, via: [] };
|
|
298
|
+
hit.w += 1;
|
|
299
|
+
if (hit.via.length < viaCap) hit.via.push(bridge);
|
|
300
|
+
acc.set(key, hit);
|
|
301
|
+
}
|
|
715
302
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const changes = tracked.changes.filter((c) => c.t >= changeFloor).map((c) => {
|
|
719
|
-
const frameIdx = tracked.frames.findIndex((f) => f.t === c.t);
|
|
720
|
-
const ids = clusterChangeIds(c);
|
|
721
|
-
return { week: weekOf(c.t), kind: c.kind, ids, names: ids.map((id) => nameOfCluster(frameIdx, id)) };
|
|
722
|
-
});
|
|
723
|
-
const monthly = rollup(events, {
|
|
724
|
-
grain: "month",
|
|
725
|
-
by: "kind",
|
|
726
|
-
from: now - (opts.monthlyMonths ?? 12) * 30 * DAY2,
|
|
727
|
-
to: now
|
|
728
|
-
});
|
|
729
|
-
const months = monthly.buckets.map((b) => b.key);
|
|
730
|
-
const series = monthly.series.map((s) => ({ kind: s.key, months, values: s.values }));
|
|
731
|
-
return {
|
|
732
|
-
generatedAt,
|
|
733
|
-
window: { from: isoDay(weeks[0].start), to: isoDay(now), weeks: weeks.map((w) => w.key) },
|
|
734
|
-
totals: { events: events.length, nodes: timeline.nodes.length, edges: timeline.edges.length },
|
|
735
|
-
series,
|
|
736
|
-
leads,
|
|
737
|
-
clusters: { frames, changes }
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// src/temporal/narrate.ts
|
|
742
|
-
function shouldNarrate(report) {
|
|
743
|
-
return report.leads.length > 0 || report.clusters.changes.length > 0;
|
|
303
|
+
}
|
|
304
|
+
return [...acc.keys()].sort().map((k) => acc.get(k));
|
|
744
305
|
}
|
|
745
|
-
function
|
|
746
|
-
const
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
);
|
|
306
|
+
function timePairs(arrivals, opts = {}) {
|
|
307
|
+
const { k = 4, halfLifeDays = 30 } = opts;
|
|
308
|
+
const halfLife = halfLifeDays * 864e5;
|
|
309
|
+
const rows = [...arrivals.entries()].filter(([, t]) => Number.isFinite(t)).sort((x, y) => x[1] - y[1] || (x[0] < y[0] ? -1 : 1));
|
|
310
|
+
const acc = /* @__PURE__ */ new Map();
|
|
311
|
+
for (let i = 0; i < rows.length; i++) {
|
|
312
|
+
for (let j = i + 1; j < Math.min(rows.length, i + 1 + k); j++) {
|
|
313
|
+
const [idA, tA] = rows[i];
|
|
314
|
+
const [idB, tB] = rows[j];
|
|
315
|
+
const w = Math.pow(0.5, Math.abs(tB - tA) / halfLife);
|
|
316
|
+
if (w < 0.01) break;
|
|
317
|
+
const [a, b] = idA < idB ? [idA, idB] : [idB, idA];
|
|
318
|
+
acc.set(`${a}|${b}`, { a, b, w, via: [] });
|
|
759
319
|
}
|
|
760
320
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
321
|
+
return [...acc.keys()].sort().map((key) => acc.get(key));
|
|
322
|
+
}
|
|
323
|
+
function geoPairs(positions, opts = {}) {
|
|
324
|
+
const { k = 4, halfDist = 0.04 } = opts;
|
|
325
|
+
const rows = [...positions.entries()].sort((x, y) => x[0] < y[0] ? -1 : 1);
|
|
326
|
+
const acc = /* @__PURE__ */ new Map();
|
|
327
|
+
for (let i = 0; i < rows.length; i++) {
|
|
328
|
+
const dists = [];
|
|
329
|
+
for (let j = 0; j < rows.length; j++) {
|
|
330
|
+
if (i === j) continue;
|
|
331
|
+
const dx = rows[i][1].x - rows[j][1].x;
|
|
332
|
+
const dy = rows[i][1].y - rows[j][1].y;
|
|
333
|
+
dists.push({ j, d: Math.hypot(dx, dy) });
|
|
766
334
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const
|
|
772
|
-
|
|
335
|
+
dists.sort((x, y) => x.d - y.d || x.j - y.j);
|
|
336
|
+
for (const { j, d } of dists.slice(0, k)) {
|
|
337
|
+
const w = Math.pow(0.5, d / halfDist);
|
|
338
|
+
if (w < 0.01) continue;
|
|
339
|
+
const [a, b] = rows[i][0] < rows[j][0] ? [rows[i][0], rows[j][0]] : [rows[j][0], rows[i][0]];
|
|
340
|
+
const key = `${a}|${b}`;
|
|
341
|
+
const prev = acc.get(key);
|
|
342
|
+
if (!prev || prev.w < w) acc.set(key, { a, b, w, via: [] });
|
|
773
343
|
}
|
|
774
344
|
}
|
|
775
|
-
return
|
|
345
|
+
return [...acc.keys()].sort().map((key) => acc.get(key));
|
|
776
346
|
}
|
|
777
|
-
function
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
347
|
+
function blendPairs(sets) {
|
|
348
|
+
const acc = /* @__PURE__ */ new Map();
|
|
349
|
+
for (const { pairs, weight } of sets) {
|
|
350
|
+
if (weight <= 0 || pairs.length === 0) continue;
|
|
351
|
+
const max = Math.max(...pairs.map((p) => p.w));
|
|
352
|
+
for (const p of pairs) {
|
|
353
|
+
const key = `${p.a}|${p.b}`;
|
|
354
|
+
const hit = acc.get(key) ?? { a: p.a, b: p.b, w: 0, via: [] };
|
|
355
|
+
hit.w += p.w / max * weight;
|
|
356
|
+
for (const v of p.via) if (hit.via.length < 6 && !hit.via.includes(v)) hit.via.push(v);
|
|
357
|
+
acc.set(key, hit);
|
|
358
|
+
}
|
|
782
359
|
}
|
|
783
|
-
return
|
|
360
|
+
return [...acc.keys()].sort().map((key) => acc.get(key));
|
|
784
361
|
}
|
|
785
362
|
|
|
786
|
-
// src/temporal/
|
|
787
|
-
function
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
363
|
+
// src/temporal/bridges.ts
|
|
364
|
+
function bridgingScores(edges, labels) {
|
|
365
|
+
const cross = /* @__PURE__ */ new Map();
|
|
366
|
+
const foreign = /* @__PURE__ */ new Map();
|
|
367
|
+
const touch = (id, otherLabel, w) => {
|
|
368
|
+
cross.set(id, (cross.get(id) ?? 0) + w);
|
|
369
|
+
(foreign.get(id) ?? foreign.set(id, /* @__PURE__ */ new Set()).get(id)).add(otherLabel);
|
|
370
|
+
};
|
|
371
|
+
for (const e of edges) {
|
|
372
|
+
const la = labels.get(e.a);
|
|
373
|
+
const lb = labels.get(e.b);
|
|
374
|
+
if (la === void 0 || lb === void 0 || la === lb) continue;
|
|
375
|
+
const w = e.weight ?? 1;
|
|
376
|
+
touch(e.a, lb, w);
|
|
377
|
+
touch(e.b, la, w);
|
|
378
|
+
}
|
|
379
|
+
let max = 0;
|
|
380
|
+
const raw = /* @__PURE__ */ new Map();
|
|
381
|
+
for (const [id, w] of cross) {
|
|
382
|
+
const r = w * foreign.get(id).size;
|
|
383
|
+
raw.set(id, r);
|
|
384
|
+
if (r > max) max = r;
|
|
385
|
+
}
|
|
386
|
+
const out = /* @__PURE__ */ new Map();
|
|
387
|
+
for (const [id] of labels) {
|
|
388
|
+
const r = raw.get(id) ?? 0;
|
|
389
|
+
const own = labels.get(id);
|
|
390
|
+
const groups = r > 0 ? [own, ...[...foreign.get(id)].sort()] : [own];
|
|
391
|
+
out.set(id, { score: max > 0 ? r / max : 0, groups });
|
|
792
392
|
}
|
|
793
393
|
return out;
|
|
794
394
|
}
|
|
795
|
-
function
|
|
796
|
-
|
|
797
|
-
const isComplete = opts.isComplete ?? ((r) => Boolean(r.website && r.city && r.sector));
|
|
798
|
-
const seen = /* @__PURE__ */ new Set();
|
|
799
|
-
const eligible = [];
|
|
800
|
-
for (const row of rows) {
|
|
801
|
-
const slug = typeof row.slug === "string" ? row.slug : "";
|
|
802
|
-
if (!slug || seen.has(slug)) continue;
|
|
803
|
-
seen.add(slug);
|
|
804
|
-
const score = scores[slug] ?? 0;
|
|
805
|
-
if (opts.cooling?.has(slug)) continue;
|
|
806
|
-
if (score <= 0 && isComplete(row)) continue;
|
|
807
|
-
eligible.push({ slug, name: typeof row.name === "string" ? row.name : slug, score });
|
|
808
|
-
}
|
|
809
|
-
return eligible.sort((a, b) => b.score - a.score).slice(0, Math.max(0, opts.limit));
|
|
810
|
-
}
|
|
811
|
-
var BURST_ORIGIN = "burst";
|
|
812
|
-
var MAX_QUERY_CHARS = 120;
|
|
813
|
-
function synthesizeBurstSources(existing, report, opts = {}) {
|
|
814
|
-
const max = opts.max ?? 4;
|
|
815
|
-
const kept = existing.filter((s) => s.origin !== BURST_ORIGIN);
|
|
816
|
-
const queries = report.leads.filter((lead) => lead.type === "company").slice(0, max).map((lead) => {
|
|
817
|
-
const where = lead.city ?? lead.sector ?? "startup";
|
|
818
|
-
return `"${lead.name}" ${where} funding OR expansion OR hiring news`.slice(0, MAX_QUERY_CHARS);
|
|
819
|
-
});
|
|
820
|
-
if (queries.length === 0) return kept;
|
|
821
|
-
return [
|
|
822
|
-
...kept,
|
|
823
|
-
{
|
|
824
|
-
kind: "web-search",
|
|
825
|
-
name: "Burst sweep (auto)",
|
|
826
|
-
enabled: true,
|
|
827
|
-
cadenceHours: opts.cadenceHours ?? 24,
|
|
828
|
-
watch: "burst",
|
|
829
|
-
origin: BURST_ORIGIN,
|
|
830
|
-
queries
|
|
831
|
-
}
|
|
832
|
-
];
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// src/temporal/flows.ts
|
|
836
|
-
function flowFrames(events, opts) {
|
|
837
|
-
const moves = events.filter((e) => e.geo !== void 0 && e.geoTo !== void 0);
|
|
838
|
-
const from = opts.from ?? moves[0]?.t;
|
|
839
|
-
const to = opts.to ?? moves[moves.length - 1]?.t;
|
|
840
|
-
if (from === void 0 || to === void 0) return [];
|
|
841
|
-
const buckets = bucketRange(from, to, opts.grain);
|
|
842
|
-
const index = new Map(buckets.map((b, i) => [b.key, i]));
|
|
843
|
-
const perBucket = buckets.map(() => /* @__PURE__ */ new Map());
|
|
844
|
-
for (const e of moves) {
|
|
845
|
-
const i = index.get(bucketKey(e.t, opts.grain));
|
|
846
|
-
if (i === void 0) continue;
|
|
847
|
-
const cell = perBucket[i];
|
|
848
|
-
const key = `${e.geo}\u2192${e.geoTo}`;
|
|
849
|
-
const flow = cell.get(key);
|
|
850
|
-
if (flow) {
|
|
851
|
-
flow.weight += e.weight;
|
|
852
|
-
flow.count += 1;
|
|
853
|
-
} else {
|
|
854
|
-
cell.set(key, { from: e.geo, to: e.geoTo, weight: e.weight, count: 1 });
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
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));
|
|
858
|
-
if (!opts.cumulative) {
|
|
859
|
-
return buckets.map((bucket, i) => ({
|
|
860
|
-
bucket,
|
|
861
|
-
flows: sortFlows([...perBucket[i].values()])
|
|
862
|
-
}));
|
|
863
|
-
}
|
|
864
|
-
const acc = /* @__PURE__ */ new Map();
|
|
865
|
-
return buckets.map((bucket, i) => {
|
|
866
|
-
for (const [key, flow] of perBucket[i]) {
|
|
867
|
-
const running = acc.get(key);
|
|
868
|
-
if (running) {
|
|
869
|
-
running.weight += flow.weight;
|
|
870
|
-
running.count += flow.count;
|
|
871
|
-
} else {
|
|
872
|
-
acc.set(key, { ...flow });
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
return { bucket, flows: sortFlows([...acc.values()].map((f) => ({ ...f }))) };
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
function topFlows(frame, n) {
|
|
879
|
-
return frame.flows.slice(0, n);
|
|
395
|
+
function topBridges(bridging, n = 5) {
|
|
396
|
+
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 }));
|
|
880
397
|
}
|
|
881
398
|
//# sourceMappingURL=index.cjs.map
|