Skip to content

Commit

Permalink
feat(banks): add support for zurcherkantonalbank
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamin-awd committed Sep 8, 2024
1 parent 64e61ff commit da4a221
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/monopoly/banks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .ocbc import Ocbc
from .standard_chartered import StandardChartered
from .uob import Uob
from .zurcherkantonalbank import ZurcherKantonalBank

banks: list[Type["BankBase"]] = [
Citibank,
Expand All @@ -21,6 +22,7 @@
Ocbc,
StandardChartered,
Uob,
ZurcherKantonalBank,
]

logger = logging.getLogger(__name__)
Expand Down
3 changes: 3 additions & 0 deletions src/monopoly/banks/zurcherkantonalbank/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .zurcherkantonalbank import ZurcherKantonalBank

__all__ = ["ZurcherKantonalBank"]
34 changes: 34 additions & 0 deletions src/monopoly/banks/zurcherkantonalbank/zurcherkantonalbank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import logging
from re import compile as regex

from monopoly.config import StatementConfig
from monopoly.constants import BankNames, DebitTransactionPatterns, EntryType
from monopoly.constants.date import ISO8601
from monopoly.identifiers import MetadataIdentifier

from ..base import BankBase

logger = logging.getLogger(__name__)


class ZurcherKantonalBank(BankBase):
debit_config = StatementConfig(
statement_type=EntryType.DEBIT,
bank_name=BankNames.ZKB,
statement_date_pattern=regex(rf"Balance as of: ({ISO8601.DD_MM_YYYY})"),
header_pattern=regex(r"(Date.*Booking text.*Debit CHF.*Credit CHF)"),
transaction_pattern=DebitTransactionPatterns.ZurcherKantonalBank,
multiline_transactions=True,
)

identifiers = [
[
MetadataIdentifier(
format="PDF 1.7",
title="SLK_Vermoegensinfo_Group",
creator="Designer",
producer="PDFlib+PDI",
)
]
]
statement_configs = [debit_config]
3 changes: 3 additions & 0 deletions src/monopoly/constants/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class DateFormats(StrEnum):
class ISO8601(RegexEnum):
DD_MM = rf"\b({DateFormats.DD}[\/\-\s]{DateFormats.MM})"
DD_MM_YY = rf"\b({DateFormats.DD}[\/\-\s]{DateFormats.MM}[\/\-\s]{DateFormats.YY})"
DD_MM_YYYY = (
rf"\b({DateFormats.DD}[\/\-\s.]{DateFormats.MM}[\/\-\s.]{DateFormats.YYYY})"
)
DD_MMM = rf"\b({DateFormats.DD}[-\s]{DateFormats.MMM})"
DD_MMM_RELAXED = DD_MMM.replace(r"[-\s]", r"(?:[-\s]|)")
DD_MMM_YY = rf"\b({DateFormats.DD}[-\s]{DateFormats.MMM}[-\s]{DateFormats.YY})"
Expand Down
8 changes: 8 additions & 0 deletions src/monopoly/constants/statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class BankNames(AutoEnum):
OCBC = auto()
STANDARD_CHARTERED = auto()
UOB = auto()
ZKB = auto()


class InternalBankNames(AutoEnum):
Expand Down Expand Up @@ -167,3 +168,10 @@ class DebitTransactionPatterns(RegexEnum):
+ SharedPatterns.AMOUNT_EXTENDED_WITHOUT_EOL
+ SharedPatterns.BALANCE
)
ZurcherKantonalBank = (
rf"(?P<transaction_date>{ISO8601.DD_MM_YYYY})\s+"
+ SharedPatterns.DESCRIPTION
+ r"(?P<amount>\d{1,3}(\'\d{3})*(\.\d+)?)\s+"
+ rf"(?P<value_date>{ISO8601.DD_MM_YYYY})\s+"
+ SharedPatterns.BALANCE
)
3 changes: 2 additions & 1 deletion src/monopoly/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ def convert_date(transaction: Transaction, transaction_date_order: DateOrder):
e.g. if the statement month is Jan/Feb 2024, transactions from
Oct/Nov/Dec should be attributed to the previous year.
"""
transaction.date += f" {statement_date.year}"
if str(statement_date.year) not in transaction.date:
transaction.date += f" {statement_date.year}"
parsed_date = parse(
transaction.date,
settings=transaction_date_order.settings,
Expand Down
15 changes: 12 additions & 3 deletions src/monopoly/statements/debit_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,27 @@ def get_debit_suffix(self, transaction_match: TransactionMatch) -> str | None:

@lru_cache
def get_withdrawal_pos(self, page_number: int) -> int | None:
return self.get_column_pos("withdraw", page_number=page_number)
common_names = ["withdraw", "debit"]
for name in common_names:
if pos := self.get_column_pos(name, page_number=page_number):
return pos
logger.warning("`withdrawal` column not found in header")
return False

@lru_cache
def get_deposit_pos(self, page_number: int) -> int | None:
return self.get_column_pos("deposit", page_number=page_number)
common_names = ["deposit", "credit"]
for name in common_names:
if pos := self.get_column_pos(name, page_number=page_number):
return pos
logger.warning("`deposit` column not found in header")
return False

@lru_cache
def get_column_pos(self, column_type: str, page_number: int) -> int | None:
pattern = re.compile(rf"{column_type}[\w()$]*", re.IGNORECASE)
if match := pattern.search(self.header):
return self.get_header_pos(match.group(), page_number)
logger.warning(f"`{column_type}` column not found in header")
return None

@lru_cache
Expand Down
6 changes: 4 additions & 2 deletions src/monopoly/statements/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,16 @@ def remove_extra_whitespace(cls, value: str) -> str:
@field_validator(Columns.AMOUNT, mode="before")
def prepare_amount_for_float_coercion(cls, amount: str) -> str:
"""
Replaces commas, whitespaces and parentheses in string representation of floats
Replaces commas, whitespaces, apostrophes and parentheses in string
representation of floats
e.g.
1'234.00 -> 1234.00
1,234.00 -> 1234.00
(-10.00) -> -10.00
(-1.56 ) -> -1.56
"""
if isinstance(amount, str):
return re.sub(r"[,)(\s]", "", amount)
return re.sub(r"[,)(\s']", "", amount)
return amount

# pylint: disable=bad-classmethod-argument
Expand Down

0 comments on commit da4a221

Please sign in to comment.