tariochbctools 1.4.1__py2.py3-none-any.whl → 1.5.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ from typing import Any
5
5
 
6
6
  import yaml
7
7
  from awardwallet import AwardWalletClient
8
- from awardwallet.api import AccessLevel
8
+ from awardwallet.client import AccessLevel
9
9
 
10
10
 
11
11
  def get_link_url(client):
@@ -33,19 +33,18 @@ def generate(client):
33
33
  connected_users = client.list_connected_users()
34
34
 
35
35
  for user in connected_users:
36
- user_id = user["userId"]
37
- user_details = client.get_connected_user_details(user_id)
36
+ user_details = client.get_connected_user_details(user.user_id)
38
37
  account_config = {}
39
38
 
40
- for account in user_details.get("accounts", []):
41
- account_config[account["accountId"]] = {
42
- "provider": account["displayName"],
39
+ for account in user_details.accounts:
40
+ account_config[account.account_id] = {
41
+ "provider": account.display_name,
43
42
  "account": "Assets:Current:Points", # Placeholder, user should adjust
44
43
  "currency": "POINTS",
45
44
  }
46
45
 
47
- config["users"][user_id] = {
48
- "name": user["userName"],
46
+ config["users"][user.user_id] = {
47
+ "name": user.user_name,
49
48
  "all_history": False,
50
49
  "accounts": account_config,
51
50
  }
@@ -1,11 +1,13 @@
1
+ import datetime
1
2
  import logging
3
+ import re
4
+ from operator import attrgetter
2
5
  from os import path
3
- from typing import Any
4
6
 
5
7
  import beangulp
6
8
  import dateutil.parser
7
9
  import yaml
8
- from awardwallet import AwardWalletClient
10
+ from awardwallet import AwardWalletClient, model
9
11
  from beancount.core import amount, data
10
12
  from beancount.core.number import D
11
13
 
@@ -46,27 +48,33 @@ class Importer(beangulp.Importer):
46
48
  return entries
47
49
 
48
50
  def _extract_user_history(
49
- self, user: dict, user_details: dict
51
+ self, user: dict, user_details: model.GetConnectedUserDetailsResponse
50
52
  ) -> list[data.Transaction]:
51
53
  """
52
54
  User history is limited to the last 10 history elements per account
53
55
  """
54
56
  entries = []
55
- for account in user_details["accounts"]:
56
- account_id = account["accountId"]
57
-
58
- if account_id in user["accounts"]:
59
- logging.info("Extracting account ID %s", account_id)
60
- account_config = user["accounts"][account_id]
57
+ for account in user_details.accounts:
58
+ if account.account_id in user["accounts"]:
59
+ logging.info("Extracting account ID %s", account.account_id)
60
+ account_config = user["accounts"][account.account_id]
61
61
 
62
62
  entries.extend(
63
63
  self._extract_transactions(
64
- account["history"], account_config, account_id
64
+ account.history, account_config, account.account_id
65
65
  )
66
66
  )
67
+
68
+ # we fudge the date by using the latest txn (across *all* accounts)
69
+ latest_txn = max(entries, key=attrgetter("date"), default=None)
70
+ entries.extend(
71
+ self._extract_balance(account, account_config, latest_txn)
72
+ )
67
73
  else:
68
74
  logging.warning(
69
- "Ignoring account ID %s: %s", account_id, account["displayName"]
75
+ "Ignoring account ID %s: %s",
76
+ account.account_id,
77
+ account.display_name,
70
78
  )
71
79
  return entries
72
80
 
@@ -76,13 +84,15 @@ class Importer(beangulp.Importer):
76
84
  entries = []
77
85
  for account_id, account_config in user["accounts"].items():
78
86
  logging.info("Extracting account ID %s", account_id)
79
- account = client.get_account_details(account_id)["account"]
87
+ account = client.get_account_details(account_id).account
80
88
 
81
89
  entries.extend(
82
- self._extract_transactions(
83
- account["history"], account_config, account_id
84
- )
90
+ self._extract_transactions(account.history, account_config, account_id)
85
91
  )
92
+
93
+ # we fudge the date by using the latest txn (across *all* accounts)
94
+ latest_txn = max(entries, key=attrgetter("date"), default=None)
95
+ entries.extend(self._extract_balance(account, account_config, latest_txn))
86
96
  return entries
87
97
 
88
98
  def _extract_transactions(
@@ -109,7 +119,7 @@ class Importer(beangulp.Importer):
109
119
 
110
120
  def _extract_transaction(
111
121
  self,
112
- trx: dict[str, Any],
122
+ trx: model.HistoryItem,
113
123
  local_account: data.Account,
114
124
  currency: str,
115
125
  account_id: str,
@@ -121,20 +131,23 @@ class Importer(beangulp.Importer):
121
131
  trx_description = None
122
132
  trx_amount = None
123
133
 
124
- for f in trx.get("fields", []):
125
- if f["code"] == "PostingDate":
126
- trx_date = dateutil.parser.parse(f["value"]["value"]).date()
127
- if f["code"] == "Description":
128
- trx_description = f["value"]["value"].replace("\n", " ")
129
- if f["code"] == "Miles":
130
- trx_amount = D(f["value"]["value"])
131
- if f["code"] == "Info":
132
- name = f["name"].lower().replace(" ", "-")
133
- metakv[name] = f["value"]["value"].replace("\n", " ")
134
+ for f in trx.fields:
135
+ if f.code == "PostingDate":
136
+ trx_date = dateutil.parser.parse(f.value.value).date()
137
+ if f.code == "Description":
138
+ trx_description = f.value.value.replace("\n", " ")
139
+ if f.code == "Miles":
140
+ trx_amount = D(f.value.value)
141
+ if f.code == "Info":
142
+ name = re.sub(r"\W", "-", f.name).lower()
143
+ metakv[name] = f.value.value.replace("\n", " ")
134
144
 
135
145
  assert trx_date
136
146
  assert trx_description
137
- assert trx_amount is not None, f"No amount in trx: {trx}"
147
+
148
+ if trx_amount is None:
149
+ logging.warning("Skipping transaction with no amount: %s", trx)
150
+ return []
138
151
 
139
152
  meta = data.new_metadata("", 0, metakv)
140
153
  entry = data.Transaction(
@@ -158,3 +171,36 @@ class Importer(beangulp.Importer):
158
171
  )
159
172
  entries.append(entry)
160
173
  return entries
174
+
175
+ def _extract_balance(
176
+ self,
177
+ account: model.Account,
178
+ account_config: dict,
179
+ latest_txn: data.Transaction | None,
180
+ ) -> list[data.Transaction]:
181
+ local_account = account_config["account"]
182
+ currency = account_config["currency"]
183
+ balance = amount.Amount(D(account.balance_raw), currency)
184
+ metakv = {"account-id": str(account.account_id)}
185
+
186
+ optional_date = account.last_change_date or account.last_retrieve_date
187
+ if optional_date:
188
+ date = optional_date.date()
189
+ elif latest_txn:
190
+ date = latest_txn.date
191
+ else:
192
+ logging.warning(
193
+ "No date information available for balance of account %s, using today",
194
+ account.account_id,
195
+ )
196
+ date = datetime.date.today()
197
+
198
+ entry = data.Balance(
199
+ data.new_metadata("", 0, metakv),
200
+ date + datetime.timedelta(days=1),
201
+ local_account,
202
+ balance,
203
+ None,
204
+ None,
205
+ )
206
+ return [entry]
@@ -1,5 +1,5 @@
1
1
  import re
2
- from datetime import datetime
2
+ from datetime import datetime, timedelta
3
3
 
4
4
  import beangulp
5
5
  import camelot
@@ -46,6 +46,11 @@ class Importer(beangulp.Importer):
46
46
  if conversionOriginal and conversionRate:
47
47
  kv = {"original": conversionOriginal, "rate": conversionRate}
48
48
  text = text.replace("Amount: " + conversionOriginal, "")
49
+ # handle decimal seperated original amounts
50
+ [originalCcy, originalAmt] = conversionOriginal.split(" ")
51
+ text = text.replace(
52
+ "Amount: " + f"{originalCcy} {float(originalAmt):,}", ""
53
+ )
49
54
  else:
50
55
  kv = None
51
56
 
@@ -63,17 +68,34 @@ class Importer(beangulp.Importer):
63
68
  ],
64
69
  )
65
70
 
71
+ def createBalance(
72
+ self,
73
+ filepath: str,
74
+ date: str,
75
+ amt: amount.Amount,
76
+ ) -> data.Balance:
77
+ meta = data.new_metadata(filepath, 0, None)
78
+ return data.Balance(
79
+ meta,
80
+ datetime.strptime(date, "%d.%m.%Y").date() + timedelta(days=1),
81
+ self._account,
82
+ amt,
83
+ None,
84
+ None,
85
+ )
86
+
66
87
  def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
67
88
  entries = []
68
89
 
69
90
  conversionPattern = re.compile(r"(?P<original>.+) at the rate of (?P<rate>.+)")
91
+ balancePattern = re.compile(r"Balance as of (?P<date>\d\d\.\d\d\.\d\d\d\d)")
70
92
 
71
93
  tables = camelot.read_pdf(
72
94
  filepath,
73
95
  flavor="stream",
74
96
  pages="all",
75
- table_regions=["40,470,580,32"],
76
- columns=["110,300,370,440,500"],
97
+ table_regions=["40,600,580,32"],
98
+ columns=["110,305,370,440,500"],
77
99
  strip_text="\n",
78
100
  layout_kwargs={"word_margin": 0.50},
79
101
  split_text=True,
@@ -89,7 +111,7 @@ class Importer(beangulp.Importer):
89
111
  conversionOriginal = None
90
112
  conversionRate = None
91
113
  for _, row in df.iterrows():
92
- date, text, _, debit, credit, _ = tuple(row)
114
+ date, text, _, debit, credit, bal = tuple(row)
93
115
 
94
116
  # skip stuff before
95
117
  if beforeStart and "Date" != date:
@@ -98,8 +120,16 @@ class Importer(beangulp.Importer):
98
120
  beforeStart = False
99
121
  continue
100
122
 
101
- # skip stuff after
102
- if "Balance as of" in text:
123
+ # create balance and skip stuff after
124
+ balanceMatch = balancePattern.match(text)
125
+ if balanceMatch:
126
+ entries.append(
127
+ self.createBalance(
128
+ filepath,
129
+ balanceMatch.group("date"),
130
+ amount.Amount(D(bal.replace("'", "")), self.currency),
131
+ )
132
+ )
103
133
  break
104
134
 
105
135
  trxDate = date
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tariochbctools
3
- Version: 1.4.1
3
+ Version: 1.5.0
4
4
  Summary: Importers, plugins and price fetchers for Beancount
5
5
  Home-page: https://github.com/tarioch/beancounttools/
6
6
  Author: Patrick Ruckstuhl
@@ -19,7 +19,7 @@ Classifier: Topic :: Office/Business :: Financial :: Investment
19
19
  Description-Content-Type: text/x-rst; charset=UTF-8
20
20
  License-File: LICENSE.txt
21
21
  Requires-Dist: importlib-metadata; python_version < "3.8"
22
- Requires-Dist: awardwallet
22
+ Requires-Dist: awardwallet>=0.2
23
23
  Requires-Dist: beancount>=3
24
24
  Requires-Dist: beangulp
25
25
  Requires-Dist: beanprice
@@ -1,8 +1,8 @@
1
1
  tariochbctools/__init__.py,sha256=pH5i4Fj1tbXLqLtTVIdoojiplZssQn0nnud8-HXodRE,577
2
2
  tariochbctools/importers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  tariochbctools/importers/awardwalletimp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- tariochbctools/importers/awardwalletimp/config.py,sha256=EPwvUE9WBdCk9xxleJ8qDLfPy8GwcgAtG1uGSShyt4w,2460
5
- tariochbctools/importers/awardwalletimp/importer.py,sha256=0G3DcxYP_chy1zUYMCijDOwNZBBBdX8hR2sPKiVHZS8,5071
4
+ tariochbctools/importers/awardwalletimp/config.py,sha256=OEhKKkA-lJqyT6QImqlsJVAT4iiNeGwX2DKNPZN6cic,2423
5
+ tariochbctools/importers/awardwalletimp/importer.py,sha256=Z4GeK3xqQTbdNDFQVObDXqL3SWX-KPjhbZ25TtBD-cg,6754
6
6
  tariochbctools/importers/bcge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  tariochbctools/importers/bcge/importer.py,sha256=bAQpkBLURatdsfSoMQdiha-8QfExAXXvpzAgnFFMwoE,1176
8
8
  tariochbctools/importers/bitst/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -30,7 +30,7 @@ tariochbctools/importers/postfinance/importer.py,sha256=PLBbnswb_tHt8wzd7yCC4UAW
30
30
  tariochbctools/importers/quickfile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  tariochbctools/importers/quickfile/importer.py,sha256=HgS7lSi7egxkj-IWd77MS-vhepYNCRXZwVbeCornkzg,6479
32
32
  tariochbctools/importers/radicant/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- tariochbctools/importers/radicant/importer.py,sha256=d2AKncK4-5PMDJ-98gqgWYubMWpO46LJ7eY8koa6y90,4490
33
+ tariochbctools/importers/radicant/importer.py,sha256=chjHX25YoU7jaonegJ8gaV2QzvSm7sdH8a7cj-rUmPA,5602
34
34
  tariochbctools/importers/raiffeisench/importer.py,sha256=5r7hHEG0ZtEp-ePlsF4aHapBv9DJgjeVxE1m_G62bLU,912
35
35
  tariochbctools/importers/revolut/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  tariochbctools/importers/revolut/importer.py,sha256=5jI91gGIDcP2TMFcauKkA9OWCTqCIl5Phz9n6r9kOeI,3953
@@ -53,9 +53,9 @@ tariochbctools/plugins/check_portfolio_sum.py,sha256=naJ2j6BFpQhJhT2c-gfjyIdcYe0
53
53
  tariochbctools/plugins/generate_base_ccy_prices.py,sha256=4CDzUosjMWCZfsBJMLrf-i5WNCHexI2rdm5vIDYW-AI,1314
54
54
  tariochbctools/plugins/prices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  tariochbctools/plugins/prices/ibkr.py,sha256=GYCjnlF-MK-ZFPEr0M6T4iO3Etq0tMmrMlsHGInXUO8,1405
56
- tariochbctools-1.4.1.dist-info/licenses/LICENSE.txt,sha256=VR2hkz3p9Sw4hSXc7S5iZTOXGeV4h-i8AO_q0zEmtkE,1074
57
- tariochbctools-1.4.1.dist-info/METADATA,sha256=x5jAZ6K1do4OMGOoJYbteq1nf3Qw4dW1DR7693B1XCk,2175
58
- tariochbctools-1.4.1.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
59
- tariochbctools-1.4.1.dist-info/entry_points.txt,sha256=bo7wO1u-PIDHNuqsTEekC56VCAmn2i2vTRcKXxqc770,158
60
- tariochbctools-1.4.1.dist-info/top_level.txt,sha256=CiA_NepCI6zDNsaORA55zmpuJFSnTvLESraIL13xiOQ,15
61
- tariochbctools-1.4.1.dist-info/RECORD,,
56
+ tariochbctools-1.5.0.dist-info/licenses/LICENSE.txt,sha256=VR2hkz3p9Sw4hSXc7S5iZTOXGeV4h-i8AO_q0zEmtkE,1074
57
+ tariochbctools-1.5.0.dist-info/METADATA,sha256=WAKAVrIPoRzgeLpoIT5-UtSvJXRPpu5V9l4HA3Ad448,2180
58
+ tariochbctools-1.5.0.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
59
+ tariochbctools-1.5.0.dist-info/entry_points.txt,sha256=bo7wO1u-PIDHNuqsTEekC56VCAmn2i2vTRcKXxqc770,158
60
+ tariochbctools-1.5.0.dist-info/top_level.txt,sha256=CiA_NepCI6zDNsaORA55zmpuJFSnTvLESraIL13xiOQ,15
61
+ tariochbctools-1.5.0.dist-info/RECORD,,