Build a working ontology from defence logistics data
Build an ontology from an empty Python file, inspect actual graph records and their sources, and extend a maintenance example to multiple required parts. Six chapters combine plain-language explanations with a downloadable, locally tested implementation.
The question: How do we actually create, validate and use an ontology from operational source records?
You’ll learn: Construct an ontology step by step, trace RDF facts to CSV records, validate and query them, and migrate the model to support several required parts per order.
For: Domain experts, analysts and engineers; concepts are explained before the runnable commands.
Executed teaching prototype · AI-assisted source check . This is not a human expert endorsement.
Chapter 4: Inspect the real graph and define the integration contract
4. Inspect the real graph and define the integration contract#
You now have a populated graph and a checked result. This chapter connects the two: follow WO-204 to its asset and part, inspect the stock and allocations, and open the source rows behind those facts. Then define the contract another application would need to use the same model.
Step 1: Explore the records that were actually built#
The explorer below contains an export of the verified, fictional ORG-TRAIN baseline graph. It is generated from the lab artifacts. It is not a drawing of records that might exist. Selecting a record changes only this page; the viewer makes no model or business-system calls.
Follow WO-204 from graph to source
These 7 records and 6 relationships come from the lab’s generated RDF. Select a record, follow a relationship, then inspect the original source row.
Fictional scope: ORG-TRAIN · Saved comparison time: . This is a fixed teaching snapshot.
On a narrow screen, scroll the graph sideways. Every record is also available in the selector below.
WO-204 WorkOrder
- org
- ORG-TRAIN
- recordId
- WO-204
- requiredQuantity
- 2integer
- unit
- each
Follow a relationship
- WO-204 → asset →
- WO-204 → part →
Inspect the exported build identity
Schema version: 1.0.0
Input fingerprint: 51b127fe0d01615e8c3137bf0c1b8dc45983b01c1ce0ea5d6b015258520d629f
Importer SHA-256: 0f956fe682b1a33f108cfd5420a34a29ddb479e96c12c2b75cdc2650aea080ff
Exporter SHA-256: de404043247cc314794a17c1cecd59f34b7c6f6ddaefcdd291dac2fc4bb476bf
Start at WO-204. Follow its asset relationship to GEN-17 and its part relationship to PF-9. Inspect the property requiredQuantity, then open its orders.csv source record. Follow a stock snapshot and its allocations to see which values supplied the shortage calculation.
Notice the difference between a relationship and a literal property: m:part points to another identified record; m:requiredQuantity contains an integer. The source row uses FILTER_PF9, while the graph uses the canonical PF-9 identity. The alias record explains that transformation. File hashes identify the bytes used for this export; their presence does not independently certify the source.
Step 2: Ask the graph directly#
For a small executable inspection, save the following as inspect_graph.py in the lab directory after Chapter 3's build:
from rdflib import Graph
graph = Graph().parse("results/instances.ttl", format="turtle")
query = """
PREFIX m: <https://example.org/maintenance/>
SELECT ?asset ?part ?needed ?unit
WHERE {
?order a m:WorkOrder ;
m:org "ORG-TRAIN" ;
m:recordId "WO-204" ;
m:asset ?asset ;
m:part ?part ;
m:requiredQuantity ?needed ;
m:unit ?unit .
}
"""
for row in graph.query(query):
print(tuple(str(term) for term in row))Run .\.venv\Scripts\python.exe inspect_graph.py. The supplied fixture returns the GEN-17 asset IRI, PF-9 part IRI, 2, and each. Each ?name is a variable; the patterns find records having all of the stated relationships and values. SELECT chooses the returned columns.401 An empty result means this pattern found no match, so first inspect the identifiers and mapping.
The inspection query only reads relationships. Run the full business query to apply the availability and evidence checks:
.\.venv\Scripts\python.exe ontology_lab.py query --org ORG-TRAIN --order WO-204Open queries/order_inventory.rq. It binds the organization and order ID through RDFLib's initBindings, joins the matching asset, part, and stock, then sums allocations by kind. The code requires exactly one matching snapshot. It does not combine depots or identify which work order owns each reservation. The --org argument selects a local demonstration scope; it is not a login.
Establish quantities at their own grain: one stock snapshot and its allocations. Joining every inspection record into that sum could repeat the stock row and inflate it. DISTINCT cannot repair every bad join because two legitimate allocations can have the same quantity. Attach descriptive evidence after calculating the correctly scoped quantities.
Step 3: Specify each field and relationship#
A reusable integration needs more than class names. For the baseline, these are actual mappings and cardinalities; “one” means exactly one value on each accepted record. SHACL checks selected cardinalities, while the importer and query supply additional business checks.
| Source fields | Model destination | Type and multiplicity |
|---|---|---|
orders.csv: order_id, org | WorkOrder identity and scope | One canonical ID and one organization |
asset_ref | WorkOrder m:asset → Asset | One resolved asset |
part_ref, required_quantity, unit | WorkOrder m:part, m:requiredQuantity, m:unit | One part; positive integer; each |
inventory.csv: snapshot_id, part_ref, depot | StockSnapshot identity, m:part, m:depot | One part and one depot value |
on_hand, unit_ids, observed_at, revision | Snapshot quantity, physical unit values, time, revision | Nonnegative integer; distinct unit IDs; timezone-aware timestamp; positive revision |
allocations.csv: snapshot_id, kind, quantity | Allocation m:snapshot, m:kind, m:quantity | One snapshot; reserved or quarantined; nonnegative integer |
inspections.csv: asset_ref, status, source_version | Evidence m:asset, m:status, m:sourceVersion | One asset, enumerated status, and source-version string |
The record key is the full IRI built from organization, entity kind, and canonical ID. It is not a display name. A WorkOrder has one asset; an Asset may have several orders. Each Allocation has one snapshot; a snapshot may have several allocations. The baseline's one-part WorkOrder constraint is deliberate and is replaced with requirement-line objects in Chapter 6.
Document who may establish or revise each fact. The following ownership and update rules are a proposed integration contract for a real pilot, rather than services supplied by the local lab:
| Producer and owner | Authoritative contribution | Update rule to implement |
|---|---|---|
| Equipment register / asset steward | Asset identity and approved aliases | Review identity changes; preserve existing references |
| Maintenance planning / job owner | Work-order request | Version changed requirements; do not infer approval from creation |
| Stock control / depot owner | Counts, unit identities, allocation state | Publish a coherent snapshot; reject conflicting revisions |
| Inspection register / evidence owner | Observation, status, source version | Preserve the observed time and source revision |
Object, property, and relationship documentation provides useful implementation concepts, but it does not choose these domain contracts for you.403404405 The keys, source meanings, cardinalities, and ownership decisions above must be reviewed against the actual organization.
Step 4: Choose components for concrete responsibilities#
Original architecture image. Solid connections show the local executable path. The bottom row identifies services a deployed application would still need; those services are not silently claimed as part of the lab.
Select a box to inspect its role. On a small screen, scroll the diagram sideways.The bounded sample data and reviewed identity map are the lab inputs. Real source connectors would need ownership, change detection and access policy.
Read the complete diagram as text
- Versioned input files: The bounded sample data and reviewed identity map are the lab inputs. Real source connectors would need ownership, change detection and access policy.
- Python + RDFLib: The build command creates the RDF/OWL model and maps records into a graph. There is no graph-database service or LLM API hidden behind this step.
- Ontology + instances: Schema, instance data and provenance are concrete files that can be inspected independently of the website. A database service becomes a separate deployment choice.
- Query & review output: The sample query computes availability and shortage and emits its explanation. This output supports a review; it does not issue a command to a maintenance or inventory system.
- RDFLib SPARQL: The query path uses explicit organization scope and typed relationships. The CLI scope selector is a test boundary, not enterprise authentication.
- pySHACL validation: Run the supplied shape and integrity checks before using the graph. Keep the report and failed-record evidence.
- Deployment: identity: Replace the demonstration scope selector with verified principals and server-enforced policy. Protect data, derived outputs, logs and exports.
- Deployment: workflow: A durable scheduler and controlled command handler would manage publication, retries, approval expiry and downstream changes. They require their own tests.
- Deployment: operations: Backups, monitoring, schema migration, secret handling and approved network boundaries are additional operational responsibilities.
- Versioned input files → ingest → Python + RDFLib
- Python + RDFLib → write → Ontology + instances
- Ontology + instances → validate → pySHACL validation
- pySHACL validation → gate → RDFLib SPARQL
- RDFLib SPARQL → answer → Query & review output
| Component in the lab | Responsibility | Add a service when… |
|---|---|---|
| CSV and JSON fixtures | Repeatable source records | Approved systems must publish updates |
| Python importer | Identity resolution and typed mappings | Updates need scheduling, retries, and quarantine queues |
| RDFLib and RDF files | Store inspectable statements and execute SPARQL402 | Concurrent access, query volume, or availability requires a managed store |
| pySHACL plus business checks | Reject specified invalid records | Several producers need a shared ingestion release gate |
| Query and review JSON | Explain a bounded request | Authenticated users need controlled views or approved actions |
This architecture keeps source interpretation separate from query logic. If you later replace file storage with a graph service, use the same fixture and expected results to check the migration before measuring throughput.
Step 5: Reuse the model for a second question#
Chapter 6 asks which requirement lines of WO-204 are short, and which stock and allocation records explain each line? Its new PartRequirement object owns a part and quantity together. Adding a seal to the filter requirement then produces two independently inspectable paths.
That distinction matters: two m:part values and two quantities placed directly on one WorkOrder would not say which quantity belongs to which part. Giving each line an identity removes that ambiguity. The extension keeps the existing asset, part, stock, and evidence relationships and changes the portion of the model that the new question requires.
Checkpoint: what you can inspect and hand over#
You can trace the actual graph to a CSV record, run a relationship query, explain the aggregation grain, and provide a source-to-model contract. Check that the explorer and results/explanation.json agree on six on hand, two quarantined, three reserved, one available, and one short. Next, preserve that result through an explicit orchestration sequence.
W3C, SPARQL 1.1 Query Language — graph patterns and result columns; checked 14 September 2026. The query is original to this lab.
RDFLib, Getting started — graph parsing and queries; checked 14 September 2026.
Object types and instances — operational model concepts; checked 14 September 2026.
Declared property types — property modelling; checked 14 September 2026.
Relationships between object types — link modelling; checked 14 September 2026. The maintenance mapping and proposed ownership rules are this guide's design.