Athena — architecture.md

Athena — Architecture (v1)

Overview

Athena's pipeline has four layers, in order: ingestion/parsing (per source file type) → normalization (mapping everything into one common transaction/balance model) → detection/reasoning (one module per finding type / check) → output/reporting (structured, source-traceable findings). Each layer's boundary is deliberate: normalization is the single place heterogeneous formats become one shape, so every detection module operates on the same model regardless of whether the data originated in a CSV, an XML auditfile, a spreadsheet, or extracted text from a PDF or DOCX.

 [CSV] [XAF XML] [XLSX] [PDF] [DOCX]
     |      |       |     |      |
     v      v       v     v      v
 +-----------------------------------+
 |   Ingestion / Parsing (per type)  |
 +-----------------------------------+
                 |
                 v
 +-----------------------------------+
 |         Normalization             |
 |  (common transaction/balance/     |
 |   confirmation-letter model)      |
 +-----------------------------------+
                 |
                 v
 +-----------------------------------+
 |   Detection / Reasoning           |
 |  (one module per finding type     |
 |   / normal_check)                 |
 +-----------------------------------+
                 |
                 v
 +-----------------------------------+
 |   Output / Reporting              |
 |  (structured findings, source-    |
 |   traceable, review-flagged)      |
 +-----------------------------------+

1. Ingestion / parsing layer

One parser per source file type, each responsible only for turning its file into an intermediate, format-native representation — no cross-file logic here.

  • CSV (01_...bankmutaties.csv, 08_...lapping_scenario.csv) — delimited (;) parsing into row records keyed by the header. Straightforward, deterministic, schema known in advance from 10_data_dictionary.json.
  • XAF-style XML (02_...auditfile_XAF.xml) — XML parsing of generalLedgerAccounts and transactions elements. Note the seed data explicitly states this is a simplified XAF-like format, not validated against the official XAF 3.2 XSD — the parser should be schema-tolerant (namespace-aware but not XSD-strict) rather than assuming full XAF compliance.
  • XLSX (03_...saldibalans.xlsx, 07_...bankreconciliatie...xlsx) — spreadsheet parsing (sheet → rows → typed cells), reading both literal values and, where present, formula results (e.g., the reconciliation sheet's SUM(B2:B3) cell) — the computed value, not the formula text, is what normalization needs.
  • PDF (04_...rabobank...pdf, 05_...ing...pdf) — text extraction. In this training set both bank statement PDFs are text-layer PDFs (not scanned images), so ordinary PDF text extraction is sufficient to recover the tabular statement lines (date, description, af/bij amount, running balance) — no OCR was needed to read either file in this dataset. Production bank statements from real institutions may arrive as scanned/image PDFs; the ingestion layer should still be designed with an OCR fallback path for that case, but it is not exercised by this training set.
  • DOCX (06_...bankverklaring...docx) — text/table extraction from the Word document body. This training file structures its content as a field/value table (Bank, Peildatum, IBAN, Saldo, Kredietfaciliteit, Bankgaranties, Zekerheden, Geblokkeerde rekeningen, Bevoegde personen, Overige verplichtingen) — a table-aware DOCX parser recovers this directly without needing free-text NLP for this particular document. A production bank confirmation letter is not guaranteed to keep this tabular structure, which is why the extraction step downstream is LLM-assisted rather than a fixed table-column parser (see layer 3).

Each parser's output retains a pointer back to its source file name and, where meaningful, a location within that file (row number, XML element, cell reference, page) — this pointer is required by the output layer's source-traceability requirement and must not be dropped during normalization.

2. Normalization layer

Maps every parsed source into one common model, so downstream detection logic never needs to know which file format a fact came from. Based directly on the fields documented in 10_data_dictionary.json plus the additional fields observed in the bank statement PDFs and reconciliation/trial-balance spreadsheets:

  • Transactionjournal_id, booking_date, document_date, gl_account, description, counterparty, debit, credit, currency, user, manual_entry (bool), source (bank_import | manual), plus source_file/source_location for traceability. CSV rows, XAF <transaction> elements, and bank-statement PDF lines all normalize into this shape (a bank statement line becomes a transaction with source = bank_statement and the bank's own value date as booking_date, distinct from the GL's booking_date for the same economic event — this date/source distinction is exactly what kiting detection depends on).
  • Balancegl_account, account_description, debit, credit, net_balance, classification (e.g., "Liquide middelen", "Liquide middelen beperkt beschikbaar"), control_note, source_file. Trial balance rows normalize directly into this shape.
  • BankConfirmation — the structured record described in scope.md Capability 5 (bank, entity, reference_date, iban, balance, credit_facility, credit_facility_used, bank_guarantees[], securities[], blocked_accounts[], authorized_persons[], other_obligations, source_file).
  • Receipt (accounts-receivable specific, for lapping) — customer, invoice, invoice_date, amount, bank_receipt_date, bank_amount, applied_to_invoice, posting_date, source_file.

Normalization is where format-specific quirks get resolved once instead of in every detection module — e.g., Dutch decimal/thousands formatting in the DOCX/PDF text ("EUR 152.340,25") is parsed into a single canonical numeric type here, and GL account IDs are matched against the generalLedgerAccounts/trial-balance account list to attach a consistent account_description/classification regardless of which file an account ID appeared in.

3. Detection / reasoning layer

One module per finding type / normal_check, each consuming only the normalized model. The critical design split, evidenced directly by this training set, is which detections are computable by rule-based cross-referencing of structured data alone, and which require LLM-assisted extraction from unstructured/semi-structured document content:

Rule-detectable from structured data alone (no LLM inference needed):

  • Kiting — pure date/amount/account cross-referencing between GL transactions and bank-statement transactions once both are in the common Transaction model: match the internal-transfer pair by amount and counterparty description, compare booking_date (GL) against the bank's own value date on each side, flag when the incoming side's bank date exceeds the balance sheet date while the GL booking date does not.
  • Unsupported manual journal — filter Transactions where manual_entry = true and source = manual, then check for an offsetting/corroborating bank_import transaction or reconciliation-workpaper line; flag when none exists.
  • Restricted cash — filter Balances by classification containing "beperkt beschikbaar" or matching account descriptions like "G-rekening", then confirm against the BankConfirmation's blocked_accounts — both sides are structured once normalized, so this is a join, not an inference.
  • Lapping — graph/chain traversal over Receipt records: build a directed edge per row from customer to the customer who owns applied_to_invoice, and flag connected chains — this is deterministic graph logic over structured CSV data.
  • All 5 normal_checks — IBAN format validation (checksum algorithm), balance reconciliation arithmetic, confirmation-vs-register set comparison, negative-balance flagging, and date-window filtering are all deterministic computations over the normalized model.

Requires LLM-assisted extraction from unstructured/semi-structured content:

  • Bank confirmation letter extraction (Capability 5) — turning the DOCX's field/value content into the structured BankConfirmation record. In this training file the content happens to be table-formatted, which narrows the extraction task considerably, but a production confirmation letter is prose/free-form and does not guarantee that structure, so this step is designed as LLM-assisted field extraction (with the table-parse path as a fast/cheap first attempt) rather than a fixed column-mapping parser.
  • PDF bank statement parsing, where a source PDF is a scanned image rather than a text-layer PDF (not the case for either PDF in this training set, but a realistic production scenario) — OCR plus LLM-assisted line-item extraction to recover the same Transaction shape that a text-layer PDF parser would produce directly.

This split is a first-class design decision, not an implementation detail: the four ground-truth finding types (kiting, unsupported_manual_journal, restricted_cash, lapping) are all reproducible by deterministic rules once the common model exists, and v1 should implement them that way rather than reaching for LLM inference where it is not needed.

4. Output / reporting layer

Produces findings and check results in one structured, auditable format, directly modeled on 09_verwachte_controlebevindingen.json's shape:

  • Every finding carries finding_id, type, severity, source_files (the exact list of files/documents that produced the flag), a human-readable logic string explaining the pattern, and a expected_detection-shaped payload of the specific data points involved (amounts, dates, accounts, IDs) — see scope.md for the exact shape per finding type.
  • Every normal_check result carries the check name, a pass/exception/gap status, and the supporting data used to reach that status (not just a boolean).
  • Source traceability is mandatory, not optional: every emitted fact must be attributable back to the specific source file (and, where practical, the specific row/element/cell/page) it came from, carried through from the ingestion layer's source pointers via normalization. A finding that cannot cite its source documents is not a valid Athena output.
  • Every output is framed as "flagged for auditor review," never as a certified conclusion. This is a reporting-layer contract, not just a vision-doc statement: the output schema itself should make it structurally impossible to render a finding as a confirmed fact (e.g., no confirmed: true field, no severity level implying certainty beyond "flag priority").

Design considerations

  • Reproducibility/determinism. Wherever a finding is computable deterministically from structured data (all 4 ground-truth finding types plus all 5 normal_checks, as shown above), it must be implemented as deterministic rule logic, not LLM inference. Given identical input files, Athena must produce byte-identical findings on every run. This is what keeps a finding defensible in a professional-liability context — an auditor (or a regulator) can trace the exact rule and exact data points that produced a flag, with no model-sampling variance to explain. LLM-assisted extraction is reserved strictly for the unstructured-document steps identified above (bank confirmation letter content, and OCR'd/scanned bank statements), and even there, extracted structured output should be validated against expected field types/formats before being handed to the (deterministic) detection layer.
  • Clear separation between "flagged for review" and "certainty." This spans the whole pipeline, not just the output schema: detection modules compute matches against defined patterns, they do not compute or emit confidence scores framed as "likelihood of fraud" or similar; the vision.md non-goals (Athena does not certify, does not replace judgment) must be enforceable at the architecture level, primarily via the output schema constraint described above.
  • Source traceability as a pipeline-wide invariant, not just an output-layer feature — the source pointer captured at ingestion must survive normalization unchanged, since every ground-truth finding in this dataset cites the specific source_files that produced it, and the output layer cannot manufacture that traceability after the fact if it was dropped upstream.

Reacties

Nog geen reacties