add transaction CSV export and category suggestion features

This commit is contained in:
2026-10-08 16:48:03 -04:00
parent c23b8e8ef0
commit 0a1c5b2838
7 changed files with 810 additions and 18 deletions
+33
View File
@@ -2,6 +2,7 @@ import yaml
from datetime import datetime, timedelta
from pathlib import Path
from typing import TypedDict
from typing_extensions import NotRequired
import ynab
@@ -19,6 +20,14 @@ class YNABTransaction(TypedDict):
account_name: str
cleared: str
import_id: str | None
subtransactions: NotRequired[list["YNABSubTransaction"]]
class YNABSubTransaction(TypedDict):
"""Category and amount data for one line of a split transaction."""
category_name: str | None
amount_milliCurrency: int
class YNABConfig(TypedDict):
@@ -75,6 +84,7 @@ class YNABClient:
self,
days: int = 30,
exclude_cleared_statuses: list[str] | None = None,
include_subtransactions: bool = False,
) -> list[YNABTransaction]:
"""
Fetch transactions from configured accounts for the past x days.
@@ -83,6 +93,7 @@ class YNABClient:
days: Number of days to look back (default: 30)
exclude_cleared_statuses: Optional list of normalized statuses to skip,
for example ["reconciled"]
include_subtransactions: Include category-level amounts for split transactions.
Returns:
List of transactions with transformed data
@@ -149,6 +160,17 @@ class YNABClient:
"cleared": cleared_status,
"import_id": txn.import_id,
}
if include_subtransactions:
subtransactions: list[YNABSubTransaction] = []
for sub in txn.subtransactions or []:
category_name = sub.category_name
if not category_name and sub.category_id:
category_name = category_map.get(str(sub.category_id))
subtransactions.append({
"category_name": category_name,
"amount_milliCurrency": sub.amount,
})
processed["subtransactions"] = subtransactions
transactions.append(processed)
except Exception as e:
@@ -173,6 +195,17 @@ class YNABClient:
return account.balance
raise ValueError(f"Account {account_id} not found")
def get_category_ids_by_name(self) -> dict[str, list[str]]:
"""Return category IDs grouped by case-insensitive category name."""
response = self.categories_api.get_categories(self.plan_id)
category_ids: dict[str, list[str]] = {}
for category_group in response.data.category_groups:
for category in category_group.categories:
if category.id and category.name:
key = category.name.strip().casefold()
category_ids.setdefault(key, []).append(str(category.id))
return category_ids
def get_last_reconciled_date(
self,
account_ids: list[str] | None = None,