Skip to content

Commit 1bd2f6f

Browse files
committed
v0.7.0
1 parent 8a94dc6 commit 1bd2f6f

File tree

15 files changed

+230
-4
lines changed

15 files changed

+230
-4
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ The following financial institutions are supported:
1616
* [Rakuten Bank 楽天銀行](https://www.rakuten-bank.co.jp/)
1717
* [SBI Shinsei Bank 新生銀行](https://www.sbishinseibank.co.jp/)
1818
* [SBI Sumishin Net Bank 住信SBIネット銀行](https://www.netbk.co.jp/)
19+
* USA
20+
* [Chase Sapphire Preferred Card](https://www.chase.com/)
1921

2022
To get started, see the [documentation](https://rlan.github.io/beancount-multitool).
2123

docs/institutions/chase_sp_card.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Chase Sappire Preferred VISA Card
2+
3+
[https://www.chase.com/](https://www.chase.com/)
4+
5+
## How to download transactions
6+
7+
TODO
8+
9+
## CSV file
10+
11+
Header row:
12+
13+
```csv
14+
Transaction Date,Post Date,Description,Category,Type,Amount,Memo
15+
```
16+
17+
### Regular expressions
18+
19+
Regular expressions uses `Description` for matching.
20+
21+
## Example: label all transactions as default
22+
23+
[One](https://github.com/rlan/beancount-multitool/tree/main/tests/data/chase_sp_card) of the automated tests does exactly this. Let's download and run it locally.
24+
25+
```sh
26+
wget https://raw.githubusercontent.com/rlan/beancount-multitool/main/tests/data/chase_sp_card/config.toml
27+
wget https://raw.githubusercontent.com/rlan/beancount-multitool/main/tests/data/chase_sp_card/credit_mapping.toml
28+
wget https://raw.githubusercontent.com/rlan/beancount-multitool/main/tests/data/chase_sp_card/debit_mapping.toml
29+
wget https://raw.githubusercontent.com/rlan/beancount-multitool/main/tests/data/chase_sp_card/test.bean
30+
wget https://raw.githubusercontent.com/rlan/beancount-multitool/main/tests/data/chase_sp_card/test.csv
31+
bean-mt chase_sp_card config.toml test.csv --output out.bean
32+
```

docs/institutions/index.md

+6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ title: Supported institutions
55

66
Here is a list of financial institutions whose CSV files the CLI tool can read:
77

8+
Japan
9+
810
* [JA Bank JAネットバンク](ja_bank.md)
911
* [Rakuten Bank 楽天銀行](rakuten_bank.md)
1012
* [Rakuten Card 楽天カード](rakuten_card.md)
1113
* [SBI Shinsei Bank 新生銀行](shinsei_bank.md)
1214
* [SBI Sumishin Net Bank 住信SBIネット銀行](sumishin_net_bank.md)
15+
16+
USA
17+
18+
* [Chase Sapphire Preferred Card](chase_sp_card.md)

docs/reference/changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
0.7.0
4+
5+
* Supports Chase Sapphire Preferred Card.
6+
37
0.6.1
48

59
* Typing errors in Python 3.9.

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ nav:
4141
- usage/examples.md
4242
- Financial Institutions:
4343
- institutions/index.md
44+
- institutions/chase_sp_card.md
4445
- institutions/ja_bank.md
4546
- institutions/rakuten_bank.md
4647
- institutions/rakuten_card.md

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "beancount-multitool"
3-
version = "0.6.1"
3+
version = "0.7.0"
44
description = "A CLI tool that converts financial data to Beancount files"
55
authors = ["Rick Lan <[email protected]>"]
66
license = "MIT"
+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
from decimal import Decimal
2+
from pathlib import Path
3+
4+
import pandas as pd
5+
6+
from .Institution import Institution
7+
from .MappingDatabase import MappingDatabase
8+
from .read_config import read_config
9+
from .as_transaction import as_transaction
10+
from .get_value import get_value
11+
from .get_beancount_config import get_beancount_config
12+
13+
14+
class ChaseSPCard(Institution):
15+
NAME = "chase_sp_card" # used in cli.py and in tests
16+
17+
def __init__(self, config_file: str):
18+
# params
19+
self.config_file = config_file
20+
# attributes
21+
self.config = read_config(config_file)
22+
self.beancount_config = get_beancount_config(self.config)
23+
# Use basedir of config_file to read mapping database files
24+
base_dir = Path(config_file).parent
25+
debit_file = get_value(self.config, "database", "debit_mapping")
26+
self.debit_file = str(base_dir / debit_file)
27+
self.debit_db = MappingDatabase(self.debit_file)
28+
29+
def read_transaction(self, file_name: str) -> pd.DataFrame:
30+
"""Read financial transactions into a Pandas DataFrame.
31+
32+
Parameters
33+
----------
34+
file_name : str
35+
Input file name.
36+
37+
Returns
38+
-------
39+
pd.DataFrame
40+
A dataframe after pre-processing.
41+
"""
42+
converters = {
43+
"Transaction Date": pd.to_datetime,
44+
"Post Date": pd.to_datetime,
45+
"Description": str,
46+
"Category": str,
47+
"Type": str,
48+
"Amount": str,
49+
"Memo": str,
50+
}
51+
df = pd.read_csv(file_name, converters=converters)
52+
print(f"Found {len(df.index)} transactions in {file_name}")
53+
54+
# Transaction Date,Post Date,Description,Category,Type,Amount,Memo
55+
# Lowercase names will be keyword arguments later.
56+
column_names = {
57+
"Transaction Date": "date",
58+
"Amount": "amount",
59+
"Description": "memo", # note this is all lower case
60+
}
61+
df.rename(columns=column_names, inplace=True)
62+
63+
df["amount"] = df["amount"].apply(Decimal)
64+
# Drop positive amounts as they are credit card payments
65+
df.drop(df.loc[df["amount"] > 0].index, inplace=True)
66+
# Reverse sign as all transactions are now spending.
67+
df["amount"] = -df["amount"]
68+
69+
# Reverse row order because the oldest transaction is on the bottom
70+
# Note: the index column is also reversed
71+
df = df[::-1]
72+
73+
# print(df.dtypes) # debug
74+
# print(df) # debug
75+
return df
76+
77+
def write_bean(self, df: pd.DataFrame, file_name: str) -> None:
78+
"""Write Beancount transactions to file
79+
80+
Parameters
81+
----------
82+
df : pd.DataFrame
83+
Transaction dataframe.
84+
file_name : str
85+
Output file name.
86+
87+
Returns
88+
-------
89+
None
90+
"""
91+
try:
92+
with open(file_name, "w", encoding="utf-8") as f:
93+
for row in df.index:
94+
date = df["date"][row]
95+
amount = df["amount"][row]
96+
memo = df["memo"][row]
97+
metadata = {
98+
"memo": memo,
99+
}
100+
101+
accounts = self.debit_db.match(memo)
102+
103+
account_metadata = {}
104+
for x in range(1, len(accounts)):
105+
account_metadata[f"match{x+1}"] = str(accounts[x])
106+
107+
output = as_transaction(
108+
date=date,
109+
amount=amount,
110+
metadata=metadata,
111+
account_metadata=account_metadata,
112+
**accounts[0],
113+
**self.beancount_config,
114+
)
115+
# print(output) # debug
116+
f.write(output)
117+
print(f"Written {file_name}")
118+
except IOError as e:
119+
print(f"Error encountered while writing to: {file_name}")
120+
print(e)
121+
122+
def convert(self, csv_file: str, bean_file: str):
123+
"""Convert transactions in a CSV file to a Beancount file
124+
125+
Parameters
126+
----------
127+
csv_file : str
128+
Input CSV file name.
129+
130+
bean_file : str
131+
Output Beancount file name.
132+
133+
Returns
134+
-------
135+
None
136+
"""
137+
df = self.read_transaction(csv_file)
138+
self.write_bean(df, bean_file)

src/beancount_multitool/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
# from .as_transaction import as_transaction
44
# from .read_config import read_config
5+
from .ChaseSPCard import ChaseSPCard
56
from .JABank import JABank
67
from .RakutenBank import RakutenBank
78
from .RakutenCard import RakutenCard
89
from .ShinseiBank import ShinseiBank
910
from .SumishinNetBank import SumishinNetBank
1011

1112
__INSTITUTIONS__ = []
13+
__INSTITUTIONS__.append(ChaseSPCard.NAME)
1214
__INSTITUTIONS__.append(JABank.NAME)
1315
__INSTITUTIONS__.append(RakutenBank.NAME)
1416
__INSTITUTIONS__.append(RakutenCard.NAME)
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.6.1"
1+
__version__ = "0.7.0"

src/beancount_multitool/cli.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Get available finanicial institutions
55
from beancount_multitool import __INSTITUTIONS__
66

7+
from beancount_multitool import ChaseSPCard
78
from beancount_multitool import JABank
89
from beancount_multitool import RakutenBank
910
from beancount_multitool import RakutenCard
@@ -40,7 +41,9 @@ def main(name: str, config: str, data: str, output: str):
4041
4142
DATA is the raw financial data downloaded from NAME, e.g. input.csv.
4243
"""
43-
if name == JABank.NAME:
44+
if name == ChaseSPCard.NAME:
45+
tool = ChaseSPCard(config)
46+
elif name == JABank.NAME:
4447
tool = JABank(config)
4548
elif name == RakutenBank.NAME:
4649
tool = RakutenBank(config)

tests/data/chase_sp_card/config.toml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Config for Chase Sapphire Preferred Card
2+
3+
[beancount]
4+
currency = "USD"
5+
source_account = "Liabilities:US:ChaseSapphirePreferredCard"
6+
7+
[database]
8+
# File path is relative to current file
9+
debit_mapping = "debit_mapping.toml"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Debit mapping database for Chase Sapphire Preferred Card
2+
3+
[default]
4+
regex = ""
5+
account = "Expenses:Unknown:ChaseSapphirePreferredCard"
6+
payee = "Unknown payee"
7+
narration = ""
8+
tags = ["#NoMatch"]
9+
flag = "!"
10+
11+
[blank]
12+
regex = ""
13+
account = ""
14+
payee = ""
15+
narration = ""
16+
tags = []
17+
flag = ""
18+
19+
# List mappings below

tests/data/chase_sp_card/test.bean

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
2024-04-17 * "Unknown payee" "" #NoMatch
3+
memo: "APPLE.COM/BILL"
4+
Liabilities:US:ChaseSapphirePreferredCard
5+
! Expenses:Unknown:ChaseSapphirePreferredCard 0.99 USD

tests/data/chase_sp_card/test.csv

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Transaction Date,Post Date,Description,Category,Type,Amount,Memo
2+
04/26/2024,04/26/2024,AUTOMATIC PAYMENT - THANK,,Payment,0.99,
3+
04/17/2024,04/17/2024,APPLE.COM/BILL,Shopping,Sale,-0.99,

tests/test_data.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ def assets(tmp_path, request):
2828

2929

3030
def test_data(assets):
31-
if assets["name"] == bcmt.JABank.NAME:
31+
if assets["name"] == bcmt.ChaseSPCard.NAME:
32+
app = bcmt.ChaseSPCard(assets["config_file"])
33+
elif assets["name"] == bcmt.JABank.NAME:
3234
app = bcmt.JABank(assets["config_file"])
3335
elif assets["name"] == bcmt.RakutenBank.NAME:
3436
app = bcmt.RakutenBank(assets["config_file"])

0 commit comments

Comments
 (0)