Skip to content

Commit fbd11ba

Browse files
committed
Improve locale selection for number parsing and add possibility to dump data
1 parent a77e2db commit fbd11ba

File tree

3 files changed

+118
-26
lines changed

3 files changed

+118
-26
lines changed

pytr/event.py

+86-25
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pprint
12
import re
23
from dataclasses import dataclass
34
from datetime import datetime
@@ -6,7 +7,7 @@
67

78
from babel.numbers import NumberFormatError, parse_decimal
89

9-
from pytr.utils import get_logger
10+
from pytr.utils import dump, dump_enabled, get_logger
1011

1112

1213
class EventType(Enum):
@@ -145,7 +146,9 @@ def _parse_type(event_dict: Dict[Any, Any]) -> Optional[EventType]:
145146
event_type = None
146147
else:
147148
if eventTypeStr not in events_known_ignored:
148-
log.warning(f"Ignoring event {eventTypeStr}")
149+
log.warning(f"Ignoring unknown event {eventTypeStr}")
150+
if dump_enabled():
151+
dump(f"Unknown event {eventTypeStr}: {pprint.pformat(event_dict, indent=4)}")
149152
return event_type
150153

151154
@classmethod
@@ -215,17 +218,37 @@ def _parse_shares_and_fees(cls, event_dict: Dict[Any, Any]) -> Tuple[Optional[fl
215218
Returns:
216219
Tuple[Optional[float]]: shares, fees
217220
"""
218-
return_vals = {}
221+
shares, fees = None, None
222+
dump_dict = {"eventType": event_dict["eventType"], "id": event_dict["id"]}
223+
219224
sections = event_dict.get("details", {}).get("sections", [{}])
220-
for section in sections:
221-
if section.get("title") == "Transaktion":
222-
data = section["data"]
223-
shares_dicts = list(filter(lambda x: x["title"] in ["Aktien", "Anteile"], data))
224-
fees_dicts = list(filter(lambda x: x["title"] == "Gebühr", data))
225-
titles = ["shares"] * len(shares_dicts) + ["fees"] * len(fees_dicts)
226-
for key, elem_dict in zip(titles, shares_dicts + fees_dicts):
227-
return_vals[key] = cls._parse_float_from_detail(elem_dict)
228-
return return_vals.get("shares"), return_vals.get("fees")
225+
transaction_dicts = filter(lambda x: x["title"] in ["Transaktion"], sections)
226+
for transaction_dict in transaction_dicts:
227+
dump_dict["maintitle"] = transaction_dict["title"]
228+
data = transaction_dict.get("data", [{}])
229+
shares_dicts = filter(lambda x: x["title"] in ["Aktien", "Anteile"], data)
230+
for shares_dict in shares_dicts:
231+
dump_dict["subtitle"] = shares_dict["title"]
232+
dump_dict["type"] = "shares"
233+
pref_locale = (
234+
"en"
235+
if event_dict["eventType"] in ["benefits_saveback_execution", "benefits_spare_change_execution"]
236+
and shares_dict["title"] == "Aktien"
237+
else "de"
238+
)
239+
shares = cls._parse_float_from_detail(shares_dict, dump_dict, pref_locale)
240+
break
241+
242+
fees_dicts = filter(lambda x: x["title"] == "Gebühr", data)
243+
for fees_dict in fees_dicts:
244+
dump_dict["subtitle"] = fees_dict["title"]
245+
dump_dict["type"] = "fees"
246+
fees = cls._parse_float_from_detail(fees_dict, dump_dict)
247+
break
248+
249+
break
250+
251+
return shares, fees
229252

230253
@classmethod
231254
def _parse_taxes(cls, event_dict: Dict[Any, Any]) -> Optional[float]:
@@ -237,23 +260,26 @@ def _parse_taxes(cls, event_dict: Dict[Any, Any]) -> Optional[float]:
237260
Returns:
238261
Optional[float]: taxes
239262
"""
240-
# taxes keywords
241-
taxes_keys = {"Steuer", "Steuern"}
242-
# Gather all section dicts
263+
parsed_taxes_val = None
264+
dump_dict = {"eventType": event_dict["eventType"], "id": event_dict["id"]}
265+
pref_locale = "en" if event_dict["eventType"] in ["INTEREST_PAYOUT", "trading_savingsplan_executed"] else "de"
266+
243267
sections = event_dict.get("details", {}).get("sections", [{}])
244-
# Gather all dicts pertaining to transactions
245-
transaction_dicts = filter(lambda x: x["title"] in {"Transaktion", "Geschäft"}, sections)
268+
transaction_dicts = filter(lambda x: x["title"] in ["Transaktion", "Geschäft"], sections)
246269
for transaction_dict in transaction_dicts:
247270
# Filter for taxes dicts
271+
dump_dict["maintitle"] = transaction_dict["title"]
248272
data = transaction_dict.get("data", [{}])
249-
taxes_dicts = filter(lambda x: x["title"] in taxes_keys, data)
273+
taxes_dicts = filter(lambda x: x["title"] in ["Steuer", "Steuern"], data)
250274
# Iterate over dicts containing tax information and parse each one
251275
for taxes_dict in taxes_dicts:
252-
parsed_taxes_val = cls._parse_float_from_detail(taxes_dict)
253-
if parsed_taxes_val is not None:
254-
return parsed_taxes_val
276+
dump_dict["subtitle"] = taxes_dict["title"]
277+
dump_dict["type"] = "taxes"
278+
parsed_taxes_val = cls._parse_float_from_detail(taxes_dict, dump_dict, pref_locale)
279+
break
280+
break
255281

256-
return None
282+
return parsed_taxes_val
257283

258284
@staticmethod
259285
def _parse_card_note(event_dict: Dict[Any, Any]) -> Optional[str]:
@@ -270,7 +296,11 @@ def _parse_card_note(event_dict: Dict[Any, Any]) -> Optional[str]:
270296
return None
271297

272298
@staticmethod
273-
def _parse_float_from_detail(elem_dict: Dict[str, Any]) -> Optional[float]:
299+
def _parse_float_from_detail(
300+
elem_dict: Dict[str, Any],
301+
dump_dict={"eventType": "Unknown", "id": "Unknown", "type": "Unknown"},
302+
pref_locale="de",
303+
) -> Optional[float]:
274304
"""Parses a "detail" dictionary potentially containing a float in a certain locale format
275305
276306
Args:
@@ -284,8 +314,11 @@ def _parse_float_from_detail(elem_dict: Dict[str, Any]) -> Optional[float]:
284314
return None
285315
parsed_val = re.sub(r"[^\,\.\d-]", "", unparsed_val)
286316

287-
# Prefer german locale.
288-
locales = ("de", "en")
317+
# Try the preferred locale first
318+
if pref_locale == "de":
319+
locales = ("de", "en")
320+
else:
321+
locales = ("en", "de")
289322

290323
try:
291324
result = float(parse_decimal(parsed_val, locales[0], strict=True))
@@ -294,4 +327,32 @@ def _parse_float_from_detail(elem_dict: Dict[str, Any]) -> Optional[float]:
294327
result = float(parse_decimal(parsed_val, locales[1], strict=True))
295328
except NumberFormatError:
296329
return None
330+
log.warning(
331+
f"Number {parsed_val} parsed as {locales[1]} although preference was {locales[0]}. ({dump_dict['eventType']}, {dump_dict['id']}, {dump_dict['type']})"
332+
)
333+
if dump_enabled():
334+
dump(
335+
f"Number {parsed_val} parsed as as {locales[1]} although preference was {locales[0]}: {pprint.pformat(dump_dict, indent=4)}"
336+
)
337+
return None if result == 0.0 else result
338+
339+
alternative_result = None
340+
if "," in parsed_val or "." in parsed_val:
341+
try:
342+
alternative_result = float(parse_decimal(parsed_val, locales[1], strict=True))
343+
except NumberFormatError:
344+
pass
345+
346+
if alternative_result is None:
347+
if dump_enabled():
348+
dump(f"Number {parsed_val} parsed as {locales[0]}: {pprint.pformat(dump_dict, indent=4)}")
349+
else:
350+
log.debug(
351+
f"Number {parsed_val} as {locales[0]} but could also be parsed as {locales[1]}. ({dump_dict['eventType']}, {dump_dict['id']}, {dump_dict['type']})"
352+
)
353+
if dump_enabled():
354+
dump(
355+
f"Number {parsed_val} as {locales[0]} but could also be parsed as {locales[1]}: {pprint.pformat(dump_dict, indent=4)}"
356+
)
357+
297358
return None if result == 0.0 else result

pytr/main.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from pytr.dl import DL
1616
from pytr.portfolio import Portfolio
1717
from pytr.transactions import export_transactions
18-
from pytr.utils import check_version, get_logger
18+
from pytr.utils import check_version, enable_debug_dump, get_logger
1919

2020

2121
def get_main_parser():
@@ -45,6 +45,14 @@ def formatter(prog):
4545
help="Print version information and quit",
4646
action="store_true",
4747
)
48+
parser.add_argument(
49+
"--dump-debug-data-to-file",
50+
help="Dump debug data to a given file",
51+
metavar="DEBUG_DATA_FILE",
52+
type=Path,
53+
default=None,
54+
)
55+
4856
parser_cmd = parser.add_subparsers(help="Desired action to perform", dest="command")
4957

5058
# help
@@ -238,6 +246,7 @@ def main():
238246
args = parser.parse_args()
239247
# print(vars(args))
240248

249+
enable_debug_dump(args.dump_debug_data_to_file)
241250
log = get_logger(__name__, args.verbosity)
242251
log.setLevel(args.verbosity.upper())
243252
log.debug("logging is set to debug")

pytr/utils.py

+22
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@
77
import requests
88
from packaging import version
99

10+
dump_file = None
11+
12+
13+
def enable_debug_dump(df):
14+
global dump_file
15+
dump_file = df
16+
if df is not None:
17+
with open(dump_file, "w"):
18+
pass
19+
20+
21+
def dump_enabled():
22+
return dump_file is not None
23+
24+
25+
def dump(str):
26+
if dump_file is None:
27+
return
28+
with open(dump_file, "a") as file:
29+
file.write(str + "\n")
30+
31+
1032
log_level = None
1133

1234

0 commit comments

Comments
 (0)