Created
September 27, 2025 00:24
-
-
Save majabojarska/4ae5c7ce056fecddb923d8a577ab7bc4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| This script processes the bank transaction CSV export from the Polish ING bank site | |
| into an encoding and structure ready to import into Actual (https://www.actualbudget.com/). | |
| What this does: | |
| - Reads and decodes the in_file CSV as 'windows-1250'. | |
| - Trims legal cruft and account owner data from the top and bottom of the in_file CSV. | |
| - Changes field separators from ';' to ','. | |
| - Reorders and renames fields to match Actual's import process. | |
| - Encodes the output as UTF8. | |
| """ | |
| import argparse | |
| import csv | |
| from pathlib import Path | |
| from typing import Mapping, IO | |
| import sys | |
| ENCODING_IN: str = "windows-1250" | |
| ENCODING_OUT: str = "utf8" | |
| DELIMITER_IN: str = ";" | |
| DELIMITER_OUT: str = "," | |
| OFFSET_TOP: int = 18 | |
| OFFSET_BOTTOM: int = 3 | |
| # Keys define output order w.r.t. original naming. | |
| # Values define the new column names, corresponding to the keys. | |
| FIELD_NAME_MAP: Mapping[str, str] = { | |
| "Data transakcji": "Date", | |
| "Dane kontrahenta": "Payee", | |
| "Tytuł": "Notes", | |
| "": "Category", | |
| "Kwota transakcji (waluta rachunku)": "Amount", | |
| } | |
| def _get_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser( | |
| description=( | |
| "This script processes the bank transaction CSV export from the Polish ING bank site\n" | |
| "into an encoding and structure that's ready to import into Actual (https://www.actualbudget.com/).\n" | |
| "\n" | |
| "What this does:\n" | |
| "* Reads and decodes the in_file CSV as 'windows-1250'.\n" | |
| "* Trims legal cruft and account owner data from the top and bottom of the in_file CSV.\n" | |
| "* Changes field separators from ';' to ','.\n" | |
| "* Reorders and renames fields to match Actual's import process.\n" | |
| "* Encodes the output as UTF8.\n" | |
| ), | |
| epilog="Writes output to STDOUT.", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| add_help=True, | |
| ) | |
| parser.add_argument( | |
| "csv", | |
| help="path to CSV file exported from Polish ING website", | |
| type=Path, | |
| ) | |
| return parser | |
| def _process_ing_csv(reader: IO, writer: IO) -> None: | |
| # Trim cruft at the top and bottom of the file, leaving only | |
| # the CSV header and actual data. | |
| lines: int = reader.readlines()[ | |
| # Careful with off by ones! | |
| OFFSET_TOP : (-OFFSET_BOTTOM + 1) | |
| ] | |
| reader = csv.DictReader( | |
| lines, | |
| delimiter=DELIMITER_IN, | |
| quoting=csv.QUOTE_MINIMAL, | |
| ) | |
| # Write translated header | |
| print(*FIELD_NAME_MAP.values(), sep=DELIMITER_OUT, file=writer) | |
| csv.DictWriter( | |
| sys.stdout, | |
| fieldnames=FIELD_NAME_MAP, | |
| restval="", | |
| extrasaction="ignore", | |
| quoting=csv.QUOTE_MINIMAL, | |
| ).writerows(reader) | |
| def main(): | |
| parsed = _get_parser().parse_args() | |
| with open(parsed.csv, "r", newline="", encoding=ENCODING_IN) as in_file: | |
| _process_ing_csv(reader=in_file, writer=sys.stdout) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment