Skip to content

Commit

Permalink
Fb parsing and formatting: locale aware parsing (#126)
Browse files Browse the repository at this point in the history
* 1. Locale aware parsing of float numbers (#125) 2. Minor fixes

* Fix function typehint
  • Loading branch information
pinzutu authored Oct 7, 2024
1 parent b3e70bf commit ccf9586
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 28 deletions.
67 changes: 43 additions & 24 deletions pytr/event.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from babel.numbers import parse_decimal, NumberFormatError
from dataclasses import dataclass
from datetime import datetime
from enum import auto, Enum
Expand Down Expand Up @@ -138,12 +139,12 @@ def _parse_type_dependent_params(
if event_type is PPEventType.DIVIDEND:
isin = cls._parse_isin(event_dict)
taxes = cls._parse_taxes(event_dict)

elif isinstance(event_type, ConditionalEventType):
isin = cls._parse_isin(event_dict)
shares, fees = cls._parse_shares_and_fees(event_dict)
taxes = cls._parse_taxes(event_dict)

elif event_type is PPEventType.INTEREST:
taxes = cls._parse_taxes(event_dict)

Expand Down Expand Up @@ -176,15 +177,17 @@ def _parse_isin(event_dict: Dict[Any, Any]) -> str:
isin = isin2
return isin

@staticmethod
def _parse_shares_and_fees(event_dict: Dict[Any, Any]) -> Tuple[Optional[float]]:
@classmethod
def _parse_shares_and_fees(
cls, event_dict: Dict[Any, Any]
) -> Tuple[Optional[float]]:
"""Parses the amount of shares and the applicable fees
Args:
event_dict (Dict[Any, Any]): _description_
Returns:
Tuple[Optional[float]]: [shares, fees]
Tuple[Optional[float]]: shares, fees
"""
return_vals = {}
sections = event_dict.get("details", {}).get("sections", [{}])
Expand All @@ -196,27 +199,25 @@ def _parse_shares_and_fees(event_dict: Dict[Any, Any]) -> Tuple[Optional[float]]
)
fees_dicts = list(filter(lambda x: x["title"] == "Gebühr", data))
titles = ["shares"] * len(shares_dicts) + ["fees"] * len(fees_dicts)
for key, elem_dict in zip(titles, shares_dicts + fees_dicts):
elem_unparsed = elem_dict.get("detail", {}).get("text", "")
elem_parsed = re.sub("[^\,\.\d-]", "", elem_unparsed).replace(
",", "."
)
return_vals[key] = (
None
if elem_parsed == "" or float(elem_parsed) == 0.0
else float(elem_parsed)
)
locales = [
"en" if e["title"] == "Aktien" else "de"
for e in shares_dicts + fees_dicts
]
for key, elem_dict, locale in zip(
titles, shares_dicts + fees_dicts, locales
):
return_vals[key] = cls._parse_float_from_detail(elem_dict, locale)
return return_vals["shares"], return_vals["fees"]

@staticmethod
def _parse_taxes(event_dict: Dict[Any, Any]) -> Tuple[Optional[float]]:
@classmethod
def _parse_taxes(cls, event_dict: Dict[Any, Any]) -> Optional[float]:
"""Parses the levied taxes
Args:
event_dict (Dict[Any, Any]): _description_
Returns:
Tuple[Optional[float]]: [taxes]
Optional[float]: taxes
"""
# taxes keywords
taxes_keys = {"Steuer", "Steuern"}
Expand All @@ -232,12 +233,9 @@ def _parse_taxes(event_dict: Dict[Any, Any]) -> Tuple[Optional[float]]:
taxes_dicts = filter(lambda x: x["title"] in taxes_keys, data)
# Iterate over dicts containing tax information and parse each one
for taxes_dict in taxes_dicts:
unparsed_taxes_val = taxes_dict.get("detail", {}).get("text", "")
parsed_taxes_val = re.sub("[^\,\.\d-]", "", unparsed_taxes_val).replace(
",", "."
)
if parsed_taxes_val != "" and float(parsed_taxes_val) != 0.0:
return float(parsed_taxes_val)
parsed_taxes_val = cls._parse_float_from_detail(taxes_dict, "de")
if parsed_taxes_val is not None:
return parsed_taxes_val

@staticmethod
def _parse_card_note(event_dict: Dict[Any, Any]) -> Optional[str]:
Expand All @@ -251,3 +249,24 @@ def _parse_card_note(event_dict: Dict[Any, Any]) -> Optional[str]:
"""
if event_dict.get("eventType", "").startswith("card_"):
return event_dict["eventType"]

@staticmethod
def _parse_float_from_detail(
elem_dict: Dict[str, Any], locale: str
) -> Optional[float]:
"""Parses a "detail" dictionary potentially containing a float in a certain locale format
Args:
str (Dict[str, Any]): _description_
locale (str): _description_
Returns:
Optional[float]: parsed float value or None
"""
unparsed_val = elem_dict.get("detail", {}).get("text", "")
parsed_val = re.sub(r"[^\,\.\d-]", "", unparsed_val)
try:
parsed_val = float(parse_decimal(parsed_val, locale))
except NumberFormatError as e:
return None
return None if parsed_val == 0.0 else parsed_val
8 changes: 4 additions & 4 deletions pytr/event_formatter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from babel.numbers import format_decimal
from copy import deepcopy

from .event import Event, PPEventType, ConditionalEventType
from .translation import setup_translation
Expand Down Expand Up @@ -57,9 +56,10 @@ def format(self, event: Event) -> str:
kwargs["date"] = event.date.strftime("%Y-%m-%d")
if isinstance(event.event_type, PPEventType):
kwargs["type"] = self.translate(event.event_type.value)
kwargs["value"] = format_decimal(
event.value, locale=self.lang, decimal_quantization=True
)
if event.value is not None:
kwargs["value"] = format_decimal(
event.value, locale=self.lang, decimal_quantization=True
)
kwargs["note"] = (
self.translate(event.note) + " - " + event.title
if event.note is not None
Expand Down

0 comments on commit ccf9586

Please sign in to comment.