#!/usr/bin/env python3 import csv from decimal import Decimal, InvalidOperation import sys from pathlib import Path def convert(in_path, out_path): # Read Halifax CSV and write a cleaned CSV for Actual with open(in_path, newline="", encoding="utf-8-sig") as f_in, \ open(out_path, "w", newline="", encoding="utf-8") as f_out: reader = csv.DictReader(f_in) # Output columns tailored for Actual’s CSV import fieldnames = ["Date", "Payee", "Description", "Amount"] writer = csv.DictWriter(f_out, fieldnames=fieldnames) writer.writeheader() for row in reader: if not row: continue # Date in Halifax export (dd/mm/yyyy) date = (row.get("Transaction Date") or "").strip() if not date: # Skip lines without a date continue desc = (row.get("Transaction Description") or "").strip() payee = desc or (row.get("Transaction Type") or "").strip() # Halifax has separate Debit / Credit columns debit_str = (row.get("Debit Amount") or "").replace(",", "").strip() credit_str = (row.get("Credit Amount") or "").replace(",", "").strip() amount = None # Debits (money going out) → negative if debit_str: try: amount = -Decimal(debit_str) except InvalidOperation: amount = None # Credits (money coming in) → positive elif credit_str: try: amount = Decimal(credit_str) except InvalidOperation: amount = None # If we still don't have an amount, skip the row if amount is None: continue writer.writerow({ "Date": date, "Payee": payee, "Description": desc, "Amount": f"{amount:.2f}", }) def main(): if len(sys.argv) < 2: print("Usage: python clean_halifax_actual.py input.csv [output.csv]") sys.exit(1) in_path = Path(sys.argv[1]) if len(sys.argv) >= 3: out_path = Path(sys.argv[2]) else: # Default to inputname_actual.csv out_path = in_path.with_name(in_path.stem + "_actual.csv") convert(in_path, out_path) print(f"Written cleaned file to {out_path}") if __name__ == "__main__": main()