cd /news/artificial-intelligence/rea-framework-bank-ontology-a-comple… Β· home β€Ί topics β€Ί artificial-intelligence β€Ί article
[ARTICLE Β· art-34551] src=superml.dev β†— pub= topic=artificial-intelligence verified=true sentiment=Β· neutral

REA Framework & Bank Ontology: A Complete Tutorial

A tutorial on the REA (Resources, Events, Agents) framework applied to banking ontology covers McCarthy's 1982 model, OWL ontology building with Python and RDFLib, SPARQL queries, and AI/ML integration patterns. The tutorial explains how REA preserves economic reality over traditional accounting and maps to industry standards like FIBO and XBRL.

read28 min views1 publishedJun 20, 2026
REA Framework & Bank Ontology: A Complete Tutorial
Image: Superml (auto-discovered)

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 #

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.

── more in #artificial-intelligence 4 stories Β· sorted by recency
── more on @william e. mccarthy 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/rea-framework-bank-o…] indexed:0 read:28min 2026-06-20 Β· β€”