From 6e815e0b99e5d479989c8344dd0ae11f5c877499 Mon Sep 17 00:00:00 2001 From: Benjamin Dornel Date: Sun, 8 Sep 2024 17:51:16 +0800 Subject: [PATCH] feat(banks): add support for UOB --- src/monopoly/banks/__init__.py | 2 ++ src/monopoly/banks/uob/__init__.py | 3 ++ src/monopoly/banks/uob/uob.py | 49 +++++++++++++++++++++++++++++ src/monopoly/config.py | 6 ++-- src/monopoly/constants/statement.py | 17 ++++++++++ src/monopoly/generic/handler.py | 6 ++-- 6 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 src/monopoly/banks/uob/__init__.py create mode 100644 src/monopoly/banks/uob/uob.py diff --git a/src/monopoly/banks/__init__.py b/src/monopoly/banks/__init__.py index b4abbbf3..068862ab 100644 --- a/src/monopoly/banks/__init__.py +++ b/src/monopoly/banks/__init__.py @@ -10,6 +10,7 @@ from .maybank import Maybank from .ocbc import Ocbc from .standard_chartered import StandardChartered +from .uob import Uob banks: list[Type["BankBase"]] = [ Citibank, @@ -19,6 +20,7 @@ Maybank, Ocbc, StandardChartered, + Uob, ] logger = logging.getLogger(__name__) diff --git a/src/monopoly/banks/uob/__init__.py b/src/monopoly/banks/uob/__init__.py new file mode 100644 index 00000000..7af2f765 --- /dev/null +++ b/src/monopoly/banks/uob/__init__.py @@ -0,0 +1,3 @@ +from .uob import Uob + +__all__ = ["Uob"] diff --git a/src/monopoly/banks/uob/uob.py b/src/monopoly/banks/uob/uob.py new file mode 100644 index 00000000..26164dcc --- /dev/null +++ b/src/monopoly/banks/uob/uob.py @@ -0,0 +1,49 @@ +import logging +from re import compile as regex + +from monopoly.config import StatementConfig +from monopoly.constants import ( + BankNames, + DebitTransactionPatterns, + EntryType, + StatementBalancePatterns, +) +from monopoly.constants.date import ISO8601 +from monopoly.identifiers import MetadataIdentifier + +from ..base import BankBase + +logger = logging.getLogger(__name__) + + +class Uob(BankBase): + credit_config = StatementConfig( + statement_type=EntryType.CREDIT, + bank_name=BankNames.UOB, + statement_date_pattern=regex(rf"Statement Date.*{ISO8601.DD_MMM_YYYY}"), + header_pattern=regex(r"(Description of Transaction.*Transaction Amount)"), + prev_balance_pattern=StatementBalancePatterns.UOB, + transaction_pattern=DebitTransactionPatterns.UOB, + multiline_transactions=True, + ) + + debit_config = StatementConfig( + statement_type=EntryType.DEBIT, + bank_name=BankNames.UOB, + statement_date_pattern=regex(rf"Period: .* to {ISO8601.DD_MMM_YYYY}"), + header_pattern=regex(r"(Date.*Description.*Withdrawals.*Deposits.*Balance)"), + transaction_pattern=DebitTransactionPatterns.UOB, + transaction_bound=170, + multiline_transactions=True, + ) + + identifiers = [ + [ + MetadataIdentifier( + format="PDF 1.5", + creator="Vault Rendering Engine", + producer="Rendering Engine", + ), + ] + ] + statement_configs = [credit_config, debit_config] diff --git a/src/monopoly/config.py b/src/monopoly/config.py index 11e109a9..e993addb 100644 --- a/src/monopoly/config.py +++ b/src/monopoly/config.py @@ -44,9 +44,9 @@ class StatementConfig: - `header_pattern` is a regex pattern that is used to find the 'header' line of a statement, and determine if it is a debit or credit card statement. - `transaction_bound` will cause transactions that have an amount past a certain - number of spaces will be ignored. For example, if `transaction_bound` = 5: - "01 NOV BALANCE B/F 190.77" (will be ignored) - "01 NOV YA KUN KAYA TOAST 12.00" (will be kept) + number of spaces will be ignored. For example, if `transaction_bound` = 32: + "01 NOV BALANCE B/F 190.77" (will be ignored) + "01 NOV YA KUN KAYA TOAST 12.00 " (will be kept) """ bank_name: BankNames | InternalBankNames diff --git a/src/monopoly/constants/statement.py b/src/monopoly/constants/statement.py index a9324af7..4951af1b 100644 --- a/src/monopoly/constants/statement.py +++ b/src/monopoly/constants/statement.py @@ -21,6 +21,7 @@ class BankNames(AutoEnum): MAYBANK = auto() OCBC = auto() STANDARD_CHARTERED = auto() + UOB = auto() class InternalBankNames(AutoEnum): @@ -89,6 +90,10 @@ class StatementBalancePatterns(RegexEnum): r"(?PBALANCE FROM PREVIOUS STATEMENT?)\s+" + SharedPatterns.AMOUNT_EXTENDED_WITHOUT_EOL ) + UOB = ( + r"(?PPREVIOUS BALANCE?)\s+" + + SharedPatterns.AMOUNT_EXTENDED_WITHOUT_EOL + ) class CreditTransactionPatterns(RegexEnum): @@ -126,6 +131,12 @@ class CreditTransactionPatterns(RegexEnum): + r"(?:(?PTransaction\sRef\s\d+)?)\s+" + SharedPatterns.AMOUNT_EXTENDED ) + UOB = ( + rf"(?P{ISO8601.DD_MMM})\s+" + rf"(?P{ISO8601.DD_MMM})\s+" + + SharedPatterns.DESCRIPTION + + SharedPatterns.AMOUNT_EXTENDED + ) class DebitTransactionPatterns(RegexEnum): @@ -150,3 +161,9 @@ class DebitTransactionPatterns(RegexEnum): + SharedPatterns.AMOUNT_EXTENDED_WITHOUT_EOL + SharedPatterns.BALANCE ) + UOB = ( + rf"(?P{ISO8601.DD_MMM})\s+" + + SharedPatterns.DESCRIPTION + + SharedPatterns.AMOUNT_EXTENDED_WITHOUT_EOL + + SharedPatterns.BALANCE + ) diff --git a/src/monopoly/generic/handler.py b/src/monopoly/generic/handler.py index 3f943e61..080cf892 100644 --- a/src/monopoly/generic/handler.py +++ b/src/monopoly/generic/handler.py @@ -14,14 +14,14 @@ class GenericBank(BankBase): - identifiers = [] - statement_configs = None - """ Empty bank class with variables that can be populated by the `GenericStatementHandler` class """ + identifiers = [] + statement_configs = None + class GenericStatementHandler(StatementHandler): def __init__(self, bank: Type[BankBase], pages: list[PdfPage]):