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.
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 LocationHowever, 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:
Equipment is deliberately excluded at this stage. It can be added later during commissioning and connected to the locations created here.
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:
ProductionAreais 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 Areais an actual area at the Munich plant.
The connection between the two is:
Munich Production Area
IS_INSTANCE_OF
ProductionAreaIn 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 Cypher script creates the spatial hierarchy of Global Industries.
At the highest level is the enterprise:
Global IndustriesThe enterprise contains two sites:
Global Industries
├── Munich Plant
└── Dallas PlantEach site contains four areas:
Raw Material Storage Area
Production Area
Packaging Area
Utilities AreaEach 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 SystemThe 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.
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-L1The 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-L1identifies:
Work center
Munich plant
Production area 200
Line 1The 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-L1Both may have the same display name, but they remain distinct operational entities.
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
EnterpriseEach 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 2015These 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');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.
Every location instance carries:
:Instance
:LocationIt also receives one structural label:
:Enterprise
:Site
:Area
:WorkCenterFor 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: WorkCenterBy using labels as the structural representation, the graph avoids that possibility.
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:
ProductionAreaThe connection is represented explicitly:
AREA-MUC-200
IS_INSTANCE_OF
ProductionAreaThis distinction allows the graph to use two different classification mechanisms for different purposes.
Labels support efficient operational queries:
Site
Area
WorkCenterThey answer questions such as:
Give me all work centers.
The IS_INSTANCE_OF relationship provides semantic meaning:
Warehouse
ProductionArea
PackagingArea
UtilitiesArea
StorageZone
ProductionLine
PackagingLineIt 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.
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
EnterpriseAll 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 SiteThe 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 ProductionAreaArea code 300 is typed as PackagingArea:
AREA-MUC-300 IS_INSTANCE_OF PackagingArea
AREA-DAL-300 IS_INSTANCE_OF PackagingAreaArea code 400 is typed as UtilitiesArea:
AREA-MUC-400 IS_INSTANCE_OF UtilitiesArea
AREA-DAL-400 IS_INSTANCE_OF UtilitiesAreaThis shows how domain ontologies contribute classes to the shared knowledge graph.
The production ontology provides:
ProductionArea
PackagingArea
ProductionLine
PackagingLineThe inventory ontology provides:
Warehouse
StorageZoneThe energy ontology provides:
UtilitiesAreaInstances from several operational domains can therefore coexist in one connected plant topology.
The storage work centers are typed as StorageZone.
For example:
Dry Ingredient Storage
IS_INSTANCE_OF
StorageZoneand:
Liquid Ingredient Storage
IS_INSTANCE_OF
StorageZoneProduction lines are typed as ProductionLine:
WC-MUC-200-L1
IS_INSTANCE_OF
ProductionLinePackaging lines are typed as PackagingLine:
WC-MUC-300-L1
IS_INSTANCE_OF
PackagingLineNot 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
WorkCenterand:
Compressed Air System
IS_INSTANCE_OF
WorkCenterThis 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:
WorkCenterA 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 WorkCenterThe existing instances could then be retyped without changing their stable identities.
The production area instance has the labels:
Instance
Location
AreaIt does not receive a ProductionArea label.
Instead, its domain type remains on the ontology relationship:
AREA-MUC-200
IS_INSTANCE_OF
ProductionAreaThis 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 WarehouseAn existing area could be reclassified through:
AREA-MUC-100
IS_INSTANCE_OF
BulkStorageAreawithout requiring the instance to receive an additional BulkStorageArea label.
The label layer remains small and optimized for operational access:
Enterprise
Site
Area
WorkCenterThe ontology layer carries the richer and more extensible semantic classification.
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 LocationThe relationship is directed from parent to child:
Enterprise CONTAINS Site
Site CONTAINS Area
Area CONTAINS WorkCenterThe 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 PlantSites 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-200both contain the plant code:
MUCThe script therefore creates:
Munich Plant
CONTAINS
Munich Production AreaThe Dallas site is connected only to areas with the DAL code.
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-L1share:
Plant code: MUC
Area code: 200The graph therefore creates:
Munich Production Area
CONTAINS
Munich Production Line 1The result is a traversable representation of the plant hierarchy rather than a set of disconnected location records.
Consider the Munich production line.
The ontology layer contains:
Location
└── WorkCenter
└── ProductionLineThe instance layer contains:
WC-MUC-200-L1The instance is typed through:
WC-MUC-200-L1
IS_INSTANCE_OF
ProductionLineIt 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-L1The 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
LocationThis 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.
Because both layers are connected, applications can query operational facts and ontology definitions together.
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: ProductionLineThe instance name alone would not tell us whether Line 1 is a production line or packaging line.
The ontology classification resolves that ambiguity.
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.
MATCH path =
(enterprise:Instance {id: 'GLOBAL-IND'})
-[:CONTAINS*]->
(location:Instance)
RETURN path;This displays the complete location hierarchy beneath the enterprise.
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 1Because 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.
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.
The meaning of ProductionArea is stored once in the ontology class.
Every production-area instance points to that shared definition.
A more specific class can be added later without changing the stable operational identity of the instance.
An application can ask for:
All work centersusing operational labels, or:
All production linesusing ontology classes.
An AI agent can retrieve:
WC-MUC-200-L1and 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.
The plant hierarchy combines classes from several extensions:
Warehouse
ProductionArea
PackagingArea
UtilitiesArea
StorageZone
ProductionLine
PackagingLineThese classes originate from different domain ontologies but are instantiated in one connected graph.
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-L1The equipment can also be typed against an ontology class:
CookKettle101
IS_INSTANCE_OF
CookKettleProcesses can occur at the locations:
CookingExecution4711
OCCURS_AT
WC-MUC-200-L1Events can be traced to the process and, through the process, to the plant hierarchy:
TemperatureDeviationEvent88
OCCURS_DURING
CookingExecution4711An agent could then traverse:
TemperatureDeviationEvent88
↓
CookingExecution4711
↓
WC-MUC-200-L1
↓
Munich Production Area
↓
Munich PlantThe location graph therefore becomes the spatial backbone for the operational knowledge graph.
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.
Creating an ontology gives us the class layer of the graph.
It defines concepts such as:
Enterprise
Site
Warehouse
ProductionArea
PackagingArea
UtilitiesArea
StorageZone
ProductionLine
PackagingLineCreating 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 1The IS_INSTANCE_OF relationship connects the two layers:
Operational instance
IS_INSTANCE_OF
Ontology classThe CONTAINS relationship then connects the operational instances into a representation of the actual plant topology:
Enterprise
CONTAINS Site
CONTAINS Area
CONTAINS WorkCenterThe 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.
New architecture guides, implementation tutorials and use-case blueprints, delivered as they’re published.





