Product Intelligence Neo4j Co-Marketing

Graph-Native Supply Chain Risk — A Neo4j Perspective

We built a material genealogy graph that traces every vehicle assembly from finished product to raw ore — eight levels deep, 34,713 nodes, 52,856 relationships. This is what the graph reveals that a relational database cannot.

Your supply chain risk tool knows who your suppliers are. But does it know what Lithium Hydroxide transforms into, six levels before it reaches your battery pack? Does it know that the same ore sitting at the bottom of 19 independent assembly chains is, in fact, a single point of failure for the entire vehicle programme?

We spent a year asking those questions, and they kept running into the same wall: the data model. Not the data volume, not the algorithms, not the compute — the data model. The moment you try to represent eight levels of material transformation, with typed relationships at every transition, supplier nodes attached at every depth, and TTR properties propagating upward through the chain, a relational schema becomes the enemy of the problem you are trying to solve.

This post is about what we built, why we chose Neo4j, and — through three concrete scenarios drawn from our live graph — what a connected material genealogy reveals that a flat model cannot.

34,713
Total nodes
52,856
Relationships
8
BOM depth levels
17
Relationship types
554
Suppliers mapped
540d
Maximum TTR in graph

Why the data model is the problem

An 8-level material genealogy is a recursive, variable-depth structure with heterogeneous node types at every level. L1 is a vehicle assembly. L8 is a raw ore. Nothing in between is the same kind of thing. Cathode Active Material (L3) has chemical composition properties. Nickel Sulfate (L4) has synthesis route data. Nickel Ore Laterite (L8) has extraction geography and mine-level TTR.

To represent this in a relational database, you need either a self-referencing table with recursive CTEs — which requires knowing the depth in advance and collapses under heterogeneous node schemas — or a pre-computed path table that breaks the moment you add a new relationship type. We have seven distinct relationship types just for the material transformation chain:

Material transformation chain — 7 typed transitions
// Each step is a semantically distinct industrial process
COMP_USES_MATERIAL            // Component → ComponentMaterial (BOM specification)
COMP_MAT_TRANSFORMS_TO_SEMI   // ComponentMaterial → SemiFinishedMaterial (synthesis)
SEMI_TRANSFORMS_TO_PRIMARY    // SemiFinishedMaterial → PrimaryMaterial (refining)
PRIMARY_TRANSFORMS_TO_PRECURSOR// PrimaryMaterial → PrecursorMaterial (precursor chemistry)
PRECURSOR_TRANSFORMS_TO_RAW   // PrecursorMaterial → RawMaterial (ore extraction)
CONTAINS_SUBASSEMBLY          // Assembly → SubAssembly (structural BOM)
CONTAINS_COMPONENT            // SubAssembly → Component (structural BOM)

In a relational model, seven join tables — or a polymorphic mess with a type column that loses all semantic meaning. In Neo4j, they are seven named relationship types that are first-class citizens of the query language. You can traverse selectively: "give me only paths through TRANSFORMS_TO relationships, not BOM structural edges." You can query edge-specific properties inline. And you can express "any of these seven relationship types" in a single MATCH clause — something SQL cannot do without a multi-step UNION.

The schema: 10 node labels, three domains, one graph

The PMG graph connects three domains in one structure: the BOM hierarchy (L1 Assembly to L8 Raw Material), the supplier network (who supplies what, at which depth), and the risk properties of every node. Each domain has its own node labels; all three are queryable in a single traversal.

Node label BOM level Representative node
Assembly L1Li-Ion Battery Pack (800V, 75 kWh, NMC 811)
SubAssembly L2Battery Module Assembly (6 modules)
Component L3Cathode Active Material (NMC 811)
ComponentMaterial L4Lithium Hydroxide Monohydrate (Battery Grade)
SemiFinishedMaterialL5LiOH·H₂O powder (battery synthesis intermediate)
PrimaryMaterial L6Lithium carbonate (Li₂CO₃) 99.5%
PrecursorMaterial L7Lithium concentrate (Li₂O 4.5%)
RawMaterial L8Lithium Ore (Spodumene) · Nickel Laterite Ore
Supplier DENSO Corporation, ArcelorMittal México, 3M
SupplierLocation Geographic presence node, connected via LOCATED_AT

Every material node carries structural risk properties as first-class graph data: estimated_ttr_days, is_diamond_node, diamond_assembly_count, manufacturing_complexity_index, single_source_likelihood, and concentration_country. The graph is not just storage — it is the analytics substrate. Risk properties are computed by traversal and written back to nodes. The graph is both the data warehouse and the computation engine.

The three scenarios below are drawn from the live graph. Each one shows a class of analytical question that is structurally impossible to answer without graph traversal.

Scenario A

BOM Subtree — The depth argument

One assembly fans out to 6 sub-assemblies, dozens of components, and 8 cathode materials. One MATCH clause. And this is only 4 of 8 levels.

A traditional Bill of Materials is a tree that stops at L3 or L4 — it lists parts and sub-parts but does not trace material chemistry. "Cathode Active Material (NMC 811)" is one line in a BOM. In the PMG graph, it is a node connected to eight chemical inputs, each of which traces back through industrial synthesis routes to specific ores in specific geographies.

That is the difference between knowing "you use NMC 811" and knowing "your NMC 811 depends on Lithium Hydroxide Monohydrate with a 210-day TTR, sourced through a processing chain with 90%+ concentration in two countries."

Scenario A: Li-Ion Battery Pack BOM subtree — L1 to L4, showing 6 sub-assemblies and 8 cathode materials Expand

Battery Pack (L1) → 6 SubAssemblies (L2) → Components (L3) → 8 Cathode Active Materials (L4). Every green L4 node has a full L5–L8 subtree beneath it, not shown here.

The query that produced this visualisation traverses four levels through three heterogeneous relationship types in one MATCH clause:

Cypher — BOM fan-out, L1 to L4
// Full BOM fan-out — Battery Pack to L4 materials
MATCH (a:Assembly)-[:CONTAINS_SUBASSEMBLY]->(l2:SubAssembly)
WHERE a.name CONTAINS 'Battery Pack'
WITH a, l2
MATCH (l2)-[:CONTAINS_COMPONENT]->(l3:Component)
WHERE l3.name CONTAINS 'Cathode Active Material'
WITH a, l2, l3
MATCH (l3)-[:COMP_USES_MATERIAL]->(l4:ComponentMaterial)
RETURN a.name        AS assembly,
       l2.name       AS sub_assembly,
       l3.name       AS component,
       l3.estimated_ttr_days AS component_ttr_days,
       l4.name       AS material,
       l4.estimated_ttr_days AS material_ttr_days
ORDER BY l4.name

The eight L4 nodes surfaced by this query — Nickel Sulfate, Cobalt Sulfate, Lithium Hydroxide Monohydrate, Manganese Sulfate, and four others — each carry their own estimated_ttr_days. Lithium Hydroxide Monohydrate sits at 210d. Nickel Sulfate at 60d. These are not declared by the T1 supplier. They are computed from the material graph, tracing the actual synthesis and sourcing chain beneath each node.

When China announces export controls on lithium, a procurement team needs to know which of these eight cathode materials depends on Chinese processing. This query — or a variant of it with a concentration_country filter — tells them in milliseconds. Not after a cross-system join across a procurement platform, a materials database, and a BOM tool. In one traversal, on the live graph.

The SQL equivalent of this query is a recursive CTE across four tables with heterogeneous schemas, a UNION ALL at each step, and no native way to express "any of these three relationship types." The query plan does not scale past five or six levels. The Cypher above scales to eight without modification.
Scenario B

Diamond Convergence — The hidden single point of failure

19 independent assemblies. One ore. The convergence is invisible at L3, L4, even L5. The graph detects it automatically at L8.

In a flat supplier model, the Battery Pack has a nickel supplier. The Traction Motor has a different nickel supplier. The Braking System has a third. Three independent supply chains. Three independent risk assessments. Traditional risk tools treat them exactly this way — and they are structurally correct to do so, given the data they have.

The problem is that all three paths ultimately trace through the same raw material: Nickel Laterite Ore at L8. If that ore supply is disrupted — a mine closure, an export restriction, a geopolitical event — all 19 assemblies in the vehicle programme are affected simultaneously. The convergence is invisible at every level above L8. It is only detectable from the bottom of the graph, looking upward.

Scenario B: Four assembly paths converging on Nickel Laterite Ore at L8 — diamond_assembly_count = 19 Expand

Four of 19 shortest paths shown. Each path enters through a different nickel compound at L4–L5, converging at L8. diamond_assembly_count = 19. estimated_ttr_days = 120.

This is what the PMG graph calls a diamond node: a material or supplier node where multiple independent assembly paths converge. The property is_diamond_node = true is not declared anywhere — it is computed by traversal and written back to the node. diamond_assembly_count = 19 quantifies the blast radius. The query that detects it:

Cypher — Diamond detection, bottom-up traversal
// Find all assemblies that depend on Nickel Laterite Ore
MATCH (ore:RawMaterial)
WHERE ore.name = 'Nickel Laterite Ore'
  AND ore.is_diamond_node = true
MATCH path = shortestPath(
  (a:Assembly)-[:CONTAINS_SUBASSEMBLY|CONTAINS_COMPONENT|
    COMP_USES_MATERIAL|COMP_MAT_TRANSFORMS_TO_SEMI|
    SEMI_TRANSFORMS_TO_PRIMARY|PRIMARY_TRANSFORMS_TO_PRECURSOR|
    PRECURSOR_TRANSFORMS_TO_RAW*1..8]->(ore)
)
WHERE a.level = 1
RETURN path,
       ore.diamond_assembly_count  AS assemblies_affected,
       ore.estimated_ttr_days      AS recovery_days,
       a.name                      AS assembly_name
ORDER BY a.name

That query returned 19 rows in under 200ms. The *1..8 variable-length path pattern traversed all paths between length 1 and 8 through any combination of the listed relationship types — without knowing the path lengths in advance, without conditional logic for different depth assemblies. Native graph operation. The equivalent in SQL is not merely complex. It requires pre-computing all possible paths at load time in a denormalised lookup table, and rebuilding that table entirely whenever a BOM changes.

The four paths shown in the image — Battery Pack via Nickel Sulfate, Traction Motor via Nickel coating, Braking System via Nickel-Copper-Nickel Plating, HVAC Compressor via corrosion protection — pass through entirely different intermediate nodes at L4 through L6. The convergence is not visible at any of those levels. It only surfaces when you traverse to L8 and ask the graph: how many assemblies depend on this node?

In a flat BOM, these look like 19 independent supply chains. In the graph, they are one bottleneck. That is the difference between managing 19 risks and managing 1. The diamond_assembly_count property makes this immediately actionable: any node above a threshold triggers a review. Any node at 19 — the full vehicle programme — triggers immediate escalation.

Our live graph contains 4,554+ diamond nodes — materials that feed multiple independent assemblies. The highest diamond_assembly_count in the graph is 19, for Nickel Laterite Ore. These nodes were detected automatically at graph load time by the same traversal pattern shown above. No analyst marked them. No spreadsheet maintained the list. The graph computed it.

Scenario C

TTR Depth — Where your risk actually lives

DENSO Corporation supplies your Battery Pack. Six levels below DENSO, the graph shows a 270-day TTR that DENSO's declared lead time cannot capture.

The Ford-MIT study (Simchi-Levi et al., 2015) introduced TTR — Time-to-Recover — as the right metric for supply chain risk, and proved that PI (Performance Impact) and TTR together identify hidden risk concentrations that spend-based monitoring misses. That framework was built on T1-observable data, because T1 was the data that existed. The authors were explicit about this ceiling in the paper itself.

PMG extends that methodology downward. TTR is not declared by a supplier — it is computed from the material graph. The constraining TTR for any disruption scenario is the maximum across the full dependency path, and that maximum is almost always deep in the chain, not at the T1 supplier.

Scenario C: 8-level Lithium chain with TTR on each node and suppliers attached at L1, L2, L6, L8 Expand

Vertical L1→L8 Lithium chain. Suppliers attached at four different depths via typed SUPPLIES_* relationships. TTR escalates from 210d at L4 to 270d at L8.

The TTR values across the Lithium chain in the live graph:

LevelNodeSupplierTTR
L1 Li-Ion Battery Pack (800V, 75 kWh, NMC 811) DENSO Corporation
L2 Battery Module Assembly (6 modules) 3M
L3 Cathode Active Material (NMC 811) 240d
L4 Lithium Hydroxide Monohydrate (Battery Grade) 210d
L5 LiOH·H₂O powder (synthesis intermediate) 240d
L6 Lithium carbonate (Li₂CO₃) 99.5% Silfab Solar Inc. 240d
L7 Lithium concentrate (Li₂O 4.5%) 240d
L8 Lithium Ore (Spodumene) ArcelorMittal México 270d

DENSO Corporation quotes a lead time based on their own inventory, manufacturing capacity, and T1 procurement relationships. That estimate is operationally honest. What it cannot include is the physics of the material chain beneath them. Six levels below DENSO, Lithium Ore (Spodumene) carries a 270-day TTR — the time to qualify an alternative source, restart processing, rebuild inventory through every transformation stage back up to L1.

The query that builds this chain and overlays the supplier network:

Cypher — Full L1→L8 chain with TTR and supplier overlay
// Full Lithium chain — L1 Assembly to L8 Raw Material
MATCH (a:Assembly)-[:CONTAINS_SUBASSEMBLY]->(l2:SubAssembly)
      -[:CONTAINS_COMPONENT]->(l3:Component)
      -[:COMP_USES_MATERIAL]->(l4:ComponentMaterial)
      -[:COMP_MAT_TRANSFORMS_TO_SEMI]->(l5:SemiFinishedMaterial)
      -[:SEMI_TRANSFORMS_TO_PRIMARY]->(l6:PrimaryMaterial)
      -[:PRIMARY_TRANSFORMS_TO_PRECURSOR]->(l7:PrecursorMaterial)
      -[:PRECURSOR_TRANSFORMS_TO_RAW]->(l8:RawMaterial)
WHERE a.name CONTAINS 'Battery Pack'
  AND l4.name CONTAINS 'Lithium Hydroxide'
RETURN a.name AS assembly,
       l3.name AS component,       l3.estimated_ttr_days AS l3_ttr,
       l4.name AS material,        l4.estimated_ttr_days AS l4_ttr,
       l5.name AS semi_finished,   l5.estimated_ttr_days AS l5_ttr,
       l6.name AS primary,         l6.estimated_ttr_days AS l6_ttr,
       l7.name AS precursor,       l7.estimated_ttr_days AS l7_ttr,
       l8.name AS raw_material,    l8.estimated_ttr_days AS l8_ttr

The supplier overlay is structurally important. In the PMG graph, suppliers attach to materials at every depth via typed SUPPLIES_* relationships. SupplierLocation -[:SUPPLIES_RAW_MATERIAL]-> RawMaterial is not the same edge as SupplierLocation -[:SUPPLIES_L1]-> Assembly. Each relationship type represents a different commercial and operational relationship with its own risk properties. This means you can ask: which suppliers are exposed at L6 or deeper, where substitution takes more than 180 days? — and get the answer in one traversal, without a cross-system join.

The gap between declared TTR and computed TTR can exceed 40 weeks. Not because suppliers misrepresent their capability — but because no T1 supplier has visibility into their own material chain at L6, L7, and L8. The graph closes that gap structurally. The TTR that matters for disruption planning is the maximum across the full dependency path. In this chain, that maximum is 270 days, nine levels below the assembly node that appears on a procurement dashboard.

Why Neo4j — the technical argument in four points

These three scenarios converge on the same answer to a question we get regularly: why not a relational database? Here is the technical case, without the marketing gloss.

Capability requirement Relational database Neo4j
Variable-length traversal (1..8 levels) Recursive CTE, requires known depth, no heterogeneous node types *1..8 inline, variable depth, heterogeneous node types native
Typed relationships (17 types) 17 join tables or polymorphic type column losing semantic meaning 17 named relationship types, queryable selectively in one MATCH
Bottom-up convergence detection Pre-computed path table, rebuilt on every BOM change shortestPath at query time against live graph, <200ms
Cross-domain traversal (BOM + supplier + risk) Multi-system join across BOM tool, procurement platform, risk DB One traversal, one query, three domains in one connected graph

The fourth point deserves expansion. Our three most important analytical questions each span all three domains simultaneously:

"Which assemblies are exposed to this ore disruption?" — crosses material chemistry (L4–L8) and BOM structure (L1–L3). "What is the real TTR for this T1 supplier's contribution?" — crosses the supplier network and the material transformation chain. "Which supplier should I contact first for DPP compliance?" — crosses supplier relationships, material risk scores, and geographic concentration data.

All three are single Cypher traversals on one connected graph. In a traditional architecture, each requires a separate system query, cross-system joins, and manual aggregation — a pipeline that takes hours. On the live graph, these questions run in seconds.

What the graph produces

The three scenarios above are analytical patterns — ways of interrogating a connected structure to surface risk that flat models hide. What the graph produces operationally is: a list of diamond nodes ranked by diamond_assembly_count and recovery complexity; a TTR profile for every assembly that reflects the deepest constraint in its material chain, not the T1-declared lead time; and a supplier-material map that tells a procurement team not just who supplies what, but at which depth in the material chain that supply relationship actually lives.

The prescriptive layer that sits on top of this graph — material substitution evaluation, sourcing viability simulation, cross-domain consequence analysis — runs on the same connected structure. When a disruption scenario is identified, the graph already contains the alternative paths, the supplier network around those paths, and the compliance implications of switching. The recommendation is evaluated against the live graph before it surfaces, not after.

Supply chain risk is fundamentally a graph problem. Not a table problem. Not a dashboard problem. A graph problem. The question "which of my assemblies is exposed to this disruption?" cannot be answered by querying who your suppliers are. It can only be answered by traversing what your materials are made of — all the way down to the ore.

The live graph referenced throughout this post is a modelled European BEV bill of materials — one vehicle programme, eight levels deep, 19 top-level assemblies, 554 suppliers across all tiers. It is proof of methodology, not a customer deployment. But the patterns it reveals — diamond convergence, TTR depth, supplier geography at depth — are structural properties of any modern manufactured product. They exist in your graph whether or not you have built the graph to see them.

← Back to all posts Request Pilot Access
Zoom 25%