70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
import csv
|
|
import io
|
|
from datetime import datetime
|
|
|
|
|
|
def parse_chase_csv(content: str, account_type: str) -> tuple[list[dict], list[str]]:
|
|
"""Parse a Chase CSV file into transaction dicts.
|
|
|
|
Returns (rows, errors) where rows is a list of dicts with keys:
|
|
date, amount, category, description.
|
|
"""
|
|
content = content.lstrip("\ufeff")
|
|
reader = csv.DictReader(io.StringIO(content))
|
|
|
|
if not reader.fieldnames:
|
|
return [], ["Empty CSV file"]
|
|
|
|
first_field = reader.fieldnames[0].strip()
|
|
if first_field == "Details":
|
|
return _parse_checking(reader)
|
|
elif first_field == "Transaction Date":
|
|
return _parse_credit_card(reader)
|
|
else:
|
|
return [], [f"Unrecognized CSV format (first column: '{first_field}')"]
|
|
|
|
|
|
def _parse_checking(reader: csv.DictReader) -> tuple[list[dict], list[str]]:
|
|
"""Chase checking/savings: Details,Posting Date,Description,Amount,Type,Balance,Check or Slip #"""
|
|
rows = []
|
|
errors = []
|
|
for i, row in enumerate(reader, start=2):
|
|
try:
|
|
date_str = row.get("Posting Date", "").strip()
|
|
description = row.get("Description", "").strip()
|
|
amount_str = row.get("Amount", "").strip()
|
|
if not date_str or not amount_str:
|
|
continue
|
|
rows.append({
|
|
"date": datetime.strptime(date_str, "%m/%d/%Y").date(),
|
|
"amount": float(amount_str),
|
|
"category": "Uncategorized",
|
|
"description": description,
|
|
})
|
|
except (ValueError, KeyError) as e:
|
|
errors.append(f"Row {i}: {e}")
|
|
return rows, errors
|
|
|
|
|
|
def _parse_credit_card(reader: csv.DictReader) -> tuple[list[dict], list[str]]:
|
|
"""Chase credit card: Transaction Date,Post Date,Description,Category,Type,Amount,Memo"""
|
|
rows = []
|
|
errors = []
|
|
for i, row in enumerate(reader, start=2):
|
|
try:
|
|
date_str = row.get("Transaction Date", "").strip()
|
|
description = row.get("Description", "").strip()
|
|
amount_str = row.get("Amount", "").strip()
|
|
category = row.get("Category", "").strip() or "Uncategorized"
|
|
if not date_str or not amount_str:
|
|
continue
|
|
rows.append({
|
|
"date": datetime.strptime(date_str, "%m/%d/%Y").date(),
|
|
"amount": float(amount_str),
|
|
"category": category,
|
|
"description": description,
|
|
})
|
|
except (ValueError, KeyError) as e:
|
|
errors.append(f"Row {i}: {e}")
|
|
return rows, errors
|