Warmup — Financial Knowledge Graphs, From Zero
Assumes Python and Phase 06 (retrieval). Assumes nothing about RDF, ontologies, description logic, SHACL or SPARQL. This is the phase with the most unfamiliar vocabulary in the track, and almost all of it is simpler than it sounds.
Table of Contents
- 1. The questions vectors cannot answer
- 2. RDF: everything is a triple
- 3. RDF versus labelled property graphs
- 4. RDFS and OWL: saying what things mean
- 5. The open-world assumption
- 6. SHACL: closing the world for validation
- 7. SPARQL
- 8. FIBO
- 9. Graph-grounded retrieval
- 10. Ontology governance
- 11. Lab walkthrough
- 12. Success criteria
- 13. Common mistakes
- 14. Interview Q&A
- 15. References
1. The questions vectors cannot answer
Phase 06 built retrieval that finds text resembling a question. Now consider three questions a bank asks every day:
"Which of our counterparties are ultimately controlled by an entity on the sanctions list?" "Which obligations does this master agreement create, and which are collateralized?" "If this legal entity defaults, which exposures are affected, and at what depth?"
Each one is about a path, not a passage. The answer to the first might be: Acme is owned by Northgate, which is owned by Meridian, which is controlled by a sanctioned entity. No document says that. Four documents each say one hop, and none of them shares vocabulary with the question.
Embedding similarity cannot compose hops. That is not a limitation of the model; it is a category difference. Similarity is a metric on content; ownership is a relation you traverse.
So a bank platform needs both, and the interesting designs use the graph to decide what text to retrieve (§9) rather than treating them as competing options.
The second reason for a graph is shared meaning. Four systems in a bank each have a "counterparty" table and four different definitions. An ontology is where "legal entity", "obligation" and "control" get definitions that survive crossing a system boundary — and without that, an agent joining two systems will confidently join two things that should never have been joined.
2. RDF: everything is a triple
2.1 The data model
RDF says: all data is statements of the form
$$(\text{subject},\ \text{predicate},\ \text{object})$$
ent:Northgate bank:majorityOwns ent:Acme .
ent:Acme bank:legalName "Acme Trading FZE" .
ent:Acme rdf:type bank:Corporation .
Subjects are always identifiers. Predicates are always identifiers. Objects are either an identifier (linking to another entity) or a literal (a value).
That last asymmetry matters and the lab tests it: a literal is never a subject. You can say
things about Acme; you cannot say things about the string "Acme Trading FZE". This is why
rdfs:range can give a type to an IRI object and never to a literal one.
There is no schema in the SQL sense — no table to alter, no migration. Adding a new kind of fact is adding a triple. That flexibility is the model's strength and, without SHACL (§6), its weakness.
2.2 IRIs, and why global identifiers are the point
An IRI (Internationalized Resource Identifier — a URI that allows non-ASCII) identifies a thing globally:
https://spec.edmcouncil.org/fibo/ontology/BE/LegalEntities/LegalEntity
This looks like ceremony. It is the single most valuable property of the model.
In SQL, customer_id = 4471 means something only relative to a database. Two banks merging must
build a mapping table, and so must two systems in the same bank. In RDF, if both systems say
fibo-be:LegalEntity, they mean the same thing, with no mapping — because the identifier
contains its own namespace.
The practical rule that follows: mint IRIs in a namespace you control, and never reuse one for a different concept. An IRI is a permanent commitment, which is why ontology governance (§10) is not optional.
2.3 CURIEs
Full IRIs are unreadable, so RDF uses compact URI expressions: a declared prefix and a local name.
@prefix bank: <https://bank.example.ae/ontology/> .
bank:controls ⟶ https://bank.example.ae/ontology/controls
Purely notational — CURIEs are expanded before anything else happens. The lab's PrefixMap does
this, and it has one trap worth knowing: https://example.org/x matches the CURIE shape (prefix
https, local name //example.org/x). Check for :// first, or a full IRI gets mangled into a
lookup for a nonexistent prefix. The lab has a test for exactly this, and it caught the bug.
The mirror concern: a graph that mixes expanded and abbreviated forms of the same IRI has two identifiers for one thing, which silently breaks every join. Expand at the boundary, once.
2.4 A graph is a set
Asserting the same triple twice changes nothing. That is not an implementation detail — it is what makes reasoning tractable, because forward chaining can run repeatedly without accumulating duplicates, and "did this round add anything?" is a well-defined question.
The lab's Graph.add returns a boolean: True if the triple was new. That return value is the
fixed-point signal in §4.4, and it is the reason the reasoner terminates.
3. RDF versus labelled property graphs
You will be asked to compare them, so know the difference precisely.
RDF — triples, global IRIs, formal semantics (RDFS/OWL), queried with SPARQL. Everything is a triple, including metadata about triples, which requires reification (representing a statement as a node) and is genuinely awkward.
Labelled property graph (LPG) — nodes and relationships, both carrying key/value properties.
Neo4j is the canonical implementation, Cypher the query language. Properties on relationships are
first-class: (:Company)-[:OWNS {percentage: 65, since: 2019}]->(:Company) is natural.
| RDF | LPG | |
|---|---|---|
| Identity | global IRIs | local ids |
| Properties on edges | reification (awkward) | native |
| Formal semantics | RDFS/OWL, standardized | none |
| Validation | SHACL | application code |
| Query | SPARQL (a W3C standard) | Cypher / Gremlin (vendor) |
| Federation | native — query across endpoints | not really |
| Fits | shared vocabularies, regulatory ontologies, cross-organization | operational graphs, path analytics, one owner |
The honest decision rule: use RDF when meaning must cross an organizational boundary — a regulatory ontology, a shared taxonomy, data you must federate. Use an LPG when the graph is yours, edges carry data, and you care about traversal performance.
For a bank the answer is frequently both: FIBO-aligned RDF as the vocabulary of record, and an LPG for operational path analytics — with the ontology defining what the LPG's labels mean. Neo4j's n10s plugin exists precisely to bridge them.
4. RDFS and OWL: saying what things mean
4.1 RDFS: classes, properties, domain, range
RDF Schema adds a small vocabulary for describing your vocabulary:
| Construct | Says |
|---|---|
rdf:type | this individual is a member of this class |
rdfs:subClassOf | every member of A is a member of B |
rdfs:subPropertyOf | every A-relation is also a B-relation |
rdfs:domain | anything with this property is of this class |
rdfs:range | anything that is the object of this property is of this class |
domain and range are the ones people misread. They are not constraints — they are
inference rules. Declaring bank:hasLEI rdfs:domain fibo-be:LegalEntity does not reject a
statement about a non-entity; it concludes that whatever has an LEI is a legal entity.
That is the open-world assumption arriving early (§5), and it is the reason SHACL exists.
4.2 Entailment
Entailment is what follows from what you said, without your saying it.
ent:Acme rdf:type bank:Corporation .
bank:Corporation rdfs:subClassOf fibo-be:LegalEntity .
────────────────────────────────────────────────────
ent:Acme rdf:type fibo-be:LegalEntity . ← entailed
This is the point of an ontology. You state facts once and get their consequences everywhere, including consequences nobody thought to write down.
The lab's worked example does this in a way worth watching:
ent:Northgate bank:majorityOwns ent:Acme . ← asserted
bank:majorityOwns rdfs:subPropertyOf bank:controls . ← ontology
bank:controls rdf:type owl:TransitiveProperty . ← ontology
─────────────────────────────────────────────────────────────
ent:Meridian bank:controls ent:Acme . ← entailed, 2 rules composed
Nobody asserted that Meridian controls Acme. Two rules composed — rdfs7 turned ownership into control, then transitivity chained it — and four hops up, the sanctioned entity at the top of the chain controls Acme too. That is the query Compliance actually wants, and it is answered by the ontology, not by application code walking a table.
4.3 The OWL constructs that earn their keep
OWL 2 is large. Four constructs carry most of the value in a financial graph:
owl:TransitiveProperty — if A→B and B→C then A→C. This is controls, partOf,
ancestorOf. It is the single most valuable construct here, because ultimate beneficial ownership
is transitive closure.
owl:inverseOf — controls and controlledBy are the same fact read from either end.
Declaring it once means you can traverse in either direction without storing both.
owl:SymmetricProperty — if A relates to B then B relates to A. isCounterpartyOf,
isAffiliateOf.
rdfs:subPropertyOf — a specialization hierarchy for relations. majorityOwns,
hasBoardControl and hasVetoRights might all be sub-properties of controls, so a control
query catches all three without enumerating them. This is how a regulatory definition of control
gets encoded once and used everywhere.
Also useful and not in the lab: owl:FunctionalProperty (at most one value — an entity has one
LEI), owl:disjointWith (nothing is both a Person and an Organization), owl:sameAs (two IRIs
denote the same thing — the entity-resolution construct, and a genuinely dangerous one because it
merges everything said about both).
The reasoning-profile question. OWL 2 has profiles — EL, QL, RL — that trade expressivity for tractability. OWL 2 RL is the one to know: it is designed for rule-based forward chaining exactly like the lab's, and it is what production triple stores implement. Full OWL 2 DL needs a tableau reasoner and can be exponential. If someone proposes full DL reasoning over a bank's live graph, that is the conversation to have.
4.4 Forward chaining to a fixed point
The lab's reasoner is forward-chaining: apply every rule to everything, repeatedly, until a round adds nothing.
for _ in range(max_rounds):
added = 0
added += apply_rdfs11(graph) # subClassOf transitive
added += apply_rdfs9(graph) # type propagates up
...
if added == 0:
break # fixed point
Termination is guaranteed because the rules only add triples from a finite vocabulary, so the
graph grows monotonically toward a bounded limit. The lab asserts this directly: a second
materialize adds zero triples in one round.
Rule order affects how many rounds you need and not the result, which is a nice property to have and is worth knowing you have — it means you can reorder for performance without changing semantics.
The alternative is backward chaining: derive nothing up front, and at query time expand the query to include what would be entailed. The trade:
| Forward (materialize) | Backward (query-time) | |
|---|---|---|
| Write cost | high — closure on every change | none |
| Query cost | none — it is already there | every query pays |
| Storage | the closure can be much larger than the data | none |
| Deletion | breaks — see §4.5 | correct automatically |
Production systems usually materialize, because reads dominate and the deletion problem is managed by re-materializing.
4.5 Monotonicity, and what deletion breaks
RDFS/OWL entailment is monotone: adding facts never retracts a conclusion. The lab tests this — adding an unrelated triple leaves every previous inference intact.
Monotonicity is what makes materialization a valid cache. If adding data could invalidate an inference, a materialized closure would be wrong the moment anything changed.
Deletion is the exception, and it is the operational trap. Delete one majorityOwns triple and
the materialized controls closure is now wrong — it contains conclusions no longer supported.
The graph does not know this, because the derived triples look exactly like asserted ones.
Three responses, in increasing order of sophistication:
- Re-materialize from scratch. Simple, correct, and expensive on a large graph.
- Truth maintenance — record why each derived triple exists (its justification), and retract those whose support is gone. Correct and incremental; genuinely complex to implement.
- Separate the graphs. Keep asserted and derived triples in different named graphs, so "recompute derivations" is dropping one graph and re-running. This is what most production stores do, and it is the pragmatic answer.
The design consequence for a bank: know which triples are asserted and which are derived, and never let an auditor be shown a derived triple as if it were a source record without saying so.
5. The open-world assumption
The most important idea in the phase, and the one that surprises people from a database background.
Open-world assumption (OWA): what is not stated is unknown, not false.
A database is closed-world: if there is no row, the thing does not exist. SELECT ... WHERE lei IS NULL is meaningful.
RDF and OWL are open-world, because they were designed for the web, where you never have all the data. If the graph does not say Meridian has an LEI, that means we have not been told, not it has none.
Three consequences that matter:
OWL cannot detect missing data. "Every legal entity must have an LEI" in OWL
(owl:minCardinality 1) does not reject an entity without one — it infers that it has one you
have not seen. Which is useless for data quality and is exactly the KYC question.
OWL cannot detect contradictions from absence. No NOT EXISTS.
Negation is not available in the usual sense. SPARQL has FILTER NOT EXISTS, which is
closed-world over what is in the queried graph — a query-time convenience, not a change in the
semantics.
So a bank needs a second mechanism for the question it actually asks — does this record conform to the shape we require? — and that mechanism is SHACL.
6. SHACL: closing the world for validation
SHACL (Shapes Constraint Language) is a W3C standard for validating RDF against shapes. It deliberately adopts closed-world, count-based semantics for validation, while leaving the graph's OWA semantics untouched.
A shape says: for every node of this class, this predicate must appear at least once, be a string, and match this pattern.
LegalEntityShape
targetClass fibo-be:LegalEntity
property [ path bank:hasLEI ; minCount 1 ; maxCount 1 ;
datatype xsd:string ; pattern "^[A-Z0-9]{18}[0-9]{2}$" ]
property [ path bank:legalName ; minCount 1 ; maxCount 1 ; datatype xsd:string ]
Validation produces a validation report: conforms: true/false plus a result per violation,
naming the focus node, the path and the constraint. That report is the artifact — it goes to a data
steward, into a pipeline gate, or into an evidence pack.
The constraint kinds worth knowing: minCount/maxCount, datatype, nodeKind (IRI vs literal),
pattern, minInclusive/maxInclusive, in, class, and closed (reject any predicate not
declared by the shape).
Three things the lab makes concrete:
Severity. A constraint can be Violation, Warning or Info. Only violations make conforms
false. This matters operationally: you can ship a shape as a warning, measure how much data fails
it, and promote it to a violation once the corpus is clean — rather than blocking ingestion on day
one.
Shapes target entailed types. The lab validates nodes that are LegalEntity only by
inference. This is the interesting interaction between the two halves of the phase, and it is why
materialization runs first. Running validation on the raw graph silently skips nodes.
The distinction, stated once:
| OWL | SHACL | |
|---|---|---|
| Purpose | infer new facts | validate existing ones |
| World | open | closed (for validation) |
| "No LEI stated" | unknown | a violation |
| Output | more triples | a report |
| Run it | when data changes | at ingestion, and on a schedule |
"OWL infers, SHACL validates" is the sentence. Say it in an interview and the follow-up is usually "why do you need both?", which §5 answers.
7. SPARQL
7.1 Basic graph patterns, and joins as shared variables
A SPARQL query is a set of triple patterns with variables:
SELECT ?owner ?name WHERE {
?owner bank:majorityOwns ent:Acme .
?owner bank:legalName ?name .
}
Evaluation is a join. Start with one empty binding; for each pattern, extend every surviving binding with every way that pattern matches.
The join condition is variable identity. ?owner in both patterns means the same value must
satisfy both — there is no JOIN ... ON clause because shared names are the condition. That is
the single most important thing to understand about SPARQL, and once you see it the rest is
notation.
FILTER narrows bindings with a boolean; DISTINCT deduplicates; ORDER BY and LIMIT do what
you expect.
7.2 OPTIONAL is a left join
?owner bank:controls+ ent:Acme .
OPTIONAL { ?owner bank:legalName ?name }
If an owner has no legalName, the row survives with ?name unbound. Without OPTIONAL the
owner disappears entirely.
The lab demonstrates this with ent:Opaque — a shell company with no name and no LEI, which is
exactly the entity a KYC analyst most wants to see. A query that inner-joins on legalName would
hide the most suspicious node in the graph, which is a genuinely dangerous failure and the reason
this is worth understanding rather than memorizing.
7.3 Property paths
The feature that makes SPARQL worth implementing.
| Path | Means |
|---|---|
p | exactly one hop |
p+ | one or more (transitive closure) |
p* | zero or more (includes the start) |
p? | zero or one |
^p | inverse — traverse backwards |
p1/p2 | sequence |
p1|p2 | alternative |
So:
SELECT ?owner WHERE {
?owner bank:controls+ ent:Acme .
?owner bank:onSanctionsList true .
}
"Which sanctioned entities ultimately control Acme?" — three hops, one query, no recursion in application code. The alternative in SQL is a recursive CTE that nobody on the team can review; the alternative in application code is a graph traversal that will have a subtle bug in its visited set.
Note that controls+ and owl:TransitiveProperty overlap. The difference: OWL transitivity
materializes the closure into the graph (so every query sees it, and so does validation); a
property path computes it per query. Use OWL when the relation is genuinely transitive by
definition; use a path when the traversal is a query-time question.
7.4 Cycles
Circular shareholdings are real — company A owns B owns A is a legitimate and common corporate structure, sometimes deliberately.
An unguarded transitive traversal on a cycle does not terminate. Both the reasoner and the query engine need a visited set, and the lab tests both.
The subtler consequence: with a cycle, controls+ makes A control itself. That is
logically correct under transitivity and it will surprise a downstream consumer expecting a
strict hierarchy. Decide whether your queries exclude self-loops, and do it explicitly.
8. FIBO
FIBO (Financial Industry Business Ontology) is the EDM Council's OWL ontology of financial concepts. Not a schema — a vocabulary of record for what financial things are.
Its module structure, roughly:
| Module | Covers |
|---|---|
| FND Foundations | agents, relations, dates, places, accounting basics |
| BE Business Entities | legal entities, corporations, partnerships, ownership, control |
| FBC Financial Business & Commerce | financial institutions, products, markets |
| IND Indices & Indicators | rates, indices |
| LOAN, SEC, DER | loans, securities, derivatives |
What it gives you that your own schema does not:
- Definitions that survive a boundary. "Legal entity" means the same thing to you, to a counterparty, and to a regulator who also references FIBO.
- Relationships already modelled. Control, ownership, agreements, obligations — with the subtleties (direct vs indirect control, beneficial ownership) already thought through by people who do this full time.
- Regulatory alignment. Several reporting regimes reference or align with FIBO concepts, so using it reduces the mapping work later.
How to use it without drowning. FIBO is thousands of classes and you should not adopt all of it. The workable pattern:
- Import only the modules you need — usually FND and BE to start.
- Subclass FIBO classes with your own:
bank:Corporation rdfs:subClassOf fibo-be:LegalEntity. Your local concepts inherit the shared meaning without you having to match FIBO's granularity. - Map your identifiers to FIBO properties where a standard exists (LEI, jurisdiction, legal name).
- Never modify FIBO itself. Extend it. Modifying it means you no longer have the shared vocabulary you adopted it for.
The lab's ontology is FIBO-shaped: the same structure, ten classes instead of thousands, using the real namespace pattern.
9. Graph-grounded retrieval
The graph is not only a query target. It is a way to decide what text to retrieve.
Three patterns:
Neighbourhood expansion. Given a seed entity, retrieve documents about entities within n
hops. Asked about Acme, you also surface documents about its owners — which is where the answer
often is, in a document that never mentions Acme.
Path-constrained retrieval. Retrieve only documents about entities on a path satisfying a pattern. "Documents about entities that control Acme and are on a watchlist" is a much smaller, much more relevant set than similarity alone produces.
Entity linking then expansion. Extract entities from the question, resolve them to IRIs, expand the graph, and use the expanded set to filter or boost vector retrieval. This is the shape most production "GraphRAG" systems take.
The lab implements the first, with the property that matters: expand_neighbourhood returns not
only the entities but the paths that reached them. That is the provenance for a graph-derived
claim — "we included Meridian because Acme is controlledBy Northgate is controlledBy Meridian" —
and without it a graph-grounded answer is less defensible than a text-grounded one, which defeats
the purpose.
The budget warning. Neighbourhood expansion grows fast: two hops in a well-connected graph can reach hundreds of entities. Bound the hops, restrict the predicates (the lab does both), and rank before injecting into a prompt. An unbounded expansion is a context-window incident.
10. Ontology governance
An ontology is a shared vocabulary, so changing it is a breaking change to everyone using it — which is the Phase 02 who-breaks question in a new costume.
What governance needs:
- An owner. One team, empowered to say no.
- A change process. Proposals reviewed against existing usage. "Who queries this class?" must be answerable, which means recording query patterns.
- Versioning, with the same asymmetry as everywhere else: adding a class or a sub-property is
safe; removing or narrowing one breaks consumers.
owl:deprecatedmarks retirement without deleting. - Never reuse an IRI for a different concept. Every consumer's meaning silently changes. This is the single worst thing you can do to an ontology.
- A validation gate. New instance data validated against SHACL shapes before it enters the graph, so quality problems are caught at the boundary.
The failure mode without governance is specific and common: the ontology becomes a second, worse schema. Every team adds the classes it needs, nothing is aligned, and after a year you have the mapping problem you adopted RDF to avoid — plus an unfamiliar query language.
11. Lab walkthrough
Work Lab 01 in this order.
PrefixMap.expand/shorten(§2.3). Check://first.shortenprefers the longest matching namespace.Graph.add,add_curies,match,objects,subjects,triples(§2.4).addreturnsTrueonly for a new triple. Every accessor sorts.materialize(§4.4). Implement the rules in the docstring, loop until a round adds nothing. Runtest_materialization_reaches_a_fixed_pointandtest_transitivity_composes_with_subpropertyfirst — the second is the phase's headline.ValidationReport,_validate_node,validate(§6). Cardinality first, then per-value checks, andcontinueafter a type/node-kind failure so you do not also report a pattern failure on a value of the wrong type._walk(§7.3). Breadth-first with a visited set.*includes the start. With no bound subject, the starts are every subject of the predicate (or every object, when inverse)._resolve,_join,_left_join,execute(§7.1–7.2)._left_joinkeeps the original binding when the block yields nothing.expand_neighbourhood(§9). Record the path to each node, not just the node.build_ontology,build_facts— the data the tests use; the docstrings specify it exactly.
Then python solution.py and read the six sections against §§2–9.
12. Success criteria
Without the guide open:
- Give three questions a graph answers that a vector index cannot, and say why.
- Explain why a literal is never a subject.
- Explain what an IRI buys over a local identifier.
- Compare RDF and LPG and pick one for a stated requirement.
- Explain that
rdfs:domainis an inference rule, not a constraint. - Show two rules composing to derive a fact nobody asserted.
- Name the four OWL constructs that matter in a financial graph.
- Explain monotonicity and what deletion breaks.
- State the open-world assumption and its three consequences.
- Explain why SHACL exists, in one sentence.
- Explain why shapes must target entailed types.
- Write a SPARQL query with a transitive property path.
- Explain why
OPTIONALis a left join, with the shell-company example. - Explain why cycles need a visited set, and what
controls+says about a cycle. - Describe how to use FIBO without adopting all of it.
- Describe an ontology change process and the one thing you must never do.
13. Common mistakes
Treating rdfs:domain as a constraint. It infers a type; it rejects nothing.
Expecting OWL to catch missing data. Open-world. That is SHACL's job.
Confusing OWL and SHACL. One infers, one validates.
Validating before materializing. Nodes typed only by inference are silently skipped.
Forgetting the visited set. Cycles are real and your traversal will not return.
Inner-joining where you meant OPTIONAL. You hide exactly the entities with missing data —
the ones you most want to see.
Mixing expanded and abbreviated IRIs. Two identifiers for one thing; every join silently breaks.
Parsing https://... as a CURIE. Prefix https, and a confusing KeyError.
Materializing and then deleting. The closure is now wrong and looks fine.
Showing a derived triple as a source record. In an audit, know which is which.
Reusing an IRI for a different concept. Every consumer's meaning changes silently.
Adopting all of FIBO. Import the modules you need and subclass.
Unbounded neighbourhood expansion. A context-window incident.
Proposing full OWL 2 DL reasoning over a live graph. Know the profiles; RL is the tractable one.
14. Interview Q&A
Q: You have vector search. Why add a knowledge graph?
A: "Because a large share of a bank's questions are structural rather than about resemblance. 'Which counterparties are ultimately controlled by a sanctioned entity' is a path — Acme owned by Northgate owned by Meridian controlled by a sanctioned entity — and no single document says that. Four documents each say one hop, and none of them shares vocabulary with the question. Embedding similarity can't compose hops; that's a category difference, not a model limitation. The other reason is shared meaning: four systems in the bank each have a 'counterparty' table with four different definitions, and an ontology is where those definitions survive crossing a boundary. The designs I'd actually build use the graph to decide what text to retrieve rather than treating them as competitors."
Q: What's the difference between OWL and SHACL?
A: "OWL infers, SHACL validates, and the reason you need both is the open-world assumption. RDF and OWL assume that what isn't stated is unknown, not false — they were designed for the web, where you never have all the data. So if the graph doesn't say Meridian has an LEI, OWL concludes 'we haven't been told', and an OWL cardinality constraint saying every legal entity has an LEI will infer that it has one you haven't seen rather than flagging it. Which is useless for data quality, and 'this record is missing an LEI' is precisely the KYC question. SHACL closes the world for validation purposes — count-based, closed semantics — and produces a validation report naming the focus node, the path and the constraint. One subtlety worth mentioning: shapes target entailed types, so I materialize before validating, or nodes that are legal entities only by inference get silently skipped."
Q: Show me how you'd answer the ultimate-beneficial-ownership question.
A: "Two parts. In the ontology, majorityOwns is a sub-property of controls, and controls is
an owl:TransitiveProperty. That means when I assert Northgate majority-owns Acme, two rules
compose — rdfs7 turns ownership into control, transitivity chains it — and I get Meridian controls
Acme without anyone asserting it. Then the query is one line: ?owner bank:controls+ ent:Acme . ?owner bank:onSanctionsList true. Three hops, a transitive property path, no recursion in
application code. The alternative is a recursive CTE nobody can review or a hand-written traversal
that will have a bug in its visited set — and it will, because circular shareholdings are real and
an unguarded walk doesn't terminate. I'd also record which triples are asserted and which are
derived, because an auditor should never be shown an inference as if it were a source record."
Q: You materialize inferences. What happens when data is deleted?
A: "The closure is wrong, and nothing tells you — derived triples look exactly like asserted ones. Entailment is monotone, so adding facts is always safe and that's what makes materialization a valid cache; deletion is the exception. Three responses. Re-materialize from scratch: simple, correct, expensive on a large graph. Truth maintenance: record each derived triple's justification and retract those whose support is gone — correct and incremental, and genuinely complex. Or separate asserted and derived into different named graphs, so recomputing is dropping one graph and re-running — which is what most production stores do and what I'd start with. And that separation has a second benefit: it makes 'is this a fact we were told or a fact we inferred' answerable, which matters for evidence."
Q: RDF or Neo4j?
A: "Depends on whether meaning has to cross a boundary. RDF gives global identifiers, formal
semantics, SHACL validation, a standard query language and native federation — so it's right for a
regulatory ontology, a shared taxonomy, anything where two organizations must agree what a
'counterparty' is. An LPG gives properties on relationships natively, better traversal ergonomics
and, usually, better path-analytics performance — so it's right when the graph is yours and the
edges carry data, like OWNS {percentage: 65, since: 2019}, which in RDF needs reification and is
genuinely awkward. In a bank the honest answer is often both: FIBO-aligned RDF as the vocabulary of
record, an LPG for operational path analytics, with the ontology defining what the LPG's labels
mean. What I'd push back on is choosing RDF for a purely internal operational graph — you take on
an unfamiliar query language and reification for federation benefits you never use."
Q: How would you introduce FIBO without it becoming a two-year project?
A: "Import only FND and BE to start, subclass rather than adopt — bank:Corporation rdfs:subClassOf fibo-be:LegalEntity — so my local concepts inherit the shared meaning without
matching FIBO's granularity, map identifiers where a standard exists, and never modify FIBO itself,
only extend it. The failure mode I'd be watching for is the ontology becoming a second, worse
schema: every team adds the classes it needs, nothing aligns, and after a year you have the mapping
problem you adopted RDF to avoid plus an unfamiliar query language. So governance from day one —
one owning team, a change process that can answer 'who queries this class', additive-only changes
with owl:deprecated for retirement, and never reusing an IRI for a different concept, because
that silently changes every consumer's meaning."
15. References
Standards (all W3C Recommendations, all readable)
- RDF 1.1 Concepts and Abstract Syntax — the data model.
- RDF Schema 1.1 — short; read it completely.
- OWL 2 Primer and OWL 2 Profiles — start with the primer, then read the RL profile, which is what rule-based reasoners implement.
- SHACL — the core constraint components are the first half.
- SPARQL 1.1 Query Language — §9 (property paths) is the part this phase is about.
Books
- Allemang & Hendler, Semantic Web for the Working Ontologist, 3rd ed. — the best practical introduction to RDFS/OWL modelling, and the one that explains the open-world assumption properly.
- Robinson, Webber & Eifrem, Graph Databases — the LPG side of the comparison.
FIBO
- EDM Council FIBO — the specification, the module structure, and the ontology files themselves.
- The FIBO Business Entities (BE) module is where legal entities, ownership and control live — the directly relevant part for this phase.
Implementations
- Apache Jena — triple store (TDB2), rule reasoners, SHACL, SPARQL. The reference Java stack.
- Neo4j with the n10s plugin — LPG with RDF import/export, for the hybrid position.
- RDF4J, GraphDB, Amazon Neptune, Stardog — production stores with differing reasoning support; read each one's reasoning page before choosing.
- pySHACL — the Python SHACL implementation, useful for seeing the full constraint surface.
Graph-grounded retrieval
- Edge et al., From Local to Global: A Graph RAG Approach to Query-Focused Summarization, Microsoft Research, 2024.
- Neo4j and LlamaIndex knowledge-graph index documentation, for the production shapes of §9.