mega-commit
This commit is contained in:
12
get_some_transactions_v1.py
Normal file
12
get_some_transactions_v1.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# twilio.rest has a Client too, so let's avoid a namespace collision
|
||||||
|
from plaid import Client as PlaidClient
|
||||||
|
|
||||||
|
plaid_client = PlaidClient(client_id=os.getenv('PLAID_CLIENT_ID'), secret=os.getenv('PLAID_SECRET'),
|
||||||
|
public_key=os.getenv('PLAID_PUBLIC_KEY'), environment=os.getenv('PLAID_ENV'))
|
||||||
|
|
||||||
|
|
||||||
|
def get_some_transactions(access_token: str, start_date: str, end_date: str) -> List[dict]:
|
||||||
|
return plaid_client.Transactions.get(access_token, start_date, end_date)
|
||||||
36
get_some_transactions_v2.py
Normal file
36
get_some_transactions_v2.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import math
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# twilio.rest has a Client too, so let's avoid a namespace collision
|
||||||
|
from plaid import Client as PlaidClient
|
||||||
|
|
||||||
|
plaid_client = PlaidClient(client_id=os.getenv('PLAID_CLIENT_ID'), secret=os.getenv('PLAID_SECRET'),
|
||||||
|
public_key=os.getenv('PLAID_PUBLIC_KEY'), environment=os.getenv('PLAID_ENV'))
|
||||||
|
|
||||||
|
|
||||||
|
# https://plaid.com/docs/api/#transactions
|
||||||
|
MAX_TRANSACTIONS_PER_PAGE = 500
|
||||||
|
OMIT_CATEGORIES = ["Transfer", "Credit Card", "Deposit"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_some_transactions(access_token: str, start_date: str, end_date: str) -> List[dict]:
|
||||||
|
account_ids = [account['account_id'] for account in plaid_client.Accounts.get(access_token)['accounts']
|
||||||
|
if account['subtype'] not in ['cd', 'savings']]
|
||||||
|
|
||||||
|
num_available_transactions = plaid_client.Transactions.get(access_token, start_date, end_date,
|
||||||
|
account_ids=account_ids)['total_transactions']
|
||||||
|
num_pages = math.ceil(num_available_transactions / MAX_TRANSACTIONS_PER_PAGE)
|
||||||
|
transactions = []
|
||||||
|
|
||||||
|
for page_num in range(num_pages):
|
||||||
|
transactions += [transaction
|
||||||
|
for transaction in plaid_client.Transactions.get(access_token, start_date, end_date,
|
||||||
|
account_ids=account_ids,
|
||||||
|
offset=page_num * MAX_TRANSACTIONS_PER_PAGE,
|
||||||
|
count=MAX_TRANSACTIONS_PER_PAGE)['transactions']
|
||||||
|
if transaction['category'] is None
|
||||||
|
or not any(category in OMIT_CATEGORIES
|
||||||
|
for category in transaction['category'])]
|
||||||
|
|
||||||
|
return transactions
|
||||||
17
get_yesterdays.py
Normal file
17
get_yesterdays.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from get_some_transactions_v2 import get_some_transactions
|
||||||
|
|
||||||
|
|
||||||
|
def get_yesterdays_transactions() -> List[dict]:
|
||||||
|
yesterday = ('2017-05-16' if os.getenv('PLAID_ENV') == 'sandbox'
|
||||||
|
else (datetime.date.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d'))
|
||||||
|
|
||||||
|
transactions = []
|
||||||
|
|
||||||
|
for access_id in [os.getenv('CHASE_ACCESS_TOKEN'), os.getenv('BOFA_ACCESS_TOKEN')]:
|
||||||
|
transactions += get_some_transactions(access_id, yesterday, yesterday)
|
||||||
|
|
||||||
|
return transactions
|
||||||
5
run.py
Normal file
5
run.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from get_yesterdays import get_yesterdays_transactions
|
||||||
|
from send_summary import send_summary
|
||||||
|
|
||||||
|
|
||||||
|
send_summary(get_yesterdays_transactions())
|
||||||
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
14
send_summary.py
Normal file
14
send_summary.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from twilio.rest import Client as TwilioClient
|
||||||
|
|
||||||
|
twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN'))
|
||||||
|
|
||||||
|
|
||||||
|
def send_summary(transactions: List[dict]) -> None:
|
||||||
|
total_spent = sum(transaction['amount'] for transaction in transactions)
|
||||||
|
|
||||||
|
message = f'You spent ${total_spent} yesterday. 💸'
|
||||||
|
|
||||||
|
twilio_client.api.account.messages.create(to=os.getenv('MY_CELL'), from_=os.getenv('MY_TWILIO_NUM'), body=message)
|
||||||
232
spending_summary.md
Normal file
232
spending_summary.md
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
Your bank lets you set up SMS alerts for various triggers; it might even give you the option of receiving periodic spending summaries (mine doesn't!).
|
||||||
|
But what about a daily SMS summary of your spending across *all* your accounts? This is harder to come by, so let's roll our own:
|
||||||
|
|
||||||
|
## Pre-work
|
||||||
|
|
||||||
|
1. [Nab a sandbox account](https://dashboard.plaid.com/signup) from Plaid, and put those credentials into `PLAID_CLIENT_ID`, `PLAID_SECRET`,
|
||||||
|
and `PLAID_PUBLIC_KEY` environment variables. While you're at it,
|
||||||
|
[ask for access](https://dashboard.plaid.com/overview/request-development) the development API (it'll take a few days).
|
||||||
|
For now, though, create an env var `PLAID_ENV` and set it to 'sandbox'.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PLAID_CLIENT_ID='somechars1234'
|
||||||
|
export PLAID_PUBLIC_KEY='somemorechars1234'
|
||||||
|
export PLAID_SECRET='somesecretchars1234'
|
||||||
|
export PLAID_ENV='sandbox'
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Clone the [quickstart for Plaid](https://github.com/plaid/quickstart) and reference those environment variables in the
|
||||||
|
Python quickstart's `server.py`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
PLAID_CLIENT_ID = os.getenv('PLAID_CLIENT_ID')
|
||||||
|
PLAID_SECRET = os.getenv('PLAID_SECRET')
|
||||||
|
PLAID_PUBLIC_KEY = os.getenv('PLAID_PUBLIC_KEY')
|
||||||
|
PLAID_ENV = os.getenv('PLAID_ENV')
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Run `server.py` (Python 2 only 😦) and log into Chase with the test credentials ("user_good" and
|
||||||
|
"pass_good" as of 5/24/2017). It prints the access token to your terminal: Grab that and put it into a
|
||||||
|
`CHASE_ACCESS_TOKEN` environment variable. Repeat this for Bank of America and put that access token into `BOFA_ACCESS_TOKEN`.
|
||||||
|
|
||||||
|
1. [Grab your Twilio credentials](https://www.twilio.com/console/account/settings) and
|
||||||
|
[Twilio incoming phone number](https://www.twilio.com/console/phone-numbers/incoming) make sure those are available as
|
||||||
|
environment variables too (see below).
|
||||||
|
|
||||||
|
1. Put your cell phone number in an environment variable as `MY_CELL`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export CHASE_ACCESS_TOKEN='access-sandbox-someprettysecretchars1234'
|
||||||
|
export BOFA_ACCESS_TOKEN='access-sandbox-somemoreprettysecretchars1234'
|
||||||
|
export TWILIO_SID='somechars1234'
|
||||||
|
export TWILIO_TOKEN='somesecretchars1234'
|
||||||
|
export MY_TWILIO_NUM='+11111111111'
|
||||||
|
export MY_CELL='+12222222222'
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Install just a couple of dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pip install python-plaid twilio
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get Some Transactions
|
||||||
|
|
||||||
|
Make yourself a Plaid client instance and grab some transactions from Chase:
|
||||||
|
|
||||||
|
[get_some_transactions_v1.py](https://github.com/zevaverbach/spending_summary/blob/master/get_some_transactions_v1.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# twilio.rest has a Client too, so let's avoid a namespace collision
|
||||||
|
from plaid import Client as PlaidClient
|
||||||
|
|
||||||
|
plaid_client = PlaidClient(client_id=os.getenv('PLAID_CLIENT_ID'), secret=os.getenv('PLAID_SECRET'),
|
||||||
|
public_key=os.getenv('PLAID_PUBLIC_KEY'), environment=os.getenv('PLAID_ENV'))
|
||||||
|
|
||||||
|
|
||||||
|
def get_some_transactions(access_token: str, start_date: str, end_date: str) -> List[dict]:
|
||||||
|
return plaid_client.Transactions.get(access_token, start_date, end_date)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspecting the output of `get_some_transactions`, we see that there are multiple accounts, 337 transactions among them, but only
|
||||||
|
100 transactions returned from this API call. Two of these accounts are for savings, so presumably they're only
|
||||||
|
going to have transfers rather than purchases.
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> from get_some_transactions_v1 import get_some_transactions
|
||||||
|
>>> import os
|
||||||
|
>>> some_transactions = get_some_transactions(os.getenv('CHASE_ACCESS_TOKEN'), '1972-01-01', '2017-05-26'))
|
||||||
|
>>> some_transactions['total_transactions']
|
||||||
|
337
|
||||||
|
>>> from pprint import pprint
|
||||||
|
>>> pprint(some_transactions['accounts'])
|
||||||
|
[{'account_id': 'qwp96Z11b5IBKVMl8XvLSkJXjgj6ZxIXX3o79',
|
||||||
|
'name': 'Plaid Checking',
|
||||||
|
'subtype': 'checking'
|
||||||
|
...},
|
||||||
|
{'account_id': 'Kk9ZL7NN4wSX3lR9evV8f9P4GVGk3BF33QnAM',
|
||||||
|
'name': 'Plaid Saving',
|
||||||
|
'subtype': 'savings'
|
||||||
|
...},
|
||||||
|
{'account_id': 'rEy96MWWgXukrnBW4yVphv7yl3lznosBBzo6n',
|
||||||
|
'name': 'Plaid CD',
|
||||||
|
'subtype': 'cd'
|
||||||
|
...}
|
||||||
|
{'account_id': '9rNKomMMdWTvVL4X9RP6UKb4qEqng1uJJ6nQw',
|
||||||
|
'name': 'Plaid Credit Card',
|
||||||
|
'subtype': 'credit'
|
||||||
|
...}]
|
||||||
|
>>> len(some_transactions['transactions'])
|
||||||
|
100
|
||||||
|
```
|
||||||
|
## Get **The Right** Transactions
|
||||||
|
|
||||||
|
Looking at the transactions themselves, we see that there is a `category` field which sometimes has a list of values,
|
||||||
|
sometimes `None`. Among the categories there are "Transfer", "Credit Card", and "Deposit": These aren't going to be
|
||||||
|
useful in gleaning spending activity, so we'll refactor our `get_some_transactions` function to 1) skip transactions
|
||||||
|
with those categories and 2) skip accounts with a subtype of "savings" or "cd". Let's also 3) make sure to get all
|
||||||
|
available transactions by using pagination and 4) just return transactions.
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> some_transactions['transactions'].keys()
|
||||||
|
dict_keys(['account_id', 'account_owner', 'amount', 'category', 'category_id', 'date', 'location', 'name', 'payment_meta', 'pending', 'pending_transaction_id', 'transaction_id', 'transaction_type'])
|
||||||
|
>>> {category for transaction in some_transactions['transactions'] if some_transactions['category'] for category in trans['category']}
|
||||||
|
{'Airlines and Aviation Services',
|
||||||
|
'Coffee Shop',
|
||||||
|
'Credit Card',
|
||||||
|
'Deposit',
|
||||||
|
'Fast Food',
|
||||||
|
'Food and Drink',
|
||||||
|
'Payment',
|
||||||
|
'Restaurants',
|
||||||
|
'Transfer',
|
||||||
|
'Travel'}
|
||||||
|
```
|
||||||
|
|
||||||
|
[get_some_transactions_v2.py](https://github.com/zevaverbach/spending_summary/blob/master/get_some_transactions_v2.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import math
|
||||||
|
...
|
||||||
|
# https://plaid.com/docs/api/#transactions
|
||||||
|
MAX_TRANSACTIONS_PER_PAGE = 500
|
||||||
|
OMIT_CATEGORIES = ["Transfer", "Credit Card", "Deposit"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_some_transactions(access_token: str, start_date: str, end_date: str) -> List[dict]:
|
||||||
|
account_ids = [account['account_id'] for account in plaid_client.Accounts.get(access_token)['accounts']
|
||||||
|
if account['subtype'] not in ['cd', 'savings']]
|
||||||
|
|
||||||
|
num_available_transactions = plaid_client.Transactions.get(access_token, start_date, end_date,
|
||||||
|
account_ids=account_ids)['total_transactions']
|
||||||
|
num_pages = math.ceil(num_available_transactions / MAX_TRANSACTIONS_PER_PAGE)
|
||||||
|
transactions = []
|
||||||
|
|
||||||
|
for page_num in range(num_pages):
|
||||||
|
transactions += [transaction
|
||||||
|
for transaction in plaid_client.Transactions.get(access_token, start_date, end_date,
|
||||||
|
account_ids=account_ids,
|
||||||
|
offset=page_num * MAX_TRANSACTIONS_PER_PAGE,
|
||||||
|
count=MAX_TRANSACTIONS_PER_PAGE)['transactions']
|
||||||
|
if transaction['category'] is None
|
||||||
|
or not any(category in OMIT_CATEGORIES
|
||||||
|
for category in transaction['category'])]
|
||||||
|
|
||||||
|
return transactions
|
||||||
|
```
|
||||||
|
|
||||||
|
Now there are just 265 transactions. Are any of them negative?
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> pprint([transaction for transaction in some_transactions if transaction['amount'] < 0])
|
||||||
|
[{'amount': -500,
|
||||||
|
'category': ['Travel', 'Airlines and Aviation Services'],
|
||||||
|
'name': 'United Airlines',
|
||||||
|
'transaction_type': 'special',
|
||||||
|
...},
|
||||||
|
...]
|
||||||
|
```
|
||||||
|
|
||||||
|
Okay, that seems legit -- airfare refund, I guess. Let's keep negative items in.
|
||||||
|
|
||||||
|
## Pulling it All Together
|
||||||
|
|
||||||
|
Now let's get all the transactions from yesterday, making sure to pull them from both accounts.
|
||||||
|
|
||||||
|
[get_yesterdays.py](https://github.com/zevaverbach/spending_summary/blob/master/get_yesterdays.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
...
|
||||||
|
def get_yesterdays_transactions() -> List[dict]:
|
||||||
|
yesterday = ('2017-05-16' if os.getenv('PLAID_ENV') == 'sandbox'
|
||||||
|
else (datetime.date.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d'))
|
||||||
|
|
||||||
|
transactions = []
|
||||||
|
|
||||||
|
for access_id in [os.getenv('CHASE_ACCESS_TOKEN'), os.getenv('BOFA_ACCESS_TOKEN')]:
|
||||||
|
transactions += get_transactions_from_multiple_accounts(access_id, yesterday, yesterday)
|
||||||
|
|
||||||
|
return transactions
|
||||||
|
```
|
||||||
|
|
||||||
|
As of 5/24/2017, the most recent transactions available in these sandbox accounts are from 5/16/17: Hence, the hardcoded
|
||||||
|
`yesterday` value above.
|
||||||
|
|
||||||
|
Let's send an SMS to ourselves with the total spent yesterday!
|
||||||
|
|
||||||
|
[send_summary.py](https://github.com/zevaverbach/spending_summary/blob/master/send_summary.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
...
|
||||||
|
from twilio.rest import Client as TwilioClient
|
||||||
|
|
||||||
|
twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN'))
|
||||||
|
|
||||||
|
|
||||||
|
def send_summary(transactions: List[dict]) -> None:
|
||||||
|
total_spent = sum(transaction['amount'] for transaction in transactions)
|
||||||
|
|
||||||
|
message = f'You spent ${total_spent} yesterday. 💸'
|
||||||
|
|
||||||
|
twilio_client.api.account.messages.create(to=os.getenv('MY_CELL'), from_=os.getenv('MY_TWILIO_NUM'), body=message)
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Voila!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Representing Money as a Float?
|
||||||
|
|
||||||
|
You're gonna have a bad time, 'mkay? If you build anything significant (budget tracker bot?) with the Plaid API,
|
||||||
|
be sure to convert those transaction amounts to Decimals:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import decimal
|
||||||
|
def convert_transaction_amount(transaction_amount):
|
||||||
|
return decimal.Decimal(transaction_amount)
|
||||||
|
```
|
||||||
60
spending_summary.py
Normal file
60
spending_summary.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import datetime
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from twilio.rest import Client as TwilioClient
|
||||||
|
|
||||||
|
from plaid import Client as PlaidClient
|
||||||
|
|
||||||
|
# these are access IDs from Plaid for a specific bank/credit account
|
||||||
|
ACCOUNT_ACCESS_IDS: List[str] = [os.getenv('PLAID_ACCESS_ID'), os.getenv('OTHER_PLAID_ACCESS_ID')]
|
||||||
|
MAX_TRANSACTIONS_PER_PAGE = 500
|
||||||
|
|
||||||
|
plaid_client = PlaidClient(client_id=os.getenv('PLAID_CLIENT_ID'), secret=os.getenv('PLAID_SECRET'),
|
||||||
|
public_key=os.getenv('PLAID_PUBLIC_KEY'), environment=os.getenv('PLAID_ENV'))
|
||||||
|
|
||||||
|
twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN'))
|
||||||
|
|
||||||
|
|
||||||
|
def get_num_available_transactions(access_id, start: str, end: str) -> int:
|
||||||
|
return plaid_client.Transactions.get(access_id, start, end)['total_transactions']
|
||||||
|
|
||||||
|
|
||||||
|
def get_transactions_from_account(access_id: str, start: str, end: str) -> List[dict]:
|
||||||
|
transactions = []
|
||||||
|
num_available_transactions = get_num_available_transactions(access_id, start, end)
|
||||||
|
num_pages = math.ceil(num_available_transactions / MAX_TRANSACTIONS_PER_PAGE)
|
||||||
|
|
||||||
|
for page_num in range(num_pages):
|
||||||
|
transactions += plaid_client.Transactions.get(access_id, start, end,
|
||||||
|
offset=page_num * MAX_TRANSACTIONS_PER_PAGE,
|
||||||
|
count=MAX_TRANSACTIONS_PER_PAGE)['transactions']
|
||||||
|
|
||||||
|
return transactions
|
||||||
|
|
||||||
|
|
||||||
|
def get_transactions_from_multiple_accounts(access_ids: List[str], start: str, end: str) -> List[dict]:
|
||||||
|
transactions = []
|
||||||
|
for access_id in access_ids:
|
||||||
|
transactions += get_transactions_from_account(access_id, start, end)
|
||||||
|
return transactions
|
||||||
|
|
||||||
|
|
||||||
|
def get_yesterdays_transactions() -> List[dict]:
|
||||||
|
yesterday: str = (datetime.date.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')
|
||||||
|
return get_transactions_from_multiple_accounts(ACCOUNT_ACCESS_IDS, yesterday, yesterday)
|
||||||
|
|
||||||
|
|
||||||
|
def send_summary(transactions: List[dict]) -> None:
|
||||||
|
total_spent = sum(transaction['amount']
|
||||||
|
for transaction in transactions
|
||||||
|
if transaction['amount'] > 0)
|
||||||
|
|
||||||
|
message = f'You spent ${total_spent} yesterday. 💸'
|
||||||
|
|
||||||
|
twilio_client.api.account.messages.create(to=os.getenv('MY_CELL'), from_=os.getenv('MY_TWILIO_NUM'), body=message)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
send_summary(get_yesterdays_transactions())
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Budgeting. You do it, then you stop doing it. You wake up in a cold sweat and you start tracking your spending again. It sucks: You stop.
|
|
||||||
|
|
||||||
There are tools! Horrible, awful tools that are glorified spreadsheets which think all your Amazon purchases go under `books`. Much data entered, many hairs pulled out.
|
|
||||||
|
|
||||||
Surely this is fodder for a bot.
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import datetime
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import math
|
|
||||||
from twilio.rest import Client as TwilioClient
|
|
||||||
|
|
||||||
from plaid import Client as PlaidClient
|
|
||||||
|
|
||||||
CHECK_FOR_NEW_TRANSACTIONS_EVERY_X_MINUTES = 5
|
|
||||||
ALERT_FOR_TRANSACTIONS_GTE = 500
|
|
||||||
MY_CELL = os.getenv('MY_CELL')
|
|
||||||
MY_TWILIO_NUM = os.getenv('MY_TWILIO_NUM')
|
|
||||||
PLAID_ENV = os.getenv('PLAID_ENV') or 'sandbox'
|
|
||||||
|
|
||||||
# this is an access ID from Plaid for a specific user and bank/credit account
|
|
||||||
PLAID_ACCESS_ID = os.getenv('PLAID_ACCESS_ID')
|
|
||||||
|
|
||||||
plaid_client = PlaidClient(client_id=os.getenv('PLAID_CLIENT_ID'), secret=os.getenv('PLAID_SECRET'),
|
|
||||||
public_key=os.getenv('PLAID_PUBLIC_KEY'), environment=PLAID_ENV)
|
|
||||||
|
|
||||||
twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN'))
|
|
||||||
|
|
||||||
transaction_ids = set()
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_transactions() -> List[dict]:
|
|
||||||
today = datetime.date.today().strftime('%Y-%m-%d')
|
|
||||||
total_transactions: int = plaid_client.Transactions.get(PLAID_ACCESS_ID, '1972-01-01', today)['total_transactions']
|
|
||||||
all_transactions = []
|
|
||||||
for page in range(math.ceil(total_transactions / 500)):
|
|
||||||
all_transactions += plaid_client.Transactions.get(PLAID_ACCESS_ID, '1972-01-01', today,
|
|
||||||
offset=page * 500, count=500)['transactions']
|
|
||||||
return all_transactions
|
|
||||||
|
|
||||||
|
|
||||||
def get_latest_transactions() -> List[dict]:
|
|
||||||
"""get 100 most recent transactions"""
|
|
||||||
today = datetime.date.today().strftime('%Y-%m-%d')
|
|
||||||
return [transaction
|
|
||||||
for transaction in
|
|
||||||
plaid_client.Transactions.get(PLAID_ACCESS_ID, '1972-01-01', today)['transactions']
|
|
||||||
if transaction['transaction_id'] not in transaction_ids]
|
|
||||||
|
|
||||||
|
|
||||||
def alert(transaction: dict) -> None:
|
|
||||||
message = (f'hey, a transaction hit your account that exceeds ${ALERT_FOR_TRANSACTIONS_GTE}: '
|
|
||||||
f'{transaction["date"]} {transaction["name"]} ${transaction["amount"]}')
|
|
||||||
|
|
||||||
twilio_client.api.account.messages.create(to=MY_CELL, from_=MY_TWILIO_NUM, body=message)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
if len(transaction_ids) == 0:
|
|
||||||
transaction_ids.update(transaction['transaction_id'] for transaction in get_all_transactions())
|
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
for transaction in get_latest_transactions():
|
|
||||||
|
|
||||||
transaction_ids.add(transaction['transaction_id'])
|
|
||||||
|
|
||||||
if transaction['amount'] >= ALERT_FOR_TRANSACTIONS_GTE:
|
|
||||||
alert(transaction)
|
|
||||||
|
|
||||||
time.sleep(CHECK_FOR_NEW_TRANSACTIONS_EVERY_X_MINUTES * 60)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Reference in New Issue
Block a user