quackiso

ISO-20022-Finanznachrichten (camt, pacs, pain) mit SQL abfragen

Maintainer: tempoloss

Installation und Laden

INSTALL quackiso FROM community;
LOAD quackiso;

Beispiel

-- Bank statements as rows: camt.053, camt.054 and camt.052. A glob is
-- parsed in parallel, one worker per file.
SELECT booking_date, amount, currency, credit_debit, counterparty_name
FROM read_iso20022('statements/*.xml', threads := 8)
ORDER BY booking_date;
-- What is in the folder, before choosing a reader: one row per file with
-- the message type, the reader that covers it, and the record count.
SELECT family, reader, count(*) AS files, sum(records) AS records
FROM sniff_iso20022('inbox/**/*.xml')
GROUP BY family, reader;
-- Interbank credit transfers (pacs.008, the ISO 20022 MT103).
SELECT uetr, amount, currency, debtor_name, creditor_agent_bic
FROM read_pacs008('pacs008.xml');
-- Credit transfer initiation (pain.001). The payer lives on the <PmtInf>
-- group and is carried down to every transaction in it.
SELECT payment_info_id, debtor_name, creditor_name, amount,
requested_execution_date
FROM read_pain001('pain001.xml');
-- Direct debits (pain.008): the creditor pulls, the mandate makes it legal.
SELECT mandate_id, sequence_type, debtor_name, amount
FROM read_pain008('pain008.xml');
-- Payment returns (pacs.004): what came back, beside what was settled.
-- A return with charges deducted is amount < original_amount.
SELECT return_id, amount, original_amount, return_reason_code,
original_debtor_name, original_creditor_name
FROM read_pacs004('pacs004.xml');
-- Payment status reports (pain.002). A status is stated per batch, per
-- payment group and per transaction, and status_level says which a row is.
SELECT status_level, status, reason_code, original_end_to_end_id, amount
FROM read_pain002('pain002.xml')
WHERE status_level = 'TRANSACTION';
-- Amounts are DECIMAL(38,5), so totals are exact.
SELECT currency, SUM(amount) AS total
FROM read_iso20022('statements/*.xml')
WHERE credit_debit = 'DBIT'
GROUP BY currency;

Über quackiso

quackiso liest ISO-20022-Finanznachrichten direkt in DuckDB-Tabellen. Kein Python-Vorverarbeitungsschritt, kein Glue-Code je Schema: richten Sie eine Tabellenfunktion auf Bank-XML und erhalten Sie Transaktionen als Zeilen.

Vierzehn Funktionen: dreizehn Reader, die den Zahlungslebenszyklus von Ende zu Ende in beide Richtungen abdecken, und ein Sniffer, der Dateien an sie weiterleitet.

  • read_iso20022(path) - camt.053-Auszüge, camt.054-Benachrichtigungen und camt.052-Berichte; eine Zeile pro gebuchtem Eintrag.
  • read_pacs008(path) / read_pacs009(path) - Kunden- und Interbanken-Überweisungen (ISO-20022-MT103 und MT202/MT202COV); in der COV-Form tragen die underlying_*-Spalten die Kundenüberweisung, die das Cover settlet.
  • read_pain001(path) / read_pain008(path) - Initiierung von Überweisung und Lastschrift; die zahlende bzw. einziehende Seite liegt auf der <PmtInf>- Gruppe und wird heruntergetragen, Lastschriften tragen das Mandat.
  • read_pacs003(path) - die Interbanken-Strecke einer Lastschrifteinziehung.
  • read_pain002(path) / read_pacs002(path) - Zahlungsstatusberichte, kunden- und interbankenseitig; eine Zeile pro Statusangabe, auf welcher Ebene die Bank sie auch angegeben hat, weil ein Batch angenommen oder abgelehnt werden kann, ohne dass eine einzelne Transaktion detailliert wird.
  • read_pacs004(path) / read_pacs007(path) - Rückgaben und Stornierungen: zurückkommendes settled Geld, vom Empfänger oder vom Absender zurückgeholt; der zurückgegebene Betrag steht neben dem Original, sodass Teilrückgaben sichtbar sind.
  • read_camt056(path) / read_camt055(path) / read_camt029(path) - Stornierungsanfragen (interbanken- und kundenseitig) und die Auflösung, die sie beantwortet; eine Stornierung des gesamten Batches ohne Transaktionen ist trotzdem eine Zeile.
  • sniff_iso20022(path) - Inventar vor dem Lesen: eine Zeile pro Datei mit erkanntem Nachrichtentyp, Familie, Wire-Level-Datensatzanzahl und dem zuständigen Reader. Die Identität kommt aus dem Namensraum, den epochenbedingten Containernamen oder der Envelope-Bindung; Inhaltsprobleme landen in einer error-Spalte statt den Scan abzubrechen, sodass ein Ordner gemischter Downloads eine Tabelle ist und kein Fehlschlag.

Alle Ausnahme- und Status-Reader legen die ursprünglichen Referenzen offen (UETR, End-to-End-ID, ursprüngliche Nachrichten-ID), sodass eine Zahlung über ihren gesamten Lebenszyklus verbunden werden kann: Initiierung, Settlement, Status, Stornierung, Auflösung, Rückgabe.

Beträge sind DECIMAL(38,5), niemals DOUBLE. Werte werden vom Wire-String direkt in eine skalierte Ganzzahl umgewandelt und berühren nie einen Float, sodass SUM exakt ist; ISO 20022 erlaubt 18 signifikante Stellen mit bis zu 5 Nachkommastellen, die ein 64-Bit-DECIMAL(18,5) nicht fassen kann. Ein Betrag, der nicht exakt darstellbar ist, löst einen Fehler aus, statt zu NULL zu werden und still aus einer Summe zu fallen.

Datumsangaben sind typisiert statt als Text belassen; Offsets werden nach UTC normalisiert, und sowohl die <Dt>- als auch die <DtTm>-Hüllen werden gelesen.

Das Parsen ist ein Streaming-Durchlauf über XML-Ereignisse, sodass das Maximum ein Ausgabe-Batch plus dem größten einzelnen XML-Teilbaum ist und nicht die Datei: ein 1,7-GB-Auszug mit drei Millionen Einträgen parst in 1,23 MiB Live-Heap und etwa 2 MB Resident, gemessen mit cargo test membound, und fügt einer laufenden DuckDB 7,8 MiB hinzu. Ein Glob wird parallel geparst, ein Worker pro Datei – XML hat keine sicheren Splittpunkte, daher wird ein einzelnes Dokument nie geteilt – mit threads := n zum Festlegen des Pools; gemessen 6,9× bei 8 Dateien à 35 MB.

Eine Datei des falschen Nachrichtentyps schlägt laut fehl, statt eine leere Tabelle zurückzugeben. Die Transaktionselementnamen kollidieren zwischen Familien (camt.056 und pacs.004 sagen beide TxInf, pacs.008 und pain.001 beide CdtTrfTxInf), daher ist die Identität der eigene Container der Nachricht und Zeilen entstehen nur darin.

Getestet gegen rund 260 echte Nachrichten aus mehr als einem Dutzend Quellen – darunter Goldman Sachs US/UK/EU und Wire, SIX Interbank, CBPR+, ProgressSoft, Nivaes, Prowide, OpenBankProject, Mbanq, Handelsbanken, issettled, prog-nov, salesking und Dolibarr – über dreizehn Nachrichtenfamilien und jede Epoche ihres Vokabulars: umbenannte Reason-Blöcke, umbenannte Container, namensraum-präfixierte Teilbäume, auf Gruppenebene heruntergetragene Felder und Partei- seiten, die sich in einer Rückgabe umkehren. Jedes Verhalten, das wie ein Sonderfall wirkt, stammt aus einer dieser Dateien.

Pfade sind lokale Dateien oder Globs; jede Zeile speichert ihre source_file. Entfernte URIs und XSD-Validierung fehlen bewusst; die Begründung steht in docs/adr/.

Hinzugefügte Funktionen

function_name function_type description comment examples
read_camt029 table NULL NULL
read_camt055 table NULL NULL
read_camt056 table NULL NULL
read_iso20022 table NULL NULL
read_pacs002 table NULL NULL
read_pacs003 table NULL NULL
read_pacs004 table NULL NULL
read_pacs007 table NULL NULL
read_pacs008 table NULL NULL
read_pacs009 table NULL NULL
read_pain001 table NULL NULL
read_pain002 table NULL NULL
read_pain008 table NULL NULL
sniff_iso20022 table NULL NULL

Überladene Funktionen

Diese Erweiterung fügt keine Funktionsüberladungen hinzu.

Hinzugefügte Typen

Diese Erweiterung fügt keine Typen hinzu.

Hinzugefügte Einstellungen

Diese Erweiterung fügt keine Einstellungen hinzu.