# OpenPurse

OpenPurse is a lightweight, open-source Python package that parses and flattens deeply nested ISO 20022 XML financial messages into highly usable, flat Python dictionaries (which can be easily dumped to JSON).

## Features

- **Universal Support**: Dynamically determines the message format. It fully supports both XML-based ISO 20022 schemas and legacy block-based SWIFT MT formats (like MT103 and MT202).
- **Structured Schema**: Extracts all major variables into a standard Python `PaymentMessage` `@dataclass` (with robust `flatten()` dictionary dumps available as well).
- **Robust and Fast Parsing**: Built on top of `lxml` with robust error handling, regular expressions for non-XML data, and graceful degradation for missing optional fields.
- **Zero Bloat**: Only requires `lxml`. No heavy dependencies like pandas or pydantic are needed.

## Installation

You can install `openpurse` locally:

```bash
git clone https://github.com/yourusername/openpurse.git
cd openpurse
pip install .
```

To install development dependencies (for running tests):

```bash
pip install -e .[dev]
```

## Usage

You can extract standard standard fields strictly into a structured dataclass or loosely into a dictionary:

```python
from openpurse.parser import OpenPurseParser

# Your raw ISO 20022 XML data (or MT bytes)
xml_data = b'''<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08">
    <FIToFICstmrCdtTrf>
        <GrpHdr>
            <MsgId>MSG12345</MsgId>
            ...
        </GrpHdr>
        <CdtTrfTxInf>
            <IntrBkSttlmAmt Ccy="USD">1000.50</IntrBkSttlmAmt>
        </CdtTrfTxInf>
    </FIToFICstmrCdtTrf>
</Document>'''

# Initialize the parser
parser = OpenPurseParser(xml_data)

# 1. Parse into a structured PaymentMessage dataclass (Recommended)
msg_struct = parser.parse()
print(f"ID is {msg_struct.message_id} sending {msg_struct.amount} {msg_struct.currency}")

# 2. Flatten directly into a dictionary
flat_dict = parser.flatten()
print(flat_dict)
# Output:
# {
#     "message_id": "MSG12345",
#     "end_to_end_id": None,
#     "amount": "1000.50",
#     "currency": "USD",
#     "sender_bic": None,
#     "receiver_bic": None,
#     "debtor_name": None,
#     "creditor_name": None
# }

# Or parse legacy SWIFT MT formats without changing any logic!
mt_data = b'''{1:F01BANKUS33AXXX0000000000}{2:I103BANKGB22XXXXN}{4:
:20:MT103MSG
:32A:231024EUR50000,00
-}'''

# Output: {"message_id": "MT103MSG", "amount": "50000.00", "currency": "EUR"... }

# 3. Translate between MT and MX formats
from openpurse.translator import Translator

msg_struct = parser.parse()

# Convert to MT103 byte string
mt_bytes = Translator.to_mt(msg_struct, "103")

# Convert to ISO 20022 XML (e.g. pacs.008, camt.004)
mx_bytes = Translator.to_mx(msg_struct, "camt.004")
```

## Supported Fields

Whether you call `.parse()` (which yields a `PaymentMessage` dataclass instance) or `.flatten()` (which yields a `dict`), the parser standardizes the following fields across all schemas:

- `message_id`: GrpHdr/MsgId (XML) or Block 4 :20: (MT)
- `end_to_end_id`: EndToEndId (XML)
- `amount`: Extracted value preserving decimal notation
- `currency`: 3-Letter currency code
- `sender_bic`: InstgAgt/BICFI (XML) or Header Block 1 (MT)
- `receiver_bic`: InstdAgt/BICFI (XML) or Header Block 2 (MT)
- `debtor_name`: Dbtr/Nm (XML) or :50K: tags (MT)
- `creditor_name`: Cdtr/Nm (XML) or :59: tags (MT)

Missing or optional fields gracefully return `None`.

## Tests

Testing is done using `pytest`. Currently, coverage includes mock definitions for basic `pacs` and `camt` schemas, validating graceful degradation when schemas don't provide creditor/debtor names.

```bash
pytest tests/
```
