1
+ import pprint
1
2
import re
2
3
from dataclasses import dataclass
3
4
from datetime import datetime
6
7
7
8
from babel .numbers import NumberFormatError , parse_decimal
8
9
9
- from pytr .utils import get_logger
10
+ from pytr .utils import dump , dump_enabled , get_logger
10
11
11
12
12
13
class EventType (Enum ):
@@ -145,7 +146,9 @@ def _parse_type(event_dict: Dict[Any, Any]) -> Optional[EventType]:
145
146
event_type = None
146
147
else :
147
148
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 )} " )
149
152
return event_type
150
153
151
154
@classmethod
@@ -215,17 +218,37 @@ def _parse_shares_and_fees(cls, event_dict: Dict[Any, Any]) -> Tuple[Optional[fl
215
218
Returns:
216
219
Tuple[Optional[float]]: shares, fees
217
220
"""
218
- return_vals = {}
221
+ shares , fees = None , None
222
+ dump_dict = {"eventType" : event_dict ["eventType" ], "id" : event_dict ["id" ]}
223
+
219
224
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
229
252
230
253
@classmethod
231
254
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]:
237
260
Returns:
238
261
Optional[float]: taxes
239
262
"""
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
+
243
267
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 )
246
269
for transaction_dict in transaction_dicts :
247
270
# Filter for taxes dicts
271
+ dump_dict ["maintitle" ] = transaction_dict ["title" ]
248
272
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 )
250
274
# Iterate over dicts containing tax information and parse each one
251
275
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
255
281
256
- return None
282
+ return parsed_taxes_val
257
283
258
284
@staticmethod
259
285
def _parse_card_note (event_dict : Dict [Any , Any ]) -> Optional [str ]:
@@ -270,7 +296,7 @@ def _parse_card_note(event_dict: Dict[Any, Any]) -> Optional[str]:
270
296
return None
271
297
272
298
@staticmethod
273
- def _parse_float_from_detail (elem_dict : Dict [str , Any ]) -> Optional [float ]:
299
+ def _parse_float_from_detail (elem_dict : Dict [str , Any ], dump_dict , pref_locale = "de" ) -> Optional [float ]:
274
300
"""Parses a "detail" dictionary potentially containing a float in a certain locale format
275
301
276
302
Args:
@@ -284,8 +310,11 @@ def _parse_float_from_detail(elem_dict: Dict[str, Any]) -> Optional[float]:
284
310
return None
285
311
parsed_val = re .sub (r"[^\,\.\d-]" , "" , unparsed_val )
286
312
287
- # Prefer german locale.
288
- locales = ("de" , "en" )
313
+ # Try the preferred locale first
314
+ if pref_locale == "de" :
315
+ locales = ("de" , "en" )
316
+ else :
317
+ locales = ("en" , "de" )
289
318
290
319
try :
291
320
result = float (parse_decimal (parsed_val , locales [0 ], strict = True ))
@@ -294,4 +323,32 @@ def _parse_float_from_detail(elem_dict: Dict[str, Any]) -> Optional[float]:
294
323
result = float (parse_decimal (parsed_val , locales [1 ], strict = True ))
295
324
except NumberFormatError :
296
325
return None
326
+ log .warning (
327
+ f"Number { parsed_val } parsed as { locales [1 ]} although preference was { locales [0 ]} . ({ dump_dict ['eventType' ]} , { dump_dict ['id' ]} , { dump_dict ['type' ]} )"
328
+ )
329
+ if dump_enabled ():
330
+ dump (
331
+ f"Number { parsed_val } parsed as as { locales [1 ]} although preference was { locales [0 ]} : { pprint .pformat (dump_dict , indent = 4 )} "
332
+ )
333
+ return None if result == 0.0 else result
334
+
335
+ alternative_result = None
336
+ if "," in parsed_val or "." in parsed_val :
337
+ try :
338
+ alternative_result = float (parse_decimal (parsed_val , locales [1 ], strict = True ))
339
+ except NumberFormatError :
340
+ pass
341
+
342
+ if alternative_result is None :
343
+ if dump_enabled ():
344
+ dump (f"Number { parsed_val } parsed as { locales [0 ]} : { pprint .pformat (dump_dict , indent = 4 )} " )
345
+ else :
346
+ log .debug (
347
+ f"Number { parsed_val } as { locales [0 ]} but could also be parsed as { locales [1 ]} . ({ dump_dict ['eventType' ]} , { dump_dict ['id' ]} , { dump_dict ['type' ]} )"
348
+ )
349
+ if dump_enabled ():
350
+ dump (
351
+ f"Number { parsed_val } as { locales [0 ]} but could also be parsed as { locales [1 ]} : { pprint .pformat (dump_dict , indent = 4 )} "
352
+ )
353
+
297
354
return None if result == 0.0 else result
0 commit comments