Industrial Intelligence Architecture

How to Build a Knowledge Graph for Manufacturing Operations

The core and domain ontologies define what may exist in a manufacturing environment. Here we use them to build a knowledge graph of what actually exists.

Kudzai Manditereza
Kudzai Manditereza
Aug 18, 2026
·

In the previous articles, we created a core manufacturing ontology and extended it with domain-specific concepts for production, quality, maintenance, inventory, and energy.

Those ontologies define the types of things that may exist in a manufacturing environment:

Location
    ├── Enterprise                    (core)
    ├── Site                          (core)
    ├── Area                          (core)
    │       ├── Warehouse             (inventory)
    │       ├── ProductionArea        (production)
    │       ├── PackagingArea         (production)
    │       └── UtilitiesArea         (energy)
    └── WorkCenter                    (core)
            ├── StorageZone           (inventory)
            ├── ProductionLine        (production)
            └── PackagingLine         (production)

The core supplies the four ISA-95 levels; the domain extensions specialise Area and WorkCenter rather than attaching to Location directly. That distinction matters later, when instances are typed and labelled.

They also define the relationships that may connect those concepts:

Location CONTAINS Location
Entity IS_LOCATED_AT Location
Process OCCURS_AT Location

However, an ontology is still only a semantic specification.

It tells us what an enterprise, site, production area, or production line means. It does not yet tell us which enterprises, sites, areas, and lines actually exist in a particular organization.

To create a knowledge graph, we must populate the ontology with instances of those concepts.

In this article, we will create the first instance layer of the manufacturing knowledge graph: the plant-location hierarchy for a fictional company called Global Industries.

The graph will contain:

  • One enterprise
  • Two manufacturing sites
  • Four operational areas at each site
  • Production, packaging, storage, clean-in-place, and utility work centers
  • Semantic links from every instance to its ontology class
  • Spatial containment relationships between the instances

Equipment is deliberately excluded at this stage. It can be added later during commissioning and connected to the locations created here.


The class layer and the instance layer

The most important distinction in the knowledge graph is the distinction between a class and an instance.

A class describes a category of things.

For example:

ProductionArea

is an ontology class. It defines what a production area means within the manufacturing domain.

An instance is a particular thing that exists in operational reality.

For example:

Munich Production Area

is an actual area at the Munich plant.

The connection between the two is:

Munich Production Area
    IS_INSTANCE_OF
ProductionArea

In Neo4j, the class layer may contain a node such as:

(:Class {
    name: "ProductionArea",
    uri: ".../production/ProductionArea"
})

The instance layer may contain:

(:Instance:Location:Area {
    id: "AREA-MUC-200",
    name: "Production Area",
    isa95_code: 200
})

The two layers are connected through:

(:Instance)-[:IS_INSTANCE_OF]->(:Class)

This gives the graph two complementary kinds of information:

Instance layer:
What actually exists?

Class layer:
What does that thing mean?

The knowledge graph emerges when actual operational entities are connected to the formal concepts defined by the ontology.


The first knowledge-graph layer: plant topology

The Cypher script creates the spatial hierarchy of Global Industries.

At the highest level is the enterprise:

Global Industries

The enterprise contains two sites:

Global Industries
├── Munich Plant
└── Dallas Plant

Each site contains four areas:

Raw Material Storage Area
Production Area
Packaging Area
Utilities Area

Each area contains one or more work centers.

For example, the Munich plant contains:

Munich Plant
├── Raw Material Storage Area
│   ├── Dry Ingredient Storage
│   └── Liquid Ingredient Storage
│
├── Production Area
│   ├── Line 1
│   └── CIP Station 1
│
├── Packaging Area
│   └── Line 1
│
└── Utilities Area
    ├── Compressed Air System
    ├── Cooling Water System
    ├── Hot Water System
    └── Steam System

The Dallas plant has a similar structure, although it uses a combined ingredients-storage work center rather than separate dry and liquid storage areas.

This location hierarchy provides the spatial foundation upon which later knowledge-graph layers can be built.

Equipment can be placed inside the work centers. Processes can occur at those locations. Events can be traced to the site, area, and work center where they occurred.


1. Establishing stable instance identities

Before creating the locations, the script defines a uniqueness constraint:

CREATE CONSTRAINT instance_id IF NOT EXISTS
FOR (i:Instance)
REQUIRE i.id IS UNIQUE;

Every operational instance receives a stable id.

Examples include:

GLOBAL-IND
SITE-MUC
SITE-DAL
AREA-MUC-200
WC-MUC-200-L1

The identifiers follow a consistent structure.

Sites use:

SITE-<plant>

Areas use:

AREA-<plant>-<area code>

Work centers use:

WC-<plant>-<area code>-<short name>

For example:

WC-MUC-200-L1

identifies:

Work center
Munich plant
Production area 200
Line 1

The stable identifier is used as the canonical identity of the instance.

Names such as Line 1 are not sufficient because the Munich and Dallas plants may both have a production line called Line 1.

The ID removes that ambiguity:

WC-MUC-200-L1
WC-DAL-200-L1

Both may have the same display name, but they remain distinct operational entities.


2. Creating instance nodes

The enterprise is created with:

MERGE (e:Instance:Location {id: 'GLOBAL-IND'})
ON CREATE SET e.created_at = datetime()
SET e:Enterprise,
    e.name = 'Global Industries',
    e.headquarters = 'Munich, DE',
    e.founded = date('2005-01-01');

This node has several labels:

Instance
Location
Enterprise

Each label serves a different purpose.

Instance identifies the node as part of the knowledge-graph instance layer.

Location identifies it as an instance grounded in the core Location concept.

Enterprise identifies its structural level in the plant hierarchy.

The same pattern is used for the Munich site:

MERGE (muc:Instance:Location {id: 'SITE-MUC'})
ON CREATE SET muc.created_at = datetime()
SET muc:Site,
    muc.name = 'Munich Plant',
    muc.location = 'Munich, DE',
    muc.latitude = 48.1351,
    muc.longitude = 11.5820,
    muc.timezone = 'Europe/Berlin',
    muc.commissioned = date('2015-04-01');

The node now contains facts about a real manufacturing site:

ID: SITE-MUC
Name: Munich Plant
Location: Munich, DE
Timezone: Europe/Berlin
Commissioned: 1 April 2015

These are instance-level facts.

They describe the particular Munich plant rather than the general concept of a manufacturing site.

The Dallas plant is created in the same way:

MERGE (dal:Instance:Location {id: 'SITE-DAL'})
ON CREATE SET dal.created_at = datetime()
SET dal:Site,
    dal.name = 'Dallas Plant',
    dal.location = 'Dallas, TX, US',
    dal.latitude = 32.7767,
    dal.longitude = -96.7970,
    dal.timezone = 'America/Chicago',
    dal.commissioned = date('2021-09-15');

3. Making instance creation repeatable

The script uses MERGE rather than CREATE:

MERGE (muc:Instance:Location {id: 'SITE-MUC'})

This means Neo4j first looks for an existing location instance with the ID SITE-MUC.

If it exists, Neo4j reuses it.

If it does not exist, Neo4j creates it.

The creation timestamp is only added when the node is first created:

ON CREATE SET muc.created_at = datetime()

The remaining properties are synchronized every time the script runs:

SET muc:Site,
    muc.name = 'Munich Plant',
    muc.location = 'Munich, DE',
    ...

This makes the script idempotent.

It can be rerun without creating a second Munich plant, second production area, or duplicate work center.

The deployment behavior is therefore:

New instance:
Create it and add created_at.

Existing instance:
Reuse it and update its managed properties.

This is particularly useful when a knowledge graph is deployed through repeatable migration or initialization scripts.


4. Using labels for operational access

Every location instance carries:

:Instance
:Location

It also receives one structural label:

:Enterprise
:Site
:Area
:WorkCenter

For example:

(:Instance:Location:Area {
    id: "AREA-MUC-200",
    name: "Production Area"
})

These structural labels make common operational queries simple.

To retrieve all locations:

MATCH (location:Instance:Location)
RETURN location;

To retrieve all sites:

MATCH (site:Instance:Site)
RETURN site.id, site.name;

To retrieve all work centers:

MATCH (workCenter:Instance:WorkCenter)
RETURN workCenter.id, workCenter.name;

An earlier version of the script carried the structural level as a level property alongside the label. That duplicated the same fact in two places, so it was migrated away with a one-time cleanup:

MATCH (i:Instance:Location)
WHERE i.level IS NOT NULL
REMOVE i.level;

The cleanup has since been retired from the script — migration statements are scaffolding, and once every deployed graph has run one it becomes dead weight that obscures which lines still do work. The label is now the only representation of the level.

Without this cleanup, a node could theoretically have:

Label: Area
level: WorkCenter

By using labels as the structural representation, the graph avoids that possibility.


5. Labels are not the ontology class

The structural labels do not replace the ontology.

Consider the Munich production area:

(:Instance:Location:Area {
    id: "AREA-MUC-200",
    name: "Production Area"
})

The Area label tells an application where the instance sits in the structural hierarchy.

However, the ontology gives it a more specific semantic type:

ProductionArea

The connection is represented explicitly:

AREA-MUC-200
    IS_INSTANCE_OF
ProductionArea

This distinction allows the graph to use two different classification mechanisms for different purposes.

Operational labels

Labels support efficient operational queries:

Site
Area
WorkCenter

They answer questions such as:

Give me all work centers.

Ontology classes

The IS_INSTANCE_OF relationship provides semantic meaning:

Warehouse
ProductionArea
PackagingArea
UtilitiesArea
StorageZone
ProductionLine
PackagingLine

It answers questions such as:

Which locations are production lines?

or:

What does this area represent within the manufacturing ontology?

A node may therefore have the structural label Area while being semantically typed as ProductionArea, PackagingArea, UtilitiesArea, or Warehouse.


6. Typing instances against ontology classes

The enterprise is connected to the Enterprise class:

MATCH
    (i:Instance:Location {id: 'GLOBAL-IND'}),
    (c:Class {name: 'Enterprise'})
MERGE (i)-[:IS_INSTANCE_OF]->(c);

The resulting pattern is:

Global Industries
    IS_INSTANCE_OF
Enterprise

All site instances are typed as Site:

MATCH
    (i:Instance:Location),
    (c:Class {name: 'Site'})
WHERE i.id STARTS WITH 'SITE-'
MERGE (i)-[:IS_INSTANCE_OF]->(c);

This creates:

Munich Plant IS_INSTANCE_OF Site
Dallas Plant IS_INSTANCE_OF Site

The areas receive more domain-specific classifications.

Area code 100 is typed as Warehouse:

MATCH
    (i:Instance:Location),
    (c:Class {name: 'Warehouse'})
WHERE i.id STARTS WITH 'AREA-'
  AND split(i.id, '-')[2] = '100'
MERGE (i)-[:IS_INSTANCE_OF]->(c);

Area code 200 is typed as ProductionArea:

AREA-MUC-200 IS_INSTANCE_OF ProductionArea
AREA-DAL-200 IS_INSTANCE_OF ProductionArea

Area code 300 is typed as PackagingArea:

AREA-MUC-300 IS_INSTANCE_OF PackagingArea
AREA-DAL-300 IS_INSTANCE_OF PackagingArea

Area code 400 is typed as UtilitiesArea:

AREA-MUC-400 IS_INSTANCE_OF UtilitiesArea
AREA-DAL-400 IS_INSTANCE_OF UtilitiesArea

This shows how domain ontologies contribute classes to the shared knowledge graph.

The production ontology provides:

ProductionArea
PackagingArea
ProductionLine
PackagingLine

The inventory ontology provides:

Warehouse
StorageZone

The energy ontology provides:

UtilitiesArea

Instances from several operational domains can therefore coexist in one connected plant topology.


7. Typing work centers

The storage work centers are typed as StorageZone.

For example:

Dry Ingredient Storage
    IS_INSTANCE_OF
StorageZone

and:

Liquid Ingredient Storage
    IS_INSTANCE_OF
StorageZone

Production lines are typed as ProductionLine:

WC-MUC-200-L1
    IS_INSTANCE_OF
ProductionLine

Packaging lines are typed as PackagingLine:

WC-MUC-300-L1
    IS_INSTANCE_OF
PackagingLine

Not every work center has a domain-specific class.

The clean-in-place station and the utility work centers are typed against the generic WorkCenter class:

CIP Station 1
    IS_INSTANCE_OF
WorkCenter

and:

Compressed Air System
    IS_INSTANCE_OF
WorkCenter

This is a useful feature of an extensible ontology.

The knowledge graph does not have to wait until every possible domain class has been defined.

An instance can initially be classified against a general class:

WorkCenter

A more specialized class can be introduced later if the semantic distinction becomes valuable.

For example, a later extension might define:

CIPStation SUBCLASS_OF WorkCenter
SteamUtilityCenter SUBCLASS_OF WorkCenter
CompressedAirUtilityCenter SUBCLASS_OF WorkCenter

The existing instances could then be retyped without changing their stable identities.


8. Keeping domain subtypes off operational labels

The production area instance has the labels:

Instance
Location
Area

It does not receive a ProductionArea label.

Instead, its domain type remains on the ontology relationship:

AREA-MUC-200
    IS_INSTANCE_OF
ProductionArea

This design prevents the instance nodes from accumulating a new Neo4j label for every ontology class.

If a new domain concept is introduced, the graph does not necessarily need a new operational label.

For example, suppose a later ontology introduces:

BulkStorageArea SUBCLASS_OF Warehouse

An existing area could be reclassified through:

AREA-MUC-100
    IS_INSTANCE_OF
BulkStorageArea

without requiring the instance to receive an additional BulkStorageArea label.

The label layer remains small and optimized for operational access:

Enterprise
Site
Area
WorkCenter

The ontology layer carries the richer and more extensible semantic classification.


9. Creating spatial relationships between instances

Typing the instances tells us what each location is.

The graph must also represent how the locations are related.

The script uses the core CONTAINS relationship:

Location CONTAINS Location

The relationship is directed from parent to child:

Enterprise CONTAINS Site
Site CONTAINS Area
Area CONTAINS WorkCenter

Enterprise to site

The enterprise is connected to both sites:

MATCH
    (e:Instance:Location {id: 'GLOBAL-IND'}),
    (s:Instance:Site)
MERGE (e)-[:CONTAINS]->(s);

The resulting graph is:

Global Industries
├── Munich Plant
└── Dallas Plant

Site to area

Sites are connected to areas by comparing the plant code embedded in their IDs:

MATCH
    (s:Instance:Site),
    (a:Instance:Area)
WHERE split(s.id, '-')[1] = split(a.id, '-')[1]
MERGE (s)-[:CONTAINS]->(a);

For example:

SITE-MUC
AREA-MUC-200

both contain the plant code:

MUC

The script therefore creates:

Munich Plant
    CONTAINS
Munich Production Area

The Dallas site is connected only to areas with the DAL code.

Area to work center

Areas are connected to work centers by matching both the plant code and the area code:

MATCH
    (a:Instance:Area),
    (wc:Instance:WorkCenter)
WHERE split(a.id, '-')[1] = split(wc.id, '-')[1]
  AND split(a.id, '-')[2] = split(wc.id, '-')[2]
MERGE (a)-[:CONTAINS]->(wc);

For example:

AREA-MUC-200
WC-MUC-200-L1

share:

Plant code: MUC
Area code: 200

The graph therefore creates:

Munich Production Area
    CONTAINS
Munich Production Line 1

The result is a traversable representation of the plant hierarchy rather than a set of disconnected location records.


10. The complete class-to-instance pattern

Consider the Munich production line.

The ontology layer contains:

Location
    └── WorkCenter
            └── ProductionLine

The instance layer contains:

WC-MUC-200-L1

The instance is typed through:

WC-MUC-200-L1
    IS_INSTANCE_OF
ProductionLine

It is placed within the operational topology through:

Global Industries
    CONTAINS Munich Plant

Munich Plant
    CONTAINS Munich Production Area

Munich Production Area
    CONTAINS WC-MUC-200-L1

The complete path is therefore:

Global Industries
    CONTAINS
Munich Plant
    CONTAINS
Munich Production Area
    CONTAINS
Munich Production Line 1
    IS_INSTANCE_OF
ProductionLine
    SUBCLASS_OF
WorkCenter
    SUBCLASS_OF
Location

This path connects operational reality to semantic meaning.

The left side tells us where the line exists in the organization.

The right side tells us what kind of thing the line is.


11. Querying the instance and class layers together

Because both layers are connected, applications can query operational facts and ontology definitions together.

Retrieve an instance and its class

MATCH
    (instance:Instance {id: 'WC-MUC-200-L1'})
    -[:IS_INSTANCE_OF]->
    (class:Class)
RETURN
    instance.id,
    instance.name,
    labels(instance),
    class.name,
    class.description;

The result tells us:

Instance: WC-MUC-200-L1
Name: Line 1
Structural level: WorkCenter
Ontology class: ProductionLine

The instance name alone would not tell us whether Line 1 is a production line or packaging line.

The ontology classification resolves that ambiguity.

Retrieve all production lines

MATCH
    (line:Instance)
    -[:IS_INSTANCE_OF]->
    (:Class {name: 'ProductionLine'})
RETURN
    line.id,
    line.name;

This returns the production-line instances without relying on naming conventions such as whether the name contains the word production.

Retrieve a plant topology

MATCH path =
    (enterprise:Instance {id: 'GLOBAL-IND'})
    -[:CONTAINS*]->
    (location:Instance)
RETURN path;

This displays the complete location hierarchy beneath the enterprise.

Find the parent context of a work center

MATCH
    (site:Instance:Site)
    -[:CONTAINS]->
    (area:Instance:Area)
    -[:CONTAINS]->
    (workCenter:Instance {id: 'WC-MUC-200-L1'})
RETURN
    site.name AS site,
    area.name AS area,
    workCenter.name AS workCenter;

The result is:

Site: Munich Plant
Area: Production Area
Work center: Line 1

Query all instances grounded in Location

Because every location instance has the Location label:

MATCH (location:Instance:Location)
RETURN
    location.id,
    location.name,
    labels(location);

An ontology-driven query could also traverse the class hierarchy:

MATCH
    (instance:Instance)
    -[:IS_INSTANCE_OF]->
    (class:Class)
    -[:SUBCLASS_OF*0..]->
    (:Class {name: 'Location'})
RETURN
    instance.id,
    instance.name,
    class.name AS specificClass;

This uses the ontology itself to identify instances whose class ultimately specializes Location.


12. Why preserve both layers?

It may appear simpler to represent the Munich production area only as:

(:ProductionArea {
    id: "AREA-MUC-200"
})

However, that would combine the operational and semantic layers into a single Neo4j label.

The explicit class-instance model offers several advantages.

Ontology definitions remain centralized

The meaning of ProductionArea is stored once in the ontology class.

Every production-area instance points to that shared definition.

Domain models can evolve independently

A more specific class can be added later without changing the stable operational identity of the instance.

Applications can query at different levels

An application can ask for:

All work centers

using operational labels, or:

All production lines

using ontology classes.

Agents can traverse from facts to meaning

An AI agent can retrieve:

WC-MUC-200-L1

and then follow IS_INSTANCE_OF to determine that it represents a production line.

From there, it can inspect the ontology to discover relationships and constraints associated with production lines.

Cross-domain concepts remain connected

The plant hierarchy combines classes from several extensions:

Warehouse
ProductionArea
PackagingArea
UtilitiesArea
StorageZone
ProductionLine
PackagingLine

These classes originate from different domain ontologies but are instantiated in one connected graph.


13. Preparing the graph for equipment and operations

The location graph is the first instance layer because most later operational facts require spatial context.

Equipment can later be added and connected to the work centers:

CookKettle101
    IS_LOCATED_AT
WC-MUC-200-L1

The equipment can also be typed against an ontology class:

CookKettle101
    IS_INSTANCE_OF
CookKettle

Processes can occur at the locations:

CookingExecution4711
    OCCURS_AT
WC-MUC-200-L1

Events can be traced to the process and, through the process, to the plant hierarchy:

TemperatureDeviationEvent88
    OCCURS_DURING
CookingExecution4711

An agent could then traverse:

TemperatureDeviationEvent88
    ↓
CookingExecution4711
    ↓
WC-MUC-200-L1
    ↓
Munich Production Area
    ↓
Munich Plant

The location graph therefore becomes the spatial backbone for the operational knowledge graph.


A design consideration: class identity

The current script matches ontology classes by their name property:

MATCH (c:Class {name: 'ProductionLine'})

This is readable and works as long as class names remain unique and stable.

A hardened production implementation may instead match classes by their canonical URI:

MATCH (c:Class {
    uri: 'http://industry40.tv/ontology/production/ProductionLine'
})

The URI is intended to remain stable even if the display name changes.

This does not change the class-instance design. It strengthens how the script resolves the intended ontology class.


Conclusion

Creating an ontology gives us the class layer of the graph.

It defines concepts such as:

Enterprise
Site
Warehouse
ProductionArea
PackagingArea
UtilitiesArea
StorageZone
ProductionLine
PackagingLine

Creating a knowledge graph requires us to populate those classes with real operational instances:

Global Industries
Munich Plant
Dallas Plant
Munich Production Area
Munich Line 1
Dallas Packaging Line 1

The IS_INSTANCE_OF relationship connects the two layers:

Operational instance
    IS_INSTANCE_OF
Ontology class

The CONTAINS relationship then connects the operational instances into a representation of the actual plant topology:

Enterprise
    CONTAINS Site
        CONTAINS Area
            CONTAINS WorkCenter

The resulting graph does more than store plant records.

It represents actual locations, preserves their organizational and spatial relationships, and connects each one to the formal meaning defined by the ontology.

That is the transition from ontology to knowledge graph:

The ontology defines the kinds of things that may exist. The knowledge graph identifies the things that actually exist, connects them to their classes, and represents the relationships they have in operational reality.

With the location instance layer established, the next step is to add equipment, materials, agents, processes, events, and states to the same graph.

Index

Get the Next Guide

New architecture guides, implementation tutorials and use-case blueprints, delivered as they’re published.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

More guides

All guides →
Connecting Industrial Assets to a Knowledge Graph Using MQTT, Node-RED, and Neo4j
Semantic Data Layer
We build a small, complete pipeline that takes an MQTT message from a shop-floor system, transforms it into a Cypher write, and updates the knowledge graph in real time.
Kudzai Manditereza
Kudzai Manditereza
Aug 18, 2026
Connecting Industrial Assets to a Knowledge Graph Using MQTT, Node-RED, and Neo4j
How to Build a Knowledge Graph for Manufacturing Operations
Semantic Data Layer
The core and domain ontologies define what may exist in a manufacturing environment. Here we use them to build a knowledge graph of what actually exists.
Kudzai Manditereza
Kudzai Manditereza
Aug 18, 2026
How to Build a Knowledge Graph for Manufacturing Operations
Building an Energy Operations Domain Ontology in Manufacturing
Semantic Data Layer
The core manufacturing ontology already gives us shared concepts. Here we extend it with the classes and relationships needed to describe energy and plant utilities.
Kudzai Manditereza
Kudzai Manditereza
Aug 18, 2026
Building an Energy Operations Domain Ontology in Manufacturing