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 #
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 disbursementCredit line agreement commits the bank to lend up to a limitRepayment schedule commits the borrower to future paymentsTrading 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#> .
@prefix rea: <http://superml.dev/ontology/rea#> .
@prefix bank: <http://superml.dev/ontology/bank#> .
@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: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: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)
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)
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
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)
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:
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" .
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 .
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" ;
rea:decrements inst:bank_cash_usd ;
rea:increments inst:loan_receivable_001 ;
rea:asProvider inst:first_national_bank ;
rea:asReceiver inst:customer_john_doe .
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 .
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
g = Graph()
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_))
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.")
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)
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)
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
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)
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
def add_individual(uri, rdf_type, label):
g.add((uri, RDF.type, rdf_type))
g.add((uri, RDFS.label, Literal(label)))
return uri
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")
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 = 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 = 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))
g.add((disbursement, REA.duality, repayment))
7.5 Serialize and Query
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")
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
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/> .
bank:LoanReceivable
rdfs:subClassOf fibo-fbc-dae-dbt:Loan ;
owl:equivalentClass fibo-fbc-dae-dbt:TermLoan .
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:
sql = "SELECT loan_bal FROM tbl_core_lending WHERE cust_id = ?"
sparql = """
SELECT (SUM(?amount) AS ?totalRepaid)
WHERE {
?event a bank:LoanRepayment ;
rea:amount ?amount ;
rea:asProvider inst:customer_john_doe .
}
"""
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
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)
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#>
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.
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.
Exercise 3: Add a Commitment Layer
Extend the loan example from Part 6 with the commitment lifecycle. Specifically:
- Create a
LoanApplicationCommitment
instance - Create a
LoanApprovalCommitment
that fulfils the application commitment - Link the
LoanDisbursement
event 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)
WHERE {
}
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
RiskWeightCategory
class with individuals (e.g.,RiskWeight100
,RiskWeight50
) - An
owl:ObjectProperty
calledbank:riskWeight
linkingLoanReceivable
toRiskWeightCategory
- 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 principlesOWL 2 Primer (W3C)β the formal language REA ontologies are written inSPARQL 1.1 Query Language (W3C)β how to query RDF knowledge graphs
Tools
RDFLib (Python)β build and query RDF/OWL graphs in PythonProtΓ©gΓ©β open-source GUI ontology editor; great for visualizing class hierarchiesApache Jenaβ Java-based RDF/SPARQL platform, widely used in enterprise settingsStardog/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 Financesuperml.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.