Ontology engineering · a working brief

Start with the questions, not the classes.

A practical case for building every ontology module from Competency Questions — and a method your team can run without a single ontologist in the room.

Written for Data analysts Database engineers new to ontologies

The argument, up front

Thesis

An ontology module is not “done” when it looks complete. It is done when it can answer the questions the business actually asks. So write those questions first — and let them decide what to build, and prove when to stop.

Left to instinct, a modelling effort drifts toward describing the world — endless classes, ever-finer distinctions, no natural finish line. Competency Questions flip the polarity: they turn a vague “model the domain” into a concrete, checkable list of “the system must be able to answer this.” That is a requirement you can build to and a test you can pass. It is also, conveniently, the exact way your team already thinks.

01 — DEFINITION

What is a Competency Question?

A Competency Question (CQ) is a plain-language question that your ontology — once built and populated with data — must be able to answer. The set of CQs defines the competency of the model: its job description. The idea comes from Grüninger & Fox (1995) and is now the backbone of modern ontology methodologies.

You already use this pattern under other names. A CQ is the ontology’s equivalent of a user story, an acceptance criterion, or — most precisely for a database engineer — the query the schema must be able to serve. If you would never design a table without knowing the reports it has to feed, you already believe in Competency Questions.

The bridge to what you already know

In your world todayIn the ontology worldSame idea?
Table / schemaClass · the TBox (the model)The shape of things
Row / recordIndividual · the ABox (the data)An instance
Column / foreign keyProperty (data / object property)An attribute or link
The queries the schema must serveCompetency QuestionsThe requirements
SQL SELECTSPARQL queryHow you ask
Unit / acceptance testA CQ run against sample dataProof it works
Bounded context / schema boundaryOntology moduleA cohesive slice

Anatomy of a good CQ

A well-formed CQ quietly names the pieces of the model you will need. Read this one and watch the vocabulary fall out:

Who is the producer and the underwriter on a given policy, and which coverages does it include?
Concepts it names:
policy:Policypolicy:Producerpolicy:Underwritercoverage:Coverage
Properties & links it needs:
policy:producerpolicy:underwritercoverage:hasCoveragepolicy:policyNumber

Notice what happened: without drawing a single box, the question named four classes and four properties — every one of which already exists in policy.ttl and coverage.ttland told us they belong together. The question is doing the modelling. (It even reaches the role-reification pattern: policy:Producer is defined as whatever core:hasRole policy:ProducerRole, realized through the policy:producer property.)

02 — THE CASE

Why start here?

Especially for a team without deep ontology experience, starting from questions is not just tidy — it is the thing that keeps the project finishable, honest, and yours.

scope

It draws a finish line

Modelling has no natural end — you can always add one more distinction. A fixed set of CQs turns “are we done?” into a yes/no check: can we answer all of them, yes.

focus

It prevents over-building

If no question needs a class, that class doesn’t get built. CQs are a ruthless filter against modelling for its own sake — the classic trap for newcomers.

language

It speaks business, not OWL

CQs are written in plain domain language, so underwriters, analysts and engineers can all read, challenge and sign off on them — no ontology training required.

testable

It makes “correct” provable

Each CQ becomes a query you can run against sample data. Pass or fail. The model stops being a matter of opinion and becomes a matter of evidence.

boundaries

It reveals the modules

Group the CQs by theme and the natural module seams appear — Policy, Claim, Party, Coverage. Cohesive modules are easier to build, reuse and own.

leverage

It uses skills you have

You already know the questions the business asks and how to express them as queries. CQ-driven modelling meets you exactly there instead of demanding formal logic first.

The failure mode this avoids

Without CQs, teams model top-down from a whiteboard, produce an elegant hierarchy nobody asked for, discover months later it can’t answer a basic operational question, and can never agree on when to stop. CQs replace “model everything” with “model exactly enough.”

03 — HOW TO FORMULATE

How to write them

You do not invent CQs — you harvest them. They are hiding in the work your organisation already does.

Where to find them

  • Existing reports & dashboards. Every KPI on a dashboard is a CQ someone already needs answered.
  • Your SQL query logs. The queries analysts run most often are competency questions in disguise — the highest-value ones.
  • Stakeholder interviews. Ask an underwriter or adjuster: “What do you need to look up to do your job?” Write down the questions verbatim.
  • Operational decisions & regulations. Anything the business must decide or report on is a question the model must support.

The recipe for one good CQ

form

Start with an interrogative

Which… / What… / Who… / When… / How many… Prefer questions that return things or values over yes/no questions — they surface more of the model.

content

Name real domain terms

Use the words the business uses: policy, peril, insured party, incurred loss. Each noun is a candidate class; each relationship a candidate property.

grain

One question, one answer shape

If a question needs the word “and” three times, it’s several CQs. Split until each asks for a single, clear result set.

independence

Stay solution-free

Describe what you need to know, never how it’s stored. “Which policies renew this month,” not “join policy to renewal_dim.”

Good vs. weak — the same intent, two outcomes

Weak CQs

  • “How does insurance work?” — too broad; no bounded answer, defines no module.
  • “Show me everything about a claim.” — vague; “everything” names nothing to build.
  • “Is policy P active?” — trivial yes/no; surfaces almost no structure on its own.
  • “Get rows where status_cd = 2.” — leaks the implementation; not a business question.

Strong CQs

  • “Which perils are covered under a policy on a given date?” — bounded, time-aware, names classes.
  • “What is the total incurred loss for a claim, by coverage?” — a clear result with a grouping.
  • “Who is the insured party on a policy and which broker sold it?” — reveals parties and roles.
  • “Which open claims exceed their coverage limit?” — a decision the business must make.
04 — HOW TO VALIDATE

How to validate them

This is where CQs earn their keep. A validated CQ is a passing test — and this is the part of the workflow that will feel most familiar to an engineer. The technique has a name: test-driven ontology development.

Turn each CQ into a runnable test

Take a CQ, hand-craft a tiny bit of example data (a handful of individuals — the ABox), then write the SPARQL query that answers it. If the model can return the right answer, the CQ passes. If you can’t even express the query, the model is missing a class or property — a precise, actionable gap.

? CQ — “What premium, limit and deductible apply to each coverage on policy PN-100?”
# The CQ, made executable against the real vocabulary — this IS the acceptance test
PREFIX policy:   <https://data.chubb.com/ontology/ins/policy/>
PREFIX coverage: <https://data.chubb.com/ontology/ins/coverage/>

SELECT ?coverage ?premium ?limit ?deductible
WHERE {
  ?policy   policy:policyNumber   "PN-100" ;
            coverage:hasCoverage  ?coverage .
  OPTIONAL { ?coverage coverage:hasPremium    ?premium }
  OPTIONAL { ?coverage coverage:hasLimit      ?limit }
  OPTIONAL { ?coverage coverage:hasDeductible ?deductible }
}

Every term here resolves to something real: policy:policyNumber, coverage:hasCoverage, and the three money properties whose ranges (Premium, Limit, Deductible) are all core:MoneyAmount. Run it against a sample policy in resource/: rows back → the module supports the question. Nothing back, or a term you can’t even name → you’ve found exactly what to fix, before anyone builds on top of it.

Check coverage in both directions

completeness

Every CQ is answerable

No competency question should be left without a passing query. An unanswerable CQ is a backlog item, not a failure.

no orphans

Every element earns its place

Reverse the check: does each class and property appear in at least one CQ’s query? If nothing asks for it, question why it exists.

A living test suite

Keep the CQ-to-SPARQL tests and re-run them whenever the module changes. Like a regression suite, they catch the day a “small refactor” quietly breaks a question the business depends on. Run against the current modules, your validation dashboard looks like this:

Who produced and underwrote policy PN-100?PASS
What premium, limit & deductible apply to each coverage?PASS
Which claims were filed under a policy, and who claimed?PASS
Which open claims exceed their coverage limit?GAP — Claim has no status or amount

Why does that last one fail? Writing the query is the diagnosis — two lines simply can’t be filled in:

# "Which OPEN claims exceed their coverage limit?" — attempting it exposes the gaps
SELECT ?claim ?incurred ?limit
WHERE {
  ?policy policy:policyNumber   "PN-100" ;
          policy:hasClaim       ?claim ;
          coverage:hasCoverage  ?cov .
  ?cov    coverage:hasLimit     ?limit .          # ✓ Limit exists (⊑ core:MoneyAmount)

  # ✗ nothing records whether a Claim is open or closed
  # ?claim claim:hasStatus claim:Open .

  # ✗ nothing gives a Claim its incurred amount
  # ?claim claim:incurredAmount ?incurred .

  # FILTER(?incurred > ?limit)   ← cannot run: ?incurred never binds
}

That single GAP is the whole method working as intended: an unambiguous, prioritised instruction for the next iteration — give claim:Claim a status and an incurred amount — discovered by a test, not by a production incident. It even points at a clue already sitting in the model: policy:PolicyStatus is declared as a class, yet no property connects a policy to one. A CQ just told you to finish it.

You already have the machinery

None of this is theoretical. misc/competency.ttl defines CompetencyQuestion as a real class — each CQ is an individual with a question and an optional sparqlQuery — and misc/policy-cq.ttl already holds the first one. Every question you validate becomes one more individual in that file, carrying the SPARQL you wrote as its sparqlQuery. The dashboard above is just those individuals, executed.

05 — END TO END

The process, start to finish

Put together, CQ-driven modelling is a short, repeatable loop. Build one module at a time; each pass through the loop leaves you with a small, tested, trustworthy piece.

Elicit the questions — analyst leads

Harvest CQs from reports, query logs and interviews. Aim for 15–30 real questions before filtering. Capture them verbatim, in business language.

Agree a glossary — whole team

List the nouns and verbs the CQs use and define each one plainly. Resolve synonyms now (is it insured, policyholder or customer?). This kills ambiguity early.

Group into modules — whole team

Cluster the CQs by theme. Each tight cluster is one module to build. Pick the highest-value cluster and set the rest aside — you’ll loop back.

Extract the model — engineer + analyst

From that cluster’s CQs, pull out the classes, properties and relationships they demand — and only those. This is your draft TBox.

Make sample data — engineer leads

Hand-build a handful of example individuals (the ABox) that a real answer would need. Small is fine — it just has to exercise the questions.

Formalise & run each CQ — engineer leads

Write the SPARQL for every CQ and run it against the sample data. Green means the model answers it; red means a precise, named gap.

Iterate until green — whole team

Fix gaps, re-run, review answers with stakeholders (“is this what you meant?”). When every CQ passes and reads true, the module is done.

↺ Then loop back to step 3 with the next cluster of questions — and keep every test as a regression suite.

06 — WORKED EXAMPLE

One loop, in miniature

Here is the whole method compressed into a single small module, so the shape is concrete.

1 · The harvested questions (a “Claim” cluster)

  1. Which claims were filed under a given policy, and who is each claimant?
  2. What loss gave rise to a given claim?
  3. Which open claims exceed their coverage limit?

2 · The model those questions demand

Already in the model:
claim:Claimpolicy:Policycoverage:Coverageclaim:Loss policy:hasClaimclaim:claimantclaim:hasLosscoverage:hasLimit

Revealed as missing by Q3:
claim:hasStatusclaim:incurredAmount

— and nothing else. No reinsurance, no broker commission: no question in this cluster asked for them, so they wait for their own module.

3 · The proof — and the next task, for free

A few example individuals in resource/ plus three SPARQL queries. Q1 and Q2 return the expected rows → PASS. Q3 can bind neither a status nor an amount → GAP — which becomes a precise, agreed backlog item for the next loop, not a surprise in production. Total elapsed: an afternoon, not a quarter.

07 — NEXT WEEK

Your first working session

You need no new tooling to begin — a shared doc and a whiteboard are enough for the first pass. Here is a 90-minute agenda that produces real output.

0:00Pick one narrow area. Not “insurance” — something like “claims on property policies.”
0:10Brain-dump questions. Everyone writes real questions the business asks about that area. Pull a few from recent SQL and dashboards.
0:40Filter & sharpen. Cut the vague and the yes/no; rewrite the survivors into strong CQs using the recipe above.
1:00Underline the nouns. In each CQ, mark the domain terms — that’s your first draft glossary and class list, for free.
1:20Bank & commit. Save the CQ list as the module’s spec. Next session: sample data and the first SPARQL tests.

The one habit to keep

Every time someone proposes adding a class or property, ask one question: “Which competency question needs it?” If there isn’t one, either write the CQ that justifies it — or don’t build it. That single reflex is most of the discipline.