REA Framework & Bank Ontology: A Complete Tutorial
A hands-on tutorial on the REA (Resources, Events, Agents) framework applied to banking ontology — from McCarthy's 1982 origins to building a working OWL ontology with Python, RDFLib, SPARQL queries, and AI/ML integration patterns.
Table of Contents
REA Framework & Bank Ontology: A Complete Tutorial
Level: Intermediate
Time to complete: 60–90 minutes
Prerequisites: Basic understanding of databases, some familiarity with Python or JSON; no prior ontology experience required
Learning Objectives
By the end of this tutorial you will be able to:
- Explain the three REA primitives and how they relate to each other
- Map real banking transactions onto the REA model
- Read and write basic REA Turtle/OWL notation
- Build a working REA ontology for a simple loan lifecycle using Python and RDFLib
- Understand how REA connects to FIBO, XBRL, and modern AI/ML data pipelines in banking
Table of Contents
- What is REA? The 1982 Idea That Never Got Old
- The Three Primitives — Resources, Events, Agents
- REA Relationships — How the Pieces Connect
- What Makes It an Ontology?
- Why Banks Use REA Ontology
- Tutorial: Build a Bank REA Ontology in Turtle
- Tutorial: Build the Same Ontology in Python with RDFLib
- Real-World Example: Modelling a Loan Lifecycle
- REA Meets FIBO — The Industry Standard Layer
- REA as a Foundation for AI/ML in Banking
- Exercises
- Further Reading
Part 1 — What is REA? The 1982 Idea That Never Got Old
In 1982, William E. McCarthy, a professor at Michigan State University, published a paper titled “The REA Accounting Model: A Generalized Framework for Accounting Systems in a Shared Data Environment.” The core argument was simple and radical at the same time: traditional double-entry bookkeeping destroys information.
When a bank records a debit of $10,000 to a loan receivable and a credit of $10,000 to cash, it captures the accounting result of a disbursement — but it throws away the economic reality underneath it: who requested the loan, what collateral was pledged, which officer approved it, what the disbursement event actually consisted of. You get the balance sheet entry; you lose the story.
McCarthy’s REA model proposed recording the economic reality directly, in three kinds of things:
┌───────────────────────────────────────────┐
│ REA FRAMEWORK │
│ │
│ RESOURCE ←── EVENT ──→ RESOURCE │
│ │ │
│ AGENT(s) │
└───────────────────────────────────────────┘
- Resource — something of economic value that is increased or decreased
- Event — an economic occurrence that changes the value of resources
- Agent — an individual or organization that participates in an event
That’s the whole model. Three nouns, and the relationships between them. Everything in financial accounting — every ledger, every statement, every report — can be derived from instances of these three things.
Why does this matter today, 40+ years later? Because every modern banking challenge — regulatory reporting, AI-driven decisioning, data interoperability between core systems — requires exactly what REA provides: a single, unambiguous semantic layer that describes what actually happened economically, not just what the accounting system recorded.
Part 2 — The Three Primitives: Resources, Events, Agents
2.1 Resources
A Resource is anything of economic value that a bank holds, lends, borrows, or exchanges. In the REA model, a resource has a quantity or value that can be increased or decreased by events.
Banking resources — concrete examples:
| Resource | Description | Key Attribute |
|---|---|---|
Cash | Physical or central bank deposits | Balance, currency |
LoanReceivable | Amount owed by a borrower | Principal, rate, term |
Deposit | Customer’s claim on the bank | Balance, rate type |
SecurityPosition | Holdings of bonds, equities | Units, market value |
CreditLine | Approved but undrawn credit | Limit, drawn amount |
Collateral | Asset pledged against a loan | Type, appraised value |
FeeReceivable | Earned but unpaid fees | Amount, due date |
A critical point: a Resource in REA is not an account. A LoanReceivable account in the general ledger is a derived view of loan resource instances. The resource itself is the actual loan — its terms, its parties, its economic substance.
2.2 Events
An Event is an economic occurrence — something that happened and changed the quantity or value of one or more resources. Events are the heart of the REA model. They are what gets recorded; everything else is computed from them.
Banking events — concrete examples:
| Event | Decrements | Increments | Description |
|---|---|---|---|
LoanDisbursement | Cash | LoanReceivable | Bank pays out loan principal |
LoanRepayment | LoanReceivable | Cash | Borrower repays principal |
InterestPayment | Cash (borrower) | InterestIncome | Borrower pays interest |
DepositReceived | Cash | DepositLiability | Customer deposits funds |
Withdrawal | DepositLiability | Cash | Customer withdraws funds |
SecurityPurchase | Cash | SecurityPosition | Bank buys a bond |
SecuritySale | SecurityPosition | Cash | Bank sells a bond |
FeeCharge | FeeReceivable | FeeIncome | Bank charges a service fee |
WireTransfer | DepositLiability (sender) | DepositLiability (receiver) | Funds move between accounts |
Notice that every event has a duality: something is given up and something is received. This is the REA equivalent of double-entry — but instead of debits and credits, you have economic give and receive.
2.3 Agents
An Agent is a person or organization that participates in an event — either as the provider (giving up the resource) or the receiver (gaining it).
Banking agents — concrete examples:
| Agent | Role | Examples |
|---|---|---|
Customer | Borrower, depositor, investor | Retail client, SME, corporate |
Bank | Lender, custodian, counterparty | The institution itself |
Employee | Internal actor, authorizer | Loan officer, trader, teller |
Counterparty | Trading party, correspondent | Another bank, broker-dealer |
Regulator | Supervisory authority | OCC, Fed, FCA, ECB |
ThirdParty | Service provider, guarantor | Insurer, credit bureau, servicer |
In REA, agents are linked to events through participation relationships. A loan disbursement event has the bank participating as provider (of cash) and the customer participating as receiver. The same customer then participates as provider (of repayments) in subsequent loan repayment events.
Part 3 — REA Relationships: How the Pieces Connect
The primitives alone aren’t enough. REA defines four core relationship types that wire everything together.
3.1 Duality
The most important relationship in REA. Every economic exchange involves at least two events: one that decrements a resource and one that increments a resource. These paired events are connected by a duality relationship.
LoanDisbursement ←──────── duality ────────→ LoanRepayment
(Bank gives cash) (Bank receives cash back)
DepositReceived ←──────── duality ────────→ Withdrawal
(Bank receives cash) (Bank gives cash back)
The duality relationship is what makes REA accountable. Every give must have a corresponding receive, either now (spot transaction) or in the future (credit transaction via commitments — see 3.4).
3.2 Participation
Participation links an Agent to an Event, specifying their role.
Customer ──── participates-in ──── LoanRepayment ──── participates-in ──── Bank
(as provider) (as receiver)
Participation can carry additional attributes: the timestamp when the agent authorized the event, their role designation, the delegation chain, and whether they are the primary or secondary participant. For compliance purposes, participation is often the most auditable part of the model — it captures who did what.
3.3 Stockflow
Stockflow links an Event to the Resources it affects. It distinguishes whether the event increments or decrements the resource quantity.
LoanDisbursement ──── decrements ──── CashResource
LoanDisbursement ──── increments ──── LoanReceivableResource
The name “stockflow” comes from the distinction between stocks (resources, which have a balance at a point in time) and flows (events, which change that balance over time). Think of it exactly like physics: water level in a tank (stock) is determined by the flow of water in and out.
3.4 Commitment (REA Extension)
McCarthy later extended the model with Commitments — planned or promised economic events that haven’t happened yet. In banking, this is enormously useful.
COMMITMENT ──── fulfilled-by ──── EVENT
│
├── reserves Resource (notional)
└── involves Agent (as obligated party)
Banking uses of commitments:
- Loan approval commits the bank to future disbursement
- Credit line agreement commits the bank to lend up to a limit
- Repayment schedule commits the borrower to future payments
- Trading order commits a counterparty to a future securities exchange
Commitments let you model the lifecycle of a banking product — from application through to final settlement — without conflating promises with economic facts.
Part 4 — What Makes It an Ontology?
The word “ontology” comes from philosophy (the study of what exists), but in computer science it means something specific: a formal, machine-readable vocabulary of concepts and their relationships in a domain.
An REA framework becomes an REA ontology when you express it in a formal language — typically OWL (Web Ontology Language) using RDF (Resource Description Framework) syntax — that allows:
- Inference: a reasoner can derive new facts from existing ones
- Interoperability: any system that understands OWL can consume the data
- Constraint checking: the ontology can validate that data conforms to the model
- Querying: SPARQL can retrieve facts across the entire graph
The basic building blocks in OWL/RDF:
| OWL Term | REA Meaning | Example |
|---|---|---|
owl:Class | A type of REA primitive | rea:Resource, rea:Event |
owl:ObjectProperty | A relationship | rea:duality, rea:participatesIn |
owl:DatatypeProperty | A scalar attribute | rea:quantity, rea:date |
owl:Individual | A specific instance | loan:Disbursement_2026_001 |
rdfs:subClassOf | Specialization | bank:LoanReceivable subClassOf rea:Resource |
When you hear “banking ontology” — whether it’s the FIBO (Financial Industry Business Ontology), XBRL taxonomies, or a bank’s internal knowledge graph — REA often sits underneath as the economic substrate, even if not explicitly named.
Part 5 — Why Banks Use REA Ontology
5.1 Regulatory Reporting
Regulatory reporting (Basel III, FINREP, COREP, FR Y-9C) requires banks to slice the same economic reality many different ways: by risk weight, by maturity bucket, by counterparty type, by product. If the underlying data is stored as accounting entries, each new regulatory view requires a custom ETL pipeline. If the underlying data is stored as REA events, the regulatory view is just a different query over the same graph.
5.2 Interoperability Between Core Systems
A large bank typically has dozens of core systems: loan origination, core banking, trade finance, treasury, collateral management, risk engines. Each has its own data model. REA ontology provides a canonical semantic layer that all systems can map to — so a loan in the origination system and a loan receivable in the core banking system are understood as representations of the same rea:Resource instance.
5.3 Audit Trails and Compliance
REA naturally produces audit trails. Every economic event is a first-class record, with participating agents, timestamps, and links to the resources affected. This maps directly to what regulators require: who authorized what, when, and what changed as a result.
5.4 AI and ML Data Pipelines
This is the most exciting modern application. LLMs and ML models trained on or querying banking data need to understand what entities mean, not just what columns exist. An REA ontology tells an AI agent that a LoanRepayment event is semantically related to a LoanDisbursement event through a duality relationship — enabling a credit risk agent to reason about the full lifecycle of a loan rather than treating each transaction row as an isolated fact.
5.5 FIBO Alignment
The Financial Industry Business Ontology (FIBO), maintained by the Object Management Group (OMG), is the closest thing banking has to an industry-standard ontology. FIBO’s transaction and event layers are heavily influenced by REA. If your bank is building FIBO-compliant data models, understanding REA is the prerequisite.
Part 6 — Tutorial: Build a Bank REA Ontology in Turtle
Turtle (.ttl) is the most readable syntax for RDF/OWL. Let’s build a minimal but complete REA ontology for a bank loan product, step by step.
Step 1: Set Up Prefixes
Every Turtle file starts with namespace declarations. Think of these as imports.
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
# Our REA base ontology namespace
@prefix rea: <http://superml.dev/ontology/rea#> .
# Our bank-specific extension namespace
@prefix bank: <http://superml.dev/ontology/bank#> .
# Our instance data namespace (actual loans, events, etc.)
@prefix inst: <http://superml.dev/data/bank#> .
<http://superml.dev/ontology/bank>
a owl:Ontology ;
rdfs:label "SuperML Bank REA Ontology" ;
rdfs:comment "REA-based ontology for core banking operations" .
Step 2: Define the Core REA Classes
# ─── REA PRIMITIVES ────────────────────────────────────────────────────────────
rea:Resource a owl:Class ;
rdfs:label "Economic Resource" ;
rdfs:comment "Something of economic value that can be increased or decreased." .
rea:Event a owl:Class ;
rdfs:label "Economic Event" ;
rdfs:comment "An occurrence that changes the quantity or value of resources." .
rea:Agent a owl:Class ;
rdfs:label "Economic Agent" ;
rdfs:comment "A person or organization that participates in economic events." .
rea:Commitment a owl:Class ;
rdfs:label "Economic Commitment" ;
rdfs:comment "A promise or obligation to perform a future economic event." .
Step 3: Define REA Relationships (Object Properties)
# ─── REA RELATIONSHIPS ─────────────────────────────────────────────────────────
rea:duality a owl:ObjectProperty ;
rdfs:label "duality" ;
rdfs:comment "Links a give-event to its corresponding receive-event." ;
rdfs:domain rea:Event ;
rdfs:range rea:Event .
rea:participatesIn a owl:ObjectProperty ;
rdfs:label "participates in" ;
rdfs:domain rea:Agent ;
rdfs:range rea:Event .
rea:asProvider a owl:ObjectProperty ;
rdfs:subPropertyOf rea:participatesIn ;
rdfs:label "participates as provider" ;
rdfs:comment "The agent giving up the resource in this event." .
rea:asReceiver a owl:ObjectProperty ;
rdfs:subPropertyOf rea:participatesIn ;
rdfs:label "participates as receiver" ;
rdfs:comment "The agent gaining the resource in this event." .
rea:decrements a owl:ObjectProperty ;
rdfs:label "decrements" ;
rdfs:comment "Stockflow: this event reduces the quantity of this resource." ;
rdfs:domain rea:Event ;
rdfs:range rea:Resource .
rea:increments a owl:ObjectProperty ;
rdfs:label "increments" ;
rdfs:comment "Stockflow: this event increases the quantity of this resource." ;
rdfs:domain rea:Event ;
rdfs:range rea:Resource .
rea:fulfilledBy a owl:ObjectProperty ;
rdfs:label "fulfilled by" ;
rdfs:comment "The actual event that fulfils this commitment." ;
rdfs:domain rea:Commitment ;
rdfs:range rea:Event .
rea:reservesResource a owl:ObjectProperty ;
rdfs:label "reserves resource" ;
rdfs:domain rea:Commitment ;
rdfs:range rea:Resource .
Step 4: Define Banking-Specific Subclasses (Resources)
# ─── BANKING RESOURCES ─────────────────────────────────────────────────────────
bank:CashResource a owl:Class ;
rdfs:subClassOf rea:Resource ;
rdfs:label "Cash" ;
rdfs:comment "Central bank deposits or vault cash held by the bank." .
bank:LoanReceivable a owl:Class ;
rdfs:subClassOf rea:Resource ;
rdfs:label "Loan Receivable" ;
rdfs:comment "Principal amount owed to the bank by a borrower." .
bank:DepositLiability a owl:Class ;
rdfs:subClassOf rea:Resource ;
rdfs:label "Deposit Liability" ;
rdfs:comment "The bank's obligation to return funds deposited by a customer." .
bank:InterestIncome a owl:Class ;
rdfs:subClassOf rea:Resource ;
rdfs:label "Interest Income" ;
rdfs:comment "Earned interest revenue." .
bank:Collateral a owl:Class ;
rdfs:subClassOf rea:Resource ;
rdfs:label "Collateral" ;
rdfs:comment "Asset pledged by a borrower to secure a loan." .
Step 5: Define Banking-Specific Subclasses (Events)
# ─── BANKING EVENTS ────────────────────────────────────────────────────────────
bank:LoanDisbursement a owl:Class ;
rdfs:subClassOf rea:Event ;
rdfs:label "Loan Disbursement" ;
rdfs:comment "The bank pays out loan principal to the borrower." .
bank:LoanRepayment a owl:Class ;
rdfs:subClassOf rea:Event ;
rdfs:label "Loan Repayment" ;
rdfs:comment "The borrower repays principal to the bank." .
bank:InterestPayment a owl:Class ;
rdfs:subClassOf rea:Event ;
rdfs:label "Interest Payment" ;
rdfs:comment "The borrower pays interest to the bank." .
bank:DepositReceived a owl:Class ;
rdfs:subClassOf rea:Event ;
rdfs:label "Deposit Received" ;
rdfs:comment "The bank receives funds from a customer." .
bank:Withdrawal a owl:Class ;
rdfs:subClassOf rea:Event ;
rdfs:label "Withdrawal" ;
rdfs:comment "The bank pays out funds to a customer." .
Step 6: Define Banking Agents
# ─── BANKING AGENTS ────────────────────────────────────────────────────────────
bank:RetailCustomer a owl:Class ;
rdfs:subClassOf rea:Agent ;
rdfs:label "Retail Customer" .
bank:CorporateCustomer a owl:Class ;
rdfs:subClassOf rea:Agent ;
rdfs:label "Corporate Customer" .
bank:BankEntity a owl:Class ;
rdfs:subClassOf rea:Agent ;
rdfs:label "Bank Entity" ;
rdfs:comment "The bank itself, acting as a party to transactions." .
bank:LoanOfficer a owl:Class ;
rdfs:subClassOf rea:Agent ;
rdfs:label "Loan Officer" ;
rdfs:comment "Employee who authorizes loan decisions." .
Step 7: Add Scalar Properties (Datatype Properties)
# ─── DATA PROPERTIES ───────────────────────────────────────────────────────────
rea:quantity a owl:DatatypeProperty ;
rdfs:label "quantity" ;
rdfs:domain rea:Resource ;
rdfs:range xsd:decimal .
rea:eventDate a owl:DatatypeProperty ;
rdfs:label "event date" ;
rdfs:domain rea:Event ;
rdfs:range xsd:date .
rea:amount a owl:DatatypeProperty ;
rdfs:label "amount" ;
rdfs:domain rea:Event ;
rdfs:range xsd:decimal .
rea:currency a owl:DatatypeProperty ;
rdfs:range xsd:string .
bank:interestRate a owl:DatatypeProperty ;
rdfs:label "interest rate" ;
rdfs:domain bank:LoanReceivable ;
rdfs:range xsd:decimal .
bank:maturityDate a owl:DatatypeProperty ;
rdfs:label "maturity date" ;
rdfs:domain bank:LoanReceivable ;
rdfs:range xsd:date .
bank:loanId a owl:DatatypeProperty ;
rdfs:label "loan ID" ;
rdfs:range xsd:string .
Step 8: Create Instance Data
Now let’s record an actual loan disbursement in our ontology:
# ─── INSTANCE DATA: LOAN DISBURSEMENT ─────────────────────────────────────────
# The Agents
inst:customer_john_doe a bank:RetailCustomer ;
rdfs:label "John Doe" .
inst:first_national_bank a bank:BankEntity ;
rdfs:label "First National Bank" .
inst:officer_jane_smith a bank:LoanOfficer ;
rdfs:label "Jane Smith, Loan Officer" .
# The Resources
inst:bank_cash_usd a bank:CashResource ;
rdfs:label "Bank USD Cash Pool" ;
rea:quantity "50000000.00"^^xsd:decimal ;
rea:currency "USD" .
inst:loan_receivable_001 a bank:LoanReceivable ;
rdfs:label "Personal Loan #L-2026-001" ;
bank:loanId "L-2026-001" ;
rea:quantity "25000.00"^^xsd:decimal ;
rea:currency "USD" ;
bank:interestRate "0.0875"^^xsd:decimal ;
bank:maturityDate "2031-06-11"^^xsd:date .
# The Disbursement Event
inst:disbursement_001 a bank:LoanDisbursement ;
rdfs:label "Disbursement for Loan L-2026-001" ;
rea:eventDate "2026-06-11"^^xsd:date ;
rea:amount "25000.00"^^xsd:decimal ;
rea:currency "USD" ;
# Stockflow: what this event does to resources
rea:decrements inst:bank_cash_usd ;
rea:increments inst:loan_receivable_001 ;
# Participation: who is involved
rea:asProvider inst:first_national_bank ;
rea:asReceiver inst:customer_john_doe .
# The first scheduled Repayment Event
inst:repayment_001a a bank:LoanRepayment ;
rdfs:label "Repayment 1 for Loan L-2026-001" ;
rea:eventDate "2026-07-11"^^xsd:date ;
rea:amount "516.31"^^xsd:decimal ;
rea:currency "USD" ;
rea:decrements inst:loan_receivable_001 ;
rea:increments inst:bank_cash_usd ;
rea:asProvider inst:customer_john_doe ;
rea:asReceiver inst:first_national_bank .
# Duality: the disbursement and repayment are dual events
inst:disbursement_001 rea:duality inst:repayment_001a .
Part 7 — Tutorial: Build the Same Ontology in Python with RDFLib
RDFLib is the standard Python library for working with RDF/OWL graphs. Let’s build the same ontology programmatically.
Setup
pip install rdflib
7.1 Core Setup and Namespaces
from rdflib import Graph, Namespace, Literal, URIRef
from rdflib.namespace import RDF, RDFS, OWL, XSD
from datetime import date
# Initialize graph
g = Graph()
# Define namespaces
REA = Namespace("http://superml.dev/ontology/rea#")
BANK = Namespace("http://superml.dev/ontology/bank#")
INST = Namespace("http://superml.dev/data/bank#")
g.bind("rea", REA)
g.bind("bank", BANK)
g.bind("inst", INST)
g.bind("owl", OWL)
g.bind("xsd", XSD)
7.2 Define Classes Programmatically
def define_class(uri, label, comment=None, parent=OWL.Thing):
"""Helper: declare an OWL class with label and optional comment."""
g.add((uri, RDF.type, OWL.Class))
g.add((uri, RDFS.label, Literal(label)))
g.add((uri, RDFS.subClassOf, parent))
if comment:
g.add((uri, RDFS.comment, Literal(comment)))
def define_obj_prop(uri, label, domain=None, range_=None, parent=None):
"""Helper: declare an OWL ObjectProperty."""
g.add((uri, RDF.type, OWL.ObjectProperty))
g.add((uri, RDFS.label, Literal(label)))
if domain: g.add((uri, RDFS.domain, domain))
if range_: g.add((uri, RDFS.range, range_))
if parent: g.add((uri, RDFS.subPropertyOf, parent))
def define_data_prop(uri, label, domain=None, range_=XSD.string):
"""Helper: declare an OWL DatatypeProperty."""
g.add((uri, RDF.type, OWL.DatatypeProperty))
g.add((uri, RDFS.label, Literal(label)))
if domain: g.add((uri, RDFS.domain, domain))
g.add((uri, RDFS.range, range_))
# ── REA Base Classes ──────────────────────────────────────────────────────────
define_class(REA.Resource, "Economic Resource",
"Something of economic value.")
define_class(REA.Event, "Economic Event",
"An occurrence that changes resource values.")
define_class(REA.Agent, "Economic Agent",
"A party that participates in events.")
define_class(REA.Commitment, "Economic Commitment",
"A promise to perform a future event.")
# ── Banking Resource Subclasses ───────────────────────────────────────────────
define_class(BANK.CashResource, "Cash", parent=REA.Resource)
define_class(BANK.LoanReceivable, "Loan Receivable", parent=REA.Resource)
define_class(BANK.DepositLiability, "Deposit Liability", parent=REA.Resource)
define_class(BANK.InterestIncome, "Interest Income", parent=REA.Resource)
define_class(BANK.Collateral, "Collateral", parent=REA.Resource)
# ── Banking Event Subclasses ──────────────────────────────────────────────────
define_class(BANK.LoanDisbursement, "Loan Disbursement", parent=REA.Event)
define_class(BANK.LoanRepayment, "Loan Repayment", parent=REA.Event)
define_class(BANK.InterestPayment, "Interest Payment", parent=REA.Event)
define_class(BANK.DepositReceived, "Deposit Received", parent=REA.Event)
define_class(BANK.Withdrawal, "Withdrawal", parent=REA.Event)
# ── Banking Agent Subclasses ──────────────────────────────────────────────────
define_class(BANK.RetailCustomer, "Retail Customer", parent=REA.Agent)
define_class(BANK.CorporateCustomer, "Corporate Customer", parent=REA.Agent)
define_class(BANK.BankEntity, "Bank Entity", parent=REA.Agent)
define_class(BANK.LoanOfficer, "Loan Officer", parent=REA.Agent)
7.3 Define Properties
# ── REA Object Properties ─────────────────────────────────────────────────────
define_obj_prop(REA.duality, "duality",
domain=REA.Event, range_=REA.Event)
define_obj_prop(REA.participatesIn, "participates in",
domain=REA.Agent, range_=REA.Event)
define_obj_prop(REA.asProvider, "as provider",
domain=REA.Agent, range_=REA.Event,
parent=REA.participatesIn)
define_obj_prop(REA.asReceiver, "as receiver",
domain=REA.Agent, range_=REA.Event,
parent=REA.participatesIn)
define_obj_prop(REA.decrements, "decrements",
domain=REA.Event, range_=REA.Resource)
define_obj_prop(REA.increments, "increments",
domain=REA.Event, range_=REA.Resource)
define_obj_prop(REA.fulfilledBy, "fulfilled by",
domain=REA.Commitment, range_=REA.Event)
define_obj_prop(REA.reservesResource,"reserves resource",
domain=REA.Commitment, range_=REA.Resource)
# ── Datatype Properties ───────────────────────────────────────────────────────
define_data_prop(REA.quantity, "quantity",
domain=REA.Resource, range_=XSD.decimal)
define_data_prop(REA.eventDate, "event date",
domain=REA.Event, range_=XSD.date)
define_data_prop(REA.amount, "amount",
domain=REA.Event, range_=XSD.decimal)
define_data_prop(REA.currency, "currency", range_=XSD.string)
define_data_prop(BANK.interestRate, "interest rate",
domain=BANK.LoanReceivable, range_=XSD.decimal)
define_data_prop(BANK.maturityDate, "maturity date",
domain=BANK.LoanReceivable, range_=XSD.date)
define_data_prop(BANK.loanId, "loan ID", range_=XSD.string)
7.4 Create Instance Data
# ── Helper: add an individual ─────────────────────────────────────────────────
def add_individual(uri, rdf_type, label):
g.add((uri, RDF.type, rdf_type))
g.add((uri, RDFS.label, Literal(label)))
return uri
# Agents
customer = add_individual(INST.customer_john_doe, BANK.RetailCustomer, "John Doe")
bank = add_individual(INST.first_national_bank, BANK.BankEntity, "First National Bank")
officer = add_individual(INST.officer_jane_smith, BANK.LoanOfficer, "Jane Smith")
# Resources
cash = add_individual(INST.bank_cash_usd, BANK.CashResource, "Bank USD Cash Pool")
g.add((cash, REA.quantity, Literal("50000000.00", datatype=XSD.decimal)))
g.add((cash, REA.currency, Literal("USD")))
loan_recv = add_individual(INST.loan_receivable_001, BANK.LoanReceivable,
"Personal Loan #L-2026-001")
g.add((loan_recv, BANK.loanId, Literal("L-2026-001")))
g.add((loan_recv, REA.quantity, Literal("25000.00", datatype=XSD.decimal)))
g.add((loan_recv, REA.currency, Literal("USD")))
g.add((loan_recv, BANK.interestRate, Literal("0.0875", datatype=XSD.decimal)))
g.add((loan_recv, BANK.maturityDate, Literal("2031-06-11", datatype=XSD.date)))
# Disbursement Event
disbursement = add_individual(INST.disbursement_001, BANK.LoanDisbursement,
"Disbursement for Loan L-2026-001")
g.add((disbursement, REA.eventDate, Literal("2026-06-11", datatype=XSD.date)))
g.add((disbursement, REA.amount, Literal("25000.00", datatype=XSD.decimal)))
g.add((disbursement, REA.currency, Literal("USD")))
g.add((disbursement, REA.decrements, cash))
g.add((disbursement, REA.increments, loan_recv))
g.add((disbursement, REA.asProvider, bank))
g.add((disbursement, REA.asReceiver, customer))
# Repayment Event
repayment = add_individual(INST.repayment_001a, BANK.LoanRepayment,
"Repayment 1 for Loan L-2026-001")
g.add((repayment, REA.eventDate, Literal("2026-07-11", datatype=XSD.date)))
g.add((repayment, REA.amount, Literal("516.31", datatype=XSD.decimal)))
g.add((repayment, REA.currency, Literal("USD")))
g.add((repayment, REA.decrements, loan_recv))
g.add((repayment, REA.increments, cash))
g.add((repayment, REA.asProvider, customer))
g.add((repayment, REA.asReceiver, bank))
# Duality link
g.add((disbursement, REA.duality, repayment))
7.5 Serialize and Query
# ── Serialize to Turtle ───────────────────────────────────────────────────────
turtle_output = g.serialize(format="turtle")
with open("bank_rea_ontology.ttl", "w") as f:
f.write(turtle_output)
print(f"Ontology written: {len(g)} triples")
# ── SPARQL Query: find all events for a given customer ────────────────────────
from rdflib.plugins.sparql import prepareQuery
query = prepareQuery("""
PREFIX rea: <http://superml.dev/ontology/rea#>
PREFIX bank: <http://superml.dev/ontology/bank#>
PREFIX inst: <http://superml.dev/data/bank#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?eventLabel ?eventDate ?amount ?role
WHERE {
?event rea:eventDate ?eventDate ;
rea:amount ?amount ;
rdfs:label ?eventLabel .
{
?event rea:asProvider inst:customer_john_doe .
BIND("provider" AS ?role)
} UNION {
?event rea:asReceiver inst:customer_john_doe .
BIND("receiver" AS ?role)
}
}
ORDER BY ?eventDate
""", initNs={"rea": REA, "bank": BANK, "inst": INST})
print("\nEvents involving John Doe:")
print(f"{'Event':<40} {'Date':<12} {'Amount':>10} {'Role'}")
print("-" * 75)
for row in g.query(query):
print(f"{str(row.eventLabel):<40} {str(row.eventDate):<12} "
f"{str(row.amount):>10} {str(row.role)}")
Expected output:
Ontology written: 74 triples
Events involving John Doe:
Event Date Amount Role
---------------------------------------------------------------------------
Disbursement for Loan L-2026-001 2026-06-11 25000.00 receiver
Repayment 1 for Loan L-2026-001 2026-07-11 516.31 provider
7.6 Query the Duality Graph
# ── SPARQL Query: trace the duality chain for a loan ──────────────────────────
duality_query = prepareQuery("""
PREFIX rea: <http://superml.dev/ontology/rea#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?giveLabel ?receiveLabel ?amount
WHERE {
?giveEvent rea:duality ?receiveEvent ;
rdfs:label ?giveLabel ;
rea:amount ?amount .
?receiveEvent rdfs:label ?receiveLabel .
}
""")
print("\nDuality pairs (economic exchanges):")
for row in g.query(duality_query):
print(f" GIVE: {row.giveLabel}")
print(f" RECEIVE: {row.receiveLabel}")
print(f" AMOUNT: {row.amount}")
Part 8 — Real-World Example: Modelling a Loan Lifecycle
A loan doesn’t happen in a single event. It has a lifecycle — and REA, with commitments, models the whole thing. Here’s the complete lifecycle mapped to REA:
PHASE 1 — APPLICATION (Commitment)
─────────────────────────────────────────────────────────
LoanApplicationCommitment
├── involves Agent: customer (obligated to provide documentation)
├── involves Agent: bank (obligated to make a decision)
└── reservesResource: bank's lending capacity
PHASE 2 — APPROVAL (Commitment fulfilled by Commitment)
─────────────────────────────────────────────────────────
LoanApprovalCommitment
├── fulfilledBy ← LoanApplicationCommitment
├── involves Agent: bank (commits to disburse)
├── involves Agent: customer (commits to repay schedule)
└── reservesResource: LoanReceivable (prospective)
PHASE 3 — COLLATERAL PLEDGE (Event)
─────────────────────────────────────────────────────────
CollateralPledgeEvent
├── decrements: customer's CollateralAsset
├── increments: bank's CollateralHolding
├── asProvider: customer
└── asReceiver: bank
PHASE 4 — DISBURSEMENT (Event — fulfils ApprovalCommitment)
─────────────────────────────────────────────────────────
LoanDisbursementEvent
├── decrements: CashResource
├── increments: LoanReceivable
├── asProvider: bank
├── asReceiver: customer
└── duality → RepaymentScheduleEvents (future)
PHASE 5 — REPAYMENTS (Recurring Events)
─────────────────────────────────────────────────────────
LoanRepaymentEvent (×60 for a 5-year loan)
├── decrements: LoanReceivable (principal portion)
├── increments: CashResource
├── asProvider: customer
└── asReceiver: bank
InterestPaymentEvent (×60)
├── decrements: AccruedInterestReceivable
├── increments: InterestIncome
├── asProvider: customer
└── asReceiver: bank
└── duality → InterestAccrualEvent
PHASE 6 — MATURITY / CLOSE (Event)
─────────────────────────────────────────────────────────
CollateralReleaseEvent
├── decrements: bank's CollateralHolding
├── increments: customer's CollateralAsset
├── asProvider: bank
└── asReceiver: customer
This complete lifecycle lives in the ontology as a connected graph. Any query — “what is the total interest paid by this customer?”, “what collateral backs this exposure?”, “which loan officer approved the largest disbursements last quarter?” — is a SPARQL traversal over the same graph, with no ETL required.
Part 9 — REA Meets FIBO: The Industry Standard Layer
FIBO (Financial Industry Business Ontology) is the OMG/EDM Council standard ontology for financial services. It has hundreds of classes covering contracts, parties, currencies, products, and regulations. Its transaction and event layers align closely with REA.
Key FIBO namespaces you’ll encounter:
| FIBO Module | FIBO Prefix | REA Equivalent |
|---|---|---|
fibo-fnd-acc-cur | Currencies, amounts | REA scalar attributes |
fibo-fnd-pas-pas | Parties and situations | rea:Agent |
fibo-fbc-fi-fi | Financial instruments | rea:Resource subclasses |
fibo-fbc-dae-dbt | Debt instruments (loans) | bank:LoanReceivable |
fibo-fnd-rel-rel | Core relations | rea:duality, rea:participatesIn |
fibo-sec-sec-lst | Securities listings | bank:SecurityPosition |
Mapping your REA ontology to FIBO
@prefix fibo-fbc-dae-dbt: <https://spec.edmcouncil.org/fibo/ontology/FBC/DebtAndEquities/Debt/> .
@prefix fibo-fnd-pas-pas: <https://spec.edmcouncil.org/fibo/ontology/FND/Parties/Parties/> .
# Declare that our LoanReceivable is a subclass of FIBO's Loan
bank:LoanReceivable
rdfs:subClassOf fibo-fbc-dae-dbt:Loan ;
owl:equivalentClass fibo-fbc-dae-dbt:TermLoan .
# Declare that our RetailCustomer maps to FIBO's PartyInRole
bank:RetailCustomer
rdfs:subClassOf fibo-fnd-pas-pas:PartyInRole .
This alignment means your REA ontology can interoperate directly with FIBO-based regulatory reporting tools, XBRL taxonomies, and data products published by financial data vendors.
Part 10 — REA as a Foundation for AI/ML in Banking
10.1 Why LLMs Need Ontologies
A language model querying a banking database faces a fundamental ambiguity problem: loan_bal, loan_balance_amt, principal_outstanding — these all mean the same thing in different systems. Without a semantic layer, the model either hallucinates a mapping or requires extensive prompt engineering per system.
An REA ontology solves this by providing a canonical vocabulary:
# Without REA ontology: LLM must guess column meanings
sql = "SELECT loan_bal FROM tbl_core_lending WHERE cust_id = ?"
# Ambiguous: is loan_bal the original principal or the current balance?
# With REA ontology: the question is expressed in terms of instances
sparql = """
SELECT (SUM(?amount) AS ?totalRepaid)
WHERE {
?event a bank:LoanRepayment ;
rea:amount ?amount ;
rea:asProvider inst:customer_john_doe .
}
"""
# Unambiguous: this asks for the sum of all repayment event amounts
# where the customer is the provider
10.2 Building an REA-Grounded Agent
Here’s how to wire an REA ontology into an LLM-based banking agent using LangChain and RDFLib:
from rdflib import Graph
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
# Load the ontology at startup
g = Graph()
g.parse("bank_rea_ontology.ttl", format="turtle")
@tool
def query_customer_events(customer_id: str, event_type: str = None) -> str:
"""
Query all economic events involving a specific customer.
Returns a summary of events, dates, and amounts.
event_type options: 'LoanDisbursement', 'LoanRepayment',
'InterestPayment', 'DepositReceived', 'Withdrawal'
"""
type_filter = ""
if event_type:
type_filter = f"?event a bank:{event_type} ."
sparql = f"""
PREFIX rea: <http://superml.dev/ontology/rea#>
PREFIX bank: <http://superml.dev/ontology/bank#>
PREFIX inst: <http://superml.dev/data/bank#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?label ?date ?amount ?role
WHERE {{
{type_filter}
?event rdfs:label ?label ;
rea:eventDate ?date ;
rea:amount ?amount .
{{
?event rea:asProvider inst:{customer_id} .
BIND("provider" AS ?role)
}} UNION {{
?event rea:asReceiver inst:{customer_id} .
BIND("receiver" AS ?role)
}}
}}
ORDER BY ?date
"""
results = list(g.query(sparql))
if not results:
return f"No events found for customer {customer_id}."
lines = [f"{r.label} | {r.date} | ${r.amount} | {r.role}"
for r in results]
return "\n".join(lines)
@tool
def get_loan_duality_chain(loan_id: str) -> str:
"""
Retrieve the economic exchange chain (duality) for a specific loan.
Shows disbursement linked to all repayments.
"""
sparql = f"""
PREFIX rea: <http://superml.dev/ontology/rea#>
PREFIX bank: <http://superml.dev/ontology/bank#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?disburseLabel ?repayLabel ?repayDate ?repayAmount
WHERE {{
?disbursement a bank:LoanDisbursement ;
rdfs:label ?disburseLabel ;
rea:duality ?repayment .
?repayment rdfs:label ?repayLabel ;
rea:eventDate ?repayDate ;
rea:amount ?repayAmount .
}}
ORDER BY ?repayDate
"""
results = list(g.query(sparql))
if not results:
return f"No duality chain found for loan {loan_id}."
lines = [f"GIVE: {r.disburseLabel} <-> RECEIVE: {r.repayLabel} "
f"on {r.repayDate} for ${r.repayAmount}"
for r in results]
return "\n".join(lines)
# Wire into an agent
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = llm.bind_tools([query_customer_events, get_loan_duality_chain])
10.3 REA for Fraud Detection
REA’s event graph is a natural foundation for fraud detection because it captures relationships between agents and events — exactly what rule-based systems and graph neural networks need.
A fraud ring in REA looks like this: multiple Agent instances connected through a web of Event instances, all funnelling resources toward a single beneficiary. The ontology makes that pattern queryable:
PREFIX rea: <http://superml.dev/ontology/rea#>
# Find agents who are receivers in unusually many events within 24 hours
SELECT ?receiver (COUNT(?event) AS ?eventCount) (SUM(?amount) AS ?totalReceived)
WHERE {
?event a rea:Event ;
rea:eventDate ?date ;
rea:amount ?amount ;
rea:asReceiver ?receiver .
FILTER (?date >= "2026-06-10"^^xsd:date &&
?date <= "2026-06-11"^^xsd:date)
}
GROUP BY ?receiver
HAVING (COUNT(?event) > 10)
ORDER BY DESC(?totalReceived)
Part 11 — Exercises
Work through these to solidify your understanding. Suggested solutions are in comments.
Exercise 1: Model a Wire Transfer
A wire transfer involves two accounts at the same bank. Map it to REA.
Questions to answer:
- What are the Resources? (Hint: two deposit liabilities)
- What is the Event?
- Who are the Agents?
- What does the duality look like for an outgoing vs. incoming wire?
- Write the Turtle instance data.
# Your answer here:
# inst:wire_event_001 a bank:WireTransfer ;
# rea:decrements inst:sender_deposit_liability ;
# rea:increments inst:receiver_deposit_liability ;
# rea:asProvider inst:sender_customer ;
# rea:asReceiver inst:receiver_customer ;
# ...
Exercise 2: Model a Securities Trade
A bank’s trading desk buys $500,000 worth of 10-year Treasury bonds.
Questions to answer:
- What Resources are involved? (Hint: cash and a security position)
- What Events? (disbursement of cash, receipt of securities)
- What is the duality? (payment event ↔ delivery event)
- How would you model the settlement lag (T+1)?
- Write the Turtle instance data and a SPARQL query to find the trade.
# Resources
# inst:treasury_position_001 a bank:SecurityPosition ;
# bank:cusip "912828YK0" ;
# bank:faceValue "500000.00"^^xsd:decimal ;
# ...
# Events
# inst:cash_payment_001 a bank:CashPayment ;
# rea:decrements inst:bank_cash_usd ;
# rea:increments inst:broker_cash_claim ;
# rea:duality inst:securities_delivery_001 ;
# ...
Exercise 3: Add a Commitment Layer
Extend the loan example from Part 6 with the commitment lifecycle. Specifically:
- Create a
LoanApplicationCommitmentinstance - Create a
LoanApprovalCommitmentthat fulfils the application commitment - Link the
LoanDisbursementevent as fulfilling the approval commitment - Add a SPARQL query that retrieves the full commitment-to-event chain for a given loan
Exercise 4: SPARQL — Interest Accrual Report
Write a SPARQL query over the graph from Part 7 that returns:
- The loan ID
- Total principal disbursed
- Total principal repaid so far
- Outstanding principal balance (disbursed minus repaid)
PREFIX rea: <http://superml.dev/ontology/rea#>
PREFIX bank: <http://superml.dev/ontology/bank#>
SELECT ?loanId
(SUM(?disbursedAmount) AS ?totalDisbursed)
(SUM(?repaidAmount) AS ?totalRepaid)
# How would you compute the outstanding balance?
WHERE {
# Your triple patterns here
}
GROUP BY ?loanId
Exercise 5: Extend for Regulatory Reporting
The OCC requires banks to report loans by risk weight category. Add the following to the ontology:
- A
RiskWeightCategoryclass with individuals (e.g.,RiskWeight100,RiskWeight50) - An
owl:ObjectPropertycalledbank:riskWeightlinkingLoanReceivabletoRiskWeightCategory - Instance data assigning risk weights to the loan from Part 6
- A SPARQL query that groups outstanding loan balances by risk weight (the basis of a simplified RWA calculation)
Further Reading
Original Papers
- McCarthy, W.E. (1982). The REA Accounting Model: A Generalized Framework for Accounting Systems in a Shared Data Environment. The Accounting Review, 57(3), 554–578.
- Geerts, G.L. & McCarthy, W.E. (2002). An Ontological Analysis of the Primitives of the REA Enterprise Information Architecture. International Journal of Accounting Information Systems.
Standards and Ontologies
- FIBO — Financial Industry Business Ontology — OMG standard; the industry-grade extension of REA principles
- OWL 2 Primer (W3C) — the formal language REA ontologies are written in
- SPARQL 1.1 Query Language (W3C) — how to query RDF knowledge graphs
Tools
- RDFLib (Python) — build and query RDF/OWL graphs in Python
- Protégé — open-source GUI ontology editor; great for visualizing class hierarchies
- Apache Jena — Java-based RDF/SPARQL platform, widely used in enterprise settings
- Stardog / GraphDB — production-grade RDF stores with SPARQL endpoints
Banking Ontology Applied
- FIBO Use Case: XBRL to FIBO Mapping (EDM Council)
- BIS Working Paper on Semantic Data Standards in Finance
- superml.dev — The NL-2-SQL Agent Trap: Why LLMs Need an Ontology Layer
Summary
| Concept | One-Line Definition |
|---|---|
| REA | A model that records economic reality as Resources, Events, and Agents |
| Resource | Something of economic value (cash, loan, deposit, security) |
| Event | Something that happened and changed resource quantities |
| Agent | A party who participated in an event as provider or receiver |
| Duality | The link between the give-event and the receive-event in an exchange |
| Participation | The link between an agent and the event they were part of |
| Stockflow | The link between an event and the resources it increments or decrements |
| Commitment | A promise to perform a future event (loan approval, repayment schedule) |
| OWL Ontology | A formal, machine-readable version of REA using RDF/OWL syntax |
| FIBO | The industry standard banking ontology that builds on REA principles |
The core insight of REA is the one McCarthy had in 1982: don’t record accounting entries — record the economic events that generate them. In banking today, that insight is foundational to semantic data layers, regulatory reporting, AI-driven decisioning, and anything else that requires machines to understand what financial transactions mean, not just what numbers they produced.
Enterprise AI Architecture
Want more enterprise AI architecture breakdowns?
Subscribe to SuperML.