Transaction and Account Data Model

Transaction and Account Data Model Diagram

This document defines a standardised Neo4j data model for banking transactions and customer data.

LLM Version of Data Model:

1. Node Labels and Properties

Account

Labels:

  • Account: (Required) Base label for all bank accounts in the system

  • Internal: Label for accounts held within this bank (our customers' accounts). Essential for identifying fund flows leaving the organisation (Internal→External transactions) or incoming liquidity (External→Internal transactions)

  • External: Label for accounts held at other financial institutions. Critical for monitoring cross-institutional transfers and identifying potential money laundering patterns

  • HighRiskJurisdiction: Label for accounts in countries with elevated regulatory or compliance risks

Properties:

  • accountNumber (String): Unique account identifier (e.g., sort code and account number, IBAN)

  • accountType (String): Optional: Type of account (e.g., "CURRENT", "SAVINGS", "BUSINESS", "LOAN")

  • openDate (DateTime): Optional: Date and time when the account was first opened

  • closedDate (DateTime): Optional: Date and time when the account was permanently closed (null if still active)

  • suspendedDate (DateTime): Optional: Date and time when the account was temporarily suspended (null if not suspended)

Address

Properties:

  • addressLine1 (String): Primary address line containing house/building number and street name

  • addressLine2 (String): Optional: Secondary address details such as flat number, building name, or estate

  • postTown (String): Post town as defined by Royal Mail (the town or city for postal delivery)

  • postCode (String): UK postcode or international postal code for the address

  • region (String): Optional: County, state, or administrative region

  • latitude (Float): Optional: Geographic latitude coordinate for precise location mapping

  • longitude (Float): Optional: Geographic longitude coordinate for precise location mapping

  • createdAt (DateTime): Timestamp when this address was first recorded in the system

Counterparty

Properties:

  • counterpartyId (String): Unique system identifier for the counterparty entity

  • name (String): Legal name of the counterparty (person’s full name or registered company name)

  • type (String): Classification of counterparty (e.g., "INDIVIDUAL", "BUSINESS", "GOVERNMENT", "CHARITY")

  • registrationNumber (String): Optional: Official registration number (Companies House number, charity number, etc.)

  • createdAt (DateTime): Timestamp when this counterparty was first recorded in the system

Country

Properties:

  • code (String): Two-letter ISO 3166-1 alpha-2 country code (e.g., "GB" for United Kingdom, "US" for United States)

  • name (String): Official full country name in English

Customer

Properties:

  • customerId (String): Unique system identifier for the customer record

  • firstName (String): Optional: Customer’s given name (first name)

  • middleName (String): Optional: Customer’s middle name(s) or initial(s)

  • lastName (String): Optional: Customer’s family name (surname)

  • dateOfBirth (Date): Optional: Customer’s date of birth (used for identity verification and age calculations)

  • placeOfBirth (String): Optional: City or location where the customer was born (as stated on official documents)

  • countryOfBirth (String): Optional: ISO 3166-1 country code where the customer was born (may differ from nationality)

Device

Properties:

  • deviceId (String): Unique fingerprint or identifier for the device accessing the system

  • deviceType (String): Optional: Category of device (e.g., "mobile", "desktop", "tablet", "unknown")

  • userAgent (String): Optional: Browser or application user agent string containing device and software information

  • createdAt (DateTime): Timestamp when this device was first detected and recorded in the system

DrivingLicense

Properties:

  • licenseNumber (String): Unique driving licence number as printed on the physical document

  • issueDate (Date): Optional: Date when the driving licence was originally issued

  • expiryDate (Date): Optional: Date when the driving licence expires and requires renewal

  • issuingCountry (String): Optional: ISO 3166-1 country code of the authority that issued the licence

  • createdAt (DateTime): Timestamp when this driving licence record was first created in the system

Email

Properties:

  • address (String): Complete email address (e.g., "john.smith@example.com")

  • domain (String): Optional: Domain portion of the email address (e.g., "example.com")

  • createdAt (DateTime): Timestamp when this email address was first recorded in the system

Face

Properties:

  • faceId (String): Unique identifier for the face biometric record

  • embedding (List of Float): High-dimensional vector representation of facial features (typically 512-1536 dimensions)

  • createdAt (DateTime): Timestamp when this facial biometric record was created in the system

IP

Properties:

  • ipAddress (String): Internet Protocol address used to access the system (IPv4 or IPv6 format)

  • createdAt (DateTime): Timestamp when this IP address was first observed in the system

ISP

Properties:

  • name (String): Name of the Internet Service Provider or telecommunications company

  • createdAt (DateTime): Timestamp when this ISP was first recorded in the system

Location

Properties:

  • city (String): Name of the city or major population centre

  • postCode (String): Postal code or ZIP code for the general area (may be partial for privacy)

  • country (String): ISO 3166-1 country code where this location is situated

  • latitude (Float): Optional: Geographic latitude coordinate for the location centre

  • longitude (Float): Optional: Geographic longitude coordinate for the location centre

  • createdAt (DateTime): Timestamp when this location was first recorded in the system

Movement

Properties:

  • movementId (String): Unique system identifier for this individual movement

  • amount (Float): Monetary value of this specific movement (always positive)

  • currency (String): Three-letter ISO 4217 currency code (e.g., "GBP", "USD", "EUR")

  • date (DateTime): Date and time when this movement was executed or scheduled

  • description (String): Optional: Human-readable description explaining the movement’s purpose (e.g., "Insurance claim payment 1 of 3")

  • status (String): Optional: Current processing status (e.g., "PENDING", "COMPLETED", "CANCELLED", "FAILED")

  • sequenceNumber (Integer): Chronological order of this movement within a series (starts from 1)

  • authorisedBy (String): Optional: User ID or name of the person who authorised this movement

  • validatedBy (String): Optional: User ID or name of the person who provided secondary approval (for dual control processes)

  • createdAt (DateTime): Timestamp when this movement record was initially created in the system

Passport

Properties:

  • passportNumber (String): Unique passport number as printed on the document

  • issueDate (Date): Optional: Date when the passport was originally issued

  • expiryDate (Date): Optional: Date when the passport expires and requires renewal

  • issuingCountry (String): Optional: ISO 3166-1 country code of the government that issued this passport

  • nationality (String): Optional: Nationality or citizenship recorded in the passport (may differ from issuing country)

  • createdAt (DateTime): Timestamp when this passport record was first created in the system

Phone

Properties:

  • phoneNumber (String): Complete phone number including country code

  • countryCode (String): Optional: International dialling code with plus prefix (e.g., "+44", "+1")

  • createdAt (DateTime): Timestamp when this phone number was first recorded in the system

Session

Properties:

  • sessionId (String): Unique identifier for the user session or login attempt

  • status (String): Optional: Outcome of the session (e.g., "success", "failed", "suspicious", "timeout")

  • createdAt (DateTime): Timestamp when the session was initiated

Transaction

Properties:

  • transactionId (String): Unique system identifier for the transaction

  • amount (Float): Monetary value of the transaction (always positive, direction indicated by relationships)

  • currency (String): Three-letter ISO 4217 currency code (e.g., "GBP", "USD", "EUR")

  • date (DateTime): Date and time when the transaction was processed or initiated

  • message (String): Optional: Payment reference or description text (e.g., "Swift payment for services", "Invoice #12345")

  • type (String): Optional: Payment method or transaction type (e.g., "SWIFT", "ACH", "FASTER_PAYMENT", "CARD")

2. Relationship Types and Properties

:BENEFITS_TO

  • Direction: Transaction→Account

  • Properties: None

  • Description: Links a transaction to the account that receives the funds (the destination or beneficiary account)

:HAS_ACCOUNT

  • Direction: Customer→Account

  • Properties:

    • role (String): Customer’s role on the account (e.g., "owner", "joint", "authorised_signatory")

    • since (DateTime): Date and time when this account relationship was established

:HAS_ACCOUNT

  • Direction: Counterparty→Account

  • Properties:

    • since (DateTime): Date and time when this counterparty-account relationship was first recorded

:HAS_ADDRESS

  • Direction: Customer→Address

  • Properties:

    • addedAt (DateTime): When this address was first linked to the customer

    • lastChangedAt (DateTime): When the address details were last updated

    • isCurrent (Boolean): Whether this is the customer’s current residential address

:HAS_ADDRESS

  • Direction: Counterparty→Address

  • Properties:

    • since (DateTime): When this address was first linked to the counterparty

    • isCurrent (Boolean): Whether this is the counterparty’s current registered address

:HAS_DRIVING_LICENSE

  • Direction: Customer→DrivingLicense

  • Properties:

    • verificationDate (DateTime): When the driving licence was verified by the bank

    • verificationMethod (String): How verification was performed (e.g., "BRANCH_VERIFIED", "DIGITAL_SCAN", "VIDEO_KYC")

    • verificationStatus (String): Current verification status (e.g., "VERIFIED", "PENDING", "REJECTED", "EXPIRED")

:HAS_EMAIL

  • Direction: Customer→Email

  • Properties:

    • since (DateTime): When this email address was first linked to the customer record

:HAS_FACE

  • Direction: Customer→Face

  • Properties:

    • verificationDate (DateTime): When the facial biometric was verified against the customer

    • verificationMethod (String): How facial verification was performed (e.g., "SELFIE", "VIDEO_KYC", "BRANCH_PHOTO")

    • verificationStatus (String): Current verification status (e.g., "VERIFIED", "PENDING", "REJECTED")

:HAS_NATIONALITY

  • Direction: Customer→Country

  • Properties: None

  • Description: Links a customer to their country of citizenship or nationality (customers may have multiple nationalities)

:HAS_PASSPORT

  • Direction: Customer→Passport

  • Properties:

    • verificationDate (DateTime): When the passport was verified by the bank

    • verificationMethod (String): How verification was performed (e.g., "BRANCH_VERIFIED", "DIGITAL_SCAN", "VIDEO_KYC")

    • verificationStatus (String): Current verification status (e.g., "VERIFIED", "PENDING", "REJECTED", "EXPIRED")

:HAS_PHONE

  • Direction: Customer→Phone

  • Properties:

    • since (DateTime): When this phone number was first linked to the customer record

:IMPLIED

  • Direction: Transaction→Movement

  • Properties:

    • totalMovements (Integer): Total number of movements that will result from this transaction

  • Description: Links a transaction to its component movements when a single transaction results in multiple separate payments (e.g., insurance claim paid in instalments, loan disbursement in tranches)

:IS_ALLOCATED_TO

  • Direction: IP→ISP

  • Properties:

    • createdAt (DateTime): Timestamp when this IP address allocation was first recorded

  • Description: Links an IP address to the Internet Service Provider that owns or manages that IP address range

:IS_HOSTED

  • Direction: Account→Country

  • Properties: None

  • Description: Links an account to the country where it is held or domiciled (using ISO 3166-1 country codes)

:LOCATED_IN

  • Direction: Address→Country

  • Properties: None

  • Description: Links a physical address to the country where it is located (using ISO 3166-1 country codes)

:LOCATED_IN

  • Direction: IP→Location

  • Properties:

    • createdAt (DateTime): Timestamp when this IP geolocation was first determined

  • Description: Links an IP address to its approximate geographic location based on geolocation services

:LOCATED_IN

  • Direction: Location→Country

  • Properties: None

  • Description: Links a geographic location to the country where it is situated (using ISO 3166-1 country codes)

:PERFORMS

  • Direction: Account→Transaction

  • Properties: None

  • Description: Links the account that initiated or sent the transaction (the source or debtor account)

:SESSION_USES_DEVICE

  • Direction: Session→Device

  • Properties: None

  • Description: Records which device was used to establish or conduct the session

:USED_BY

  • Direction: Device→Customer

  • Properties:

    • lastUsed (DateTime): When this device was last used by the customer

:USES_IP

  • Direction: Session→IP

  • Properties: None

  • Description: Records which IP address was used to establish or conduct the session

3. Constraints and Indexes

// Constraints
CREATE CONSTRAINT customer_id IF NOT EXISTS
FOR (c:Customer) REQUIRE c.customerId IS NODE KEY;

CREATE CONSTRAINT email_address IF NOT EXISTS
FOR (e:Email) REQUIRE e.address IS NODE KEY;

CREATE CONSTRAINT phone_number IF NOT EXISTS
FOR (p:Phone) REQUIRE p.number IS NODE KEY;

CREATE CONSTRAINT passport_number IF NOT EXISTS
FOR (p:Passport) REQUIRE (p.passportNumber, p.issuingCountry) IS NODE KEY;

CREATE CONSTRAINT driving_licence_number IF NOT EXISTS
FOR (d:DrivingLicense) REQUIRE (d.licenseNumber, d.issuingCountry) IS NODE KEY;

CREATE CONSTRAINT device_id IF NOT EXISTS
FOR (d:Device) REQUIRE d.deviceId IS NODE KEY;

CREATE CONSTRAINT ip_address IF NOT EXISTS
FOR (i:IP) REQUIRE i.ipAddress IS NODE KEY;

CREATE CONSTRAINT session_id IF NOT EXISTS
FOR (s:Session) REQUIRE s.sessionId IS NODE KEY;

CREATE CONSTRAINT account_number IF NOT EXISTS
FOR (a:Account) REQUIRE a.accountNumber IS NODE KEY;

CREATE CONSTRAINT transaction_id IF NOT EXISTS
FOR (t:Transaction) REQUIRE t.transactionId IS NODE KEY;

CREATE CONSTRAINT face_id IF NOT EXISTS
FOR (f:Face) REQUIRE f.faceId IS NODE KEY;

CREATE CONSTRAINT counterparty_id IF NOT EXISTS
FOR (cp:Counterparty) REQUIRE cp.counterpartyId IS NODE KEY;

CREATE CONSTRAINT movement_id IF NOT EXISTS
FOR (m:Movement) REQUIRE m.movementId IS NODE KEY;

CREATE CONSTRAINT isp_name IF NOT EXISTS
FOR (i:ISP) REQUIRE i.name IS NODE KEY;

CREATE CONSTRAINT country_code IF NOT EXISTS
FOR (c:Country) REQUIRE c.code IS NODE KEY;

CREATE CONSTRAINT address_composite IF NOT EXISTS
FOR (a:Address) REQUIRE (a.addressLine1, a.postTown, a.postCode) IS NODE KEY;

// Additional Indexes

CREATE INDEX transaction_date_idx IF NOT EXISTS FOR (t:Transaction) ON (t.date);
CREATE INDEX transaction_amount_idx IF NOT EXISTS FOR (t:Transaction) ON (t.amount);

// Vector index for face embeddings (used for biometric matching)
CALL db.index.vector.createNodeIndex(
  'face_embedding_idx',
  'Face',
  'embedding',
  1536,  // dimensionality of the face embeddings (typical range: 512-1536)
  'cosine'  // similarity metric for comparing facial features
);

// Full Text Search

// Full-text index for customer name search
CREATE FULLTEXT INDEX customer_name_idx IF NOT EXISTS FOR (c:Customer) ON EACH [c.firstName, c.lastName, c.middleName];

4. Customer enhancements

Location Data Source Enhancement:

While reviewing this data model, we identified an opportunity to improve location accuracy for fraud detection scenarios. Currently, the model derives location data from IP addresses, but mobile device GPS coordinates provide significantly higher precision when available.

Proposed Enhancement:

  • Establish a direct LOCATED_IN relationship from Session nodes to Location nodes

  • Add a source property to the relationship indicating data origin (e.g., "IP", "Device_GPS", "WiFi_Network")

  • This would allow the system to prioritize more accurate location data while maintaining fallback options

Benefits:

  • Enhanced fraud detection through precise geolocation

  • Flexible data source hierarchy (GPS > WiFi > IP)

  • Maintains backward compatibility with IP-based location data

5. Minimal Demo Code

The following Cypher code creates a complete example dataset demonstrating all the main entities and relationships in the model. This code is suitable for testing and demonstration purposes.

//--------------------
// Create base countries
//--------------------
CREATE (uk:Country {code: "GB", name: "United Kingdom"})
CREATE (us:Country {code: "US", name: "United States"})

//--------------------
// Create customer and their identity information
//--------------------
// Create main customer node
CREATE (c:Customer {
    customerId: "CUS001",
    firstName: "John",
    middleName: "Michael",
    lastName: "Smith",
    dateOfBirth: date("1990-01-01"),
    placeOfBirth: "Manchester",
    countryOfBirth: "GB"
})

// Create customer's address
CREATE (addr:Address {
    addressLine1: "123 High Street",
    addressLine2: "Flat 4B",
    postTown: "London",
    postCode: "SW1A 1AA",
    region: "Greater London",
    latitude: 51.5074,
    longitude: -0.1278,
    createdAt: datetime("2024-01-01T00:00:00")
})

// Create customer's contact information
CREATE (e:Email {
    address: "john@example.com",
    domain: "example.com",
    createdAt: datetime("2024-01-01T00:00:00")
})
CREATE (ph:Phone {
    number: "447971020304",
    countryCode: "+44",
    createdAt: datetime("2024-01-01T00:00:00")
})

// Create customer's identity documents
CREATE (p:Passport {
    passportNumber: "123456789",
    issueDate: date("2020-01-01"),
    expiryDate: date("2030-01-01"),
    issuingCountry: "GB",
    nationality: "British",
    createdAt: datetime("2024-01-01T00:00:00")
})
CREATE (dl:DrivingLicense {
    licenseNumber: "SMITH901011J99AB",
    issueDate: date("2018-01-01"),
    expiryDate: date("2028-01-01"),
    issuingCountry: "GB",
    createdAt: datetime("2024-01-01T00:00:00")
})

// Create customer's face biometric
CREATE (f:Face {
    faceId: "FACE001",
    embedding: [0.153, 0.254, 0.255, 0.0, 0.0, 0.253, 0.200, 0.252, 0.254, 0.100, 0.253],  // Truncated example of 1536-dimension vector
    createdAt: datetime("2024-01-01T00:00:00")
})

//--------------------
// Create all identity relationships
//--------------------
// Link customer to their identity information
CREATE (c)-[:HAS_ADDRESS {
    addedAt: datetime("2024-01-01T00:00:00"),
    lastChangedAt: datetime("2024-01-01T00:00:00"),
    isCurrent: true
}]->(addr)
CREATE (addr)-[:LOCATED_IN]->(uk)
CREATE (c)-[:HAS_EMAIL {since: datetime("2024-01-01T00:00:00")}]->(e)
CREATE (c)-[:HAS_PHONE {since: datetime("2024-01-01T00:00:00")}]->(ph)
CREATE (c)-[:HAS_NATIONALITY]->(uk)

// Link customer to their identity documents
CREATE (c)-[:HAS_PASSPORT {
    verificationDate: datetime("2024-01-01T00:00:00"),
    verificationMethod: "VIDEO_KYC",
    verificationStatus: "VERIFIED"
}]->(p)
CREATE (c)-[:HAS_DRIVING_LICENSE {
    verificationDate: datetime("2024-01-01T00:00:00"),
    verificationMethod: "BRANCH_VERIFIED",
    verificationStatus: "VERIFIED"
}]->(dl)
CREATE (c)-[:HAS_FACE {
    verificationDate: datetime("2024-01-01T00:00:00"),
    verificationMethod: "SELFIE",
    verificationStatus: "VERIFIED"
}]->(f)

//--------------------
// Create digital access nodes
//--------------------
// Create device, IP, ISP, and session information
CREATE (d:Device {
    deviceId: "DEV001",
    deviceType: "desktop",
    userAgent: "Mozilla/5.0 Chrome/91.0",
    createdAt: datetime("2024-01-01T00:00:00")
})
CREATE (ip:IP {ipAddress: "192.168.1.1", createdAt: datetime("2024-01-01T00:00:00")})
CREATE (isp:ISP {name: "BT", createdAt: datetime("2024-01-01T00:00:00")})
CREATE (loc:Location {
    city: "London",
    postCode: "SW1A",
    country: "GB",
    latitude: 51.5074,
    longitude: -0.1278,
    createdAt: datetime("2024-01-01T00:00:00")
})
CREATE (s:Session {
    sessionId: "SESS001",
    status: "success",
    createdAt: datetime("2024-03-01T10:00:00")
})

// Create digital access relationships
CREATE (d)-[:USED_BY {lastUsed: datetime("2024-03-01T10:00:00")}]->(c)
CREATE (s)-[:USES_IP]->(ip)
CREATE (ip)-[:IS_ALLOCATED_TO {createdAt: datetime("2024-01-01T00:00:00")}]->(isp)
CREATE (ip)-[:LOCATED_IN {createdAt: datetime("2024-01-01T00:00:00")}]->(loc)
CREATE (loc)-[:LOCATED_IN]->(uk)
CREATE (s)-[:SESSION_USES_DEVICE]->(d)

//--------------------
// Create banking nodes
//--------------------
// Create internal and external accounts
CREATE (a:Account:Internal {
    accountNumber: "ACC001",
    accountType: "CURRENT",
    openDate: datetime("2024-01-01T00:00:00"),
    closedDate: null,
    suspendedDate: null
})
CREATE (a2:Account:External:HighRiskJurisdiction {
    accountNumber: "ACC002",
    accountType: null,
    openDate: null,
    closedDate: null,
    suspendedDate: null
})

// Create transaction
CREATE (t1:Transaction {
    transactionId: "TXN001",
    amount: 1000.00,
    currency: "GBP",
    date: datetime("2024-03-01T11:00:00"),
    message: "Payment for services",
    type: "SWIFT"
})

//--------------------
// Create banking relationships
//--------------------
// Link accounts to customer and countries
CREATE (c)-[:HAS_ACCOUNT {role: "OWNER", since: datetime("2024-01-01T00:00:00")}]->(a)
CREATE (a)-[:IS_HOSTED]->(uk)
CREATE (a2)-[:IS_HOSTED]->(us)

// Create transaction flow
CREATE (a)-[:PERFORMS]->(t1)-[:BENEFITS_TO]->(a2)

//--------------------
// Create movements for transaction (example: insurance claim paid in instalments)
//--------------------
// Create movements
CREATE (m1:Movement {
    movementId: "MOV001",
    amount: 400.00,
    currency: "GBP",
    date: datetime("2024-03-05T10:00:00"),
    description: "Insurance claim payment 1 of 3",
    status: "COMPLETED",
    sequenceNumber: 1,
    authorisedBy: "USER001",
    validatedBy: "MANAGER001",
    createdAt: datetime("2024-03-01T11:00:00")
})
CREATE (m2:Movement {
    movementId: "MOV002",
    amount: 400.00,
    currency: "GBP",
    date: datetime("2024-03-12T10:00:00"),
    description: "Insurance claim payment 2 of 3",
    status: "COMPLETED",
    sequenceNumber: 2,
    authorisedBy: "USER001",
    validatedBy: "MANAGER002",
    createdAt: datetime("2024-03-01T11:00:00")
})
CREATE (m3:Movement {
    movementId: "MOV003",
    amount: 200.00,
    currency: "GBP",
    date: datetime("2024-03-19T10:00:00"),
    description: "Insurance claim payment 3 of 3",
    sequenceNumber: 3,
    authorisedBy: "USER001",
    validatedBy: null,
    createdAt: datetime("2024-03-01T11:00:00")
})

// Link transaction to movements
CREATE (t1)-[:IMPLIED {totalMovements: 3}]->(m1)
CREATE (t1)-[:IMPLIED {totalMovements: 3}]->(m2)
CREATE (t1)-[:IMPLIED {totalMovements: 3}]->(m3)

//--------------------
// Create counterparty
//--------------------
// Create counterparty and their address
CREATE (cp:Counterparty {
    counterpartyId: "CP001",
    name: "ACME Corporation Ltd",
    type: "BUSINESS",
    registrationNumber: "12345678",
    createdAt: datetime("2024-02-01T00:00:00")
})
CREATE (cpAddr:Address {
    addressLine1: "456 Business Park",
    addressLine2: "Suite 100",
    postTown: "Birmingham",
    postCode: "B1 1AA",
    region: "West Midlands",
    latitude: 52.4862,
    longitude: -1.8904,
    createdAt: datetime("2024-02-01T00:00:00")
})

// Link counterparty to address and account
CREATE (cp)-[:HAS_ADDRESS {
    since: datetime("2024-02-01T00:00:00"),
    isCurrent: true
}]->(cpAddr)
CREATE (cpAddr)-[:LOCATED_IN]->(uk)
CREATE (cp)-[:HAS_ACCOUNT {
    since: datetime("2024-02-01T00:00:00")
}]->(a2)