#!/usr/bin/env python3 import csv from decimal import Decimal, InvalidOperation import sys from pathlib import Path from datetime import datetime def clean_money(value: str) -> Decimal | None: if not value: return None value = ( value.replace("£", "") .replace(",", "") .strip() ) if not value: return None try: return Decimal(value) except InvalidOperation: return None def parse_date(value: str) -> str | None: """ Convert '01 Dec 2025' → '12/01/2025' """ value = value.strip() if not value: return None try: dt = datetime.strptime(value, "%d %b %Y") return dt.strftime("%m/%d/%Y") except ValueError: return None def convert(in_path: Path, out_path: Path): with open(in_path, newline="", encoding="latin-1") as f_in, \ open(out_path, "w", newline="", encoding="utf-8") as f_out: reader = csv.reader(f_in) # Skip metadata lines until we hit the header for row in reader: if row and row[0] == "Date": header = row break else: raise RuntimeError("Could not find Nationwide CSV header row") dict_reader = csv.DictReader(f_in, fieldnames=header) fieldnames = ["Date", "Payee", "Description", "Amount"] writer = csv.DictWriter(f_out, fieldnames=fieldnames) writer.writeheader() for row in dict_reader: raw_date = row.get("Date") or "" date = parse_date(raw_date) if not date: continue desc = (row.get("Description") or "").strip() payee = desc paid_out = clean_money(row.get("Paid out", "")) paid_in = clean_money(row.get("Paid in", "")) if paid_out is not None: amount = -paid_out elif paid_in is not None: amount = paid_in else: continue writer.writerow({ "Date": date, "Payee": payee, "Description": desc, "Amount": f"{amount:.2f}", }) def main(): if len(sys.argv) < 2: print("Usage: python nationwide-cleaner.py input.csv [output.csv]") sys.exit(1) in_path = Path(sys.argv[1]) out_path = Path(sys.argv[2]) if len(sys.argv) >= 3 else 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()