« Phase 07 · Track Overview

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

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.

RDFLPG
Identityglobal IRIslocal ids
Properties on edgesreification (awkward)native
Formal semanticsRDFS/OWL, standardizednone
ValidationSHACLapplication code
QuerySPARQL (a W3C standard)Cypher / Gremlin (vendor)
Federationnative — query across endpointsnot really
Fitsshared vocabularies, regulatory ontologies, cross-organizationoperational 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:

ConstructSays
rdf:typethis individual is a member of this class
rdfs:subClassOfevery member of A is a member of B
rdfs:subPropertyOfevery A-relation is also a B-relation
rdfs:domainanything with this property is of this class
rdfs:rangeanything 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:inverseOfcontrols 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 costhigh — closure on every changenone
Query costnone — it is already thereevery query pays
Storagethe closure can be much larger than the datanone
Deletionbreaks — see §4.5correct 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:

  1. Re-materialize from scratch. Simple, correct, and expensive on a large graph.
  2. Truth maintenance — record why each derived triple exists (its justification), and retract those whose support is gone. Correct and incremental; genuinely complex to implement.
  3. 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:

OWLSHACL
Purposeinfer new factsvalidate existing ones
Worldopenclosed (for validation)
"No LEI stated"unknowna violation
Outputmore triplesa report
Run itwhen data changesat 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.

PathMeans
pexactly one hop
p+one or more (transitive closure)
p*zero or more (includes the start)
p?zero or one
^pinverse — traverse backwards
p1/p2sequence
p1|p2alternative

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:

ModuleCovers
FND Foundationsagents, relations, dates, places, accounting basics
BE Business Entitieslegal entities, corporations, partnerships, ownership, control
FBC Financial Business & Commercefinancial institutions, products, markets
IND Indices & Indicatorsrates, indices
LOAN, SEC, DERloans, 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:

  1. Import only the modules you need — usually FND and BE to start.
  2. 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.
  3. Map your identifiers to FIBO properties where a standard exists (LEI, jurisdiction, legal name).
  4. 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:deprecated marks 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.

  1. PrefixMap.expand / shorten (§2.3). Check :// first. shorten prefers the longest matching namespace.
  2. Graph.add, add_curies, match, objects, subjects, triples (§2.4). add returns True only for a new triple. Every accessor sorts.
  3. materialize (§4.4). Implement the rules in the docstring, loop until a round adds nothing. Run test_materialization_reaches_a_fixed_point and test_transitivity_composes_with_subproperty first — the second is the phase's headline.
  4. ValidationReport, _validate_node, validate (§6). Cardinality first, then per-value checks, and continue after a type/node-kind failure so you do not also report a pattern failure on a value of the wrong type.
  5. _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).
  6. _resolve, _join, _left_join, execute (§7.1–7.2). _left_join keeps the original binding when the block yields nothing.
  7. expand_neighbourhood (§9). Record the path to each node, not just the node.
  8. 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:domain is 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 OPTIONAL is 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)

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.