mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
feat(dashboard): deriveContainers community fallback via Louvain
This commit is contained in:
+37
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { deriveContainers } from "../containers";
|
||||
import type { GraphNode } from "@understand-anything/core/types";
|
||||
import type { GraphNode, GraphEdge } from "@understand-anything/core/types";
|
||||
|
||||
function node(id: string, filePath?: string): GraphNode {
|
||||
return {
|
||||
@@ -87,3 +87,39 @@ describe("deriveContainers — folder strategy", () => {
|
||||
expect(ungrouped.sort()).toEqual(["a", "b", "c"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deriveContainers — community fallback", () => {
|
||||
it("falls back to communities when only one folder present", () => {
|
||||
const nodes = Array.from({ length: 10 }, (_, i) =>
|
||||
node(`n${i}`, `services/n${i}.go`),
|
||||
);
|
||||
// Two clusters of 5 nodes; densely connected within, no edges between
|
||||
const edges: GraphEdge[] = [];
|
||||
for (const i of [0, 1, 2, 3, 4]) {
|
||||
for (const j of [0, 1, 2, 3, 4]) {
|
||||
if (i !== j) edges.push({ source: `n${i}`, target: `n${j}`, type: "calls" } as GraphEdge);
|
||||
}
|
||||
}
|
||||
for (const i of [5, 6, 7, 8, 9]) {
|
||||
for (const j of [5, 6, 7, 8, 9]) {
|
||||
if (i !== j) edges.push({ source: `n${i}`, target: `n${j}`, type: "calls" } as GraphEdge);
|
||||
}
|
||||
}
|
||||
const { containers } = deriveContainers(nodes, edges);
|
||||
expect(containers.length).toBeGreaterThanOrEqual(2);
|
||||
for (const c of containers) {
|
||||
expect(c.strategy).toBe("community");
|
||||
expect(c.name).toMatch(/^Cluster [A-Z]$/);
|
||||
}
|
||||
});
|
||||
|
||||
it("falls back when one folder holds > 60%", () => {
|
||||
const nodes = [
|
||||
...Array.from({ length: 8 }, (_, i) => node(`big${i}`, `big/file${i}.go`)),
|
||||
node("a", "small1/a.go"),
|
||||
node("b", "small2/b.go"),
|
||||
];
|
||||
const { containers } = deriveContainers(nodes, []);
|
||||
expect(containers.every((c) => c.strategy === "community")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
import Graph from "graphology";
|
||||
import louvain from "graphology-communities-louvain";
|
||||
import type { GraphEdge } from "@understand-anything/core/types";
|
||||
|
||||
/** Returns [nodeId, communityId] for every node provided. */
|
||||
/**
|
||||
* Run Louvain community detection over the provided node set and the
|
||||
* subset of edges whose endpoints are both in the set. Returns a map of
|
||||
* nodeId → communityId. Disconnected nodes get unique community ids so
|
||||
* they don't collapse into a single cluster.
|
||||
*/
|
||||
export function detectCommunities(
|
||||
_nodeIds: string[],
|
||||
_edges: GraphEdge[],
|
||||
nodeIds: string[],
|
||||
edges: GraphEdge[],
|
||||
): Map<string, number> {
|
||||
// Real implementation arrives in Task 3. Stub: every node in community 0.
|
||||
const m = new Map<string, number>();
|
||||
for (const id of _nodeIds) m.set(id, 0);
|
||||
return m;
|
||||
const ids = new Set(nodeIds);
|
||||
const g = new Graph({ type: "undirected", multi: false });
|
||||
for (const id of nodeIds) g.addNode(id);
|
||||
for (const e of edges) {
|
||||
if (!ids.has(e.source) || !ids.has(e.target)) continue;
|
||||
if (e.source === e.target) continue;
|
||||
if (g.hasEdge(e.source, e.target)) continue;
|
||||
g.addEdge(e.source, e.target);
|
||||
}
|
||||
// graphology-communities-louvain returns Record<nodeId, communityId>
|
||||
const result = louvain(g) as Record<string, number>;
|
||||
const map = new Map<string, number>();
|
||||
for (const id of nodeIds) {
|
||||
map.set(id, result[id] ?? -1);
|
||||
}
|
||||
// Reassign disconnected nodes (community -1) to unique ids past the max
|
||||
let next =
|
||||
Math.max(...Array.from(map.values()).filter((v) => v >= 0), -1) + 1;
|
||||
for (const [id, c] of map) {
|
||||
if (c === -1) {
|
||||
map.set(id, next++);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user