Compare commits
2 Commits
163b4a525c
...
2509c4dd70
| Author | SHA1 | Date | |
|---|---|---|---|
| 2509c4dd70 | |||
| dda8c3403d |
@@ -16,6 +16,9 @@ dependencies = [
|
|||||||
"ynab>=1.9.0",
|
"ynab>=1.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
catnab = "main:main"
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"ipdb>=0.13.13",
|
"ipdb>=0.13.13",
|
||||||
@@ -24,3 +27,6 @@ dev = [
|
|||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
pythonpath = ["src"]
|
pythonpath = ["src"]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
package = true
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class AmazonAccount:
|
|||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
|
||||||
def get_accounts(env: dict[str, str] = os.environ) -> list[AmazonAccount]:
|
def get_accounts(env: os._Environ | dict[str, str] = os.environ) -> list[AmazonAccount]:
|
||||||
accts = []
|
accts = []
|
||||||
for k, v in env.items():
|
for k, v in env.items():
|
||||||
if k == 'AMAZON_EMAIL':
|
if k == 'AMAZON_EMAIL':
|
||||||
|
|||||||
53
src/main.py
53
src/main.py
@@ -16,6 +16,29 @@ import ai
|
|||||||
import ynab_client
|
import ynab_client
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Context:
|
||||||
|
sync_days: int
|
||||||
|
budget_id: str
|
||||||
|
ynab_token: str
|
||||||
|
amz_accts: list[tuple[str, str]]
|
||||||
|
ynab_client: ynab_client.YnabClient
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
sync_days: int,
|
||||||
|
budget_id: str | None = None,
|
||||||
|
ynab_token: str | None = None,
|
||||||
|
):
|
||||||
|
self.sync_days = sync_days
|
||||||
|
self.amazon_accts = amazon.get_accounts()
|
||||||
|
self.ynab_client = ynab_client.YnabClient(
|
||||||
|
budget_id=budget_id if budget_id else os.environ['YNAB_BUDGET_ID'],
|
||||||
|
ynab_token=ynab_token if ynab_token else os.environ['YNAB_TOKEN'],
|
||||||
|
sync_days=sync_days,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TxCategory:
|
class TxCategory:
|
||||||
category_name: str | None
|
category_name: str | None
|
||||||
@@ -31,7 +54,8 @@ def tx_date_diff(amz_tx: Transaction, ynab_tx: TransactionDetail):
|
|||||||
def is_matchable_ynab(tx: TransactionDetail) -> bool:
|
def is_matchable_ynab(tx: TransactionDetail) -> bool:
|
||||||
return (
|
return (
|
||||||
# we only care about amazon transactions
|
# we only care about amazon transactions
|
||||||
'amazon' in tx.payee_name.lower()
|
tx.payee_name is not None
|
||||||
|
and 'amazon' in tx.payee_name.lower()
|
||||||
# don't categorize them if we already have, or if the user has manually approved
|
# don't categorize them if we already have, or if the user has manually approved
|
||||||
and tx.flag_color != 'purple'
|
and tx.flag_color != 'purple'
|
||||||
and not tx.approved
|
and not tx.approved
|
||||||
@@ -85,7 +109,8 @@ def get_tx_categories(
|
|||||||
details = item_details[item.title]
|
details = item_details[item.title]
|
||||||
by_category[details.category].append(item)
|
by_category[details.category].append(item)
|
||||||
|
|
||||||
subtotal = sum(i.price for i in order.items)
|
# we have already ensured that order details are fully populated, so i.price should not be None
|
||||||
|
subtotal = sum(i.price for i in order.items) # type: ignore
|
||||||
tx_categories = []
|
tx_categories = []
|
||||||
for category_name, items in by_category.items():
|
for category_name, items in by_category.items():
|
||||||
category_total = 0
|
category_total = 0
|
||||||
@@ -143,29 +168,6 @@ def build_tx_update(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Context:
|
|
||||||
sync_days: int
|
|
||||||
budget_id: str
|
|
||||||
ynab_token: str
|
|
||||||
amz_accts: list[tuple[str, str]]
|
|
||||||
ynab_client: ynab_client.YnabClient
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
sync_days: int,
|
|
||||||
budget_id: str | None = None,
|
|
||||||
ynab_token: str | None = None,
|
|
||||||
):
|
|
||||||
self.sync_days = sync_days
|
|
||||||
self.amazon_accts = amazon.get_accounts()
|
|
||||||
self.ynab_client = ynab_client.YnabClient(
|
|
||||||
budget_id=budget_id if budget_id else os.environ['YNAB_BUDGET_ID'],
|
|
||||||
ynab_token=ynab_token if ynab_token else os.environ['YNAB_TOKEN'],
|
|
||||||
sync_days=sync_days,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_account(ctx: Context, acct: AmazonAccount):
|
def sync_account(ctx: Context, acct: AmazonAccount):
|
||||||
print(f'Syncing transactions from Amazon account: {acct.email}')
|
print(f'Syncing transactions from Amazon account: {acct.email}')
|
||||||
with amazon.Session(acct) as amz:
|
with amazon.Session(acct) as amz:
|
||||||
@@ -217,6 +219,7 @@ def main():
|
|||||||
@main.command
|
@main.command
|
||||||
@click.option('--days', type=int, default=30)
|
@click.option('--days', type=int, default=30)
|
||||||
def sync(days: int):
|
def sync(days: int):
|
||||||
|
"""Auto-categorize Amazon transactions known to YNAB."""
|
||||||
ctx = Context(days)
|
ctx = Context(days)
|
||||||
for acct in amazon.get_accounts():
|
for acct in amazon.get_accounts():
|
||||||
sync_account(ctx, acct)
|
sync_account(ctx, acct)
|
||||||
|
|||||||
2
uv.lock
generated
2
uv.lock
generated
@@ -81,7 +81,7 @@ wheels = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "catnab"
|
name = "catnab"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "amazon-orders" },
|
{ name = "amazon-orders" },
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
|
|||||||
Reference in New Issue
Block a user