@odla-ai/kg 0.3.0 → 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.
- package/dist/temporal/index.cjs +205 -0
- package/dist/temporal/index.cjs.map +1 -1
- package/dist/temporal/index.d.cts +138 -1
- package/dist/temporal/index.d.ts +138 -1
- package/dist/temporal/index.js +205 -0
- package/dist/temporal/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -550,4 +550,141 @@ declare function flowFrames(events: readonly TemporalEvent[], opts: {
|
|
|
550
550
|
/** The frame's top-n flows by weight (already the frame's sort order). */
|
|
551
551
|
declare function topFlows(frame: FlowFrame, n: number): Flow[];
|
|
552
552
|
|
|
553
|
-
|
|
553
|
+
/**
|
|
554
|
+
* One threshold rule over a region's static metrics. A rule matches when the
|
|
555
|
+
* region has the metric and its value passes the comparison; regions or
|
|
556
|
+
* metrics missing from the metric map never match (and never throw).
|
|
557
|
+
*/
|
|
558
|
+
interface SpatialRule {
|
|
559
|
+
/** Stable identifier, reported on findings (e.g. `"capital-desert"`). */
|
|
560
|
+
id: string;
|
|
561
|
+
/** Metric name looked up in the region's metric record. */
|
|
562
|
+
metric: string;
|
|
563
|
+
/** Comparison direction against {@link threshold}. */
|
|
564
|
+
op: ">=" | "<=";
|
|
565
|
+
threshold: number;
|
|
566
|
+
/** Additive score boost when matched (finding score × (1 + Σ weights)). Default 1. */
|
|
567
|
+
weight?: number;
|
|
568
|
+
}
|
|
569
|
+
/** A bursting region, the rules it matched, and its boosted rank score. */
|
|
570
|
+
interface SpatialFinding {
|
|
571
|
+
/** The series key — the region identifier the events were rolled up by. */
|
|
572
|
+
region: string;
|
|
573
|
+
/** The region's strongest burst (highest score) in the set. */
|
|
574
|
+
burst: Burst;
|
|
575
|
+
/** Ids of the rules that matched, in the rules' given order. */
|
|
576
|
+
matched: string[];
|
|
577
|
+
/** `burst.score × (1 + Σ matched weights)` — rule context amplifies heat. */
|
|
578
|
+
score: number;
|
|
579
|
+
/** The region's values for every rule metric it has (evidence for narration). */
|
|
580
|
+
metrics: Record<string, number>;
|
|
581
|
+
}
|
|
582
|
+
/** Ids of the rules a region's metric record matches, in rule order. */
|
|
583
|
+
declare function matchRules(values: Record<string, number> | undefined, rules: readonly SpatialRule[]): string[];
|
|
584
|
+
/**
|
|
585
|
+
* Rank spatial anomalies: detect bursts in a geo-keyed SeriesSet (one series
|
|
586
|
+
* per region — roll up with `by: "geo"`), keep each region's strongest burst,
|
|
587
|
+
* then amplify by matched metric rules. Deterministic: score desc, then
|
|
588
|
+
* region asc. `requireMatch` drops regions matching no rule (default keeps
|
|
589
|
+
* them — a burst is a finding even without metric context).
|
|
590
|
+
*/
|
|
591
|
+
declare function spatialFindings(set: SeriesSet, metrics: Record<string, Record<string, number> | undefined>, rules: readonly SpatialRule[], opts?: BurstOptions & {
|
|
592
|
+
top?: number;
|
|
593
|
+
requireMatch?: boolean;
|
|
594
|
+
}): SpatialFinding[];
|
|
595
|
+
|
|
596
|
+
/** One undirected weighted pair, with (capped) bridging node ids as evidence. */
|
|
597
|
+
interface WeightedPair {
|
|
598
|
+
a: string;
|
|
599
|
+
b: string;
|
|
600
|
+
w: number;
|
|
601
|
+
via: string[];
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Bridge → target sets through a REIFIED relationship node (affiliation,
|
|
605
|
+
* funding_round, record): every reified node with a `bridgeLabel` edge to the
|
|
606
|
+
* bridge and a `targetLabel` edge to a target contributes that target to the
|
|
607
|
+
* bridge's set. Ids are `type:key`.
|
|
608
|
+
*/
|
|
609
|
+
declare function reifiedBridgeTargets(fragment: GraphFragment, opts: {
|
|
610
|
+
reified: string;
|
|
611
|
+
bridgeLabel: string;
|
|
612
|
+
targetLabel: string;
|
|
613
|
+
}): Map<string, Set<string>>;
|
|
614
|
+
/** Direct-edge targets: `from` node id → set of `to` node ids for one label. */
|
|
615
|
+
declare function directTargets(fragment: GraphFragment, label: string): Map<string, Set<string>>;
|
|
616
|
+
/** Compose A→B with B→C into A→C (e.g. person→companies ∘ company→counties). */
|
|
617
|
+
declare function composeTargets(ab: Map<string, Set<string>>, bc: Map<string, Set<string>>): Map<string, Set<string>>;
|
|
618
|
+
/**
|
|
619
|
+
* The bipartite projection: targets sharing a bridge become pairs, weight =
|
|
620
|
+
* number of distinct shared bridges. `maxPerBridge` skips promiscuous hubs
|
|
621
|
+
* (a bridge touching 40 targets says little about any pair). Deterministic
|
|
622
|
+
* pair order (a < b, then sorted).
|
|
623
|
+
*/
|
|
624
|
+
declare function projectBridges(bridges: Map<string, Set<string>>, opts?: {
|
|
625
|
+
maxPerBridge?: number;
|
|
626
|
+
viaCap?: number;
|
|
627
|
+
}): WeightedPair[];
|
|
628
|
+
/**
|
|
629
|
+
* Temporal cohort pairs: each node links to its k nearest neighbors in
|
|
630
|
+
* arrival time, weight decaying with the gap (0.5 per halfLife). Nodes
|
|
631
|
+
* without a date contribute nothing.
|
|
632
|
+
*/
|
|
633
|
+
declare function timePairs(arrivals: Map<string, number>, opts?: {
|
|
634
|
+
k?: number;
|
|
635
|
+
halfLifeDays?: number;
|
|
636
|
+
}): WeightedPair[];
|
|
637
|
+
/**
|
|
638
|
+
* Spatial proximity pairs: k nearest neighbors in normalized map space,
|
|
639
|
+
* weight decaying with distance (0.5 per halfDist). Nodes without a
|
|
640
|
+
* position contribute nothing. O(n²) — fine for the ≤1k-node universes
|
|
641
|
+
* lenses operate on.
|
|
642
|
+
*/
|
|
643
|
+
declare function geoPairs(positions: Map<string, XY>, opts?: {
|
|
644
|
+
k?: number;
|
|
645
|
+
halfDist?: number;
|
|
646
|
+
}): WeightedPair[];
|
|
647
|
+
/**
|
|
648
|
+
* Blend lens edge sets: each set is normalized to a max weight of 1 (a lens
|
|
649
|
+
* with big raw counts must not drown the others), scaled by its slider
|
|
650
|
+
* weight, and summed per pair. Zero-weight lenses drop out entirely.
|
|
651
|
+
*/
|
|
652
|
+
declare function blendPairs(sets: Array<{
|
|
653
|
+
pairs: WeightedPair[];
|
|
654
|
+
weight: number;
|
|
655
|
+
}>): WeightedPair[];
|
|
656
|
+
/**
|
|
657
|
+
* Fabricate the Snapshot series a lens graph replays: at each bucket end,
|
|
658
|
+
* nodes that have arrived and the blended edges among them. Slots straight
|
|
659
|
+
* into labelPropagation/trackClusters/layoutSequence.
|
|
660
|
+
*/
|
|
661
|
+
declare function lensSnapshots(arrivals: Map<string, number>, pairs: WeightedPair[], buckets: readonly TimeBucket[]): Snapshot[];
|
|
662
|
+
|
|
663
|
+
/** One node's brokerage: normalized score + the communities it connects. */
|
|
664
|
+
interface Bridging {
|
|
665
|
+
/** 0..1, 1 = the frame's strongest connector. */
|
|
666
|
+
score: number;
|
|
667
|
+
/** Communities this node touches through its edges (own community first). */
|
|
668
|
+
groups: string[];
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Score every node's brokerage against a community partition. Edges inside a
|
|
672
|
+
* community contribute nothing; an edge to a foreign community contributes
|
|
673
|
+
* its weight, and the total is multiplied by the number of DISTINCT foreign
|
|
674
|
+
* communities — a node wiring three groups together outranks one with the
|
|
675
|
+
* same raw weight into a single neighbor group. Scores are normalized to
|
|
676
|
+
* the frame's max (empty/one-community frames → all zeros).
|
|
677
|
+
*/
|
|
678
|
+
declare function bridgingScores(edges: ReadonlyArray<{
|
|
679
|
+
a: NodeId;
|
|
680
|
+
b: NodeId;
|
|
681
|
+
weight?: number;
|
|
682
|
+
}>, labels: Map<NodeId, string>): Map<NodeId, Bridging>;
|
|
683
|
+
/** The frame's strongest connectors, ranked (ties: id order). */
|
|
684
|
+
declare function topBridges(bridging: Map<NodeId, Bridging>, n?: number): Array<{
|
|
685
|
+
id: NodeId;
|
|
686
|
+
score: number;
|
|
687
|
+
groups: string[];
|
|
688
|
+
}>;
|
|
689
|
+
|
|
690
|
+
export { type Bridging, 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 SpatialFinding, type SpatialRule, type TemporalConfig, type TemporalEvent, type TemporalReport, type TimeBucket, type TimeGrain, type Timeline, type TimelineEdge, type WeightedPair, type XY, blendPairs, bridgingScores, bucketFor, bucketKey, bucketRange, buildNarrationMaterials, buildTemporalReport, buildTimeline, burstScores, byNodeId, composeTargets, detectBursts, directTargets, extractEvents, flowFrames, forceLayout, geoPairs, hash32, indexGraph, labelPropagation, layoutSequence, leadScores, lensSnapshots, matchRules, mulberry32, nodeId, parseAmount, parseDateMs, projectBridges, rankEnrichmentCandidates, rankHotspots, reifiedBridgeTargets, resolvePath, rollup, seriesKeys, shouldNarrate, shuffle, snapshotAt, snapshotSeries, spatialFindings, splitTitleBody, synthesizeBurstSources, timePairs, topBridges, topFlows, totals, trackClusters };
|
package/dist/temporal/index.d.ts
CHANGED
|
@@ -550,4 +550,141 @@ declare function flowFrames(events: readonly TemporalEvent[], opts: {
|
|
|
550
550
|
/** The frame's top-n flows by weight (already the frame's sort order). */
|
|
551
551
|
declare function topFlows(frame: FlowFrame, n: number): Flow[];
|
|
552
552
|
|
|
553
|
-
|
|
553
|
+
/**
|
|
554
|
+
* One threshold rule over a region's static metrics. A rule matches when the
|
|
555
|
+
* region has the metric and its value passes the comparison; regions or
|
|
556
|
+
* metrics missing from the metric map never match (and never throw).
|
|
557
|
+
*/
|
|
558
|
+
interface SpatialRule {
|
|
559
|
+
/** Stable identifier, reported on findings (e.g. `"capital-desert"`). */
|
|
560
|
+
id: string;
|
|
561
|
+
/** Metric name looked up in the region's metric record. */
|
|
562
|
+
metric: string;
|
|
563
|
+
/** Comparison direction against {@link threshold}. */
|
|
564
|
+
op: ">=" | "<=";
|
|
565
|
+
threshold: number;
|
|
566
|
+
/** Additive score boost when matched (finding score × (1 + Σ weights)). Default 1. */
|
|
567
|
+
weight?: number;
|
|
568
|
+
}
|
|
569
|
+
/** A bursting region, the rules it matched, and its boosted rank score. */
|
|
570
|
+
interface SpatialFinding {
|
|
571
|
+
/** The series key — the region identifier the events were rolled up by. */
|
|
572
|
+
region: string;
|
|
573
|
+
/** The region's strongest burst (highest score) in the set. */
|
|
574
|
+
burst: Burst;
|
|
575
|
+
/** Ids of the rules that matched, in the rules' given order. */
|
|
576
|
+
matched: string[];
|
|
577
|
+
/** `burst.score × (1 + Σ matched weights)` — rule context amplifies heat. */
|
|
578
|
+
score: number;
|
|
579
|
+
/** The region's values for every rule metric it has (evidence for narration). */
|
|
580
|
+
metrics: Record<string, number>;
|
|
581
|
+
}
|
|
582
|
+
/** Ids of the rules a region's metric record matches, in rule order. */
|
|
583
|
+
declare function matchRules(values: Record<string, number> | undefined, rules: readonly SpatialRule[]): string[];
|
|
584
|
+
/**
|
|
585
|
+
* Rank spatial anomalies: detect bursts in a geo-keyed SeriesSet (one series
|
|
586
|
+
* per region — roll up with `by: "geo"`), keep each region's strongest burst,
|
|
587
|
+
* then amplify by matched metric rules. Deterministic: score desc, then
|
|
588
|
+
* region asc. `requireMatch` drops regions matching no rule (default keeps
|
|
589
|
+
* them — a burst is a finding even without metric context).
|
|
590
|
+
*/
|
|
591
|
+
declare function spatialFindings(set: SeriesSet, metrics: Record<string, Record<string, number> | undefined>, rules: readonly SpatialRule[], opts?: BurstOptions & {
|
|
592
|
+
top?: number;
|
|
593
|
+
requireMatch?: boolean;
|
|
594
|
+
}): SpatialFinding[];
|
|
595
|
+
|
|
596
|
+
/** One undirected weighted pair, with (capped) bridging node ids as evidence. */
|
|
597
|
+
interface WeightedPair {
|
|
598
|
+
a: string;
|
|
599
|
+
b: string;
|
|
600
|
+
w: number;
|
|
601
|
+
via: string[];
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Bridge → target sets through a REIFIED relationship node (affiliation,
|
|
605
|
+
* funding_round, record): every reified node with a `bridgeLabel` edge to the
|
|
606
|
+
* bridge and a `targetLabel` edge to a target contributes that target to the
|
|
607
|
+
* bridge's set. Ids are `type:key`.
|
|
608
|
+
*/
|
|
609
|
+
declare function reifiedBridgeTargets(fragment: GraphFragment, opts: {
|
|
610
|
+
reified: string;
|
|
611
|
+
bridgeLabel: string;
|
|
612
|
+
targetLabel: string;
|
|
613
|
+
}): Map<string, Set<string>>;
|
|
614
|
+
/** Direct-edge targets: `from` node id → set of `to` node ids for one label. */
|
|
615
|
+
declare function directTargets(fragment: GraphFragment, label: string): Map<string, Set<string>>;
|
|
616
|
+
/** Compose A→B with B→C into A→C (e.g. person→companies ∘ company→counties). */
|
|
617
|
+
declare function composeTargets(ab: Map<string, Set<string>>, bc: Map<string, Set<string>>): Map<string, Set<string>>;
|
|
618
|
+
/**
|
|
619
|
+
* The bipartite projection: targets sharing a bridge become pairs, weight =
|
|
620
|
+
* number of distinct shared bridges. `maxPerBridge` skips promiscuous hubs
|
|
621
|
+
* (a bridge touching 40 targets says little about any pair). Deterministic
|
|
622
|
+
* pair order (a < b, then sorted).
|
|
623
|
+
*/
|
|
624
|
+
declare function projectBridges(bridges: Map<string, Set<string>>, opts?: {
|
|
625
|
+
maxPerBridge?: number;
|
|
626
|
+
viaCap?: number;
|
|
627
|
+
}): WeightedPair[];
|
|
628
|
+
/**
|
|
629
|
+
* Temporal cohort pairs: each node links to its k nearest neighbors in
|
|
630
|
+
* arrival time, weight decaying with the gap (0.5 per halfLife). Nodes
|
|
631
|
+
* without a date contribute nothing.
|
|
632
|
+
*/
|
|
633
|
+
declare function timePairs(arrivals: Map<string, number>, opts?: {
|
|
634
|
+
k?: number;
|
|
635
|
+
halfLifeDays?: number;
|
|
636
|
+
}): WeightedPair[];
|
|
637
|
+
/**
|
|
638
|
+
* Spatial proximity pairs: k nearest neighbors in normalized map space,
|
|
639
|
+
* weight decaying with distance (0.5 per halfDist). Nodes without a
|
|
640
|
+
* position contribute nothing. O(n²) — fine for the ≤1k-node universes
|
|
641
|
+
* lenses operate on.
|
|
642
|
+
*/
|
|
643
|
+
declare function geoPairs(positions: Map<string, XY>, opts?: {
|
|
644
|
+
k?: number;
|
|
645
|
+
halfDist?: number;
|
|
646
|
+
}): WeightedPair[];
|
|
647
|
+
/**
|
|
648
|
+
* Blend lens edge sets: each set is normalized to a max weight of 1 (a lens
|
|
649
|
+
* with big raw counts must not drown the others), scaled by its slider
|
|
650
|
+
* weight, and summed per pair. Zero-weight lenses drop out entirely.
|
|
651
|
+
*/
|
|
652
|
+
declare function blendPairs(sets: Array<{
|
|
653
|
+
pairs: WeightedPair[];
|
|
654
|
+
weight: number;
|
|
655
|
+
}>): WeightedPair[];
|
|
656
|
+
/**
|
|
657
|
+
* Fabricate the Snapshot series a lens graph replays: at each bucket end,
|
|
658
|
+
* nodes that have arrived and the blended edges among them. Slots straight
|
|
659
|
+
* into labelPropagation/trackClusters/layoutSequence.
|
|
660
|
+
*/
|
|
661
|
+
declare function lensSnapshots(arrivals: Map<string, number>, pairs: WeightedPair[], buckets: readonly TimeBucket[]): Snapshot[];
|
|
662
|
+
|
|
663
|
+
/** One node's brokerage: normalized score + the communities it connects. */
|
|
664
|
+
interface Bridging {
|
|
665
|
+
/** 0..1, 1 = the frame's strongest connector. */
|
|
666
|
+
score: number;
|
|
667
|
+
/** Communities this node touches through its edges (own community first). */
|
|
668
|
+
groups: string[];
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Score every node's brokerage against a community partition. Edges inside a
|
|
672
|
+
* community contribute nothing; an edge to a foreign community contributes
|
|
673
|
+
* its weight, and the total is multiplied by the number of DISTINCT foreign
|
|
674
|
+
* communities — a node wiring three groups together outranks one with the
|
|
675
|
+
* same raw weight into a single neighbor group. Scores are normalized to
|
|
676
|
+
* the frame's max (empty/one-community frames → all zeros).
|
|
677
|
+
*/
|
|
678
|
+
declare function bridgingScores(edges: ReadonlyArray<{
|
|
679
|
+
a: NodeId;
|
|
680
|
+
b: NodeId;
|
|
681
|
+
weight?: number;
|
|
682
|
+
}>, labels: Map<NodeId, string>): Map<NodeId, Bridging>;
|
|
683
|
+
/** The frame's strongest connectors, ranked (ties: id order). */
|
|
684
|
+
declare function topBridges(bridging: Map<NodeId, Bridging>, n?: number): Array<{
|
|
685
|
+
id: NodeId;
|
|
686
|
+
score: number;
|
|
687
|
+
groups: string[];
|
|
688
|
+
}>;
|
|
689
|
+
|
|
690
|
+
export { type Bridging, 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 SpatialFinding, type SpatialRule, type TemporalConfig, type TemporalEvent, type TemporalReport, type TimeBucket, type TimeGrain, type Timeline, type TimelineEdge, type WeightedPair, type XY, blendPairs, bridgingScores, bucketFor, bucketKey, bucketRange, buildNarrationMaterials, buildTemporalReport, buildTimeline, burstScores, byNodeId, composeTargets, detectBursts, directTargets, extractEvents, flowFrames, forceLayout, geoPairs, hash32, indexGraph, labelPropagation, layoutSequence, leadScores, lensSnapshots, matchRules, mulberry32, nodeId, parseAmount, parseDateMs, projectBridges, rankEnrichmentCandidates, rankHotspots, reifiedBridgeTargets, resolvePath, rollup, seriesKeys, shouldNarrate, shuffle, snapshotAt, snapshotSeries, spatialFindings, splitTitleBody, synthesizeBurstSources, timePairs, topBridges, topFlows, totals, trackClusters };
|
package/dist/temporal/index.js
CHANGED
|
@@ -759,7 +759,202 @@ function flowFrames(events, opts) {
|
|
|
759
759
|
function topFlows(frame, n) {
|
|
760
760
|
return frame.flows.slice(0, n);
|
|
761
761
|
}
|
|
762
|
+
|
|
763
|
+
// src/temporal/spatial.ts
|
|
764
|
+
function matchRules(values, rules) {
|
|
765
|
+
if (!values) return [];
|
|
766
|
+
return rules.filter((r) => {
|
|
767
|
+
const v = values[r.metric];
|
|
768
|
+
if (v === void 0) return false;
|
|
769
|
+
return r.op === ">=" ? v >= r.threshold : v <= r.threshold;
|
|
770
|
+
}).map((r) => r.id);
|
|
771
|
+
}
|
|
772
|
+
function spatialFindings(set, metrics, rules, opts = {}) {
|
|
773
|
+
const { top = 20, requireMatch = false } = opts;
|
|
774
|
+
const best = /* @__PURE__ */ new Map();
|
|
775
|
+
for (const burst of detectBursts(set, opts)) {
|
|
776
|
+
if (!best.has(burst.seriesKey)) best.set(burst.seriesKey, burst);
|
|
777
|
+
}
|
|
778
|
+
const findings = [];
|
|
779
|
+
for (const [region, burst] of best) {
|
|
780
|
+
const values = metrics[region];
|
|
781
|
+
const matched = matchRules(values, rules);
|
|
782
|
+
if (requireMatch && matched.length === 0) continue;
|
|
783
|
+
const boost = rules.filter((r) => matched.includes(r.id)).reduce((a, r) => a + (r.weight ?? 1), 0);
|
|
784
|
+
const evidence = {};
|
|
785
|
+
for (const r of rules) {
|
|
786
|
+
const v = values?.[r.metric];
|
|
787
|
+
if (v !== void 0) evidence[r.metric] = v;
|
|
788
|
+
}
|
|
789
|
+
findings.push({ region, burst, matched, score: burst.score * (1 + boost), metrics: evidence });
|
|
790
|
+
}
|
|
791
|
+
return findings.sort((a, b) => b.score - a.score || (a.region < b.region ? -1 : 1)).slice(0, top);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/temporal/lenses.ts
|
|
795
|
+
function reifiedBridgeTargets(fragment, opts) {
|
|
796
|
+
const bridgesOf = /* @__PURE__ */ new Map();
|
|
797
|
+
const targetsOf = /* @__PURE__ */ new Map();
|
|
798
|
+
for (const e of fragment.edges) {
|
|
799
|
+
if (e.from.type !== opts.reified) continue;
|
|
800
|
+
const to = `${e.to.type}:${e.to.key}`;
|
|
801
|
+
if (e.label === opts.bridgeLabel) (bridgesOf.get(e.from.key) ?? bridgesOf.set(e.from.key, []).get(e.from.key)).push(to);
|
|
802
|
+
if (e.label === opts.targetLabel) (targetsOf.get(e.from.key) ?? targetsOf.set(e.from.key, []).get(e.from.key)).push(to);
|
|
803
|
+
}
|
|
804
|
+
const out = /* @__PURE__ */ new Map();
|
|
805
|
+
for (const [reifiedKey, bridges] of bridgesOf) {
|
|
806
|
+
for (const bridge of bridges) {
|
|
807
|
+
for (const target of targetsOf.get(reifiedKey) ?? []) {
|
|
808
|
+
(out.get(bridge) ?? out.set(bridge, /* @__PURE__ */ new Set()).get(bridge)).add(target);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return out;
|
|
813
|
+
}
|
|
814
|
+
function directTargets(fragment, label) {
|
|
815
|
+
const out = /* @__PURE__ */ new Map();
|
|
816
|
+
for (const e of fragment.edges) {
|
|
817
|
+
if (e.label !== label) continue;
|
|
818
|
+
const from = `${e.from.type}:${e.from.key}`;
|
|
819
|
+
(out.get(from) ?? out.set(from, /* @__PURE__ */ new Set()).get(from)).add(`${e.to.type}:${e.to.key}`);
|
|
820
|
+
}
|
|
821
|
+
return out;
|
|
822
|
+
}
|
|
823
|
+
function composeTargets(ab, bc) {
|
|
824
|
+
const out = /* @__PURE__ */ new Map();
|
|
825
|
+
for (const [a, bs] of ab) {
|
|
826
|
+
for (const b of bs) {
|
|
827
|
+
for (const c of bc.get(b) ?? []) {
|
|
828
|
+
(out.get(a) ?? out.set(a, /* @__PURE__ */ new Set()).get(a)).add(c);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return out;
|
|
833
|
+
}
|
|
834
|
+
function projectBridges(bridges, opts = {}) {
|
|
835
|
+
const { maxPerBridge = 12, viaCap = 4 } = opts;
|
|
836
|
+
const acc = /* @__PURE__ */ new Map();
|
|
837
|
+
for (const [bridge, targetSet] of [...bridges.entries()].sort((x, y) => x[0] < y[0] ? -1 : 1)) {
|
|
838
|
+
const targets = [...targetSet].sort();
|
|
839
|
+
if (targets.length < 2 || targets.length > maxPerBridge) continue;
|
|
840
|
+
for (let i = 0; i < targets.length; i++) {
|
|
841
|
+
for (let j = i + 1; j < targets.length; j++) {
|
|
842
|
+
const key = `${targets[i]}|${targets[j]}`;
|
|
843
|
+
const hit = acc.get(key) ?? { a: targets[i], b: targets[j], w: 0, via: [] };
|
|
844
|
+
hit.w += 1;
|
|
845
|
+
if (hit.via.length < viaCap) hit.via.push(bridge);
|
|
846
|
+
acc.set(key, hit);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return [...acc.keys()].sort().map((k) => acc.get(k));
|
|
851
|
+
}
|
|
852
|
+
function timePairs(arrivals, opts = {}) {
|
|
853
|
+
const { k = 4, halfLifeDays = 30 } = opts;
|
|
854
|
+
const halfLife = halfLifeDays * 864e5;
|
|
855
|
+
const rows = [...arrivals.entries()].filter(([, t]) => Number.isFinite(t)).sort((x, y) => x[1] - y[1] || (x[0] < y[0] ? -1 : 1));
|
|
856
|
+
const acc = /* @__PURE__ */ new Map();
|
|
857
|
+
for (let i = 0; i < rows.length; i++) {
|
|
858
|
+
for (let j = i + 1; j < Math.min(rows.length, i + 1 + k); j++) {
|
|
859
|
+
const [idA, tA] = rows[i];
|
|
860
|
+
const [idB, tB] = rows[j];
|
|
861
|
+
const w = Math.pow(0.5, Math.abs(tB - tA) / halfLife);
|
|
862
|
+
if (w < 0.01) break;
|
|
863
|
+
const [a, b] = idA < idB ? [idA, idB] : [idB, idA];
|
|
864
|
+
acc.set(`${a}|${b}`, { a, b, w, via: [] });
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return [...acc.keys()].sort().map((key) => acc.get(key));
|
|
868
|
+
}
|
|
869
|
+
function geoPairs(positions, opts = {}) {
|
|
870
|
+
const { k = 4, halfDist = 0.04 } = opts;
|
|
871
|
+
const rows = [...positions.entries()].sort((x, y) => x[0] < y[0] ? -1 : 1);
|
|
872
|
+
const acc = /* @__PURE__ */ new Map();
|
|
873
|
+
for (let i = 0; i < rows.length; i++) {
|
|
874
|
+
const dists = [];
|
|
875
|
+
for (let j = 0; j < rows.length; j++) {
|
|
876
|
+
if (i === j) continue;
|
|
877
|
+
const dx = rows[i][1].x - rows[j][1].x;
|
|
878
|
+
const dy = rows[i][1].y - rows[j][1].y;
|
|
879
|
+
dists.push({ j, d: Math.hypot(dx, dy) });
|
|
880
|
+
}
|
|
881
|
+
dists.sort((x, y) => x.d - y.d || x.j - y.j);
|
|
882
|
+
for (const { j, d } of dists.slice(0, k)) {
|
|
883
|
+
const w = Math.pow(0.5, d / halfDist);
|
|
884
|
+
if (w < 0.01) continue;
|
|
885
|
+
const [a, b] = rows[i][0] < rows[j][0] ? [rows[i][0], rows[j][0]] : [rows[j][0], rows[i][0]];
|
|
886
|
+
const key = `${a}|${b}`;
|
|
887
|
+
const prev = acc.get(key);
|
|
888
|
+
if (!prev || prev.w < w) acc.set(key, { a, b, w, via: [] });
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return [...acc.keys()].sort().map((key) => acc.get(key));
|
|
892
|
+
}
|
|
893
|
+
function blendPairs(sets) {
|
|
894
|
+
const acc = /* @__PURE__ */ new Map();
|
|
895
|
+
for (const { pairs, weight } of sets) {
|
|
896
|
+
if (weight <= 0 || pairs.length === 0) continue;
|
|
897
|
+
const max = Math.max(...pairs.map((p) => p.w));
|
|
898
|
+
for (const p of pairs) {
|
|
899
|
+
const key = `${p.a}|${p.b}`;
|
|
900
|
+
const hit = acc.get(key) ?? { a: p.a, b: p.b, w: 0, via: [] };
|
|
901
|
+
hit.w += p.w / max * weight;
|
|
902
|
+
for (const v of p.via) if (hit.via.length < 6 && !hit.via.includes(v)) hit.via.push(v);
|
|
903
|
+
acc.set(key, hit);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return [...acc.keys()].sort().map((key) => acc.get(key));
|
|
907
|
+
}
|
|
908
|
+
function lensSnapshots(arrivals, pairs, buckets) {
|
|
909
|
+
const ids = [...arrivals.keys()].sort();
|
|
910
|
+
return buckets.map((bucket) => {
|
|
911
|
+
const t = bucket.end - 1;
|
|
912
|
+
const nodes = ids.filter((id) => arrivals.get(id) <= t);
|
|
913
|
+
const active = new Set(nodes);
|
|
914
|
+
const edges = pairs.filter((p) => active.has(p.a) && active.has(p.b)).map((p) => ({ a: p.a, b: p.b, weight: p.w }));
|
|
915
|
+
const activity = new Map(nodes.map((id) => [id, arrivals.get(id)]));
|
|
916
|
+
return { t, nodes, edges, activity };
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/temporal/bridges.ts
|
|
921
|
+
function bridgingScores(edges, labels) {
|
|
922
|
+
const cross = /* @__PURE__ */ new Map();
|
|
923
|
+
const foreign = /* @__PURE__ */ new Map();
|
|
924
|
+
const touch = (id, otherLabel, w) => {
|
|
925
|
+
cross.set(id, (cross.get(id) ?? 0) + w);
|
|
926
|
+
(foreign.get(id) ?? foreign.set(id, /* @__PURE__ */ new Set()).get(id)).add(otherLabel);
|
|
927
|
+
};
|
|
928
|
+
for (const e of edges) {
|
|
929
|
+
const la = labels.get(e.a);
|
|
930
|
+
const lb = labels.get(e.b);
|
|
931
|
+
if (la === void 0 || lb === void 0 || la === lb) continue;
|
|
932
|
+
const w = e.weight ?? 1;
|
|
933
|
+
touch(e.a, lb, w);
|
|
934
|
+
touch(e.b, la, w);
|
|
935
|
+
}
|
|
936
|
+
let max = 0;
|
|
937
|
+
const raw = /* @__PURE__ */ new Map();
|
|
938
|
+
for (const [id, w] of cross) {
|
|
939
|
+
const r = w * foreign.get(id).size;
|
|
940
|
+
raw.set(id, r);
|
|
941
|
+
if (r > max) max = r;
|
|
942
|
+
}
|
|
943
|
+
const out = /* @__PURE__ */ new Map();
|
|
944
|
+
for (const [id] of labels) {
|
|
945
|
+
const r = raw.get(id) ?? 0;
|
|
946
|
+
const own = labels.get(id);
|
|
947
|
+
const groups = r > 0 ? [own, ...[...foreign.get(id)].sort()] : [own];
|
|
948
|
+
out.set(id, { score: max > 0 ? r / max : 0, groups });
|
|
949
|
+
}
|
|
950
|
+
return out;
|
|
951
|
+
}
|
|
952
|
+
function topBridges(bridging, n = 5) {
|
|
953
|
+
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 }));
|
|
954
|
+
}
|
|
762
955
|
export {
|
|
956
|
+
blendPairs,
|
|
957
|
+
bridgingScores,
|
|
763
958
|
bucketFor,
|
|
764
959
|
bucketKey,
|
|
765
960
|
bucketRange,
|
|
@@ -768,21 +963,28 @@ export {
|
|
|
768
963
|
buildTimeline,
|
|
769
964
|
burstScores,
|
|
770
965
|
byNodeId,
|
|
966
|
+
composeTargets,
|
|
771
967
|
detectBursts,
|
|
968
|
+
directTargets,
|
|
772
969
|
extractEvents,
|
|
773
970
|
flowFrames,
|
|
774
971
|
forceLayout,
|
|
972
|
+
geoPairs,
|
|
775
973
|
hash32,
|
|
776
974
|
indexGraph,
|
|
777
975
|
labelPropagation,
|
|
778
976
|
layoutSequence,
|
|
779
977
|
leadScores,
|
|
978
|
+
lensSnapshots,
|
|
979
|
+
matchRules,
|
|
780
980
|
mulberry32,
|
|
781
981
|
nodeId,
|
|
782
982
|
parseAmount,
|
|
783
983
|
parseDateMs,
|
|
984
|
+
projectBridges,
|
|
784
985
|
rankEnrichmentCandidates,
|
|
785
986
|
rankHotspots,
|
|
987
|
+
reifiedBridgeTargets,
|
|
786
988
|
resolvePath,
|
|
787
989
|
rollup,
|
|
788
990
|
seriesKeys,
|
|
@@ -790,8 +992,11 @@ export {
|
|
|
790
992
|
shuffle,
|
|
791
993
|
snapshotAt,
|
|
792
994
|
snapshotSeries,
|
|
995
|
+
spatialFindings,
|
|
793
996
|
splitTitleBody,
|
|
794
997
|
synthesizeBurstSources,
|
|
998
|
+
timePairs,
|
|
999
|
+
topBridges,
|
|
795
1000
|
topFlows,
|
|
796
1001
|
totals,
|
|
797
1002
|
trackClusters
|