How Krusgraph uses graph theory
The plan is a graph: tables are nodes, relationships are edges from the many side to the one side, and every edge carries a certainty from its rule (declared or confirmed 1.0; checked against the data 0.9; R1 0.8; R2 0.6; R3 and R4 0.5). fabric_backend/graphs.py is plain Python and deterministic: the same plan always gives the same answer. What each method does for you, in plain terms:
- Roles (
analyze_schema): a table that points at several others and nothing points at is a fact table; one that is pointed at and points at little is a dimension; a table whose key is just two references (a junction) is a bridge. The numbers that decided each role are in the answer, so you can disagree. - Centrality: in/out degree says what a table refers to and what refers to it; betweenness says how often a table sits on the route between two others; PageRank on the directed graph says where the references end up (shared dimensions score high). The top ten is where to start reading an unknown schema.
- Subject areas: label propagation on the undirected graph groups tables that are more connected to each other than to the rest; each area is named after its most central table. A first cut at the domains in a lakehouse.
- Ambiguity: every relationship beyond a spanning forest closes a cycle, and any two tables on a cycle can be reached two ways. That is exactly what makes filter paths ambiguous in a Power BI model, so the answer lists the cycles and the pairs, and the maximum spanning forest (highest certainty first) says which relationships can be active at once;
semantic_model_draftuses it for active/inactive. - Paths (
join_path): the shortest path counts hops; the safest path (Dijkstra on cost = 1/certainty) prefers declared and R1 links. When they differ the answer says why. - Link prediction (
suggest_relationships): a column with a reference suffix and the type of another table's key, in no relationship yet, is a candidate; a name that resembles the target, or a target the table already reaches in two hops, ranks it higher; then the data decides — the share of its values that exist in the target key. Nothing is applied; you pass the suggestion you accept asconfirmed.
Unit-tested on the synthetic shop schema and the public benchmark schemas; analyze_schema and suggest_relationships were run against the demo workspace (synthetic shop data) on 26 October 2026: the one column outside every relationship, orders.ship_to_id, came back with every value present in addresses.address_id (15 distinct values) and well below that for the other candidates. On 27 October 2026, after the ranking was fixed, addresses.address_id came first before the data check (3.94 against 1.5 or less) and held for every value after it (inclusion 1.0). The guided demo walks through this column on the synthetic sample: the suggestion, its data check and the decision that stays yours.