Logo
Afbeelding

Controle op splitsing transacties

Geschreven door The Audit Analytics | 5 minuten

Soms worden betalingen opzettelijk opgesplitst om onder een goedkeuringsdrempel te blijven. Dit kan wijzen op ongeoorloofde transacties of pogingen om controles te omzeilen. In deze analyse gaan we banktransacties uit een CAMT.053-bestand onderzoeken om verdachte splitsingen te detecteren.

Dubbele Bankrekeningen

We gebruiken Python, en hoewel je enige basiskennis nodig hebt, houden we het begrijpelijk. Voor deze analyse heb je het volgende nodig:

  • Een CAMT.053-bestand met transactiedata.
  • De Python-pakketten pandas en xml.etree.ElementTree

Pandas kun je - als je dat nog niet gedaan hebt - installeren met:

pip install pandas

Voor een oefenbestand om deze analyse zelf uit te voeren, kun je dit test-CAMT-bestand downloaden

Stap 1: Inlezen van het CAMT.053-bestand

Een CAMT.053-bestand is een XML-formaat dat banktransacties bevat. We gebruiken pandas en xml.etree.ElementTree om het bestand te verwerken.

# inladen van ElemenTree wat we nodig hebben om de XML in te lezen. 
import xml.etree.ElementTree as ET
import pandas as pd

# Het ontleden van de CAMT 053 met een leesbaar df als resultaat
def parse_camt053(file_path):
    tree = ET.parse(file_path)
    root = tree.getroot()
    ns = {'ns': 'urn:iso:std:iso:20022:tech:xsd:camt.053.001.02'}

    transactions = []
    
    # Loop over alle entries heen
    for entry in root.findall('.//ns:Ntry', ns):
        try:
            # Als er geen bedrag gevonden wordt voor de entry, wordt deze op 0,0 gezet. 
            amount = float(entry.find('ns:Amt', ns).text) if entry.find('ns:Amt', ns) is not None else 0.0
        except ValueError:
            amount = 0.0  # Fallback bij conversiefouten
        
        # Hieronder zoekt hij de betreffende velden en koppelt hij ze aan de variabele voor ons overzicht. 
        currency = entry.find('ns:Amt', ns).attrib.get('Ccy', 'N/A') if entry.find('ns:Amt', ns) is not None else 'N/A'
        date = entry.find('.//ns:BookgDt/ns:Dt', ns).text if entry.find('.//ns:BookgDt/ns:Dt', ns) is not None else '-'
        debtor = entry.find('.//ns:Dbtr/ns:Nm', ns).text if entry.find('.//ns:Dbtr/ns:Nm', ns) is not None else '-'
        debtor_iban = entry.find('.//ns:DbtrAcct/ns:Id/ns:IBAN', ns).text if entry.find('.//ns:DbtrAcct/ns:Id/ns:IBAN', ns) is not None else '-'
        creditor = entry.find('.//ns:Cdtr/ns:Nm', ns).text if entry.find('.//ns:Cdtr/ns:Nm', ns) is not None else '-'
        creditor_iban_element = entry.find('.//ns:CdtrAcct/ns:Id/ns:IBAN', ns)
        creditor_iban = creditor_iban_element.text if creditor_iban_element is not None else '-'

        # Koppelt de entry als regel aan ons DataFrame
        transactions.append({
            "Datum": date,
            "Debiteur": debtor,
            "Debiteur IBAN": debtor_iban,
            "Crediteur": creditor,
            "Crediteur IBAN": creditor_iban,
            "Bedrag": amount,
            "Munteenheid": currency
        })
    
    return pd.DataFrame(transactions)

# Laad het CAMT.053-bestand
file_path = "camt053_dummy.xml"
df = parse_camt053(file_path)

# Toon de bovenste rijen
print(df.head())

Stap 2: Groeperen van betalingen per leverancier en datum

We willen weten of er meerdere betalingen op dezelfde dag naar dezelfde leverancier zijn gegaan.

# Je kunt Begunstigde ook vervangen voor IBAN
df_grouped = df.groupby(['Datum', 'Crediteur']).agg(
    Aantal_Transacties=('Bedrag', 'count'),  # Tel het aantal rijen per groep
    Totaal_Bedrag=('Bedrag', 'sum')  # Sommeer de bedragen
).reset_index()

print(df_grouped)

Dit laat ons zien hoeveel er per dag per leverancier is overgemaakt.

Stap 3: Detecteren van opgesplitste betalingen

Nu zoeken we naar patronen waarin meerdere betalingen optellen tot een grote factuur of waarin bedragen net onder een goedkeuringsdrempel blijven.

Laten we aannemen dat de goedkeuringsdrempel €1.000 is.

approval_limit = 1000

# Kijk of meerdere betalingen samen de drempel benaderen
df_splitsingen = df_grouped[
    (df_grouped['Totaal_Bedrag'] >= (approval_limit * 0.9)) & 
    (df_grouped['Aantal_Transacties'] > 1)
]
print(df_splitsingen)

Dit filtert de transacties waarbij het totaalbedrag per leverancier en datum minstens 90% van de goedkeuringslimiet bedraagt.

Stap 4: Markeren van verdachte splitsingen

We willen nu specifiek kijken of individuele betalingen binnen deze groepsbedragen net onder de drempel blijven.

verdachte_transacties = df[(df['Bedrag'] >= (approval_limit * 0.9)) & (df['Bedrag'] < approval_limit)]
print(verdachte_transacties)

Dit geeft ons een lijst van verdachte transacties die mogelijk bewust zijn opgesplitst.

Conclusie

Met deze stappen kunnen we eenvoudig verdachte betalingen detecteren. Wil je verder experimenteren? Probeer andere drempelwaarden, kijk per IBAN of visualiseer de resultaten met matplotlib.