Skip to learning content
← All articles
RAG free

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 3: Build the graph, validate it, and follow the evidence

3. Build the graph, validate it, and follow the evidence#

Chapter 2 created a small ontology progressively, ending with 25 triples and a relationship query. Now use the complete importer to add stock snapshots, allocations, inspection evidence, and source lineage. The baseline produces 121 instance triples. The smaller lesson and this complete build use separate output directories, so you can compare them.

Keep the original fixture unchanged for this first run. An expected result is useful only if the inputs are known. The reference clock is fixed at 14 September 2026, 08:00 UTC, so the same dated records can produce reproducible freshness checks.

Step 1: Build from explicit source records#

From the lab directory, run the build with the environment created in chapter 2:

powershell
.\.venv\Scripts\python.exe ontology_lab.py build

The command reads the CSV fixtures and constructs schema/schema.ttl, schema/shapes.ttl, results/instances.ttl, and results/dataset.trig, alongside provenance and build records. Turtle represents the populated statements; TriG carries the dataset's separate named graphs. These files are produced from the functions in ontology_lab.py; they are not the persistent place to edit the model.

Start by following the work-order row. Its fields have specific destinations:

Source valueGraph roleMeaning preserved
order_id=WO-204m:recordId on a WorkOrderThe job's canonical record identifier
org=ORG-TRAINm:orgIts synthetic organization scope
asset_ref=GENERATOR_017m:asset to an AssetCanonical generator GEN-17
part_ref=FILTER_PF9m:part to a PartTypeCanonical catalogue item PF-9
required_quantity=2m:requiredQuantityA typed integer requirement
unit=eachm:unitThe quantity counts individual filters

The mapper resolves those aliases through the explicit table before writing the relationships. It must not create a new generator simply because a source uses a different nickname. An unresolved reference is an ingestion problem to fix, not an invitation to invent an entity.

Step 2: Inspect the instance the program created#

These selected triples show the resulting work order and its typed targets. They are a readable excerpt of the instance graph, with prefixes added for clarity:

turtle
@prefix m: <https://example.org/maintenance/> .
@prefix order: <https://example.org/maintenance/id/ORG-TRAIN/order/> .
@prefix asset: <https://example.org/maintenance/id/ORG-TRAIN/asset/> .
@prefix part: <https://example.org/maintenance/id/ORG-TRAIN/part/> .

order:WO-204 a m:WorkOrder ;
    m:recordId "WO-204" ; m:org "ORG-TRAIN" ;
    m:asset asset:GEN-17 ; m:part part:PF-9 ;
    m:requiredQuantity 2 ; m:unit "each" .
asset:GEN-17 a m:Asset .
part:PF-9 a m:PartType .

Save this complete script as inspect_order.py in the lab directory. It reads the generated RDF and prints the work order's linked records. Using the class and identifier fields makes the example independent of Turtle's formatting or statement order.

python
from rdflib import Graph, Namespace, RDF

m = Namespace("https://example.org/maintenance/")
graph = Graph().parse("results/instances.ttl", format="turtle")
orders = [
    node for node in graph.subjects(RDF.type, m.WorkOrder)
    if str(graph.value(node, m.recordId)) == "WO-204"
    and str(graph.value(node, m.org)) == "ORG-TRAIN"
]
if len(orders) != 1:
    raise RuntimeError("Expected exactly one scoped work order")
order = orders[0]
asset = graph.value(order, m.asset)
part = graph.value(order, m.part)
print("order:", graph.value(order, m.recordId))
print("asset:", graph.value(asset, m.recordId))
print("part:", graph.value(part, m.recordId))
print("required:", graph.value(order, m.requiredQuantity))
print("unit:", graph.value(order, m.unit))

Run it using the same environment:

powershell
.\.venv\Scripts\python.exe inspect_order.py

The supplied fixture produced these fields when the script was checked:

text
order: WO-204
asset: GEN-17
part: PF-9
required: 2
unit: each

You have followed a schema-to-instance relationship using the actual graph. The script inspects just the work order; the full query also checks stock, allocations, and evidence before calculating availability.

Step 3: Run the acceptance checks#

Execute the validator explicitly:

powershell
.\.venv\Scripts\python.exe ontology_lab.py validate

The validator applies the supplied shapes and writes results/validation-report.txt and .ttl. For the baseline, its console result is:

json
{
  "conforms": true,
  "inference": "rdfs",
  "truthOrAuthorizationCertified": false
}

A passing report establishes that the data satisfies those selected conditions. It does not certify physical stock, legal authority, or overall maintenance readiness. SHACL evaluates the conditions provided to it; the coverage of those conditions remains an engineering decision.301

Read a failure at the level of the record and field. A missing asset relationship needs an identity or mapping correction. An invalid quantity needs the unit and source value checked. Rebuilding from corrected source records preserves the explanation of what changed; editing the generated output merely to remove an error can hide the underlying defect.

The program also checks business invariants beyond simple datatypes. North's six recorded filter units include two quarantined units and three separately reserved units. Those allocation sets must be disjoint and refer to the intended snapshot. Correct integers attached to the wrong snapshot would still produce an unreliable answer.

Step 4: Ask the maintenance question#

Run the scoped query:

powershell
.\.venv\Scripts\python.exe ontology_lab.py query --org ORG-TRAIN --order WO-204

The supplied fixture produces one available filter and a shortage of one. The result retains the inputs: required two, on hand six, quarantined two, reserved three, and unit each. A bare answer of “no” would prevent the coordinator from seeing whether the problem was stock, stale evidence, or an incorrect mapping.

The command writes results/explanation.json and results/human-review-request.json. The request is marked UNSUBMITTED_LOCAL_DRAFT and changes no inventory. The organization argument selects a local test scope; Chapter 5 collects the implemented and production-required boundaries in one table.

Chapter 4 opens the query itself and examines the calculation. Before moving on, follow results/provenance.ttl back to the source row and file. The build uses PROV entities and activities to record derivation.302 Locators such as orders.csv#record=2 identify CSV records, counting the header as record 1. Hashes in results/manifest.json identify input bytes; they do not prove the supplier was correct. A later source revision requires a new, checked build.

Step 5: Place neural extraction behind a candidate boundary#

Unstructured maintenance notes would need an additional intake path. A future extractor could read “the two North filters are on hold” and propose an allocation candidate. That phrase alone does not establish which snapshot or individual units it concerns, or whether “on hold” means reservation or quarantine. Preserve the passage and ask for the missing context.

The executable path and the optional neural extension

Original pipeline diagram. The top two rows are the executable CSV path. Bottom row: optional neural extension, NOT IMPLEMENTED. A text-extraction model could propose candidate records for review; no neural model is run by this lab.

Select a box to inspect its role. On a small screen, scroll the diagram sideways.
readmapcheckreportuseproposereview
CSV source files

Read source records with explicit identifiers, units, organization scope and timestamps. Keep immutable input hashes with the output generation.

Read the complete diagram as text
  • CSV source files: Read source records with explicit identifiers, units, organization scope and timestamps. Keep immutable input hashes with the output generation.
  • Resolve & normalize: Apply the reviewed identity mapping and typed conversions. Unknown identities or inconsistent records are rejected instead of guessed.
  • Build RDF records: Create instances of the ontology classes and their relationships using RDFLib. Attach source provenance so a query result can be traced back.
  • Run SHACL checks: Check required fields, data types, relationships and relevant integrity rules. OWL domain and range statements are not a substitute for this validation gate.
  • Inspect the result: Read the validation report and correct failed inputs or mappings. A successful syntax parse alone is not sufficient to publish trusted data.
  • Query the graph: Use the validated graph to answer the sample maintenance question. Reviewable evidence and an explicit shortage are the output; no stock is allocated by this tutorial.
  • Optional: source text: NOT IMPLEMENTED. A separately approved text or document source could feed an extraction stage. The article paper studies text-to-graph extraction; the downloadable lab starts with CSV records.
  • Optional: extract candidates: NOT IMPLEMENTED. A model could propose entities and relationships with quoted evidence and uncertainty. A proposal would still need identity resolution, validation and review.
  • Optional: review candidates: NOT IMPLEMENTED. A reviewer would compare the candidates against the source and publish reviewed structured records at the intake boundary. No neural output enters this lab.
  • CSV source files → read → Resolve & normalize
  • Resolve & normalize → map → Build RDF records
  • Build RDF records → check → Run SHACL checks
  • Run SHACL checks → report → Inspect the result
  • Inspect the result → use → Query the graph
  • Optional: source text → propose → Optional: extract candidates
  • Optional: extract candidates → review → Optional: review candidates

The proposed extension is: source passage → candidate entities and relationships → canonical identity resolution → explicit checks → human review → published facts. It is not implemented in this lab. The current runnable path ingests the structured CSV fixture deterministically. The PDF extraction guide explains the separate document-reading problem.

Fasina, Sawyerr, and Murainah report precision 0.822, recall 0.824, and F1 0.823 for their WebNLG text-to-RDF experiment.303 Those are the authors' measurements, not results from this maintenance lab. Their approach motivates checking extracted candidates; a high extraction score still does not establish physical stock or operational authority.

An extractor's confidence is a review signal, not a truth label. OWL's open-world semantics also mean missing knowledge is not automatically false; OWL is not a validation firewall.304 Keep uncertainty, contradictions, and unresolved references outside accepted operational facts until the relevant checks and reviewers resolve them.

For a future extraction test, label the expected facts first. If a synthetic trial has 9 correct predictions, 3 incorrect predictions, and 1 missed fact, its illustrative scores are:

formula
Precision = TP ÷ (TP + FP) = 9 ÷ 12 = 0.75
Recall = TP ÷ (TP + FN) = 9 ÷ 10 = 0.90
F1 = 2TP ÷ (2TP + FP + FN) = 18 ÷ 22 ≈ 0.818

That arithmetic is a teaching example, not an executed neural evaluation. Record which kinds of mistakes matter: confusing quarantine with reservation may be more consequential than missing a descriptive nickname.

Checkpoint: what you created and what to inspect#

You have the complete schema, shapes, populated RDF, source lineage, validation report, and one-filter shortage result. Confirm that the inspection script prints GEN-17, PF-9, two, and each. Find orders.csv#record=2 in the provenance and compare its source values with the graph. Chapter 4 provides an interactive viewer of these actual exported records and explains the query and integration contract.

Source 301 ↗

W3C, Shapes Constraint Language — data-graph validation and report semantics; checked 14 September 2026.

Source 302 ↗

W3C, PROV-O: The PROV Ontology — entity, activity, and derivation terms; checked 14 September 2026.

Source 303 ↗

E. P. Fasina, B. A. Sawyerr, and A. Murainah, On the Construction of Knowledge Graphs from Unstructured Text using Neuro-Symbolic Networks, UIJSLICTR 17(1), 121–145 (2026), table 6, printed page 138; discussion, pages 139–140 — full text checked 14 September 2026. This was a text-to-RDF research experiment.

Source 304 ↗

W3C, OWL 2 Web Ontology Language Primer — sections 2 and 4.2, open-world distinction; checked 14 September 2026.