Software Architecture Document#
Document ID |
DHF-004 |
|---|---|
Version |
1.0 |
Date |
2026-03-22 |
Author |
camt053 Engineering |
Status |
Released |
ISO 13485 Clause |
7.3.4 (Design and Development Review) |
1. Module Architecture#
The camt053 library is organized into focused packages, each with a single responsibility, all backed by one shared service facade:
Package |
Responsibility |
|---|---|
|
The single shared facade. Exposes the core capabilities as plain
functions returning plain data ( |
|
Namespace-agnostic statement parser ( |
|
Business mapping from a parsed statement entry to a flat reversing-entry
record ( |
|
Reversing-entry rendering via the bundled Jinja2 template
( |
|
BIC validation ( |
|
Path traversal protection ( |
|
FastAPI REST API application ( |
|
Click + Rich command-line interface ( |
|
The camt.053.001.14 reversal directory, containing the Jinja2 template
( |
|
JSON Schemas for the flat reversing-entry records of each supported
message type ( |
|
The official ISO 20022 XSD reference schemas for every supported message
and version (camt.052 / camt.053 / camt.054, |
Top-level modules:
__init__.py— Public API exports (services,parse_statement, the typed model, and__version__)__main__.py—main()entry point forpython -m camt053constants.py—valid_xml_typeslist,message_namesmap,STATEMENT_CONTAINERS,return_reason_names,REVERSAL_MESSAGE_TYPE,reverse_credit_debit(),BASE_DIR,SCHEMAS_DIR,TEMPLATES_DIRmodels.py— Typed dataclasses (Account,Balance,TransactionDetails,Entry,Statement,ParsedDocument)exceptions.py— Exception hierarchy rooted atCamt053Error
2. Data Flow#
The primary data flow is the one-shot reversing-entry workflow: inbound camt XML → typed model → reason filter → reversal builder → rendered + XSD-validated camt.053.001.14:
Inbound camt.05x statement XML
│
▼
services.generate_reversal(xml, reason_code="AC04")
│
├─▶ parse_document(xml) # parse/statement_parser.py
│ │
│ ├─▶ defusedxml.parse() # XXE / billion-laughs safe
│ ├─▶ local-name matching # namespace-agnostic (.001.01–.14)
│ └─▶ ParsedDocument # group header → statements →
│ # account, balances, entries →
│ # transaction details
│
├─▶ build_reversal_records(stmt, reason_code) # reversal/reversal.py
│ │
│ ├─▶ entries_with_reason() # filter by return reason code
│ ├─▶ reverse_credit_debit() # CRDT ⇄ DBIT
│ └─▶ flat records # RvslInd=true, refs, RtrInf,
│ # counterparty (RltdPties)
│
└─▶ generate_reversal_xml(records) # xml/generate_xml.py
│
├─▶ Environment(autoescape=True) # Jinja2 rendering
├─▶ template.render(...) # camt.053.001.14 XML
└─▶ validate_xml_string_via_xsd() # official ISO 20022 XSD
│
└─▶ Validated reversal document returned
The read-only paths (parse_statement, list_entries, filter_entries)
share the same parser and stop at the typed model, returning JSON-serialisable
data. build_reversal and generate expose the mapping and rendering
stages independently for callers that already hold records.
3. Parsing and Reversal Strategy#
3.1 Namespace-agnostic parsing#
Real-world statements arrive in many ISO versions, so the parser
(parse/statement_parser.py) matches elements by local name rather than
by a fixed namespace URI. The supported message is resolved from the document
container’s local name (constants.STATEMENT_CONTAINERS), and the exact
version is recovered from the namespace URI when present:
Container element |
Message type |
Name |
|---|---|---|
|
camt.052.001.14 |
Bank To Customer Account Report |
|
camt.053.001.14 |
Bank To Customer Statement |
|
camt.054.001.14 |
Bank To Customer Debit Credit Notification |
Each repeated Stmt / Rpt / Ntfctn becomes a Statement; each
Ntry becomes an Entry carrying its amount, credit/debit indicator,
status, dates, reversal indicator, and per-transaction TransactionDetails
(references, return reason, counterparty).
3.2 Return reason codes#
Entries are selected for reversal by ISO external return reason code. The codes
the library recognises are defined once in constants.return_reason_names and
surfaced through parse/reason_codes.py:
return_reason_names = {
"AC01": "Incorrect Account Number",
"AC04": "Closed Account Number", # headline / default case
"AC06": "Blocked Account",
"AG01": "Transaction Forbidden",
"AM04": "Insufficient Funds",
"MD07": "End Customer Deceased",
"RR01": "Missing Debtor Account Or Identification",
"RR04": "Regulatory Reason",
}
3.3 Reversing-entry mapping#
For each selected booked entry, reversal/reversal.py builds a flat record
that the camt.053.001.14 template renders. The mapping:
flips the credit/debit indicator (
reverse_credit_debit():CRDT⇄DBIT),sets
reversal_indicator(RvslInd) totrue,carries the original references (
InstrId/EndToEndId/TxId),records the return reason in
RtrInfwith a human-readableAddtlInf(viadescribe_reason()),preserves the counterparty (
RltdPties) and the source account context, and synthesises a closing booked balance where the source carries none.
Adding support for a future ISO version requires only adding the message-type
string to valid_xml_types (with its message_names and
STATEMENT_CONTAINERS entries) and bundling its official XSD under xsd/.
4. Exception Hierarchy#
Camt053Error (base)
├── StatementParseError
│ (empty/malformed/unrecognised statement XML; optional line)
├── AccountValidationError
│ ├── InvalidIBANError
│ │ (fields: message, iban, field, reason)
│ ├── InvalidBICError
│ │ (fields: message, bic, field, reason)
│ ├── InvalidLEIError
│ │ (fields: message, lei, field, reason)
│ └── MissingRequiredFieldError
│ (fields: message, field, row_number, required_fields)
├── XMLGenerationError
│ │ (Jinja2 rendering failures, XSD validation failures)
│ └── ReversalGenerationError
│ (no matching entry, or reversal fails XSD validation)
├── ConfigurationError
│ (invalid / unsupported message-type strings)
├── DataSourceError
│ (file not found, unsupported input)
└── SchemaValidationError (alias: XSDValidationError)
(fields: message, errors: list)
All exceptions inherit from Camt053Error to enable catch-all handling at
API and CLI boundaries.
5. Security Architecture#
5.1 XML External Entity (XXE) Prevention#
Module:
camt053/parse/statement_parser.py,camt053/xml/validate_via_xsd.pyControl: All XML parsing of untrusted bank files uses
defusedxml.ElementTreeinstead of the standard library’sxml.etree.ElementTreeProtection: Prevents XML bombs (billion-laughs), entity expansion attacks, and external entity injection
Requirement: NFR-101
5.2 Path Traversal Protection#
Module:
camt053/security/path_validator.pyControl:
validate_path(untrusted_path, must_exist, base_dir)resolves paths withos.path.realpath()and rejects any path containing..or resolving outside allowed directoriesAllowed directories: current working directory,
tempfile.gettempdir(),/var/tmp(Unix only)Requirement: NFR-102
5.3 Log Sanitization#
Module:
camt053/security/path_validator.pyControl:
sanitize_for_log(user_input, max_length=100)strips control characters (newlines, carriage returns, null bytes) and truncates input before log emissionRequirement: NFR-103
5.4 Template Injection Prevention#
Module:
camt053/xml/generate_xml.pyControl:
Environment(loader=FileSystemLoader(...), autoescape=True)Protection: All template variables are auto-escaped when rendering the reversal, preventing server-side template injection (SSTI)
Requirement: NFR-104
5.5 Container Security#
Module:
DockerfileControl: REST API runs as
appuser(non-root), slim base image, health check endpointRequirement: NFR-105
6. Interface Specifications#
6.1 Python API (services facade)#
from camt053 import services
# Headline one-shot workflow — returns validated camt.053.001.14 XML
services.generate_reversal(
xml: str, # incoming statement XML
reason_code: str = "AC04", # ISO external return reason
msg_id: str | None = None,
creation_date_time: str | None = None,
) -> str
# Read-only paths — return JSON-serialisable plain data
services.parse_statement(xml: str) -> dict
services.list_entries(xml: str) -> list[dict]
services.filter_entries(xml: str, reason_code: str = "AC04") -> list[dict]
# Records-in entry points and reference/validation helpers
services.build_reversal(xml, reason_code) -> list[dict]
services.generate(records: list[dict]) -> str
services.list_message_types() / services.list_return_reasons()
services.validate_identifier(kind, value) / services.validate_records(...)
6.2 CLI#
camt053 parse -i <statement.xml | ->
camt053 entries -i <statement.xml | -> [-r <reason>]
camt053 reverse -i <statement.xml | -> [-r <reason>] [-o <output>]
camt053 message-types
camt053 reasons
camt053 validate-id -k <iban|bic|lei> -v <value>
The parse, entries, and reverse commands accept -i - to read
from stdin, so they compose in a pipeline. Exit codes: 0 (success),
1 (parse / reversal / validation error).
6.3 REST API#
Method |
Endpoint |
Purpose |
|---|---|---|
GET |
|
Liveness probe |
GET |
|
List supported message types |
GET |
|
List known return reason codes |
GET |
|
Reversing-entry input JSON Schema |
GET |
|
Required reversing-entry fields |
GET |
|
Validate an IBAN, BIC, or LEI |
POST |
|
Validate flat reversing-entry records |
POST |
|
Parse an incoming statement into structured data |
POST |
|
List statement entries (optionally filtered by reason) |
POST |
|
Generate a reversing entry (returns |
6.4 Companion servers#
The camt053-mcp (Model Context Protocol) and camt053-lsp (Language
Server) packages call the same services facade, so the MCP tools and the
editor diagnostics behave identically to the CLI and REST API.
7. Design Decisions and Rationale#
Decision |
Choice |
Rationale |
|---|---|---|
Template engine |
Jinja2 |
Mature, well-documented, supports autoescape; separates the camt.053.001.14 XML structure from the reversal data |
XML parser |
defusedxml |
Drop-in replacement for stdlib with XXE protection; essential for parsing untrusted bank files |
Parsing strategy |
Namespace-agnostic, local-name matching |
Reads every ISO version ( |
Shared facade |
One |
CLI, REST API, MCP, and LSP all call the same backend, so every interface behaves identically |
Validation library |
xmlschema + jsonschema |
Official XSD / JSON Schema implementations; comprehensive error reporting |
CLI framework |
Click + Rich |
Declarative option syntax, automatic help text, readable tabular output |
REST framework |
FastAPI |
Automatic OpenAPI docs, Pydantic validation, type hints |
Typed model |
Dependency-free dataclasses |
Light, JSON-serialisable model that the parser returns and the reversal builder consumes; trivially testable |
Output validation |
Mandatory XSD check |
Every generated reversal is validated against the official ISO 20022 XSD before return; generation fails if validation fails |
Exception hierarchy |
Single base class |
Enables catch-all at boundaries while preserving specific error context |
Path security |
Allowlist directories |
Defense-in-depth; even if application logic is wrong, path jail prevents traversal |