@odla-ai/kg 0.2.3 → 0.3.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.
@@ -0,0 +1,553 @@
1
+ import { h as GraphNode, G as GraphFragment, a as SourceEntry } from '../sources-nJyD0JUP.js';
2
+ import { Value } from '@odla-ai/db';
3
+ import '@odla-ai/ai';
4
+
5
+ /**
6
+ * FNV-1a 32-bit hash of a string. Used to derive a per-identity PRNG seed
7
+ * (`mulberry32(hash32(id) ^ seed)`), which makes node placement independent
8
+ * of the order nodes arrive in.
9
+ */
10
+ declare function hash32(s: string): number;
11
+ /**
12
+ * mulberry32: a tiny, fast, seedable PRNG returning floats in [0, 1).
13
+ * The same seed always yields the same sequence — replayable layouts and
14
+ * community assignments depend on this.
15
+ */
16
+ declare function mulberry32(seed: number): () => number;
17
+ /**
18
+ * Fisher–Yates shuffle into a NEW array, driven by the supplied random source
19
+ * (pass a seeded `mulberry32` for a deterministic permutation).
20
+ */
21
+ declare function shuffle<T>(items: readonly T[], rand: () => number): T[];
22
+
23
+ /** Supported bucketing granularities. */
24
+ type TimeGrain = "week" | "month" | "quarter";
25
+ /** One half-open time bucket: `start` inclusive, `end` exclusive (epoch ms). */
26
+ interface TimeBucket {
27
+ key: string;
28
+ start: number;
29
+ end: number;
30
+ }
31
+ /**
32
+ * Parse a date-ish value to epoch ms, or null if unparseable. Accepts
33
+ * `"YYYY-MM-DD"` (UTC midnight, with real calendar validation — `2026-02-31`
34
+ * is null), full ISO strings, and finite numbers (already epoch ms).
35
+ */
36
+ declare function parseDateMs(v: unknown): number | null;
37
+ /** The bucket key containing epoch-ms `t`: `"2026-07"`, `"2026-Q3"`, or `"2026-W29"`. */
38
+ declare function bucketKey(t: number, grain: TimeGrain): string;
39
+ /**
40
+ * Expand a bucket key back to its half-open [start, end) range. Throws on a
41
+ * key that matches no known grain format.
42
+ */
43
+ declare function bucketFor(key: string): TimeBucket;
44
+ /**
45
+ * Every bucket from the one containing `from` through the one containing `to`,
46
+ * in order. Returns [] when from > to.
47
+ */
48
+ declare function bucketRange(from: number, to: number, grain: TimeGrain): TimeBucket[];
49
+
50
+ /** A node reference: the `{type, key}` pair used by fragment edges. */
51
+ interface NodeRef {
52
+ type: string;
53
+ key: string;
54
+ }
55
+ /** Canonical string id for a node reference: `"type:key"`. */
56
+ type NodeId = string;
57
+ /** Make the canonical `"type:key"` id for a node reference. */
58
+ declare function nodeId(ref: NodeRef): NodeId;
59
+ /** Compare two refs by canonical id (stable orderings everywhere). */
60
+ declare function byNodeId(a: NodeRef, b: NodeRef): number;
61
+ /** Indexed view of one GraphFragment. */
62
+ interface GraphIndex {
63
+ /** The node for an id, if present in the fragment. */
64
+ node(id: NodeId): GraphNode | undefined;
65
+ /** Forward-label adjacency: targets of `label` edges out of `id`. */
66
+ out(id: NodeId, label: string): NodeRef[];
67
+ /** Every node of one entity type. */
68
+ nodesOf(type: string): GraphNode[];
69
+ }
70
+ /** Build the index (one pass over nodes + edges). */
71
+ declare function indexGraph(fragment: GraphFragment): GraphIndex;
72
+ /**
73
+ * Walk a sequence of forward edge labels from `start`, breadth-first, and
74
+ * return the deduplicated frontier after the last hop, sorted by id.
75
+ * An empty label list returns just `start`.
76
+ */
77
+ declare function resolvePath(g: GraphIndex, start: NodeRef, labels: readonly string[]): NodeRef[];
78
+
79
+ /**
80
+ * Where an event's geography lives: walk `path` (forward edge labels) from the
81
+ * event node, then read `attr` off the landing node — or, with no `attr`, use
82
+ * the landing node's key (e.g. a `region` entity slug). With no `path`, the
83
+ * event node itself is the landing node.
84
+ */
85
+ interface GeoSpec {
86
+ path?: readonly string[];
87
+ attr?: string;
88
+ }
89
+ /** How to turn one entity type into temporal events. */
90
+ interface EventSpec {
91
+ /** Event entity type, e.g. `"funding_round"` or `"signal"`. */
92
+ type: string;
93
+ /** Attr holding the event date (parsed with `parseDateMs`; undated nodes are skipped). */
94
+ dateAttr: string;
95
+ /** Attr holding a magnitude, parsed with `parseAmount`; unparseable → weight 1. */
96
+ weightAttr?: string;
97
+ /** Override hook for weights the attr grammar can't express. */
98
+ weightOf?: (attrs: Record<string, Value>) => number | undefined;
99
+ /** Edge-label paths from the event node to its participant entities. */
100
+ attach?: readonly (readonly string[])[];
101
+ /** Geography of the event (choropleth/hotspot keying). */
102
+ geo?: GeoSpec;
103
+ /** Destination geography — only for flow-shaped events (money moving A→B). */
104
+ geoTo?: GeoSpec;
105
+ }
106
+ /** The extraction contract: one spec per event entity type. */
107
+ interface TemporalConfig {
108
+ events: EventSpec[];
109
+ }
110
+ /** One timestamped event extracted from the graph. */
111
+ interface TemporalEvent {
112
+ /** Epoch ms (UTC). */
113
+ t: number;
114
+ /** The spec's entity type. */
115
+ kind: string;
116
+ /** The event node itself. */
117
+ node: NodeRef;
118
+ /** Attached participant entities, deduped, sorted by id. */
119
+ refs: NodeRef[];
120
+ /** Magnitude (defaults to 1). */
121
+ weight: number;
122
+ /** Source geography key, when resolvable. */
123
+ geo?: string;
124
+ /** Destination geography key, when resolvable (flow events). */
125
+ geoTo?: string;
126
+ }
127
+ /**
128
+ * Parse a human money/quantity string to a number: `"$12.5M"` → 12500000,
129
+ * `"12,500,000"` → 12500000, `"500k"` → 500000, numbers pass through.
130
+ * Returns undefined for anything else (never NaN).
131
+ */
132
+ declare function parseAmount(v: unknown): number | undefined;
133
+ /**
134
+ * Extract the event stream: for every spec, every node of that type with a
135
+ * parseable date becomes one event. Sorted by time, tie-broken by node id —
136
+ * the ordering is total and reproducible.
137
+ */
138
+ declare function extractEvents(fragment: GraphFragment, config: TemporalConfig): TemporalEvent[];
139
+
140
+ /** One named series, index-aligned with its SeriesSet's buckets. */
141
+ interface Series {
142
+ key: string;
143
+ values: number[];
144
+ }
145
+ /** A grain, its buckets, and every series over them (sorted by key). */
146
+ interface SeriesSet {
147
+ grain: TimeGrain;
148
+ buckets: TimeBucket[];
149
+ series: Series[];
150
+ }
151
+ /**
152
+ * How to key events into series: `"kind"` (event type), `"geo"` (source
153
+ * geography; geo-less events are dropped), `"ref"` (fan one event into one
154
+ * increment per attached participant, keyed by node id), or a custom function
155
+ * returning zero or more keys per event.
156
+ */
157
+ type SeriesKeyer = "kind" | "geo" | "ref" | ((e: TemporalEvent) => string | readonly string[] | undefined);
158
+ /** The series keys one event contributes to under a keyer (used by rollup and by hotspot contributor attachment). */
159
+ declare function seriesKeys(e: TemporalEvent, by: SeriesKeyer): readonly string[];
160
+ /**
161
+ * Roll events up into bucketed series. The bucket range spans `from`..`to`
162
+ * when given (zero-padded even where nothing happened — burst baselines need
163
+ * the quiet buckets), else the events' own time span. Events outside the
164
+ * range are ignored.
165
+ */
166
+ declare function rollup(events: readonly TemporalEvent[], opts: {
167
+ grain: TimeGrain;
168
+ by?: SeriesKeyer;
169
+ measure?: "count" | "weight";
170
+ from?: number;
171
+ to?: number;
172
+ }): SeriesSet;
173
+ /** Per-bucket sum across every series in the set (the "all" line). */
174
+ declare function totals(set: SeriesSet): Series;
175
+
176
+ /** Tuning for {@link burstScores}/{@link detectBursts}. */
177
+ interface BurstOptions {
178
+ /** Trailing buckets forming the baseline (excluding the scored bucket). Default 6. */
179
+ window?: number;
180
+ /** z needed to OPEN a burst run. Default 2. */
181
+ zEnter?: number;
182
+ /** z needed to EXTEND an open run (hysteresis). Default 1. */
183
+ zExit?: number;
184
+ /** Minimum trailing buckets before scoring (cold start → z 0). Default 3. */
185
+ minBaseline?: number;
186
+ /**
187
+ * Floor on the baseline sigma. sqrt(mean) is always applied (Poisson-
188
+ * consistent for counts); this guards the all-zero baseline. Default 1.
189
+ */
190
+ minSigma?: number;
191
+ }
192
+ /**
193
+ * Per-bucket burst z-scores for one series: (value − trailing mean) /
194
+ * max(trailing stdev, sqrt(mean), minSigma). Cold-start buckets score 0.
195
+ */
196
+ declare function burstScores(values: readonly number[], opts?: BurstOptions): number[];
197
+ /** One detected burst: a maximal hysteresis run within one series. */
198
+ interface Burst {
199
+ seriesKey: string;
200
+ /** Bucket indexes, inclusive. */
201
+ start: number;
202
+ end: number;
203
+ /** Highest z in the run. */
204
+ peak: number;
205
+ /** Σ max(0, z) over the run — rewards both height and duration. */
206
+ score: number;
207
+ }
208
+ /**
209
+ * All bursts across a SeriesSet, ranked score desc (ties: earlier start,
210
+ * then series key). A run opens at z ≥ zEnter and extends while z ≥ zExit.
211
+ */
212
+ declare function detectBursts(set: SeriesSet, opts?: BurstOptions): Burst[];
213
+ /** A burst plus its bucket keys and top contributing events — a ranked lead. */
214
+ interface Hotspot extends Burst {
215
+ bucketKeys: string[];
216
+ contributors: TemporalEvent[];
217
+ }
218
+ /**
219
+ * Detect, rank, and enrich bursts into explorable leads. `by` must be the
220
+ * same keyer the SeriesSet was rolled up with — it re-derives each event's
221
+ * series keys to attach contributors (top per-lead by weight, then time).
222
+ */
223
+ declare function rankHotspots(set: SeriesSet, events: readonly TemporalEvent[], opts?: BurstOptions & {
224
+ by?: SeriesKeyer;
225
+ top?: number;
226
+ contributorsPer?: number;
227
+ }): Hotspot[];
228
+
229
+ /** One dated undirected edge (canonical order: a < b). */
230
+ interface TimelineEdge {
231
+ a: NodeId;
232
+ b: NodeId;
233
+ t: number;
234
+ kind: string;
235
+ weight: number;
236
+ }
237
+ /** The replayable structure built once from the event stream. */
238
+ interface Timeline {
239
+ events: TemporalEvent[];
240
+ /** Every node ever seen, sorted by id. */
241
+ nodes: NodeId[];
242
+ /** Dated edges, time-ordered. */
243
+ edges: TimelineEdge[];
244
+ /** Node id → first time it appeared. */
245
+ firstSeen: Map<NodeId, number>;
246
+ }
247
+ /**
248
+ * Build the timeline. `"entities"` (default) projects each event onto its
249
+ * participants — every ref pair co-occurring in one event becomes an edge
250
+ * (companies↔investors↔people), and event nodes vanish. `"full"` keeps the
251
+ * event node and links it to each ref (bipartite).
252
+ */
253
+ declare function buildTimeline(events: readonly TemporalEvent[], opts?: {
254
+ mode?: "entities" | "full";
255
+ }): Timeline;
256
+ /** The graph as it stood at time `t`. */
257
+ interface Snapshot {
258
+ t: number;
259
+ /** Nodes first seen at or before t, sorted. */
260
+ nodes: NodeId[];
261
+ /** Aggregated undirected edges (weight = co-occurrence count). */
262
+ edges: Array<{
263
+ a: NodeId;
264
+ b: NodeId;
265
+ weight: number;
266
+ }>;
267
+ /** Node id → its latest event time ≤ t (drives recency glow). */
268
+ activity: Map<NodeId, number>;
269
+ }
270
+ /**
271
+ * Materialize the graph at `t`: cumulative by default, or only edges within
272
+ * the trailing `window` ms when given (rolling co-activity).
273
+ */
274
+ declare function snapshotAt(timeline: Timeline, t: number, opts?: {
275
+ window?: number;
276
+ }): Snapshot;
277
+ /** One snapshot per bucket, each taken at the bucket's inclusive end. */
278
+ declare function snapshotSeries(timeline: Timeline, buckets: readonly TimeBucket[], opts?: {
279
+ window?: number;
280
+ }): Snapshot[];
281
+
282
+ /**
283
+ * Asynchronous label propagation with deterministic (seeded) tie-breaking:
284
+ * - labels start as each node's own id;
285
+ * - each round visits nodes in a seeded-shuffled order (one PRNG stream
286
+ * reused across rounds) and adopts the label with the greatest total
287
+ * incident edge weight; if the node's CURRENT label ties for the max it is
288
+ * kept, otherwise a tied max is picked via the same seeded stream (a
289
+ * lexicographic tie-break would systematically bleed communities toward
290
+ * alphabetically small ids — verified on the two-triangles fixture);
291
+ * - stops on a round with no change, or after `maxRounds`;
292
+ * - finally each community's label is canonicalized to its smallest member
293
+ * id, so the output is independent of propagation history.
294
+ * Isolated nodes keep their own label (singleton communities).
295
+ */
296
+ declare function labelPropagation(nodes: readonly NodeId[], edges: ReadonlyArray<{
297
+ a: NodeId;
298
+ b: NodeId;
299
+ weight?: number;
300
+ }>, opts?: {
301
+ seed?: number;
302
+ maxRounds?: number;
303
+ }): Map<NodeId, string>;
304
+
305
+ /** One cluster in one frame (members sorted). */
306
+ interface Cluster {
307
+ id: string;
308
+ members: NodeId[];
309
+ }
310
+ /** All clusters at one instant, id-sorted. */
311
+ interface ClusterFrame {
312
+ t: number;
313
+ clusters: Cluster[];
314
+ }
315
+ /** A structural change between consecutive frames. */
316
+ type ClusterChange = {
317
+ kind: "birth";
318
+ t: number;
319
+ id: string;
320
+ } | {
321
+ kind: "death";
322
+ t: number;
323
+ id: string;
324
+ } | {
325
+ kind: "merge";
326
+ t: number;
327
+ into: string;
328
+ from: string[];
329
+ } | {
330
+ kind: "split";
331
+ t: number;
332
+ from: string;
333
+ into: string[];
334
+ };
335
+ /**
336
+ * Thread stable cluster ids through per-frame label maps. A current cluster
337
+ * inherits the id of its best-Jaccard predecessor at ≥ minJaccard (ties:
338
+ * more shared members, then smaller id); when several current clusters claim
339
+ * one predecessor the best keeps the id and the rest record a split; a
340
+ * current cluster absorbing ≥2 predecessors records a merge. Unmatched
341
+ * current clusters are births (id = "c:" + smallest member); vanished ids
342
+ * are deaths.
343
+ */
344
+ declare function trackClusters(frames: ReadonlyArray<{
345
+ t: number;
346
+ labels: Map<NodeId, string>;
347
+ }>, opts?: {
348
+ minJaccard?: number;
349
+ }): {
350
+ frames: ClusterFrame[];
351
+ changes: ClusterChange[];
352
+ };
353
+
354
+ /** A unit-square position (scale to pixels at draw time). */
355
+ interface XY {
356
+ x: number;
357
+ y: number;
358
+ }
359
+ /** Tuning for {@link forceLayout}. */
360
+ interface LayoutOptions {
361
+ /** Force iterations. Default 300 (cold); use fewer for warm refinement. */
362
+ iterations?: number;
363
+ /** Seed mixed into every node's hash-derived initial position. Default 1. */
364
+ seed?: number;
365
+ /**
366
+ * Repulsion multiplier. Default 0.1 — measured on a real ~800-node OSINT
367
+ * graph as the point where nothing pins to the clamped boundary.
368
+ */
369
+ repulsion?: number;
370
+ /** Pull toward the center, proportional to distance. Default 0.5. */
371
+ gravity?: number;
372
+ /** Warm-start positions (nodes present here start where they were). */
373
+ positions?: ReadonlyMap<NodeId, XY>;
374
+ }
375
+ /**
376
+ * Lay out one graph. Init priority per node: warm `positions` entry →
377
+ * hash-disc from (id, seed). Forces per iteration (accumulated over
378
+ * sorted-id order, applied synchronously): pairwise repulsion k²/d, edge
379
+ * attraction d²/k scaled by min(weight, 4), center gravity. Linear cooling
380
+ * caps displacement; results clamp to [0,1]².
381
+ */
382
+ declare function forceLayout(nodes: readonly NodeId[], edges: ReadonlyArray<{
383
+ a: NodeId;
384
+ b: NodeId;
385
+ weight?: number;
386
+ }>, opts?: LayoutOptions): Map<NodeId, XY>;
387
+ /**
388
+ * Lay out a snapshot sequence: frame 0 runs cold (`iterations`), each later
389
+ * frame warm-starts from the previous frame's positions and refines for
390
+ * `warmIterations` — existing nodes drift instead of re-scrambling, so
391
+ * inter-frame motion means something.
392
+ */
393
+ declare function layoutSequence(snapshots: readonly Snapshot[], opts?: LayoutOptions & {
394
+ warmIterations?: number;
395
+ }): Map<NodeId, XY>[];
396
+
397
+ /** Tuning for {@link buildTemporalReport}. */
398
+ interface ReportOptions {
399
+ /** Analysis anchor (epoch ms). Default: the latest event time. Inject for determinism. */
400
+ now?: number;
401
+ /** Weekly cluster/lead window. Default 155 days (~22 frames). */
402
+ windowDays?: number;
403
+ /** Monthly density-series span. Default 12 months. */
404
+ monthlyMonths?: number;
405
+ /** Ranked leads kept. Default 8. */
406
+ topLeads?: number;
407
+ /** Contributing events per lead. Default 3. */
408
+ contributorsPer?: number;
409
+ /** Trailing weeks of cluster changes kept. Default 4. */
410
+ changeWeeks?: number;
411
+ /** Burst detector tuning. Default `{ window: 6 }`. */
412
+ burst?: BurstOptions;
413
+ }
414
+ /** One ranked hotspot lead: a bursting entity plus its evidence. */
415
+ interface ReportLead {
416
+ key: string;
417
+ type: string;
418
+ name: string;
419
+ city?: string;
420
+ sector?: string;
421
+ score: number;
422
+ peak: number;
423
+ window: string[];
424
+ contributors: Array<{
425
+ kind: string;
426
+ key: string;
427
+ title: string;
428
+ date: string;
429
+ }>;
430
+ }
431
+ /** One cluster-evolution change, name-resolved for display. */
432
+ interface ReportClusterChange {
433
+ week: string;
434
+ kind: "birth" | "death" | "merge" | "split";
435
+ ids: string[];
436
+ names: string[];
437
+ }
438
+ /** The cached/served analysis product — everything JSON-native and bounded (≲20KB). */
439
+ interface TemporalReport {
440
+ generatedAt: string;
441
+ window: {
442
+ from: string;
443
+ to: string;
444
+ weeks: string[];
445
+ };
446
+ totals: {
447
+ events: number;
448
+ nodes: number;
449
+ edges: number;
450
+ };
451
+ series: Array<{
452
+ kind: string;
453
+ months: string[];
454
+ values: number[];
455
+ }>;
456
+ leads: ReportLead[];
457
+ clusters: {
458
+ frames: Array<{
459
+ week: string;
460
+ count: number;
461
+ top: Array<{
462
+ id: string;
463
+ size: number;
464
+ label: string;
465
+ }>;
466
+ }>;
467
+ changes: ReportClusterChange[];
468
+ };
469
+ }
470
+ /** Build the full temporal report for a graph fragment. Empty input → zeroed report. */
471
+ declare function buildTemporalReport(fragment: GraphFragment, config: TemporalConfig, opts?: ReportOptions): TemporalReport;
472
+
473
+ /** True when the report has anything worth narrating (any lead or recent cluster change). */
474
+ declare function shouldNarrate(report: TemporalReport): boolean;
475
+ /**
476
+ * The deterministic materials block handed to the narrative LLM call:
477
+ * window/totals header, top leads with evidence, cluster changes, and
478
+ * per-kind monthly density lines. No prose, no interpretation.
479
+ */
480
+ declare function buildNarrationMaterials(report: TemporalReport, opts?: {
481
+ topLeads?: number;
482
+ }): string;
483
+ /** First-line `# title` heuristic shared with the digest workflow. */
484
+ declare function splitTitleBody(text: string, fallbackTitle: string): {
485
+ title: string;
486
+ body: string;
487
+ };
488
+
489
+ /**
490
+ * Burst score per natural key for one entity type, from a report's leads:
491
+ * `{ leads: [{key: "company:acme", score: 7.3}] }` → `{ acme: 7.3 }`.
492
+ */
493
+ declare function leadScores(report: Pick<TemporalReport, "leads">, type?: string): Record<string, number>;
494
+ /** One selected enrichment target. */
495
+ interface EnrichmentCandidate {
496
+ slug: string;
497
+ name: string;
498
+ score: number;
499
+ }
500
+ /**
501
+ * Rank enrichment candidates: burst score descending, ties keep the input
502
+ * (creation) order. Cooling rows are skipped at selection. Complete rows are
503
+ * skipped UNLESS they carry a burst score — a bursting complete company
504
+ * deserves a re-check. With no scores this reproduces the legacy behavior
505
+ * exactly (input order, incomplete rows only).
506
+ */
507
+ declare function rankEnrichmentCandidates(rows: ReadonlyArray<Record<string, unknown>>, opts: {
508
+ limit: number;
509
+ scores?: Record<string, number>;
510
+ cooling?: ReadonlySet<string>;
511
+ isComplete?: (row: Record<string, unknown>) => boolean;
512
+ }): EnrichmentCandidate[];
513
+ /**
514
+ * Replace the synthesized burst-sweep source with a fresh one derived from
515
+ * the report's top company leads (or remove it when there are none).
516
+ * Hand-authored entries — anything without `origin: "burst"` — are returned
517
+ * untouched, in order. Queries follow
518
+ * `"<name>" <city|sector|startup> funding OR expansion OR hiring news`.
519
+ */
520
+ declare function synthesizeBurstSources(existing: readonly SourceEntry[], report: Pick<TemporalReport, "leads">, opts?: {
521
+ max?: number;
522
+ cadenceHours?: number;
523
+ }): SourceEntry[];
524
+
525
+ /** One aggregated movement between two geography keys within a bucket. */
526
+ interface Flow {
527
+ from: string;
528
+ to: string;
529
+ /** Summed event weights (e.g. dollars). */
530
+ weight: number;
531
+ /** Number of contributing events. */
532
+ count: number;
533
+ }
534
+ /** All flows for one time bucket, sorted weight-desc then from/to. */
535
+ interface FlowFrame {
536
+ bucket: TimeBucket;
537
+ flows: Flow[];
538
+ }
539
+ /**
540
+ * Aggregate flow events into per-bucket frames over `from`..`to` (default:
541
+ * the events' span). With `cumulative`, each frame includes everything up to
542
+ * and including its bucket — replay-style accumulation.
543
+ */
544
+ declare function flowFrames(events: readonly TemporalEvent[], opts: {
545
+ grain: TimeGrain;
546
+ from?: number;
547
+ to?: number;
548
+ cumulative?: boolean;
549
+ }): FlowFrame[];
550
+ /** The frame's top-n flows by weight (already the frame's sort order). */
551
+ declare function topFlows(frame: FlowFrame, n: number): Flow[];
552
+
553
+ export { type Burst, type BurstOptions, type Cluster, type ClusterChange, type ClusterFrame, type EnrichmentCandidate, type EventSpec, type Flow, type FlowFrame, type GeoSpec, type GraphIndex, type Hotspot, type LayoutOptions, type NodeId, type NodeRef, type ReportClusterChange, type ReportLead, type ReportOptions, type Series, type SeriesKeyer, type SeriesSet, type Snapshot, type TemporalConfig, type TemporalEvent, type TemporalReport, type TimeBucket, type TimeGrain, type Timeline, type TimelineEdge, type XY, bucketFor, bucketKey, bucketRange, buildNarrationMaterials, buildTemporalReport, buildTimeline, burstScores, byNodeId, detectBursts, extractEvents, flowFrames, forceLayout, hash32, indexGraph, labelPropagation, layoutSequence, leadScores, mulberry32, nodeId, parseAmount, parseDateMs, rankEnrichmentCandidates, rankHotspots, resolvePath, rollup, seriesKeys, shouldNarrate, shuffle, snapshotAt, snapshotSeries, splitTitleBody, synthesizeBurstSources, topFlows, totals, trackClusters };