mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
feat(dashboard): aggregateContainerEdges (directional, types-set)
This commit is contained in:
+61
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { aggregateContainerEdges } from "../edgeAggregation";
|
||||
import type { GraphEdge } from "@understand-anything/core/types";
|
||||
|
||||
const ce = (source: string, target: string, type: string = "calls"): GraphEdge =>
|
||||
({ source, target, type }) as GraphEdge;
|
||||
|
||||
describe("aggregateContainerEdges", () => {
|
||||
it("returns empty arrays for empty input", () => {
|
||||
const r = aggregateContainerEdges([], new Map());
|
||||
expect(r.intraContainer).toEqual([]);
|
||||
expect(r.interContainerAggregated).toEqual([]);
|
||||
});
|
||||
|
||||
it("preserves intra-container edges as-is", () => {
|
||||
const m = new Map([
|
||||
["a", "auth"],
|
||||
["b", "auth"],
|
||||
]);
|
||||
const r = aggregateContainerEdges([ce("a", "b")], m);
|
||||
expect(r.intraContainer).toHaveLength(1);
|
||||
expect(r.interContainerAggregated).toEqual([]);
|
||||
});
|
||||
|
||||
it("merges multiple same-direction inter edges into one", () => {
|
||||
const m = new Map([
|
||||
["a", "auth"],
|
||||
["b", "auth"],
|
||||
["c", "cart"],
|
||||
["d", "cart"],
|
||||
]);
|
||||
const edges = [ce("a", "c"), ce("a", "d"), ce("b", "c", "imports")];
|
||||
const r = aggregateContainerEdges(edges, m);
|
||||
expect(r.interContainerAggregated).toHaveLength(1);
|
||||
const agg = r.interContainerAggregated[0];
|
||||
expect(agg.sourceContainerId).toBe("auth");
|
||||
expect(agg.targetContainerId).toBe("cart");
|
||||
expect(agg.count).toBe(3);
|
||||
expect(agg.types.sort()).toEqual(["calls", "imports"]);
|
||||
});
|
||||
|
||||
it("treats opposite directions as separate aggregated edges", () => {
|
||||
const m = new Map([
|
||||
["a", "auth"],
|
||||
["c", "cart"],
|
||||
]);
|
||||
const r = aggregateContainerEdges([ce("a", "c"), ce("c", "a")], m);
|
||||
expect(r.interContainerAggregated).toHaveLength(2);
|
||||
const dirs = r.interContainerAggregated.map(
|
||||
(e) => `${e.sourceContainerId}→${e.targetContainerId}`,
|
||||
);
|
||||
expect(dirs.sort()).toEqual(["auth→cart", "cart→auth"]);
|
||||
});
|
||||
|
||||
it("ignores edges whose endpoints have no container mapping", () => {
|
||||
const m = new Map([["a", "auth"]]);
|
||||
const r = aggregateContainerEdges([ce("a", "z")], m);
|
||||
expect(r.intraContainer).toEqual([]);
|
||||
expect(r.interContainerAggregated).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { KnowledgeGraph } from "@understand-anything/core/types";
|
||||
import type { GraphEdge, KnowledgeGraph } from "@understand-anything/core/types";
|
||||
|
||||
export interface LayerEdgeAggregation {
|
||||
sourceLayerId: string;
|
||||
@@ -133,3 +133,73 @@ export function findCrossLayerFileNodes(
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface AggregatedContainerEdge {
|
||||
sourceContainerId: string;
|
||||
targetContainerId: string;
|
||||
count: number;
|
||||
types: string[];
|
||||
}
|
||||
|
||||
export interface ContainerEdgeBuckets {
|
||||
intraContainer: GraphEdge[];
|
||||
interContainerAggregated: AggregatedContainerEdge[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Bucket edges into intra-container (preserved) and inter-container
|
||||
* (aggregated by directed (source,target) container pair).
|
||||
*
|
||||
* Direction is significant: A→B and B→A produce two independent
|
||||
* aggregated edges. Edges whose endpoints have no container mapping
|
||||
* are dropped (treat them as pre-filtered).
|
||||
*/
|
||||
export function aggregateContainerEdges(
|
||||
edges: GraphEdge[],
|
||||
nodeToContainer: Map<string, string>,
|
||||
): ContainerEdgeBuckets {
|
||||
const intra: GraphEdge[] = [];
|
||||
const interMap = new Map<
|
||||
string,
|
||||
{
|
||||
sourceContainerId: string;
|
||||
targetContainerId: string;
|
||||
count: number;
|
||||
types: Set<string>;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const e of edges) {
|
||||
const sc = nodeToContainer.get(e.source);
|
||||
const tc = nodeToContainer.get(e.target);
|
||||
if (!sc || !tc) continue;
|
||||
if (sc === tc) {
|
||||
intra.push(e);
|
||||
continue;
|
||||
}
|
||||
const key = `${sc} ${tc}`;
|
||||
const existing = interMap.get(key);
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
existing.types.add(e.type);
|
||||
} else {
|
||||
interMap.set(key, {
|
||||
sourceContainerId: sc,
|
||||
targetContainerId: tc,
|
||||
count: 1,
|
||||
types: new Set([e.type]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const interContainerAggregated: AggregatedContainerEdge[] = [...interMap.values()].map(
|
||||
(v) => ({
|
||||
sourceContainerId: v.sourceContainerId,
|
||||
targetContainerId: v.targetContainerId,
|
||||
count: v.count,
|
||||
types: [...v.types],
|
||||
}),
|
||||
);
|
||||
|
||||
return { intraContainer: intra, interContainerAggregated };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user