> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Org Chart from JSON Exports

> Turn two flat JSON exports of companies and people into a connected org-chart graph, with deterministic node IDs doing the deduplication

export const orgChartGraph = {
  "nodes": [{
    "id": "pers-6",
    "name": "Emma Thompson",
    "type": "Person"
  }, {
    "id": "pers-2",
    "name": "Bob Williams",
    "type": "Person"
  }, {
    "id": "pers-1",
    "name": "Alice Johnson",
    "type": "Person"
  }, {
    "id": "depa-6",
    "name": "HR",
    "type": "Department"
  }, {
    "id": "depa-11",
    "name": "Sales",
    "type": "Department"
  }, {
    "id": "depa-8",
    "name": "Manufacturing",
    "type": "Department"
  }, {
    "id": "comp-5",
    "name": "TechNova Inc.",
    "type": "Company"
  }, {
    "id": "depa-5",
    "name": "Engineering",
    "type": "Department"
  }, {
    "id": "depa-9",
    "name": "Marketing",
    "type": "Department"
  }, {
    "id": "pers-7",
    "name": "Jane Smith",
    "type": "Person"
  }, {
    "id": "depa-3",
    "name": "Administration",
    "type": "Department"
  }, {
    "id": "depa-7",
    "name": "Healthcare",
    "type": "Department"
  }, {
    "id": "pers-8",
    "name": "John Doe",
    "type": "Person"
  }, {
    "id": "depa-10",
    "name": "Research & Development",
    "type": "Department"
  }, {
    "id": "comp-6",
    "name": "Company",
    "type": "CompanyType"
  }, {
    "id": "comp-2",
    "name": "MediCare Plus",
    "type": "Company"
  }, {
    "id": "depa-2",
    "name": "Accounting",
    "type": "Department"
  }, {
    "id": "comp-3",
    "name": "NextGen Robotics",
    "type": "Company"
  }, {
    "id": "comp-1",
    "name": "GreenFuture Solutions",
    "type": "Company"
  }, {
    "id": "pers-10",
    "name": "Sophia White",
    "type": "Person"
  }, {
    "id": "pers-5",
    "name": "Emily Davis",
    "type": "Person"
  }, {
    "id": "comp-4",
    "name": "Skyline Financials",
    "type": "Company"
  }, {
    "id": "depa-1",
    "name": "AI Development",
    "type": "Department"
  }, {
    "id": "depa-4",
    "name": "Customer Support",
    "type": "Department"
  }, {
    "id": "pers-9",
    "name": "Michael Brown",
    "type": "Person"
  }, {
    "id": "pers-4",
    "name": "David Wilson",
    "type": "Person"
  }, {
    "id": "pers-3",
    "name": "Chris Martin",
    "type": "Person"
  }],
  "links": [{
    "source": "depa-6",
    "target": "pers-10",
    "relation": "employees",
    "edge_text": "HR employees Sophia White."
  }, {
    "source": "depa-11",
    "target": "pers-1",
    "relation": "employees",
    "edge_text": "Sales employees Alice Johnson."
  }, {
    "source": "depa-8",
    "target": "pers-3",
    "relation": "employees",
    "edge_text": "Manufacturing employees Chris Martin."
  }, {
    "source": "comp-5",
    "target": "depa-5",
    "relation": "departments",
    "edge_text": "TechNova Inc. departments Engineering."
  }, {
    "source": "comp-5",
    "target": "depa-9",
    "relation": "departments",
    "edge_text": "TechNova Inc. departments Marketing."
  }, {
    "source": "comp-5",
    "target": "comp-6",
    "relation": "is_type",
    "edge_text": "TechNova Inc. is type Company."
  }, {
    "source": "depa-5",
    "target": "pers-8",
    "relation": "employees",
    "edge_text": "Engineering employees John Doe."
  }, {
    "source": "depa-9",
    "target": "pers-7",
    "relation": "employees",
    "edge_text": "Marketing employees Jane Smith."
  }, {
    "source": "depa-3",
    "target": "pers-4",
    "relation": "employees",
    "edge_text": "Administration employees David Wilson."
  }, {
    "source": "depa-7",
    "target": "pers-5",
    "relation": "employees",
    "edge_text": "Healthcare employees Emily Davis."
  }, {
    "source": "comp-2",
    "target": "comp-6",
    "relation": "is_type",
    "edge_text": "MediCare Plus is type Company."
  }, {
    "source": "comp-2",
    "target": "depa-7",
    "relation": "departments",
    "edge_text": "MediCare Plus departments Healthcare."
  }, {
    "source": "comp-2",
    "target": "depa-3",
    "relation": "departments",
    "edge_text": "MediCare Plus departments Administration."
  }, {
    "source": "depa-2",
    "target": "pers-9",
    "relation": "employees",
    "edge_text": "Accounting employees Michael Brown."
  }, {
    "source": "comp-3",
    "target": "comp-6",
    "relation": "is_type",
    "edge_text": "NextGen Robotics is type Company."
  }, {
    "source": "comp-3",
    "target": "depa-1",
    "relation": "departments",
    "edge_text": "NextGen Robotics departments AI Development."
  }, {
    "source": "comp-3",
    "target": "depa-8",
    "relation": "departments",
    "edge_text": "NextGen Robotics departments Manufacturing."
  }, {
    "source": "comp-3",
    "target": "depa-6",
    "relation": "departments",
    "edge_text": "NextGen Robotics departments HR."
  }, {
    "source": "comp-1",
    "target": "comp-6",
    "relation": "is_type",
    "edge_text": "GreenFuture Solutions is type Company."
  }, {
    "source": "comp-1",
    "target": "depa-10",
    "relation": "departments",
    "edge_text": "GreenFuture Solutions departments Research & Development."
  }, {
    "source": "comp-1",
    "target": "depa-11",
    "relation": "departments",
    "edge_text": "GreenFuture Solutions departments Sales."
  }, {
    "source": "comp-1",
    "target": "depa-4",
    "relation": "departments",
    "edge_text": "GreenFuture Solutions departments Customer Support."
  }, {
    "source": "comp-4",
    "target": "comp-6",
    "relation": "is_type",
    "edge_text": "Skyline Financials is type Company."
  }, {
    "source": "comp-4",
    "target": "depa-2",
    "relation": "departments",
    "edge_text": "Skyline Financials departments Accounting."
  }, {
    "source": "depa-1",
    "target": "pers-6",
    "relation": "employees",
    "edge_text": "AI Development employees Emma Thompson."
  }, {
    "source": "depa-4",
    "target": "pers-2",
    "relation": "employees",
    "edge_text": "Customer Support employees Bob Williams."
  }]
};

export const CogneeGraph = ({data, height = 440, label, fallbackSrc, fallbackAlt}) => {
  const D3_SRC = "https://d3js.org/d3.v7.min.js";
  const COLORS = {
    Entity: "#6510F4",
    EntityType: "#A78BFA",
    DocumentChunk: "#78716C",
    TextSummary: "#2563EB",
    TextDocument: "#A8A29E",
    Company: "#6510F4",
    Department: "#A550FF",
    Person: "#0EA5E9",
    CompanyType: "#94A3B8",
    _default: "#94A3B8"
  };
  const loadD3 = () => {
    if (typeof window === "undefined") return Promise.reject(new Error("no window"));
    if (window.d3 && window.d3.forceSimulation) return Promise.resolve(window.d3);
    if (window.__cogneeD3Promise) return window.__cogneeD3Promise;
    window.__cogneeD3Promise = new Promise((resolve, reject) => {
      const script = document.createElement("script");
      script.src = D3_SRC;
      script.onload = () => window.d3 && window.d3.forceSimulation ? resolve(window.d3) : reject(new Error("d3 loaded without the force module"));
      script.onerror = () => reject(new Error("could not load d3 from " + D3_SRC));
      document.head.appendChild(script);
    });
    return window.__cogneeD3Promise;
  };
  const isDark = () => document.documentElement.classList.contains("dark");
  const colorForNode = d => d.color || COLORS[d.type] || COLORS._default;
  const labelFor = d => {
    const name = d.name || "";
    return name.length > 30 ? name.slice(0, 29) + "…" : name;
  };
  const drawGraph = (host, data, d3, height, label) => {
    const width = host.clientWidth || 680;
    const nodes = data.nodes.map(n => Object.assign({}, n));
    const links = data.links.map(l => Object.assign({}, l));
    const degree = {};
    links.forEach(l => {
      degree[l.source] = (degree[l.source] || 0) + 1;
      degree[l.target] = (degree[l.target] || 0) + 1;
    });
    host.textContent = "";
    const svg = d3.select(host).append("svg").attr("width", "100%").attr("height", height).attr("viewBox", [0, 0, width, height]).attr("role", "img").attr("aria-label", label || "Knowledge graph").style("display", "block").style("max-width", "100%").style("cursor", "grab");
    const root = svg.append("g");
    const zoom = d3.zoom().scaleExtent([0.3, 6]).on("zoom", event => root.attr("transform", event.transform));
    svg.call(zoom);
    const link = root.append("g").attr("stroke-linecap", "round").selectAll("line").data(links).join("line").attr("stroke", () => isDark() ? "#4b5563" : "#cbd5e1").attr("stroke-width", 1.2);
    const node = root.append("g").selectAll("g").data(nodes).join("g").style("cursor", "pointer");
    node.append("circle").attr("r", d => 6 + Math.min(degree[d.id] || 0, 6)).attr("fill", d => colorForNode(d)).attr("stroke", () => isDark() ? "#111827" : "#ffffff").attr("stroke-width", 1.5);
    node.append("title").text(d => d.name + " — " + d.type + (d.description ? "\n" + d.description : ""));
    node.append("text").text(d => labelFor(d)).attr("x", d => 9 + Math.min(degree[d.id] || 0, 6)).attr("y", 4).attr("font-size", 11).attr("font-family", "system-ui, -apple-system, sans-serif").attr("fill", () => isDark() ? "#e5e7eb" : "#374151").attr("paint-order", "stroke").attr("stroke", () => isDark() ? "#0b0b0d" : "#ffffff").attr("stroke-width", 3);
    const neighbors = {};
    links.forEach(l => {
      (neighbors[l.source] = neighbors[l.source] || ({}))[l.target] = true;
      (neighbors[l.target] = neighbors[l.target] || ({}))[l.source] = true;
    });
    const hasDetails = nodes.some(n => n.description) || links.some(l => l.edge_text);
    const details = hasDetails ? document.createElement("div") : null;
    const DETAILS_HINT = "Hover a node to read its description and the text stored on its edges.";
    const muted = () => isDark() ? "#9ca3af" : "#6b7280";
    const strong = () => isDark() ? "#e5e7eb" : "#374151";
    const showDetails = d => {
      if (!details) return;
      details.textContent = "";
      const head = document.createElement("div");
      head.style.cssText = "font-weight:600;color:" + strong();
      head.textContent = d.name + " · " + d.type;
      details.appendChild(head);
      if (d.description) {
        const desc = document.createElement("div");
        desc.style.cssText = "margin-top:4px;color:" + strong();
        desc.textContent = d.description;
        details.appendChild(desc);
      }
      const incident = links.filter(l => l.source.id === d.id || l.target.id === d.id);
      if (incident.length) {
        const list = document.createElement("ul");
        list.style.cssText = "margin:6px 0 0;padding-left:18px;color:" + muted();
        incident.forEach(l => {
          const item = document.createElement("li");
          item.style.cssText = "margin:2px 0";
          const rel = document.createElement("code");
          rel.style.cssText = "font-size:11px";
          rel.textContent = l.source.name + " —" + (l.relation || "") + "→ " + l.target.name;
          item.appendChild(rel);
          if (l.edge_text) {
            item.appendChild(document.createTextNode(" " + l.edge_text));
          }
          list.appendChild(item);
        });
        details.appendChild(list);
      }
    };
    const resetDetails = () => {
      if (!details) return;
      details.textContent = DETAILS_HINT;
      details.style.color = muted();
    };
    node.on("mouseenter", (event, d) => {
      node.style("opacity", o => o.id === d.id || neighbors[d.id] && neighbors[d.id][o.id] ? 1 : 0.15);
      link.style("opacity", l => l.source.id === d.id || l.target.id === d.id ? 1 : 0.08);
      showDetails(d);
    }).on("mouseleave", () => {
      node.style("opacity", 1);
      link.style("opacity", 1);
      resetDetails();
    });
    const draw = () => {
      link.attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y);
      node.attr("transform", d => "translate(" + d.x + "," + d.y + ")");
    };
    const setLabelScale = counter => {
      node.selectAll("text").attr("font-size", 11 * counter).attr("stroke-width", 3 * counter).attr("y", 4 * counter).attr("x", d => (9 + Math.min(degree[d.id] || 0, 6)) * counter);
    };
    const scaleFor = box => {
      const pad = 14;
      return Math.min((width - pad * 2) / box.width, (height - pad * 2) / box.height, 1.4);
    };
    const fit = () => {
      let box = root.node().getBBox();
      if (!(box.width > 0) || !(box.height > 0)) return;
      let scale = scaleFor(box);
      for (let pass = 0; pass < 3; pass++) {
        setLabelScale(Math.min(Math.max(1 / scale, 1), 1.9));
        box = root.node().getBBox();
        scale = scaleFor(box);
      }
      const tx = (width - scale * (box.x * 2 + box.width)) / 2;
      const ty = (height - scale * (box.y * 2 + box.height)) / 2;
      svg.call(zoom.transform, d3.zoomIdentity.translate(tx, ty).scale(scale));
    };
    const simulation = d3.forceSimulation(nodes).force("link", d3.forceLink(links).id(d => d.id).distance(70).strength(0.6)).force("charge", d3.forceManyBody().strength(-320)).force("center", d3.forceCenter(width / 2, height / 2)).force("collide", d3.forceCollide(34));
    simulation.stop();
    for (let i = 0; i < 320; i++) simulation.tick();
    draw();
    fit();
    simulation.on("tick", draw);
    node.call(d3.drag().on("start", (event, d) => {
      if (!event.active) simulation.alphaTarget(0.3).restart();
      d.fx = d.x;
      d.fy = d.y;
    }).on("drag", (event, d) => {
      d.fx = event.x;
      d.fy = event.y;
    }).on("end", (event, d) => {
      if (!event.active) simulation.alphaTarget(0);
      d.fx = null;
      d.fy = null;
    }));
    const counts = {};
    const swatch = {};
    nodes.forEach(n => {
      counts[n.type] = (counts[n.type] || 0) + 1;
      if (!swatch[n.type]) swatch[n.type] = colorForNode(n);
    });
    const legend = document.createElement("div");
    legend.style.cssText = "display:flex;flex-wrap:wrap;gap:12px;padding:8px 2px 0;font-size:12px;" + "font-family:system-ui,-apple-system,sans-serif;color:" + (isDark() ? "#9ca3af" : "#6b7280");
    Object.keys(counts).sort().forEach(type => {
      const item = document.createElement("span");
      item.style.cssText = "display:inline-flex;align-items:center;gap:5px";
      const dot = document.createElement("span");
      dot.style.cssText = "width:9px;height:9px;border-radius:50%;background:" + swatch[type];
      item.appendChild(dot);
      item.appendChild(document.createTextNode(type + " (" + counts[type] + ")"));
      legend.appendChild(item);
    });
    host.appendChild(legend);
    if (details) {
      details.style.cssText = "margin-top:8px;padding:8px 10px;border-radius:6px;font-size:12px;" + "line-height:1.45;font-family:system-ui,-apple-system,sans-serif;" + "min-height:3.2em;background:" + (isDark() ? "#111827" : "#f8fafc");
      resetDetails();
      host.appendChild(details);
    }
    return () => simulation.stop();
  };
  const hostRef = useRef(null);
  const fallbackRef = useRef(null);
  useEffect(() => {
    let cancelled = false;
    let teardown = null;
    let themeObserver = null;
    let sizeObserver = null;
    const hideFallback = () => {
      if (fallbackRef.current) fallbackRef.current.style.display = "none";
    };
    const whenVisible = host => host.offsetParent !== null ? Promise.resolve() : new Promise(resolve => {
      const done = () => {
        if (sizeObserver) sizeObserver.disconnect();
        resolve();
      };
      sizeObserver = new ResizeObserver(() => {
        if (host.offsetParent !== null) done();
      });
      sizeObserver.observe(host);
      setTimeout(done, 10000);
    });
    loadD3().then(d3 => hostRef.current ? whenVisible(hostRef.current).then(() => d3) : d3).then(d3 => {
      if (cancelled || !hostRef.current) return;
      teardown = drawGraph(hostRef.current, data, d3, height, label);
      hideFallback();
      let wasDark = isDark();
      themeObserver = new MutationObserver(() => {
        if (isDark() === wasDark || cancelled || !hostRef.current) return;
        wasDark = isDark();
        if (teardown) teardown();
        teardown = drawGraph(hostRef.current, data, d3, height, label);
      });
      themeObserver.observe(document.documentElement, {
        attributes: true,
        attributeFilter: ["class"]
      });
    }).catch(err => {
      console.warn("[CogneeGraph] falling back to the static image:", err.message);
    });
    return () => {
      cancelled = true;
      if (teardown) teardown();
      if (themeObserver) themeObserver.disconnect();
      if (sizeObserver) sizeObserver.disconnect();
    };
  }, [data, height, label]);
  return <div style={{
    position: "relative",
    minHeight: height
  }} data-cognee-graph="">
      <div ref={hostRef} style={{
    minHeight: height
  }} />
      {fallbackSrc ? <img ref={fallbackRef} src={fallbackSrc} alt={fallbackAlt || label || "Knowledge graph"} style={{
    position: "absolute",
    top: 0,
    left: 0,
    width: "100%",
    height: height,
    objectFit: "contain"
  }} /> : null}
    </div>;
};

You have your org data as plain exports — one JSON file listing companies and their departments, another listing people and where they work — and you want it in memory as a connected graph of companies, departments, and employees, without an LLM re-deriving structure you already know.

## What You'll Build

Two JSON files — `companies.json` and `people.json` — are loaded and mapped onto typed `DataPoint` classes, then written straight into the graph and vector stores by a two-task custom pipeline: no chunking, no extraction, no cognify pass. What comes out is an org chart you can open in a browser: `Company` nodes linked to their `Department` nodes, each department linked to the `Person` nodes that work in it, with every name embedded for semantic search. Node IDs are derived from the data itself, so the same department name reaching the pipeline twice lands on one node rather than two.

The script closes by asking the graph a question, so the same run that builds the org chart also shows it is retrievable.

The complete runnable script is in
[`examples/demos/custom_pipelines/organizational_hierarchy`](https://github.com/topoteretes/cognee/tree/main/examples/demos/custom_pipelines/organizational_hierarchy) —
this page walks through its key moments rather than reproducing it.

## Features in Play

* [DataPoints](/core-concepts/building-blocks/datapoints) — `Person`, `Department`, `Company`, and `CompanyType` are the node types; `index_fields` decides what gets embedded and `identity_fields` derives the deterministic ID that collapses repeats
* [Custom Data Models](/guides/custom-data-models) — `add_data_points` persists the typed objects and their nesting as nodes and edges, with no LLM in the path
* [Custom Tasks and Pipelines](/guides/custom-tasks-pipelines) — two `Task` objects run by `run_tasks` against a dataset: one maps JSON to DataPoints, one stores them
* [Graph Visualization](/guides/graph-visualization) — the script renders the org chart to an HTML file you can open
* [Search](/core-concepts/main-operations/legacy-operations/search) — the script finishes with a `GRAPH_COMPLETION` query run with `only_context=True`, proving the hand-declared nodes are retrievable like any other memory without pulling an LLM into the demo

## How It Looks

This is the graph the script writes, loaded from a real run of it. Drag a node to pull it out of the tangle, scroll to zoom, and hover one to fade out everything it does not touch — hovering a department is the quickest way to see exactly who works in it.

<Frame caption="The org chart from a real run: one CompanyType node at the centre, five companies around it, each linked to the departments it declares, and each department to the people in it. Hover a node to list its edges with the text the run stored on each.">
  <CogneeGraph data={orgChartGraph} height={440} label="Org chart knowledge graph with 27 nodes and 26 edges" fallbackSrc="/images/examples/org-chart-graph.png" fallbackAlt="Force-directed graph with a central CompanyType node joined to five Company nodes, each Company joined to its Department nodes, and each Department joined to the Person nodes working in it, with one company hovered so the panel below lists its type and department edges." />
</Frame>

Every node and edge here comes from the run's `visualize_graph` export, so the shape is exactly what lands in your graph store: `Research & Development` sits on GreenFuture with no one attached to it, because the company declares the department but no one in `people.json` works there.

## What to Expect

The lines below come from one real run of the script, trimmed. Nothing in this demo calls an LLM, so the node and edge counts are identical on every run — only the UUIDs, timestamps, and paths change. A first run also prints a long block of relational migrations as cognee builds its tables; that block is setup, not part of the demo.

**The pipeline starts with both JSON files already in hand.** `build_lightweight_data_object` has wrapped the two files into a single `LightweightData` DataPoint, so the payload printed at the start of the run is the whole input — five companies and ten people — before any task has touched it.

```text theme={null}
status='PipelineRunStarted' pipeline_run_id=UUID('40da9a6f-3948-43be-a90f-e551570c40dd') dataset_id=UUID('94add405-52f0-5e30-871b-b2e8eeda31c7') dataset_name='test_dataset' payload=[LightweightData(id=UUID('c22f2fa6-5eee-5a20-a8d5-f3a1d10dbbbd'), ...
```

**The two tasks finish in about three seconds.** `ingest_files` maps the payload and `add_data_points` writes the result. There is no extraction step between them, which is why the whole pipeline costs less than a single LLM call would.

```text theme={null}
2026-09-11T15:24:05.784975 [info     ] Function task started: `ingest_files` [run_tasks_base]
2026-09-11T15:24:05.792215 [info     ] Coroutine task started: `add_data_points` [run_tasks_base]
...
2026-09-11T15:24:08.755657 [info     ] Coroutine task completed: `add_data_points` [run_tasks_base]
2026-09-11T15:24:08.762174 [info     ] Function task completed: `ingest_files` [run_tasks_base]
2026-09-11T15:24:08.768537 [info     ] Pipeline run completed: `07f15429-bcc9-56e7-8058-6ff9d3fd6372` [run_tasks_with_telemetry()]
```

**The graph that lands is 27 nodes and 26 edges, and the arithmetic is what tells you the mapping was right.** Five `Company` nodes, eleven `Department` nodes, ten `Person` nodes, and the one `CompanyType` all five companies point at make 27. Eleven company-to-department edges, ten department-to-employee edges, and five `is_type` edges make 26. Had a department name landed as two nodes, both numbers would be higher.

```text theme={null}
2026-09-11T15:24:08.791366 [info     ] Retrieved 27 nodes and 26 edges in 0.00 seconds [cognee.shared.logging_utils]
2026-09-11T15:24:08.797151 [info     ] Neighborhood retrieval (2-hop): 27 nodes and 26 edges in 0.01s [cognee.shared.logging_utils]
```

**One warning is expected and does not mean the run failed.** `add_data_points` did embed every `index_fields` value — the `Person_name`, `Department_name`, and `Company_name` collections are written and searchable. The warning comes from the renderer's semantic-map layer, which only recognizes cognee's built-in node types and so has nothing to plot for custom DataPoints. The graph view itself is complete.

```text theme={null}
2026-09-11T15:24:08.798832 [info     ] fetch_node_embeddings: resolved 0/27 node embeddings across 0 collection(s) [embedding_join]
2026-09-11T15:24:08.798890 [warning  ] fetch_node_embeddings: no embeddings resolved — the semantic map will be empty. Missing collections: none. Unmapped node types: ['Department', 'Company', 'Person', 'CompanyType']. [embedding_join]
```

**The closing search returns the LLM input, not a sentence.** It asks "Who works for GreenFuture Solutions?" with `only_context=True`, so what comes back is the prompt built around the graph neighbourhood the query matched — the company, its departments, and the people connected to them, rendered as text — rather than an LLM's answer. That is the point: GreenFuture's departments and employees coming back proves the hand-declared DataPoints are retrievable, and it costs an embedding call instead of a completion. The run above predates the query, so no output from it is shown here.

## Before You Start

* Complete [Quickstart](/getting-started/quickstart) to understand basic operations
* Ensure you have [LLM Providers](/setup-configuration/llm-providers) configured — an embedding provider is all this demo needs. `add_data_points` embeds every `index_fields` value, and the closing search embeds the query; nothing in the script calls a completion model
* Run the script from a checkout of the cognee repo: it reads `companies.json` and `people.json` from the `data/` folder next to it, and writes its HTML render into a sibling `.artifacts/` folder
* The script starts with `prune.prune_data()` and `prune.prune_system(metadata=True)`, which wipes the configured instance — point it at a scratch instance rather than memory you want to keep

## How It Works

### Stage 1: Model the Org Chart as Deduplicating DataPoints

Source: `examples/demos/custom_pipelines/organizational_hierarchy/organizational_hierarchy_pipeline_example.py`

```python theme={null}
class Person(DataPoint):
    name: str
    # "index_fields": fields to embed for vector search
    # "identity_fields": fields used to generate deterministic IDs (deduplication)
    metadata: dict = {"index_fields": ["name"], "identity_fields": ["name"]}


class Department(DataPoint):
    name: str
    employees: list[Person]
    metadata: dict = {"index_fields": ["name"], "identity_fields": ["name"]}
```

These classes are the entire schema of the resulting graph — `Company` and `CompanyType` follow the same pattern below them. Two things happen per class: `index_fields` names the field that gets embedded, so the graph is searchable by name; `identity_fields` makes the node's UUID a deterministic function of that name, so building `Person(name="John Doe")` twice lands on one node instead of two. The `employees` field is what makes this a graph rather than two tables — a list of `Person` objects on a `Department` becomes edges from that department to those people.

### Stage 2: Map the JSON Rows onto Nested DataPoints

Source: `examples/demos/custom_pipelines/organizational_hierarchy/organizational_hierarchy_pipeline_example.py`

```python theme={null}
def ingest_files(data: list[Any]) -> list[Company]:
    # With identity_fields, DataPoints with the same name automatically get the same UUID.
    # No manual dict-based deduplication needed — just create instances freely.
    all_companies: list[Company] = []

    # Single CompanyType node shared across all data items (deterministic ID via identity_fields)
    company_type = CompanyType()

    for data_item in data:
        people = data_item.people
        companies = data_item.companies

        # Build departments with their employees
        dept_employees: dict[str, list[Person]] = {}
        for person in people:
            dept_name = person["department"]
            if dept_name not in dept_employees:
                dept_employees[dept_name] = []
            dept_employees[dept_name].append(Person(name=person["name"]))

        departments = {
            name: Department(name=name, employees=employees)
            for name, employees in dept_employees.items()
        }

        for company in companies:
            company_departments = [
                departments.get(dept_name, Department(name=dept_name, employees=[]))
                for dept_name in company["departments"]
            ]
            all_companies.append(
                Company(name=company["name"], departments=company_departments, is_type=company_type)
            )

    return all_companies
```

This is the first pipeline task, and it is ordinary Python: group people by their `department` field, build one `Department` per group, then hang the departments each company declares off a `Company`. Nothing here checks whether a node already exists. A company that declares a department nobody works in still gets a `Department` node — created on the spot with no employees — and if another company declares the same one, `identity_fields` gives both objects the same UUID, so the two collapse into a single shared node when they are stored. The function returns only the companies; the departments and people reach the graph because they hang off them.

### Stage 3: Run the Two-Task Pipeline

Source: `examples/demos/custom_pipelines/organizational_hierarchy/organizational_hierarchy_pipeline_example.py`

```python theme={null}
    # Run tasks expects a list of data even if it is just one document
    data = [{"companies": companies, "people": people}]

    pipeline = run_tasks(
        [Task(ingest_files), Task(add_data_points)],
        dataset_id=datasets[0].id,
        data=build_lightweight_data_object(data),
        incremental_loading=False,
    )
```

`main()` prunes, calls `setup()` to create the relational tables, resolves the default user, and creates a `test_dataset` with `load_or_create_datasets` before this point — the dataset is what the pipeline run is tracked against. `run_tasks` then chains the two tasks: `ingest_files` returns `Company` objects and `add_data_points` writes them, their nested departments and people, and the edges between them into the graph and vector stores. `build_lightweight_data_object` wraps the raw dict in a `LightweightData` DataPoint with a `uuid5` ID, because the pipeline expects DataPoints rather than plain dicts. Once the pipeline drains, `visualize_graph` writes the org chart to `.artifacts/organizational_hierarchy_pipeline_example.html`.

### Stage 4: Query the Graph You Just Built

Source: `examples/demos/custom_pipelines/organizational_hierarchy/organizational_hierarchy_pipeline_example.py`

```python theme={null}
    # Ask a question against the graph that was just built. only_context=True
    # returns the prompt the LLM would receive (retrieval context included)
    # instead of an answer, so this example needs only an embedding provider
    # configured - no LLM.
    results = await search(
        query_text="Who works for GreenFuture Solutions?",
        query_type=SearchType.GRAPH_COMPLETION,
        dataset_ids=datasets[0].id,
        user=user,
        only_context=True,
    )
```

This is what turns the demo from an import into a round trip. Nothing about the query knows the graph was hand-declared rather than extracted by cognify — `GRAPH_COMPLETION` embeds the question, finds the matching nodes, and walks company → department → employee edges out from them exactly as it would over an LLM-built graph. `only_context=True` stops there and hands back the prompt instead of sending it to a completion model, which is what keeps the whole script free of completion calls. Passing `dataset_ids` and `user` scopes the search to the `test_dataset` this run created.

`search` returns a list with one entry per dataset searched. In the default multi-user mode each entry is a dict whose `search_result` key holds the context; with `ENABLE_BACKEND_ACCESS_CONTROL=false` each entry is the context string itself. The script's closing loop reads `result.search_result` as an attribute, which neither shape has, so as of cognee#5049 the final print raises `AttributeError` after the graph and render are already written. Until that is fixed upstream, read the entry with `result["search_result"]` in multi-user mode, or print `results` directly.

## Run It

```bash theme={null}
uv run python examples/demos/custom_pipelines/organizational_hierarchy/organizational_hierarchy_pipeline_example.py
```

It prints the pipeline statuses walked through above, logs the path of the render it wrote to `.artifacts/organizational_hierarchy_pipeline_example.html`, next to the script — open that file to see companies linked to departments linked to people — and closes with the "Who works for GreenFuture Solutions?" query. The graph and the render are complete before that query runs, so they are there to inspect even though the script's final print currently fails as described in Stage 4.

<Columns cols={2}>
  <Card title="DataPoints" icon="circle" href="/core-concepts/building-blocks/datapoints">
    How `index_fields`, `identity_fields`, and nested models shape the graph a DataPoint becomes.
  </Card>

  <Card title="Custom Data Models" icon="circle-stop" href="/guides/custom-data-models">
    More on modeling your own node types and storing them with `add_data_points`.
  </Card>

  <Card title="Custom Tasks and Pipelines" icon="workflow" href="/guides/custom-tasks-pipelines">
    Writing your own tasks and running them as a pipeline, step by step.
  </Card>

  <Card title="Reading the Visualization" icon="eye" href="/guides/reading-the-visualization">
    What each tab of the rendered org chart shows, and which one to reach for.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.