From 8bc5b30d75c239dc56d92a9889ac85d48b7c2eb0 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Thu, 17 Jul 2025 08:49:02 +0100 Subject: [PATCH 01/17] FIX: converted Transactions2014 to a reusable list --- main.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index 44cfd5b..03148f3 100644 --- a/main.py +++ b/main.py @@ -2,9 +2,10 @@ #this opens the csv file and then maps each line to a dictionary with the column headers as the keys. with open ('./DataFiles/Transactions2014.csv', mode = 'r') as file: Transactions2014 = csv.DictReader(file) + Transactions2014List = list(Transactions2014) accountsList = [] #loops through transactions adding all names to a list of accounts - for lines in Transactions2014: + for lines in Transactions2014List: accountsList.append(lines['From']) accountsList.append(lines['To']) #removes duplicates @@ -14,11 +15,7 @@ for account in accountsList: accountsDict[account] = 0 -#i am unsure why it requires me to open a with block again -#adds the transactions to the accounts in the dictionary -with open ('./DataFiles/Transactions2014.csv', mode = 'r') as file: - Transactions2014 = csv.DictReader(file) - for lines in Transactions2014: + for lines in Transactions2014List: accountsDict[lines['From']] -= float(lines['Amount']) accountsDict[lines['To']] += float(lines['Amount']) From b7d160bbe11e0c40f88dcdaf5679611694c2a597 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Thu, 17 Jul 2025 08:53:42 +0100 Subject: [PATCH 02/17] FIX: converted to pence to avoid floats --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 03148f3..17db395 100644 --- a/main.py +++ b/main.py @@ -16,8 +16,8 @@ accountsDict[account] = 0 for lines in Transactions2014List: - accountsDict[lines['From']] -= float(lines['Amount']) - accountsDict[lines['To']] += float(lines['Amount']) + accountsDict[lines['From']] -= int(float(lines['Amount'])*100) + accountsDict[lines['To']] += int(float(lines['Amount'])*100) print(accountsList) From 22b69eb72752731d7cfa377b4d971a4f10f0b18f Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Thu, 17 Jul 2025 08:59:30 +0100 Subject: [PATCH 03/17] FEAT: extracted code to function --- main.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index 17db395..8f6cff6 100644 --- a/main.py +++ b/main.py @@ -3,11 +3,13 @@ with open ('./DataFiles/Transactions2014.csv', mode = 'r') as file: Transactions2014 = csv.DictReader(file) Transactions2014List = list(Transactions2014) + +def extractAccounts(transactions): accountsList = [] #loops through transactions adding all names to a list of accounts - for lines in Transactions2014List: - accountsList.append(lines['From']) - accountsList.append(lines['To']) + for lines in transactions: + accountsList.append(lines['From']) + accountsList.append(lines['To']) #removes duplicates accountsList = list(set(accountsList)) accountsDict = {} @@ -18,9 +20,10 @@ for lines in Transactions2014List: accountsDict[lines['From']] -= int(float(lines['Amount'])*100) accountsDict[lines['To']] += int(float(lines['Amount'])*100) + print(accountsList) + print(accountsDict) +extractAccounts(Transactions2014List) -print(accountsList) -print(accountsDict) #TODO deal with rounding errors \ No newline at end of file From 8c3a444247117249db1c4769dd03cfeccef1de74 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Thu, 17 Jul 2025 09:16:15 +0100 Subject: [PATCH 04/17] FEAT: added function to list all transactions for a specified account --- main.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 8f6cff6..73d12d8 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ Transactions2014 = csv.DictReader(file) Transactions2014List = list(Transactions2014) -def extractAccounts(transactions): +def List_All(transactions): accountsList = [] #loops through transactions adding all names to a list of accounts for lines in transactions: @@ -23,7 +23,23 @@ def extractAccounts(transactions): print(accountsList) print(accountsDict) -extractAccounts(Transactions2014List) +def List(account, transactions): + listOfTransactions = [] + for lines in transactions: + if lines['From'] == account or lines['To'] == account: + listOfTransactions.append(lines) + print(listOfTransactions) + + + + + + + +List_All(Transactions2014List) +List('Rob S', Transactions2014List) + +#TODO list all should output the names of each person and the total amout they owe or are owed -#TODO deal with rounding errors \ No newline at end of file +#TODO list [acount] should also print a list of every transaction, with a date and narative for that account with that name From 4c3c4b67ad487f84426fc419e1b621651f6a79b0 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Thu, 17 Jul 2025 11:38:59 +0100 Subject: [PATCH 05/17] IN-PROGRESS: attempted to make scripts work for running list-all from command line further work needed --- poetry.lock | 18 ++++++++++++++++++ pyproject.toml | 9 +++++++++ .../DataFiles}/Transactions2014.csv | 0 main.py => supportbank/main.py | 5 ++--- 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 poetry.lock rename {DataFiles => supportbank/DataFiles}/Transactions2014.csv (100%) rename main.py => supportbank/main.py (88%) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..61d287a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,18 @@ +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. + +[[package]] +name = "poetry-core" +version = "2.1.3" +description = "Poetry PEP 517 Build Backend" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["main"] +files = [ + {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"}, + {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "^3.13" +content-hash = "bacfbb6094459c50837708522c2aedc7bd702769442e30545f19d527839b476c" diff --git a/pyproject.toml b/pyproject.toml index dce8eb1..305839d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,16 @@ requires-python = ">=3.13" dependencies = [ ] +[tool.poetry] +package-mode = false + +[tool.poetry.dependencies] +python = "^3.13" +poetry-core = ">=2.0.0,<3.0.0" [build-system] requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +test = "supportbank.main:Test" \ No newline at end of file diff --git a/DataFiles/Transactions2014.csv b/supportbank/DataFiles/Transactions2014.csv similarity index 100% rename from DataFiles/Transactions2014.csv rename to supportbank/DataFiles/Transactions2014.csv diff --git a/main.py b/supportbank/main.py similarity index 88% rename from main.py rename to supportbank/main.py index 73d12d8..2953291 100644 --- a/main.py +++ b/supportbank/main.py @@ -31,7 +31,8 @@ def List(account, transactions): print(listOfTransactions) - +def Test(): + print("that worked") @@ -41,5 +42,3 @@ def List(account, transactions): #TODO list all should output the names of each person and the total amout they owe or are owed - -#TODO list [acount] should also print a list of every transaction, with a date and narative for that account with that name From 6c33091ce5527d7931e1975f1f88823a9f9f6ddc Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Thu, 17 Jul 2025 13:54:46 +0100 Subject: [PATCH 06/17] FEAT: commenced logging --- .../DataFiles/DodgyTransactions2015.csv | 52 +++++++++++++++++++ supportbank/main.py | 30 ++++++++--- 2 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 supportbank/DataFiles/DodgyTransactions2015.csv diff --git a/supportbank/DataFiles/DodgyTransactions2015.csv b/supportbank/DataFiles/DodgyTransactions2015.csv new file mode 100644 index 0000000..e443ddf --- /dev/null +++ b/supportbank/DataFiles/DodgyTransactions2015.csv @@ -0,0 +1,52 @@ +Date,From,To,Narrative,Amount +01/01/2015,Laura B,Sarah T,Misc Morale,10.54 +04/01/2015,Stephen S,Gergana I,Lunch,3.22 +07/01/2015,Ben B,Jon A,Fantasy Football,9.93 +11/01/2015,Ben B,Todd,Lunch,7.33 +12/01/2015,Laura B,Ben B,Beers,8.94 +15/01/2015,Todd,Rob S,Beers,10.19 +16/01/2015,Laura B,Jon A,Lego Assistance,0.69 +18/01/2015,Jon A,Sarah T,Coffee,10.73 +22/01/2015,Gergana I,Todd,Coffee,0.8 +24/01/2015,Gergana I,Chris W,Coffee,8.49 +27/01/2015,Laura B,Tim L,Jenkins Fees,0.52 +29/01/2015,Sam N,Chris W,Lunch,9.01 +01/02/2015,Stephen S,Laura B,Coffee,7.53 +02/02/2015,Stephen S,Rob S,Fantasy Football,7.43 +04/02/2015,Rob S,Stephen S,Fantasy Football,5.78 +06/02/2015,Dan W,Gergana I,Automated Testing Services,10.29 +07/02/2015,Jon A,Chris W,Rails Consultancy,7.37 +10/02/2015,Sam N,Gergana I,Sandbox Help,9.84 +11/02/2015,Chris W,Stephen S,Misc Morale,0.68 +13/02/2015,Laura B,Gergana I,Beers,6.56 +16/02/2015,Chris W,Dan W,Jenkins Fees,1.28 +17/02/2015,Sarah T,Dan W,Sandbox Help,11.48 +18/02/2015,Stephen S,Rob S,Lego Assistance,4.83 +21/02/2015,Dan W,Ben B,Pokemon Training,0.5 +24/02/2015,Todd,Gergana I,Coffee,9.08 +26/02/2015,Stephen S,Jon A,Pokemon Training,4.23 +01/03/2015,Ben B,Sam N,Lunch,One Cheeseburger +02/03/2015,Todd,Chris W,Sandbox Help,1.92 +04/03/2015,Laura B,Jon A,Sandcastle Help,9.09 +06/03/2015,Stephen S,Gergana I,Lego Assistance,9.4 +08/03/2015,Ben B,Sam N,Pokemon Training,11.32 +11/03/2015,Stephen S,Jon A,Sandcastle Help,11.57 +15/03/2015,Todd,Sam N,Pokemon Training,2.29 +19/03/2015,Laura B,Chris W,Coffee,9.96 +21/03/2015,Jon A,Gergana I,Beers,7.54 +25/03/2015,Gergana I,Ben B,Automated Testing Services,2.39 +28/03/2015,Stephen S,Tim L,Lunch,5.83 +29/03/2015,Gergana I,Ben B,Audit and Other Financial Services,10.85 +30/03/2015,Tim L,Todd,Pokemon Training,8.6 +02/04/2015,Stephen S,Chris W,Pokemon Training,1.11 +05/04/2015,Chris W,Ben B,Lunch,0.96 +09/04/2015,Chris W,Rob S,Audit and Other Financial Services,11.79 +12/04/2015,Gergana I,Laura B,Stationary Items,8.37 +13/04/2015,Laura B,Tim L,Services Rendered,1.27 +15/04/2015,Ben B,Sam N,Lego Assistance,8.5 +19/04/2015,Stephen S,Chris W,White Water Rafting,8.91 +23/04/2015,Tim L,Todd,Misc Morale,8.1 +24/04/2015,Tim L,Sarah T,Arcade Social,6.38 +26/04/2015,Jon A,Todd,Stationary Items,8.44 +27/04/2015,Ben B,Jon A,Pokemon Training,10.91 +Last Thursday,Sarah T,Dan W,Beers,5.42 diff --git a/supportbank/main.py b/supportbank/main.py index 2953291..b1f82d5 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -1,19 +1,30 @@ + +#! I am aware that I havent quite got there with making the scripts run correctly. WIP. +#! I am also aware that I have used a mixture of camel/snake/etc will tidy up at the end. + import csv -#this opens the csv file and then maps each line to a dictionary with the column headers as the keys. +import logging +logger = logging.getLogger() +logging.basicConfig(filename='SupportBank.log', filemode='w', level=logging.DEBUG) +logger.debug('I am in the log file') + with open ('./DataFiles/Transactions2014.csv', mode = 'r') as file: Transactions2014 = csv.DictReader(file) Transactions2014List = list(Transactions2014) + logger.info('Transactions2014 read and transoformed to list') + +with open ('./DataFiles/DodgyTransactions2015.csv', mode = 'r') as file: + Transactions2015 = csv.DictReader(file) + Transactions2015List = list(Transactions2015) + logger.info('Transactions2015 read and transoformed to list') def List_All(transactions): accountsList = [] - #loops through transactions adding all names to a list of accounts for lines in transactions: accountsList.append(lines['From']) accountsList.append(lines['To']) - #removes duplicates accountsList = list(set(accountsList)) accountsDict = {} - #sets balance to 0 in account dictionary for account in accountsList: accountsDict[account] = 0 @@ -28,7 +39,8 @@ def List(account, transactions): for lines in transactions: if lines['From'] == account or lines['To'] == account: listOfTransactions.append(lines) - print(listOfTransactions) + print(lines) + #print(listOfTransactions) def Test(): @@ -37,8 +49,10 @@ def Test(): -List_All(Transactions2014List) -List('Rob S', Transactions2014List) +#List_All(Transactions2014List) +#List('Rob S', Transactions2014List) +List_All(Transactions2015List) +List('Sarah T', Transactions2015List) + -#TODO list all should output the names of each person and the total amout they owe or are owed From 97d44dafcf9367d402d577c473644547d302022d Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Thu, 17 Jul 2025 16:45:08 +0100 Subject: [PATCH 07/17] FIX: entrypoints in toml file work now --- pyproject.toml | 3 ++- supportbank/__init__.py | 0 supportbank/main.py | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 supportbank/__init__.py diff --git a/pyproject.toml b/pyproject.toml index 305839d..e5dbda5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,4 +22,5 @@ requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -test = "supportbank.main:Test" \ No newline at end of file +test = "main:Test" +List_All = "main:List_All" diff --git a/supportbank/__init__.py b/supportbank/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/supportbank/main.py b/supportbank/main.py index b1f82d5..e22ae28 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -1,7 +1,8 @@ #! I am aware that I havent quite got there with making the scripts run correctly. WIP. #! I am also aware that I have used a mixture of camel/snake/etc will tidy up at the end. - +#TODO change to classes "accounts" and "transactions" +#TODO is there a way of passing parameters to entry points on console import csv import logging logger = logging.getLogger() From 39cc1dc6e56bc535a3a0fa4ef04d00fd2f85134c Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Fri, 18 Jul 2025 09:12:36 +0100 Subject: [PATCH 08/17] FEAT/FIX: you can now use arguements in the command line to run list_all for the file you want --- poetry.lock | 128 +++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 6 ++- supportbank/main.py | 66 ++++++++++------------- 3 files changed, 159 insertions(+), 41 deletions(-) diff --git a/poetry.lock b/poetry.lock index 61d287a..999da36 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,131 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +[[package]] +name = "black" +version = "25.1.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, + {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, + {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, + {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, + {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, + {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, + {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, + {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, + {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, + {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, + {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, + {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, + {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, + {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, + {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, + {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, + {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, + {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, + {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, + {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, + {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, + {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.2.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + [[package]] name = "poetry-core" version = "2.1.3" @@ -15,4 +141,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = "^3.13" -content-hash = "bacfbb6094459c50837708522c2aedc7bd702769442e30545f19d527839b476c" +content-hash = "030ac4bc503940959814905e3dc89a14c83db26b6422776f69dd4946792cf0c8" diff --git a/pyproject.toml b/pyproject.toml index e5dbda5..1413387 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,10 +17,12 @@ package-mode = false python = "^3.13" poetry-core = ">=2.0.0,<3.0.0" +[tool.poetry.group.dev.dependencies] +black = "^25.1.0" + [build-system] requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -test = "main:Test" -List_All = "main:List_All" +List_All = "main:cli_List_All" diff --git a/supportbank/main.py b/supportbank/main.py index e22ae28..53e5b67 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -1,59 +1,49 @@ +#! I am also aware that I have used a mixture of camel/snake/etc will tidy up at the end. Also why doesnt python have multi-line comments +# TODO change to classes "accounts" and "transactions" +#! Now in the console you can run "poetry run List_All Transactions2014" or other files as you like -#! I am aware that I havent quite got there with making the scripts run correctly. WIP. -#! I am also aware that I have used a mixture of camel/snake/etc will tidy up at the end. -#TODO change to classes "accounts" and "transactions" -#TODO is there a way of passing parameters to entry points on console import csv import logging -logger = logging.getLogger() -logging.basicConfig(filename='SupportBank.log', filemode='w', level=logging.DEBUG) -logger.debug('I am in the log file') +import sys -with open ('./DataFiles/Transactions2014.csv', mode = 'r') as file: - Transactions2014 = csv.DictReader(file) - Transactions2014List = list(Transactions2014) - logger.info('Transactions2014 read and transoformed to list') +logger = logging.getLogger() +logging.basicConfig(filename="SupportBank.log", filemode="w", level=logging.DEBUG) +logger.debug("I am in the log file") -with open ('./DataFiles/DodgyTransactions2015.csv', mode = 'r') as file: - Transactions2015 = csv.DictReader(file) - Transactions2015List = list(Transactions2015) - logger.info('Transactions2015 read and transoformed to list') +def read_transactions(filename): + with open(filename, mode='r') as file: + reader = csv.DictReader(file) + transactions = list(reader) + logger.info(f'{filename} read and transformed to list') + return transactions def List_All(transactions): accountsList = [] for lines in transactions: - accountsList.append(lines['From']) - accountsList.append(lines['To']) + accountsList.append(lines["From"]) + accountsList.append(lines["To"]) accountsList = list(set(accountsList)) accountsDict = {} for account in accountsList: accountsDict[account] = 0 - for lines in Transactions2014List: - accountsDict[lines['From']] -= int(float(lines['Amount'])*100) - accountsDict[lines['To']] += int(float(lines['Amount'])*100) + for lines in transactions: + accountsDict[lines["From"]] -= int(float(lines["Amount"]) * 100) + accountsDict[lines["To"]] += int(float(lines["Amount"]) * 100) print(accountsList) print(accountsDict) +def cli_List_All(): + if len(sys.argv) < 2: + print("Usage: poetry run List_All ") + sys.exit(1) + filename = f"./DataFiles/{sys.argv[1]}.csv" + transactions = read_transactions(filename) + List_All(transactions) + def List(account, transactions): listOfTransactions = [] for lines in transactions: - if lines['From'] == account or lines['To'] == account: + if lines["From"] == account or lines["To"] == account: listOfTransactions.append(lines) - print(lines) - #print(listOfTransactions) - - -def Test(): - print("that worked") - - - - -#List_All(Transactions2014List) -#List('Rob S', Transactions2014List) -List_All(Transactions2015List) -List('Sarah T', Transactions2015List) - - - + print(lines) \ No newline at end of file From d2afd251473301491b355dabc5db4008b6a33050 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Fri, 18 Jul 2025 10:33:36 +0100 Subject: [PATCH 09/17] FEAT/REFACTOR: changed to classes --- pyproject.toml | 1 + supportbank/main.py | 64 ++++++++++++++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1413387..2937b0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,3 +26,4 @@ build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] List_All = "main:cli_List_All" +list_account = "main:cli_List" \ No newline at end of file diff --git a/supportbank/main.py b/supportbank/main.py index 53e5b67..14e351e 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -10,28 +10,49 @@ logging.basicConfig(filename="SupportBank.log", filemode="w", level=logging.DEBUG) logger.debug("I am in the log file") +class Transaction: + def __init__(self, data): + self.Date = data["Date"] + self.From = data["From"] + self.To = data["To"] + self.Narrative = data["Narrative"] + self.Amount = float(data["Amount"]) + +class Account: + def __init__(self, name): + self.name = name + self.balance = 0 + self.transactions = [] + + def apply_transaction(self, transaction): + if transaction.From == self.name: + self.balance -= int(transaction.Amount * 100) + if transaction.To == self.name: + self.balance += int(transaction.Amount * 100) + self.transactions.append(transaction) + def read_transactions(filename): with open(filename, mode='r') as file: reader = csv.DictReader(file) - transactions = list(reader) + transactions = [Transaction(row) for row in reader] logger.info(f'{filename} read and transformed to list') return transactions def List_All(transactions): - accountsList = [] - for lines in transactions: - accountsList.append(lines["From"]) - accountsList.append(lines["To"]) - accountsList = list(set(accountsList)) accountsDict = {} - for account in accountsList: - accountsDict[account] = 0 - for lines in transactions: - accountsDict[lines["From"]] -= int(float(lines["Amount"]) * 100) - accountsDict[lines["To"]] += int(float(lines["Amount"]) * 100) - print(accountsList) - print(accountsDict) + for tx in transactions: + for name in [tx.From, tx.To]: + if name not in accountsDict: + accountsDict[name] = Account(name) + accountsDict[tx.From].apply_transaction(tx) + accountsDict[tx.To].apply_transaction(tx) + + print("Balances:") + for account in accountsDict.values(): + print(f"{account.name}: £{account.balance / 100:.2f}") + print("If you wish to see a detailed list of transactions do the following:") + print("poetry run list_account ") def cli_List_All(): if len(sys.argv) < 2: @@ -42,8 +63,15 @@ def cli_List_All(): List_All(transactions) def List(account, transactions): - listOfTransactions = [] - for lines in transactions: - if lines["From"] == account or lines["To"] == account: - listOfTransactions.append(lines) - print(lines) \ No newline at end of file + for tx in transactions: + if tx.From == account or tx.To == account: + print(f"{tx.Date} | From: {tx.From} | To: {tx.To} | {tx.Narrative} | £{tx.Amount:.2f}") + +def cli_List(): + if len(sys.argv) < 3: + print("Usage: poetry run list_account ") + sys.exit(1) + account = sys.argv[1] + filename = f"./DataFiles/{sys.argv[2]}.csv" + transactions = read_transactions(filename) + List(account, transactions) From be79c7704abc595f80aea38eaf59a43c3e24d069 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Fri, 18 Jul 2025 11:38:38 +0100 Subject: [PATCH 10/17] FEAT: changed to user input --- pyproject.toml | 3 +-- supportbank/main.py | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2937b0e..f6f31bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,5 +25,4 @@ requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -List_All = "main:cli_List_All" -list_account = "main:cli_List" \ No newline at end of file +supportbank = "main:main" \ No newline at end of file diff --git a/supportbank/main.py b/supportbank/main.py index 14e351e..5e2b554 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -8,7 +8,7 @@ logger = logging.getLogger() logging.basicConfig(filename="SupportBank.log", filemode="w", level=logging.DEBUG) -logger.debug("I am in the log file") +logger.info("Logging started.") class Transaction: def __init__(self, data): @@ -17,12 +17,14 @@ def __init__(self, data): self.To = data["To"] self.Narrative = data["Narrative"] self.Amount = float(data["Amount"]) + logger.info("Transaction created successfully") class Account: def __init__(self, name): self.name = name self.balance = 0 self.transactions = [] + logger.info("Account created successfully.") def apply_transaction(self, transaction): if transaction.From == self.name: @@ -51,8 +53,7 @@ def List_All(transactions): print("Balances:") for account in accountsDict.values(): print(f"{account.name}: £{account.balance / 100:.2f}") - print("If you wish to see a detailed list of transactions do the following:") - print("poetry run list_account ") + logger.info("Accounts and balances displayed successfully") def cli_List_All(): if len(sys.argv) < 2: @@ -75,3 +76,33 @@ def cli_List(): filename = f"./DataFiles/{sys.argv[2]}.csv" transactions = read_transactions(filename) List(account, transactions) + +def main(): + print("Welcome to SupportBank") + while True: + print("\nWhat would you like to do?") + print("1. List all balances") + print("2. List transactions for an account") + print("3. Quit") + choice = input("Enter the number of your choice: ").strip() + + if choice == "1": + filename = input("Enter CSV filename (without .csv): ").strip() + try: + transactions = read_transactions(f"./DataFiles/{filename}.csv") + List_All(transactions) + except FileNotFoundError: + print("File not found.") + elif choice == "2": + account = input("Enter account name: ").strip() + filename = input("Enter CSV filename (without .csv): ").strip() + try: + transactions = read_transactions(f"./DataFiles/{filename}.csv") + List(account, transactions) + except FileNotFoundError: + print("File not found.") + elif choice == "3": + print("Goodbye!") + break + else: + print("Invalid choice. Please try again.") From ceec104316f3a03285b17b47cff4d867f7d9c43e Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Fri, 18 Jul 2025 11:56:28 +0100 Subject: [PATCH 11/17] FEAT: added listing of available csv files --- supportbank/main.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/supportbank/main.py b/supportbank/main.py index 5e2b554..8845184 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -1,10 +1,9 @@ #! I am also aware that I have used a mixture of camel/snake/etc will tidy up at the end. Also why doesnt python have multi-line comments -# TODO change to classes "accounts" and "transactions" -#! Now in the console you can run "poetry run List_All Transactions2014" or other files as you like import csv import logging import sys +import os logger = logging.getLogger() logging.basicConfig(filename="SupportBank.log", filemode="w", level=logging.DEBUG) @@ -40,6 +39,21 @@ def read_transactions(filename): logger.info(f'{filename} read and transformed to list') return transactions +def list_available_csv_files(): + data_dir = "./DataFiles" + try: + files = [f[:-4] for f in os.listdir(data_dir) if f.endswith(".csv")] + if not files: + print("No CSV files found in ./DataFiles/") + else: + print("Available CSV files:") + for f in files: + print(f" - {f}") + return files + except FileNotFoundError: + print("The ./DataFiles directory does not exist") + return [] + def List_All(transactions): accountsDict = {} @@ -64,9 +78,14 @@ def cli_List_All(): List_All(transactions) def List(account, transactions): + account_found = False for tx in transactions: if tx.From == account or tx.To == account: + account_found = True print(f"{tx.Date} | From: {tx.From} | To: {tx.To} | {tx.Narrative} | £{tx.Amount:.2f}") + if not account_found: + print(f"Account '{account}' not found in any transactions.") + def cli_List(): if len(sys.argv) < 3: @@ -87,6 +106,9 @@ def main(): choice = input("Enter the number of your choice: ").strip() if choice == "1": + available = list_available_csv_files() + if not available: + continue filename = input("Enter CSV filename (without .csv): ").strip() try: transactions = read_transactions(f"./DataFiles/{filename}.csv") @@ -95,12 +117,16 @@ def main(): print("File not found.") elif choice == "2": account = input("Enter account name: ").strip() + available = list_available_csv_files() + if not available: + continue filename = input("Enter CSV filename (without .csv): ").strip() try: transactions = read_transactions(f"./DataFiles/{filename}.csv") List(account, transactions) except FileNotFoundError: print("File not found.") + elif choice == "3": print("Goodbye!") break From 184548484986567e03c2da4135ee76e0cfd3ca7c Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Fri, 18 Jul 2025 12:15:45 +0100 Subject: [PATCH 12/17] FIX: logging changed --- supportbank/main.py | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/supportbank/main.py b/supportbank/main.py index 8845184..93839b5 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -16,14 +16,12 @@ def __init__(self, data): self.To = data["To"] self.Narrative = data["Narrative"] self.Amount = float(data["Amount"]) - logger.info("Transaction created successfully") class Account: def __init__(self, name): self.name = name self.balance = 0 self.transactions = [] - logger.info("Account created successfully.") def apply_transaction(self, transaction): if transaction.From == self.name: @@ -33,11 +31,14 @@ def apply_transaction(self, transaction): self.transactions.append(transaction) def read_transactions(filename): - with open(filename, mode='r') as file: - reader = csv.DictReader(file) - transactions = [Transaction(row) for row in reader] - logger.info(f'{filename} read and transformed to list') - return transactions + try: + with open(filename, mode='r') as file: + reader = csv.DictReader(file) + transactions = [Transaction(row) for row in reader] + logger.info(f'{filename} read and transformed to list') + return transactions + except Exception as e: + logger.error(f"failed to read {filename}: {e}") def list_available_csv_files(): data_dir = "./DataFiles" @@ -45,18 +46,21 @@ def list_available_csv_files(): files = [f[:-4] for f in os.listdir(data_dir) if f.endswith(".csv")] if not files: print("No CSV files found in ./DataFiles/") + logger.warning("No CSV files found in ./DataFiles/") else: print("Available CSV files:") for f in files: print(f" - {f}") + logger.info("available CSV files listed") return files except FileNotFoundError: print("The ./DataFiles directory does not exist") + logger.warning("The ./DataFiles directory does not exist") return [] def List_All(transactions): accountsDict = {} - + logger.info("showing account balances") for tx in transactions: for name in [tx.From, tx.To]: if name not in accountsDict: @@ -69,32 +73,16 @@ def List_All(transactions): print(f"{account.name}: £{account.balance / 100:.2f}") logger.info("Accounts and balances displayed successfully") -def cli_List_All(): - if len(sys.argv) < 2: - print("Usage: poetry run List_All ") - sys.exit(1) - filename = f"./DataFiles/{sys.argv[1]}.csv" - transactions = read_transactions(filename) - List_All(transactions) - def List(account, transactions): account_found = False + logger.info(f"listing transactions for {account}") for tx in transactions: if tx.From == account or tx.To == account: account_found = True print(f"{tx.Date} | From: {tx.From} | To: {tx.To} | {tx.Narrative} | £{tx.Amount:.2f}") if not account_found: print(f"Account '{account}' not found in any transactions.") - - -def cli_List(): - if len(sys.argv) < 3: - print("Usage: poetry run list_account ") - sys.exit(1) - account = sys.argv[1] - filename = f"./DataFiles/{sys.argv[2]}.csv" - transactions = read_transactions(filename) - List(account, transactions) + logger.warning(f"Account '{account}' not found in any transactions.") def main(): print("Welcome to SupportBank") @@ -104,6 +92,7 @@ def main(): print("2. List transactions for an account") print("3. Quit") choice = input("Enter the number of your choice: ").strip() + logger.info(f"User selected choice: {choice}") if choice == "1": available = list_available_csv_files() From c9bd0779fbb17926c59afff56b8b8cfd7fa7cc59 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Fri, 18 Jul 2025 14:15:03 +0100 Subject: [PATCH 13/17] FIX/FEAT: correctly handles dodgy transactions --- supportbank/main.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/supportbank/main.py b/supportbank/main.py index 93839b5..9a37faf 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -2,7 +2,7 @@ import csv import logging -import sys +from datetime import datetime import os logger = logging.getLogger() @@ -31,14 +31,30 @@ def apply_transaction(self, transaction): self.transactions.append(transaction) def read_transactions(filename): - try: - with open(filename, mode='r') as file: - reader = csv.DictReader(file) - transactions = [Transaction(row) for row in reader] - logger.info(f'{filename} read and transformed to list') - return transactions - except Exception as e: - logger.error(f"failed to read {filename}: {e}") + with open(filename, mode='r') as file: + reader = csv.DictReader(file) + transactions = [] + line_number = 2 + + for row in reader: + try: + float(row["Amount"]) + + datetime.strptime(row["Date"], "%d/%m/%Y") + + transaction = Transaction(row) + transactions.append(transaction) + + except ValueError as e: + account_name = row.get("From", "UNKNOWN") + logger.warning(f"Invalid transaction on line {line_number}: {row} — {e}") + print(f"Skipped invalid transaction on line {line_number} involving account '{account_name}'. See log for details.") + + line_number += 1 + + logger.info(f"{filename} read with {len(transactions)} valid transactions") + return transactions + def list_available_csv_files(): data_dir = "./DataFiles" From 68ee054a28da87e05021515fb8d6ea54cf171025 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Fri, 18 Jul 2025 16:45:27 +0100 Subject: [PATCH 14/17] CHORE: added json and xml file --- supportbank/DataFiles/Transactions2013.json | 1017 ++++++++++++++ .../DataFiles/Unconfirmed 360039.crdownload | 1195 +++++++++++++++++ 2 files changed, 2212 insertions(+) create mode 100644 supportbank/DataFiles/Transactions2013.json create mode 100644 supportbank/DataFiles/Unconfirmed 360039.crdownload diff --git a/supportbank/DataFiles/Transactions2013.json b/supportbank/DataFiles/Transactions2013.json new file mode 100644 index 0000000..760e217 --- /dev/null +++ b/supportbank/DataFiles/Transactions2013.json @@ -0,0 +1,1017 @@ +[ + { + "Date": "2013-01-01T00:00:00", + "FromAccount": "Jon A", + "ToAccount": "Gergana I", + "Narrative": "Sandbox Help", + "Amount": 2.14 + }, + { + "Date": "2013-01-04T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Dan W", + "Narrative": "Coffee", + "Amount": 10.53 + }, + { + "Date": "2013-01-05T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Tim L", + "Narrative": "Sandcastle Help", + "Amount": 10.57 + }, + { + "Date": "2013-01-09T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Rob S", + "Narrative": "Coffee", + "Amount": 8.19 + }, + { + "Date": "2013-01-13T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Sarah T", + "Narrative": "Coffee", + "Amount": 1.01 + }, + { + "Date": "2013-01-15T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Laura B", + "Narrative": "Golf Bets", + "Amount": 2.71 + }, + { + "Date": "2013-01-18T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Tim L", + "Narrative": "Jenkins Fees", + "Amount": 7.17 + }, + { + "Date": "2013-01-22T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Rob S", + "Narrative": "Sandcastle Help", + "Amount": 11.12 + }, + { + "Date": "2013-01-25T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Jon A", + "Narrative": "Coffee", + "Amount": 4.86 + }, + { + "Date": "2013-01-28T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Ben B", + "Narrative": "Services Rendered", + "Amount": 1.92 + }, + { + "Date": "2013-01-30T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Todd", + "Narrative": "Jenkins Fees", + "Amount": 11.95 + }, + { + "Date": "2013-01-31T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Chris W", + "Narrative": "Poker Winnings", + "Amount": 7.55 + }, + { + "Date": "2013-02-04T00:00:00", + "FromAccount": "Sam N", + "ToAccount": "Sarah T", + "Narrative": "Arcade Social", + "Amount": 5.52 + }, + { + "Date": "2013-02-06T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Rob S", + "Narrative": "Snooker Night", + "Amount": 8.07 + }, + { + "Date": "2013-02-07T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Laura B", + "Narrative": "Beers", + "Amount": 7.63 + }, + { + "Date": "2013-02-10T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Todd", + "Narrative": "Snooker Night", + "Amount": 10.4 + }, + { + "Date": "2013-02-12T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Chris W", + "Narrative": "Misc Morale", + "Amount": 5.76 + }, + { + "Date": "2013-02-15T00:00:00", + "FromAccount": "Sam N", + "ToAccount": "Chris W", + "Narrative": "Lunch", + "Amount": 1.54 + }, + { + "Date": "2013-02-18T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Sam N", + "Narrative": "Coffee", + "Amount": 6.98 + }, + { + "Date": "2013-02-22T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Tim L", + "Narrative": "Lunch", + "Amount": 8.77 + }, + { + "Date": "2013-02-26T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Stephen S", + "Narrative": "Audit and Other Financial Services", + "Amount": 5.94 + }, + { + "Date": "2013-02-27T00:00:00", + "FromAccount": "Jon A", + "ToAccount": "Chris W", + "Narrative": "Rails Consultancy", + "Amount": 10.58 + }, + { + "Date": "2013-03-02T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Stephen S", + "Narrative": "Audit and Other Financial Services", + "Amount": 1.73 + }, + { + "Date": "2013-03-06T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Sam N", + "Narrative": "Stationary Items", + "Amount": 7.26 + }, + { + "Date": "2013-03-08T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Sarah T", + "Narrative": "Pokemon Training", + "Amount": 6.2 + }, + { + "Date": "2013-03-12T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Jon A", + "Narrative": "Automated Testing Services", + "Amount": 0.9 + }, + { + "Date": "2013-03-14T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Chris W", + "Narrative": "Rails Consultancy", + "Amount": 8.97 + }, + { + "Date": "2013-03-17T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Chris W", + "Narrative": "Audit and Other Financial Services", + "Amount": 3.34 + }, + { + "Date": "2013-03-20T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Rob S", + "Narrative": "Stationary Items", + "Amount": 2.85 + }, + { + "Date": "2013-03-22T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Laura B", + "Narrative": "Git-Fu Rendered", + "Amount": 4.15 + }, + { + "Date": "2013-03-24T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Stephen S", + "Narrative": "Coffee", + "Amount": 2.39 + }, + { + "Date": "2013-03-27T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Dan W", + "Narrative": "Jenkins Fees", + "Amount": 7.82 + }, + { + "Date": "2013-03-28T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Stephen S", + "Narrative": "Sandbox Help", + "Amount": 6.36 + }, + { + "Date": "2013-04-01T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Tim L", + "Narrative": "Coffee", + "Amount": 4.77 + }, + { + "Date": "2013-04-03T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Stephen S", + "Narrative": "Lunch", + "Amount": 2.64 + }, + { + "Date": "2013-04-05T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Chris W", + "Narrative": "Coffee", + "Amount": 3.4 + }, + { + "Date": "2013-04-08T00:00:00", + "FromAccount": "Tim L", + "ToAccount": "Todd", + "Narrative": "Jenkins Fees", + "Amount": 4.27 + }, + { + "Date": "2013-04-09T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Gergana I", + "Narrative": "Misc Morale", + "Amount": 2.6 + }, + { + "Date": "2013-04-11T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Sarah T", + "Narrative": "Lunch", + "Amount": 3 + }, + { + "Date": "2013-04-15T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Chris W", + "Narrative": "Coffee", + "Amount": 5.49 + }, + { + "Date": "2013-04-18T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Ben B", + "Narrative": "Lunch", + "Amount": 9.07 + }, + { + "Date": "2013-04-21T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Sarah T", + "Narrative": "Sandcastle Help", + "Amount": 5.32 + }, + { + "Date": "2013-04-24T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Tim L", + "Narrative": "Fantasy Football", + "Amount": 1.15 + }, + { + "Date": "2013-04-25T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Jon A", + "Narrative": "Coffee", + "Amount": 2.47 + }, + { + "Date": "2013-04-26T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Jon A", + "Narrative": "Misc Morale", + "Amount": 9.1 + }, + { + "Date": "2013-04-27T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Chris W", + "Narrative": "Rails Consultancy", + "Amount": 9.91 + }, + { + "Date": "2013-05-01T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Dan W", + "Narrative": "Git-Fu Rendered", + "Amount": 9.85 + }, + { + "Date": "2013-05-04T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Dan W", + "Narrative": "Coffee", + "Amount": 1.34 + }, + { + "Date": "2013-05-05T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Ben B", + "Narrative": "Beers", + "Amount": 0.51 + }, + { + "Date": "2013-05-06T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Dan W", + "Narrative": "Git-Fu Rendered", + "Amount": 4.92 + }, + { + "Date": "2013-05-08T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Tim L", + "Narrative": "Coffee", + "Amount": 5.94 + }, + { + "Date": "2013-05-09T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Tim L", + "Narrative": "White Water Rafting", + "Amount": 2.27 + }, + { + "Date": "2013-05-13T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Ben B", + "Narrative": "Coffee", + "Amount": 11.07 + }, + { + "Date": "2013-05-14T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Dan W", + "Narrative": "Rails Consultancy", + "Amount": 9.96 + }, + { + "Date": "2013-05-18T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Sarah T", + "Narrative": "Lunch", + "Amount": 11.94 + }, + { + "Date": "2013-05-22T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Sam N", + "Narrative": "Pokemon Training", + "Amount": 9.24 + }, + { + "Date": "2013-05-25T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Chris W", + "Narrative": "Sandbox Help", + "Amount": 2.85 + }, + { + "Date": "2013-05-29T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Ben B", + "Narrative": "Audit and Other Financial Services", + "Amount": 1.51 + }, + { + "Date": "2013-06-02T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Ben B", + "Narrative": "Golf Bets", + "Amount": 11.83 + }, + { + "Date": "2013-06-03T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Rob S", + "Narrative": "Lunch", + "Amount": 2.16 + }, + { + "Date": "2013-06-05T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Dan W", + "Narrative": "Golf Bets", + "Amount": 1.96 + }, + { + "Date": "2013-06-06T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Jon A", + "Narrative": "Pokemon Training", + "Amount": 1.06 + }, + { + "Date": "2013-06-10T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Gergana I", + "Narrative": "Lego Assistance", + "Amount": 10.81 + }, + { + "Date": "2013-06-12T00:00:00", + "FromAccount": "Jon A", + "ToAccount": "Ben B", + "Narrative": "Sandcastle Help", + "Amount": 6.75 + }, + { + "Date": "2013-06-13T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Sarah T", + "Narrative": "Poker Winnings", + "Amount": 5.15 + }, + { + "Date": "2013-06-16T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Dan W", + "Narrative": "Misc Morale", + "Amount": 9.24 + }, + { + "Date": "2013-06-19T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Jon A", + "Narrative": "Coffee", + "Amount": 3.68 + }, + { + "Date": "2013-06-21T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Todd", + "Narrative": "Lunch", + "Amount": 5.43 + }, + { + "Date": "2013-06-22T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Tim L", + "Narrative": "Stationary Items", + "Amount": 0.98 + }, + { + "Date": "2013-06-23T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Jon A", + "Narrative": "Audit and Other Financial Services", + "Amount": 6.12 + }, + { + "Date": "2013-06-26T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Sarah T", + "Narrative": "Lunch", + "Amount": 8.39 + }, + { + "Date": "2013-06-27T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Ben B", + "Narrative": "Beers", + "Amount": 10.41 + }, + { + "Date": "2013-06-30T00:00:00", + "FromAccount": "Tim L", + "ToAccount": "Laura B", + "Narrative": "Lunch", + "Amount": 6.51 + }, + { + "Date": "2013-07-01T00:00:00", + "FromAccount": "Tim L", + "ToAccount": "Jon A", + "Narrative": "Misc Morale", + "Amount": 1.48 + }, + { + "Date": "2013-07-04T00:00:00", + "FromAccount": "Tim L", + "ToAccount": "Chris W", + "Narrative": "Poker Winnings", + "Amount": 5.63 + }, + { + "Date": "2013-07-05T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Sam N", + "Narrative": "Lunch", + "Amount": 6.73 + }, + { + "Date": "2013-07-07T00:00:00", + "FromAccount": "Jon A", + "ToAccount": "Rob S", + "Narrative": "Lunch", + "Amount": 6.69 + }, + { + "Date": "2013-07-10T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Sarah T", + "Narrative": "Lunch", + "Amount": 3.88 + }, + { + "Date": "2013-07-13T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Tim L", + "Narrative": "Misc Morale", + "Amount": 2.08 + }, + { + "Date": "2013-07-16T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Gergana I", + "Narrative": "Coffee", + "Amount": 6.95 + }, + { + "Date": "2013-07-19T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Stephen S", + "Narrative": "Arcade Social", + "Amount": 3.21 + }, + { + "Date": "2013-07-23T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Stephen S", + "Narrative": "Services Rendered", + "Amount": 11.17 + }, + { + "Date": "2013-07-27T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Tim L", + "Narrative": "Coffee", + "Amount": 4.29 + }, + { + "Date": "2013-07-30T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Todd", + "Narrative": "Beers", + "Amount": 0.69 + }, + { + "Date": "2013-07-31T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Sam N", + "Narrative": "Pokemon Training", + "Amount": 1.2 + }, + { + "Date": "2013-08-01T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Todd", + "Narrative": "Lunch", + "Amount": 11.18 + }, + { + "Date": "2013-08-05T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Tim L", + "Narrative": "Jenkins Fees", + "Amount": 11.34 + }, + { + "Date": "2013-08-07T00:00:00", + "FromAccount": "Jon A", + "ToAccount": "Rob S", + "Narrative": "Poker Winnings", + "Amount": 10.78 + }, + { + "Date": "2013-08-08T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Stephen S", + "Narrative": "Lunch", + "Amount": 10.07 + }, + { + "Date": "2013-08-11T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Chris W", + "Narrative": "Lunch", + "Amount": 7.27 + }, + { + "Date": "2013-08-13T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Chris W", + "Narrative": "Sandcastle Help", + "Amount": 9.96 + }, + { + "Date": "2013-08-16T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Ben B", + "Narrative": "Misc Morale", + "Amount": 10.91 + }, + { + "Date": "2013-08-19T00:00:00", + "FromAccount": "Tim L", + "ToAccount": "Sam N", + "Narrative": "Coffee", + "Amount": 7.51 + }, + { + "Date": "2013-08-22T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Laura B", + "Narrative": "Misc Morale", + "Amount": 7.05 + }, + { + "Date": "2013-08-23T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Sam N", + "Narrative": "Beers", + "Amount": 0.6 + }, + { + "Date": "2013-08-26T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Rob S", + "Narrative": "Lunch", + "Amount": 3.85 + }, + { + "Date": "2013-08-27T00:00:00", + "FromAccount": "Sam N", + "ToAccount": "Gergana I", + "Narrative": "Fantasy Football", + "Amount": 5.45 + }, + { + "Date": "2013-08-28T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Chris W", + "Narrative": "Jenkins Fees", + "Amount": 11.13 + }, + { + "Date": "2013-08-31T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Laura B", + "Narrative": "Pokemon Training", + "Amount": 5.54 + }, + { + "Date": "2013-09-03T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Ben B", + "Narrative": "Coffee", + "Amount": 9.48 + }, + { + "Date": "2013-09-05T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Tim L", + "Narrative": "Lunch", + "Amount": 10.58 + }, + { + "Date": "2013-09-07T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Laura B", + "Narrative": "Lunch", + "Amount": 3.32 + }, + { + "Date": "2013-09-10T00:00:00", + "FromAccount": "Tim L", + "ToAccount": "Ben B", + "Narrative": "Misc Morale", + "Amount": 8.68 + }, + { + "Date": "2013-09-12T00:00:00", + "FromAccount": "Tim L", + "ToAccount": "Laura B", + "Narrative": "Lego Assistance", + "Amount": 2.53 + }, + { + "Date": "2013-09-13T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Laura B", + "Narrative": "Services Rendered", + "Amount": 1.96 + }, + { + "Date": "2013-09-16T00:00:00", + "FromAccount": "Sam N", + "ToAccount": "Rob S", + "Narrative": "Beers", + "Amount": 2.81 + }, + { + "Date": "2013-09-19T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Gergana I", + "Narrative": "Beers", + "Amount": 5.52 + }, + { + "Date": "2013-09-22T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Dan W", + "Narrative": "Sandbox Help", + "Amount": 9.28 + }, + { + "Date": "2013-09-23T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Dan W", + "Narrative": "Coffee", + "Amount": 5.13 + }, + { + "Date": "2013-09-27T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Sarah T", + "Narrative": "Stationary Items", + "Amount": 7.05 + }, + { + "Date": "2013-10-01T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Tim L", + "Narrative": "Coffee", + "Amount": 10.13 + }, + { + "Date": "2013-10-04T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Ben B", + "Narrative": "Coffee", + "Amount": 5.96 + }, + { + "Date": "2013-10-08T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Laura B", + "Narrative": "Lunch", + "Amount": 5.33 + }, + { + "Date": "2013-10-11T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Rob S", + "Narrative": "Lunch", + "Amount": 10.62 + }, + { + "Date": "2013-10-12T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Chris W", + "Narrative": "Jenkins Fees", + "Amount": 7.78 + }, + { + "Date": "2013-10-16T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Rob S", + "Narrative": "Arcade Social", + "Amount": 5.71 + }, + { + "Date": "2013-10-17T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Jon A", + "Narrative": "Arcade Social", + "Amount": 7.71 + }, + { + "Date": "2013-10-20T00:00:00", + "FromAccount": "Sam N", + "ToAccount": "Sarah T", + "Narrative": "Lunch", + "Amount": 10.6 + }, + { + "Date": "2013-10-23T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Todd", + "Narrative": "Lego Assistance", + "Amount": 3.42 + }, + { + "Date": "2013-10-27T00:00:00", + "FromAccount": "Sam N", + "ToAccount": "Dan W", + "Narrative": "Misc Morale", + "Amount": 6.89 + }, + { + "Date": "2013-10-29T00:00:00", + "FromAccount": "Dan W", + "ToAccount": "Rob S", + "Narrative": "Misc Morale", + "Amount": 9.99 + }, + { + "Date": "2013-11-01T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Laura B", + "Narrative": "Lunch", + "Amount": 7.01 + }, + { + "Date": "2013-11-05T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Rob S", + "Narrative": "Spline Reticulation", + "Amount": 8.3 + }, + { + "Date": "2013-11-07T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Sarah T", + "Narrative": "Lunch", + "Amount": 10.09 + }, + { + "Date": "2013-11-09T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Tim L", + "Narrative": "Stationary Items", + "Amount": 11 + }, + { + "Date": "2013-11-12T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Sam N", + "Narrative": "Misc Morale", + "Amount": 1.25 + }, + { + "Date": "2013-11-15T00:00:00", + "FromAccount": "Jon A", + "ToAccount": "Tim L", + "Narrative": "Sandbox Help", + "Amount": 9.13 + }, + { + "Date": "2013-11-18T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Dan W", + "Narrative": "Services Rendered", + "Amount": 9.4 + }, + { + "Date": "2013-11-21T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Chris W", + "Narrative": "Misc Morale", + "Amount": 2.53 + }, + { + "Date": "2013-11-24T00:00:00", + "FromAccount": "Tim L", + "ToAccount": "Sam N", + "Narrative": "Rails Consultancy", + "Amount": 7.59 + }, + { + "Date": "2013-11-28T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Todd", + "Narrative": "Golf Bets", + "Amount": 2.15 + }, + { + "Date": "2013-11-29T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Sam N", + "Narrative": "Pokemon Training", + "Amount": 4.01 + }, + { + "Date": "2013-12-01T00:00:00", + "FromAccount": "Sam N", + "ToAccount": "Sarah T", + "Narrative": "Lunch", + "Amount": 6.35 + }, + { + "Date": "2013-12-02T00:00:00", + "FromAccount": "Jon A", + "ToAccount": "Todd", + "Narrative": "Misc Morale", + "Amount": 10.19 + }, + { + "Date": "2013-12-05T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Sam N", + "Narrative": "Coffee", + "Amount": 9 + }, + { + "Date": "2013-12-09T00:00:00", + "FromAccount": "Laura B", + "ToAccount": "Tim L", + "Narrative": "Rails Consultancy", + "Amount": 9.39 + }, + { + "Date": "2013-12-12T00:00:00", + "FromAccount": "Chris W", + "ToAccount": "Tim L", + "Narrative": "Coffee", + "Amount": 11.44 + }, + { + "Date": "2013-12-13T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Tim L", + "Narrative": "Misc Morale", + "Amount": 9.19 + }, + { + "Date": "2013-12-17T00:00:00", + "FromAccount": "Sarah T", + "ToAccount": "Ben B", + "Narrative": "Pokemon Training", + "Amount": 8.72 + }, + { + "Date": "2013-12-19T00:00:00", + "FromAccount": "Todd", + "ToAccount": "Ben B", + "Narrative": "Pokemon Training", + "Amount": 8.72 + }, + { + "Date": "2013-12-22T00:00:00", + "FromAccount": "Gergana I", + "ToAccount": "Sam N", + "Narrative": "Lunch", + "Amount": 1.32 + }, + { + "Date": "2013-12-24T00:00:00", + "FromAccount": "Ben B", + "ToAccount": "Rob S", + "Narrative": "Snooker Night", + "Amount": 10.14 + }, + { + "Date": "2013-12-27T00:00:00", + "FromAccount": "Stephen S", + "ToAccount": "Todd", + "Narrative": "Spline Reticulation", + "Amount": 4.34 + }, + { + "Date": "2013-12-28T00:00:00", + "FromAccount": "Jon A", + "ToAccount": "Chris W", + "Narrative": "Beers", + "Amount": 5.99 + }, + { + "Date": "2013-12-30T00:00:00", + "FromAccount": "Rob S", + "ToAccount": "Sam N", + "Narrative": "Misc Morale", + "Amount": 2.54 + } +] \ No newline at end of file diff --git a/supportbank/DataFiles/Unconfirmed 360039.crdownload b/supportbank/DataFiles/Unconfirmed 360039.crdownload new file mode 100644 index 0000000..10c4487 --- /dev/null +++ b/supportbank/DataFiles/Unconfirmed 360039.crdownload @@ -0,0 +1,1195 @@ + + + + Snooker Night + 9.22 + + Gergana I + Jon A + + + + Coffee + 6.98 + + Sam N + Dan W + + + + Lunch + 10.67 + + Stephen S + Sarah T + + + + Misc Morale + 7.19 + + Rob S + Sarah T + + + + Fantasy Football + 10.73 + + Tim L + Chris W + + + + Automated Testing Services + 10.1 + + Tim L + Rob S + + + + Beers + 1.79 + + Sarah T + Sam N + + + + Beers + 11.1 + + Dan W + Todd + + + + Coffee + 10.89 + + Chris W + Ben B + + + + Pokemon Training + 3.12 + + Chris W + Tim L + + + + Snooker Night + 2.29 + + Rob S + Tim L + + + + Spline Reticulation + 7.32 + + Chris W + Rob S + + + + Stationary Items + 6.1 + + Stephen S + Gergana I + + + + Lunch + 7.72 + + Stephen S + Todd + + + + Misc Morale + 5.2 + + Stephen S + Laura B + + + + Pokemon Training + 4.67 + + Sam N + Rob S + + + + Coffee + 11.72 + + Ben B + Chris W + + + + Pokemon Training + 6.45 + + Rob S + Jon A + + + + Sandcastle Help + 8.46 + + Todd + Ben B + + + + Lunch + 10.36 + + Laura B + Todd + + + + Pokemon Training + 3.88 + + Chris W + Stephen S + + + + Beers + 9.81 + + Sam N + Dan W + + + + Lunch + 3.67 + + Gergana I + Rob S + + + + Lunch + 0.63 + + Dan W + Tim L + + + + Jenkins Fees + 2.61 + + Tim L + Ben B + + + + Coffee + 4.85 + + Rob S + Ben B + + + + Lunch + 3.14 + + Stephen S + Gergana I + + + + Arcade Social + 9.01 + + Gergana I + Chris W + + + + Lego Assistance + 6.33 + + Gergana I + Rob S + + + + Misc Morale + 11.84 + + Dan W + Stephen S + + + + Spline Reticulation + 9.1 + + Jon A + Sarah T + + + + Coffee + 8.41 + + Stephen S + Dan W + + + + Lunch + 2.38 + + Stephen S + Tim L + + + + Snooker Night + 1.9 + + Gergana I + Laura B + + + + Sandcastle Help + 1.89 + + Sarah T + Gergana I + + + + Stationary Items + 3.8 + + Laura B + Dan W + + + + Fantasy Football + 11.63 + + Dan W + Chris W + + + + Misc Morale + 7.32 + + Dan W + Todd + + + + Audit and Other Financial Services + 6.06 + + Sam N + Chris W + + + + Arcade Social + 11.17 + + Tim L + Chris W + + + + Lego Assistance + 5.49 + + Sarah T + Dan W + + + + Git-Fu Rendered + 0.8 + + Gergana I + Jon A + + + + Lunch + 5.43 + + Sam N + Todd + + + + Lunch + 10.46 + + Laura B + Dan W + + + + Beers + 11.7 + + Laura B + Rob S + + + + Pokemon Training + 11.09 + + Stephen S + Ben B + + + + Audit and Other Financial Services + 8.44 + + Todd + Dan W + + + + Fantasy Football + 0.93 + + Laura B + Sam N + + + + Jenkins Fees + 8.17 + + Laura B + Ben B + + + + Automated Testing Services + 4.79 + + Jon A + Tim L + + + + Snooker Night + 8.24 + + Sarah T + Laura B + + + + Coffee + 9.17 + + Gergana I + Ben B + + + + Pokemon Training + 11.4 + + Todd + Sam N + + + + Sandbox Help + 1.55 + + Sam N + Dan W + + + + Lunch + 4.3 + + Chris W + Rob S + + + + Automated Testing Services + 8.6 + + Rob S + Sam N + + + + Fantasy Football + 10.88 + + Todd + Chris W + + + + Misc Morale + 5.19 + + Sarah T + Chris W + + + + White Water Rafting + 10.73 + + Tim L + Ben B + + + + Arcade Social + 10.73 + + Gergana I + Tim L + + + + Spline Reticulation + 10.84 + + Chris W + Jon A + + + + Coffee + 3.51 + + Laura B + Jon A + + + + Automated Testing Services + 1.78 + + Sarah T + Dan W + + + + Lunch + 3.11 + + Dan W + Stephen S + + + + Lego Assistance + 3.75 + + Sarah T + Chris W + + + + Misc Morale + 4.15 + + Todd + Stephen S + + + + Stationary Items + 9.43 + + Laura B + Chris W + + + + Golf Bets + 1.45 + + Ben B + Chris W + + + + Lunch + 8.71 + + Laura B + Sam N + + + + Lunch + 4.8 + + Stephen S + Todd + + + + Misc Morale + 6.57 + + Dan W + Jon A + + + + Sandcastle Help + 10.78 + + Chris W + Stephen S + + + + Poker Winnings + 0.52 + + Laura B + Jon A + + + + Pokemon Training + 10.86 + + Stephen S + Tim L + + + + Snooker Night + 7.98 + + Jon A + Dan W + + + + Jenkins Fees + 7.6 + + Sarah T + Ben B + + + + Git-Fu Rendered + 7.73 + + Laura B + Rob S + + + + Misc Morale + 9.1 + + Tim L + Ben B + + + + Golf Bets + 1.2 + + Jon A + Laura B + + + + Pokemon Training + 3.57 + + Tim L + Todd + + + + Services Rendered + 1.33 + + Ben B + Chris W + + + + Coffee + 8.55 + + Stephen S + Jon A + + + + Lego Assistance + 10.01 + + Jon A + Rob S + + + + Beers + 5.83 + + Jon A + Sam N + + + + Coffee + 10.52 + + Rob S + Todd + + + + Jenkins Fees + 11.59 + + Todd + Dan W + + + + Coffee + 9.47 + + Jon A + Chris W + + + + Misc Morale + 4.34 + + Rob S + Sam N + + + + Lunch + 10.41 + + Stephen S + Sam N + + + + Sandbox Help + 2.12 + + Tim L + Sam N + + + + Snooker Night + 1.74 + + Laura B + Dan W + + + + Misc Morale + 1.92 + + Chris W + Laura B + + + + Rails Consultancy + 3.2 + + Chris W + Dan W + + + + Beers + 5.38 + + Chris W + Todd + + + + Automated Testing Services + 8.57 + + Todd + Tim L + + + + White Water Rafting + 8.85 + + Sarah T + Stephen S + + + + Misc Morale + 11.22 + + Laura B + Stephen S + + + + Coffee + 0.58 + + Dan W + Gergana I + + + + Audit and Other Financial Services + 10.69 + + Sarah T + Rob S + + + + Coffee + 7.72 + + Sarah T + Dan W + + + + Services Rendered + 9.11 + + Tim L + Sam N + + + + Fantasy Football + 8.65 + + Tim L + Jon A + + + + Coffee + 8.36 + + Sarah T + Dan W + + + + Snooker Night + 3.57 + + Todd + Sam N + + + + Pokemon Training + 10.2 + + Dan W + Tim L + + + + Jenkins Fees + 7.89 + + Stephen S + Chris W + + + + Lunch + 4.41 + + Sarah T + Chris W + + + + Git-Fu Rendered + 10.89 + + Todd + Tim L + + + + Lunch + 0.64 + + Stephen S + Jon A + + + + Beers + 10.25 + + Sam N + Jon A + + + + Sandcastle Help + 4.73 + + Rob S + Gergana I + + + + Golf Bets + 11.83 + + Todd + Chris W + + + + Lunch + 4.42 + + Tim L + Stephen S + + + + Coffee + 3.66 + + Jon A + Ben B + + + + Lunch + 10.25 + + Laura B + Todd + + + + Lunch + 2.48 + + Chris W + Chris W + + + + Lunch + 10.62 + + Laura B + Dan W + + + + Misc Morale + 9.01 + + Jon A + Gergana I + + + + Audit and Other Financial Services + 10.6 + + Ben B + Laura B + + + + Beers + 4.34 + + Todd + Dan W + + + + Sandcastle Help + 10.4 + + Todd + Ben B + + + + Coffee + 2.13 + + Sam N + Laura B + + + + Beers + 6.22 + + Dan W + Sam N + + + + Coffee + 9.29 + + Gergana I + Chris W + + + + Coffee + 11.07 + + Dan W + Sam N + + + + Coffee + 7.83 + + Ben B + Todd + + + + Golf Bets + 5.18 + + Sarah T + Tim L + + + + Beers + 6.66 + + Sam N + Sarah T + + + + Pokemon Training + 3.74 + + Tim L + Sarah T + + + + Coffee + 11.93 + + Tim L + Dan W + + + + Lunch + 2.99 + + Jon A + Tim L + + + + Automated Testing Services + 5.95 + + Stephen S + Chris W + + + + Coffee + 7.08 + + Dan W + Rob S + + + + Git-Fu Rendered + 6.63 + + Rob S + Ben B + + + + Lego Assistance + 0.92 + + Dan W + Gergana I + + + + Coffee + 1.49 + + Rob S + Todd + + + + Services Rendered + 10.78 + + Chris W + Todd + + + + Git-Fu Rendered + 4.53 + + Chris W + Sarah T + + + + Misc Morale + 2.92 + + Gergana I + Sarah T + + + + Jenkins Fees + 7.88 + + Jon A + Tim L + + + + Coffee + 9.31 + + Tim L + Sarah T + + + + Rails Consultancy + 2.44 + + Sam N + Laura B + + + + Stationary Items + 8.89 + + Sarah T + Jon A + + + + Spline Reticulation + 8.73 + + Tim L + Stephen S + + + + Lunch + 10.28 + + Sarah T + Laura B + + + + Lego Assistance + 6.07 + + Stephen S + Sarah T + + + + Coffee + 6.28 + + Tim L + Jon A + + + + Automated Testing Services + 10.98 + + Rob S + Chris W + + + + Services Rendered + 0.77 + + Tim L + Todd + + + \ No newline at end of file From 937254d683f075ffb8ca616f89c152e70694ed68 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Mon, 21 Jul 2025 08:58:33 +0100 Subject: [PATCH 15/17] FEAT: now handles .json files: this was done with assistance from copilot so that i can get used to using ai --- .../DataFiles/Unconfirmed 360039.crdownload | 1195 ----------------- supportbank/main.py | 163 ++- 2 files changed, 124 insertions(+), 1234 deletions(-) delete mode 100644 supportbank/DataFiles/Unconfirmed 360039.crdownload diff --git a/supportbank/DataFiles/Unconfirmed 360039.crdownload b/supportbank/DataFiles/Unconfirmed 360039.crdownload deleted file mode 100644 index 10c4487..0000000 --- a/supportbank/DataFiles/Unconfirmed 360039.crdownload +++ /dev/null @@ -1,1195 +0,0 @@ - - - - Snooker Night - 9.22 - - Gergana I - Jon A - - - - Coffee - 6.98 - - Sam N - Dan W - - - - Lunch - 10.67 - - Stephen S - Sarah T - - - - Misc Morale - 7.19 - - Rob S - Sarah T - - - - Fantasy Football - 10.73 - - Tim L - Chris W - - - - Automated Testing Services - 10.1 - - Tim L - Rob S - - - - Beers - 1.79 - - Sarah T - Sam N - - - - Beers - 11.1 - - Dan W - Todd - - - - Coffee - 10.89 - - Chris W - Ben B - - - - Pokemon Training - 3.12 - - Chris W - Tim L - - - - Snooker Night - 2.29 - - Rob S - Tim L - - - - Spline Reticulation - 7.32 - - Chris W - Rob S - - - - Stationary Items - 6.1 - - Stephen S - Gergana I - - - - Lunch - 7.72 - - Stephen S - Todd - - - - Misc Morale - 5.2 - - Stephen S - Laura B - - - - Pokemon Training - 4.67 - - Sam N - Rob S - - - - Coffee - 11.72 - - Ben B - Chris W - - - - Pokemon Training - 6.45 - - Rob S - Jon A - - - - Sandcastle Help - 8.46 - - Todd - Ben B - - - - Lunch - 10.36 - - Laura B - Todd - - - - Pokemon Training - 3.88 - - Chris W - Stephen S - - - - Beers - 9.81 - - Sam N - Dan W - - - - Lunch - 3.67 - - Gergana I - Rob S - - - - Lunch - 0.63 - - Dan W - Tim L - - - - Jenkins Fees - 2.61 - - Tim L - Ben B - - - - Coffee - 4.85 - - Rob S - Ben B - - - - Lunch - 3.14 - - Stephen S - Gergana I - - - - Arcade Social - 9.01 - - Gergana I - Chris W - - - - Lego Assistance - 6.33 - - Gergana I - Rob S - - - - Misc Morale - 11.84 - - Dan W - Stephen S - - - - Spline Reticulation - 9.1 - - Jon A - Sarah T - - - - Coffee - 8.41 - - Stephen S - Dan W - - - - Lunch - 2.38 - - Stephen S - Tim L - - - - Snooker Night - 1.9 - - Gergana I - Laura B - - - - Sandcastle Help - 1.89 - - Sarah T - Gergana I - - - - Stationary Items - 3.8 - - Laura B - Dan W - - - - Fantasy Football - 11.63 - - Dan W - Chris W - - - - Misc Morale - 7.32 - - Dan W - Todd - - - - Audit and Other Financial Services - 6.06 - - Sam N - Chris W - - - - Arcade Social - 11.17 - - Tim L - Chris W - - - - Lego Assistance - 5.49 - - Sarah T - Dan W - - - - Git-Fu Rendered - 0.8 - - Gergana I - Jon A - - - - Lunch - 5.43 - - Sam N - Todd - - - - Lunch - 10.46 - - Laura B - Dan W - - - - Beers - 11.7 - - Laura B - Rob S - - - - Pokemon Training - 11.09 - - Stephen S - Ben B - - - - Audit and Other Financial Services - 8.44 - - Todd - Dan W - - - - Fantasy Football - 0.93 - - Laura B - Sam N - - - - Jenkins Fees - 8.17 - - Laura B - Ben B - - - - Automated Testing Services - 4.79 - - Jon A - Tim L - - - - Snooker Night - 8.24 - - Sarah T - Laura B - - - - Coffee - 9.17 - - Gergana I - Ben B - - - - Pokemon Training - 11.4 - - Todd - Sam N - - - - Sandbox Help - 1.55 - - Sam N - Dan W - - - - Lunch - 4.3 - - Chris W - Rob S - - - - Automated Testing Services - 8.6 - - Rob S - Sam N - - - - Fantasy Football - 10.88 - - Todd - Chris W - - - - Misc Morale - 5.19 - - Sarah T - Chris W - - - - White Water Rafting - 10.73 - - Tim L - Ben B - - - - Arcade Social - 10.73 - - Gergana I - Tim L - - - - Spline Reticulation - 10.84 - - Chris W - Jon A - - - - Coffee - 3.51 - - Laura B - Jon A - - - - Automated Testing Services - 1.78 - - Sarah T - Dan W - - - - Lunch - 3.11 - - Dan W - Stephen S - - - - Lego Assistance - 3.75 - - Sarah T - Chris W - - - - Misc Morale - 4.15 - - Todd - Stephen S - - - - Stationary Items - 9.43 - - Laura B - Chris W - - - - Golf Bets - 1.45 - - Ben B - Chris W - - - - Lunch - 8.71 - - Laura B - Sam N - - - - Lunch - 4.8 - - Stephen S - Todd - - - - Misc Morale - 6.57 - - Dan W - Jon A - - - - Sandcastle Help - 10.78 - - Chris W - Stephen S - - - - Poker Winnings - 0.52 - - Laura B - Jon A - - - - Pokemon Training - 10.86 - - Stephen S - Tim L - - - - Snooker Night - 7.98 - - Jon A - Dan W - - - - Jenkins Fees - 7.6 - - Sarah T - Ben B - - - - Git-Fu Rendered - 7.73 - - Laura B - Rob S - - - - Misc Morale - 9.1 - - Tim L - Ben B - - - - Golf Bets - 1.2 - - Jon A - Laura B - - - - Pokemon Training - 3.57 - - Tim L - Todd - - - - Services Rendered - 1.33 - - Ben B - Chris W - - - - Coffee - 8.55 - - Stephen S - Jon A - - - - Lego Assistance - 10.01 - - Jon A - Rob S - - - - Beers - 5.83 - - Jon A - Sam N - - - - Coffee - 10.52 - - Rob S - Todd - - - - Jenkins Fees - 11.59 - - Todd - Dan W - - - - Coffee - 9.47 - - Jon A - Chris W - - - - Misc Morale - 4.34 - - Rob S - Sam N - - - - Lunch - 10.41 - - Stephen S - Sam N - - - - Sandbox Help - 2.12 - - Tim L - Sam N - - - - Snooker Night - 1.74 - - Laura B - Dan W - - - - Misc Morale - 1.92 - - Chris W - Laura B - - - - Rails Consultancy - 3.2 - - Chris W - Dan W - - - - Beers - 5.38 - - Chris W - Todd - - - - Automated Testing Services - 8.57 - - Todd - Tim L - - - - White Water Rafting - 8.85 - - Sarah T - Stephen S - - - - Misc Morale - 11.22 - - Laura B - Stephen S - - - - Coffee - 0.58 - - Dan W - Gergana I - - - - Audit and Other Financial Services - 10.69 - - Sarah T - Rob S - - - - Coffee - 7.72 - - Sarah T - Dan W - - - - Services Rendered - 9.11 - - Tim L - Sam N - - - - Fantasy Football - 8.65 - - Tim L - Jon A - - - - Coffee - 8.36 - - Sarah T - Dan W - - - - Snooker Night - 3.57 - - Todd - Sam N - - - - Pokemon Training - 10.2 - - Dan W - Tim L - - - - Jenkins Fees - 7.89 - - Stephen S - Chris W - - - - Lunch - 4.41 - - Sarah T - Chris W - - - - Git-Fu Rendered - 10.89 - - Todd - Tim L - - - - Lunch - 0.64 - - Stephen S - Jon A - - - - Beers - 10.25 - - Sam N - Jon A - - - - Sandcastle Help - 4.73 - - Rob S - Gergana I - - - - Golf Bets - 11.83 - - Todd - Chris W - - - - Lunch - 4.42 - - Tim L - Stephen S - - - - Coffee - 3.66 - - Jon A - Ben B - - - - Lunch - 10.25 - - Laura B - Todd - - - - Lunch - 2.48 - - Chris W - Chris W - - - - Lunch - 10.62 - - Laura B - Dan W - - - - Misc Morale - 9.01 - - Jon A - Gergana I - - - - Audit and Other Financial Services - 10.6 - - Ben B - Laura B - - - - Beers - 4.34 - - Todd - Dan W - - - - Sandcastle Help - 10.4 - - Todd - Ben B - - - - Coffee - 2.13 - - Sam N - Laura B - - - - Beers - 6.22 - - Dan W - Sam N - - - - Coffee - 9.29 - - Gergana I - Chris W - - - - Coffee - 11.07 - - Dan W - Sam N - - - - Coffee - 7.83 - - Ben B - Todd - - - - Golf Bets - 5.18 - - Sarah T - Tim L - - - - Beers - 6.66 - - Sam N - Sarah T - - - - Pokemon Training - 3.74 - - Tim L - Sarah T - - - - Coffee - 11.93 - - Tim L - Dan W - - - - Lunch - 2.99 - - Jon A - Tim L - - - - Automated Testing Services - 5.95 - - Stephen S - Chris W - - - - Coffee - 7.08 - - Dan W - Rob S - - - - Git-Fu Rendered - 6.63 - - Rob S - Ben B - - - - Lego Assistance - 0.92 - - Dan W - Gergana I - - - - Coffee - 1.49 - - Rob S - Todd - - - - Services Rendered - 10.78 - - Chris W - Todd - - - - Git-Fu Rendered - 4.53 - - Chris W - Sarah T - - - - Misc Morale - 2.92 - - Gergana I - Sarah T - - - - Jenkins Fees - 7.88 - - Jon A - Tim L - - - - Coffee - 9.31 - - Tim L - Sarah T - - - - Rails Consultancy - 2.44 - - Sam N - Laura B - - - - Stationary Items - 8.89 - - Sarah T - Jon A - - - - Spline Reticulation - 8.73 - - Tim L - Stephen S - - - - Lunch - 10.28 - - Sarah T - Laura B - - - - Lego Assistance - 6.07 - - Stephen S - Sarah T - - - - Coffee - 6.28 - - Tim L - Jon A - - - - Automated Testing Services - 10.98 - - Rob S - Chris W - - - - Services Rendered - 0.77 - - Tim L - Todd - - - \ No newline at end of file diff --git a/supportbank/main.py b/supportbank/main.py index 9a37faf..9bc2646 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -1,6 +1,7 @@ #! I am also aware that I have used a mixture of camel/snake/etc will tidy up at the end. Also why doesnt python have multi-line comments import csv +import json import logging from datetime import datetime import os @@ -9,13 +10,19 @@ logging.basicConfig(filename="SupportBank.log", filemode="w", level=logging.DEBUG) logger.info("Logging started.") + class Transaction: def __init__(self, data): - self.Date = data["Date"] - self.From = data["From"] - self.To = data["To"] - self.Narrative = data["Narrative"] - self.Amount = float(data["Amount"]) + # Support both CSV and JSON schemas, with all possible key casings + self.Date = data.get("Date") or data.get("date") + self.From = ( + data.get("From") or data.get("fromAccount") or data.get("FromAccount") + ) + self.To = data.get("To") or data.get("toAccount") or data.get("ToAccount") + self.Narrative = data.get("Narrative") or data.get("narrative") + amount = data.get("Amount") or data.get("amount") + self.Amount = float(amount) + class Account: def __init__(self, name): @@ -30,50 +37,96 @@ def apply_transaction(self, transaction): self.balance += int(transaction.Amount * 100) self.transactions.append(transaction) -def read_transactions(filename): - with open(filename, mode='r') as file: - reader = csv.DictReader(file) - transactions = [] - line_number = 2 - for row in reader: +def read_transactions(filename): + transactions = [] + if filename.endswith(".csv"): + with open(filename, mode="r") as file: + reader = csv.DictReader(file) + line_number = 2 + for row in reader: + try: + float(row["Amount"]) + datetime.strptime(row["Date"], "%d/%m/%Y") + transaction = Transaction(row) + transactions.append(transaction) + except ValueError as e: + account_name = row.get("From", "UNKNOWN") + logger.warning( + f"Invalid transaction on line {line_number}: {row} — {e}" + ) + print( + f"Skipped invalid transaction on line {line_number} involving account '{account_name}'. See log for details." + ) + line_number += 1 + if filename.endswith(".json"): + with open(filename, mode="r") as file: try: - float(row["Amount"]) - - datetime.strptime(row["Date"], "%d/%m/%Y") - - transaction = Transaction(row) - transactions.append(transaction) - - except ValueError as e: - account_name = row.get("From", "UNKNOWN") - logger.warning(f"Invalid transaction on line {line_number}: {row} — {e}") - print(f"Skipped invalid transaction on line {line_number} involving account '{account_name}'. See log for details.") - - line_number += 1 - - logger.info(f"{filename} read with {len(transactions)} valid transactions") - return transactions + data = json.load(file) + for idx, row in enumerate(data, start=1): + try: + # Support both Amount/amount and Date/date keys + amount = row.get("Amount") or row.get("amount") + float(amount) + date_val = row.get("Date") or row.get("date") + # JSON date format may differ, try several common formats + parsed = False + for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"): + try: + datetime.strptime(date_val, fmt) + parsed = True + break + except (ValueError, TypeError): + continue + if not parsed: + raise ValueError(f"Unrecognized date format: {date_val}") + transaction = Transaction(row) + transactions.append(transaction) + except ValueError as e: + account_name = ( + row.get("From") + or row.get("fromAccount") + or row.get("FromAccount") + or "UNKNOWN" + ) + logger.warning( + f"Invalid transaction in JSON at index {idx}: {row} — {e}" + ) + print( + f"Skipped invalid transaction in JSON at index {idx} involving account '{account_name}'. See log for details." + ) + except json.JSONDecodeError as e: + logger.error(f"Failed to parse JSON file {filename}: {e}") + print(f"Failed to parse JSON file: {e}") + else: + print("Unsupported file type. Only .csv and .json are supported.") + logger.warning(f"Unsupported file type: {filename}") + logger.info(f"{filename} read with {len(transactions)} valid transactions") + return transactions def list_available_csv_files(): + # removed duplicate/empty definition data_dir = "./DataFiles" try: - files = [f[:-4] for f in os.listdir(data_dir) if f.endswith(".csv")] + files = [ + f for f in os.listdir(data_dir) if f.endswith(".csv") or f.endswith(".json") + ] if not files: - print("No CSV files found in ./DataFiles/") - logger.warning("No CSV files found in ./DataFiles/") + print("No CSV or JSON files found in ./DataFiles/") + logger.warning("No CSV or JSON files found in ./DataFiles/") else: - print("Available CSV files:") + print("Available data files:") for f in files: print(f" - {f}") - logger.info("available CSV files listed") + logger.info("Available data files listed") return files except FileNotFoundError: print("The ./DataFiles directory does not exist") logger.warning("The ./DataFiles directory does not exist") return [] + def List_All(transactions): accountsDict = {} logger.info("showing account balances") @@ -87,19 +140,45 @@ def List_All(transactions): print("Balances:") for account in accountsDict.values(): print(f"{account.name}: £{account.balance / 100:.2f}") + logger.info("Accounts and balances displayed successfully") + +def list_available_data_files(): + data_dir = "./DataFiles" + try: + files = [ + f for f in os.listdir(data_dir) if f.endswith(".csv") or f.endswith(".json") + ] + if not files: + print("No CSV or JSON files found in ./DataFiles/") + logger.warning("No CSV or JSON files found in ./DataFiles/") + else: + print("Available data files:") + for f in files: + print(f" - {f}") + logger.info("Available data files listed") + return files + except FileNotFoundError: + print("The ./DataFiles directory does not exist") + logger.warning("The ./DataFiles directory does not exist") + return [] + + def List(account, transactions): account_found = False logger.info(f"listing transactions for {account}") for tx in transactions: if tx.From == account or tx.To == account: account_found = True - print(f"{tx.Date} | From: {tx.From} | To: {tx.To} | {tx.Narrative} | £{tx.Amount:.2f}") + print( + f"{tx.Date} | From: {tx.From} | To: {tx.To} | {tx.Narrative} | £{tx.Amount:.2f}" + ) if not account_found: print(f"Account '{account}' not found in any transactions.") logger.warning(f"Account '{account}' not found in any transactions.") + def main(): print("Welcome to SupportBank") while True: @@ -111,23 +190,29 @@ def main(): logger.info(f"User selected choice: {choice}") if choice == "1": - available = list_available_csv_files() + available = list_available_data_files() if not available: continue - filename = input("Enter CSV filename (without .csv): ").strip() + filename = input("Enter filename (with extension .csv or .json): ").strip() + if not (filename.endswith(".csv") or filename.endswith(".json")): + print("Invalid file extension. Please enter a .csv or .json file.") + continue try: - transactions = read_transactions(f"./DataFiles/{filename}.csv") + transactions = read_transactions(f"./DataFiles/{filename}") List_All(transactions) except FileNotFoundError: print("File not found.") elif choice == "2": account = input("Enter account name: ").strip() - available = list_available_csv_files() + available = list_available_data_files() if not available: continue - filename = input("Enter CSV filename (without .csv): ").strip() + filename = input("Enter filename (with extension .csv or .json): ").strip() + if not (filename.endswith(".csv") or filename.endswith(".json")): + print("Invalid file extension. Please enter a .csv or .json file.") + continue try: - transactions = read_transactions(f"./DataFiles/{filename}.csv") + transactions = read_transactions(f"./DataFiles/{filename}") List(account, transactions) except FileNotFoundError: print("File not found.") From 07a86487af766adafc149481d0fb57175c72a317 Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Mon, 21 Jul 2025 10:11:28 +0100 Subject: [PATCH 16/17] FEAT: XML support added - again experimenting with the power of AI --- supportbank/DataFiles/Transactions2012.xml | 1195 ++++++++++++++++++++ supportbank/main.py | 129 ++- 2 files changed, 1287 insertions(+), 37 deletions(-) create mode 100644 supportbank/DataFiles/Transactions2012.xml diff --git a/supportbank/DataFiles/Transactions2012.xml b/supportbank/DataFiles/Transactions2012.xml new file mode 100644 index 0000000..10c4487 --- /dev/null +++ b/supportbank/DataFiles/Transactions2012.xml @@ -0,0 +1,1195 @@ + + + + Snooker Night + 9.22 + + Gergana I + Jon A + + + + Coffee + 6.98 + + Sam N + Dan W + + + + Lunch + 10.67 + + Stephen S + Sarah T + + + + Misc Morale + 7.19 + + Rob S + Sarah T + + + + Fantasy Football + 10.73 + + Tim L + Chris W + + + + Automated Testing Services + 10.1 + + Tim L + Rob S + + + + Beers + 1.79 + + Sarah T + Sam N + + + + Beers + 11.1 + + Dan W + Todd + + + + Coffee + 10.89 + + Chris W + Ben B + + + + Pokemon Training + 3.12 + + Chris W + Tim L + + + + Snooker Night + 2.29 + + Rob S + Tim L + + + + Spline Reticulation + 7.32 + + Chris W + Rob S + + + + Stationary Items + 6.1 + + Stephen S + Gergana I + + + + Lunch + 7.72 + + Stephen S + Todd + + + + Misc Morale + 5.2 + + Stephen S + Laura B + + + + Pokemon Training + 4.67 + + Sam N + Rob S + + + + Coffee + 11.72 + + Ben B + Chris W + + + + Pokemon Training + 6.45 + + Rob S + Jon A + + + + Sandcastle Help + 8.46 + + Todd + Ben B + + + + Lunch + 10.36 + + Laura B + Todd + + + + Pokemon Training + 3.88 + + Chris W + Stephen S + + + + Beers + 9.81 + + Sam N + Dan W + + + + Lunch + 3.67 + + Gergana I + Rob S + + + + Lunch + 0.63 + + Dan W + Tim L + + + + Jenkins Fees + 2.61 + + Tim L + Ben B + + + + Coffee + 4.85 + + Rob S + Ben B + + + + Lunch + 3.14 + + Stephen S + Gergana I + + + + Arcade Social + 9.01 + + Gergana I + Chris W + + + + Lego Assistance + 6.33 + + Gergana I + Rob S + + + + Misc Morale + 11.84 + + Dan W + Stephen S + + + + Spline Reticulation + 9.1 + + Jon A + Sarah T + + + + Coffee + 8.41 + + Stephen S + Dan W + + + + Lunch + 2.38 + + Stephen S + Tim L + + + + Snooker Night + 1.9 + + Gergana I + Laura B + + + + Sandcastle Help + 1.89 + + Sarah T + Gergana I + + + + Stationary Items + 3.8 + + Laura B + Dan W + + + + Fantasy Football + 11.63 + + Dan W + Chris W + + + + Misc Morale + 7.32 + + Dan W + Todd + + + + Audit and Other Financial Services + 6.06 + + Sam N + Chris W + + + + Arcade Social + 11.17 + + Tim L + Chris W + + + + Lego Assistance + 5.49 + + Sarah T + Dan W + + + + Git-Fu Rendered + 0.8 + + Gergana I + Jon A + + + + Lunch + 5.43 + + Sam N + Todd + + + + Lunch + 10.46 + + Laura B + Dan W + + + + Beers + 11.7 + + Laura B + Rob S + + + + Pokemon Training + 11.09 + + Stephen S + Ben B + + + + Audit and Other Financial Services + 8.44 + + Todd + Dan W + + + + Fantasy Football + 0.93 + + Laura B + Sam N + + + + Jenkins Fees + 8.17 + + Laura B + Ben B + + + + Automated Testing Services + 4.79 + + Jon A + Tim L + + + + Snooker Night + 8.24 + + Sarah T + Laura B + + + + Coffee + 9.17 + + Gergana I + Ben B + + + + Pokemon Training + 11.4 + + Todd + Sam N + + + + Sandbox Help + 1.55 + + Sam N + Dan W + + + + Lunch + 4.3 + + Chris W + Rob S + + + + Automated Testing Services + 8.6 + + Rob S + Sam N + + + + Fantasy Football + 10.88 + + Todd + Chris W + + + + Misc Morale + 5.19 + + Sarah T + Chris W + + + + White Water Rafting + 10.73 + + Tim L + Ben B + + + + Arcade Social + 10.73 + + Gergana I + Tim L + + + + Spline Reticulation + 10.84 + + Chris W + Jon A + + + + Coffee + 3.51 + + Laura B + Jon A + + + + Automated Testing Services + 1.78 + + Sarah T + Dan W + + + + Lunch + 3.11 + + Dan W + Stephen S + + + + Lego Assistance + 3.75 + + Sarah T + Chris W + + + + Misc Morale + 4.15 + + Todd + Stephen S + + + + Stationary Items + 9.43 + + Laura B + Chris W + + + + Golf Bets + 1.45 + + Ben B + Chris W + + + + Lunch + 8.71 + + Laura B + Sam N + + + + Lunch + 4.8 + + Stephen S + Todd + + + + Misc Morale + 6.57 + + Dan W + Jon A + + + + Sandcastle Help + 10.78 + + Chris W + Stephen S + + + + Poker Winnings + 0.52 + + Laura B + Jon A + + + + Pokemon Training + 10.86 + + Stephen S + Tim L + + + + Snooker Night + 7.98 + + Jon A + Dan W + + + + Jenkins Fees + 7.6 + + Sarah T + Ben B + + + + Git-Fu Rendered + 7.73 + + Laura B + Rob S + + + + Misc Morale + 9.1 + + Tim L + Ben B + + + + Golf Bets + 1.2 + + Jon A + Laura B + + + + Pokemon Training + 3.57 + + Tim L + Todd + + + + Services Rendered + 1.33 + + Ben B + Chris W + + + + Coffee + 8.55 + + Stephen S + Jon A + + + + Lego Assistance + 10.01 + + Jon A + Rob S + + + + Beers + 5.83 + + Jon A + Sam N + + + + Coffee + 10.52 + + Rob S + Todd + + + + Jenkins Fees + 11.59 + + Todd + Dan W + + + + Coffee + 9.47 + + Jon A + Chris W + + + + Misc Morale + 4.34 + + Rob S + Sam N + + + + Lunch + 10.41 + + Stephen S + Sam N + + + + Sandbox Help + 2.12 + + Tim L + Sam N + + + + Snooker Night + 1.74 + + Laura B + Dan W + + + + Misc Morale + 1.92 + + Chris W + Laura B + + + + Rails Consultancy + 3.2 + + Chris W + Dan W + + + + Beers + 5.38 + + Chris W + Todd + + + + Automated Testing Services + 8.57 + + Todd + Tim L + + + + White Water Rafting + 8.85 + + Sarah T + Stephen S + + + + Misc Morale + 11.22 + + Laura B + Stephen S + + + + Coffee + 0.58 + + Dan W + Gergana I + + + + Audit and Other Financial Services + 10.69 + + Sarah T + Rob S + + + + Coffee + 7.72 + + Sarah T + Dan W + + + + Services Rendered + 9.11 + + Tim L + Sam N + + + + Fantasy Football + 8.65 + + Tim L + Jon A + + + + Coffee + 8.36 + + Sarah T + Dan W + + + + Snooker Night + 3.57 + + Todd + Sam N + + + + Pokemon Training + 10.2 + + Dan W + Tim L + + + + Jenkins Fees + 7.89 + + Stephen S + Chris W + + + + Lunch + 4.41 + + Sarah T + Chris W + + + + Git-Fu Rendered + 10.89 + + Todd + Tim L + + + + Lunch + 0.64 + + Stephen S + Jon A + + + + Beers + 10.25 + + Sam N + Jon A + + + + Sandcastle Help + 4.73 + + Rob S + Gergana I + + + + Golf Bets + 11.83 + + Todd + Chris W + + + + Lunch + 4.42 + + Tim L + Stephen S + + + + Coffee + 3.66 + + Jon A + Ben B + + + + Lunch + 10.25 + + Laura B + Todd + + + + Lunch + 2.48 + + Chris W + Chris W + + + + Lunch + 10.62 + + Laura B + Dan W + + + + Misc Morale + 9.01 + + Jon A + Gergana I + + + + Audit and Other Financial Services + 10.6 + + Ben B + Laura B + + + + Beers + 4.34 + + Todd + Dan W + + + + Sandcastle Help + 10.4 + + Todd + Ben B + + + + Coffee + 2.13 + + Sam N + Laura B + + + + Beers + 6.22 + + Dan W + Sam N + + + + Coffee + 9.29 + + Gergana I + Chris W + + + + Coffee + 11.07 + + Dan W + Sam N + + + + Coffee + 7.83 + + Ben B + Todd + + + + Golf Bets + 5.18 + + Sarah T + Tim L + + + + Beers + 6.66 + + Sam N + Sarah T + + + + Pokemon Training + 3.74 + + Tim L + Sarah T + + + + Coffee + 11.93 + + Tim L + Dan W + + + + Lunch + 2.99 + + Jon A + Tim L + + + + Automated Testing Services + 5.95 + + Stephen S + Chris W + + + + Coffee + 7.08 + + Dan W + Rob S + + + + Git-Fu Rendered + 6.63 + + Rob S + Ben B + + + + Lego Assistance + 0.92 + + Dan W + Gergana I + + + + Coffee + 1.49 + + Rob S + Todd + + + + Services Rendered + 10.78 + + Chris W + Todd + + + + Git-Fu Rendered + 4.53 + + Chris W + Sarah T + + + + Misc Morale + 2.92 + + Gergana I + Sarah T + + + + Jenkins Fees + 7.88 + + Jon A + Tim L + + + + Coffee + 9.31 + + Tim L + Sarah T + + + + Rails Consultancy + 2.44 + + Sam N + Laura B + + + + Stationary Items + 8.89 + + Sarah T + Jon A + + + + Spline Reticulation + 8.73 + + Tim L + Stephen S + + + + Lunch + 10.28 + + Sarah T + Laura B + + + + Lego Assistance + 6.07 + + Stephen S + Sarah T + + + + Coffee + 6.28 + + Tim L + Jon A + + + + Automated Testing Services + 10.98 + + Rob S + Chris W + + + + Services Rendered + 0.77 + + Tim L + Todd + + + \ No newline at end of file diff --git a/supportbank/main.py b/supportbank/main.py index 9bc2646..d22c3bb 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -5,6 +5,7 @@ import logging from datetime import datetime import os +import xml.etree.ElementTree as ET logger = logging.getLogger() logging.basicConfig(filename="SupportBank.log", filemode="w", level=logging.DEBUG) @@ -13,7 +14,6 @@ class Transaction: def __init__(self, data): - # Support both CSV and JSON schemas, with all possible key casings self.Date = data.get("Date") or data.get("date") self.From = ( data.get("From") or data.get("fromAccount") or data.get("FromAccount") @@ -59,17 +59,15 @@ def read_transactions(filename): f"Skipped invalid transaction on line {line_number} involving account '{account_name}'. See log for details." ) line_number += 1 - if filename.endswith(".json"): + elif filename.endswith(".json"): with open(filename, mode="r") as file: try: data = json.load(file) for idx, row in enumerate(data, start=1): try: - # Support both Amount/amount and Date/date keys amount = row.get("Amount") or row.get("amount") float(amount) date_val = row.get("Date") or row.get("date") - # JSON date format may differ, try several common formats parsed = False for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"): try: @@ -98,44 +96,83 @@ def read_transactions(filename): except json.JSONDecodeError as e: logger.error(f"Failed to parse JSON file {filename}: {e}") print(f"Failed to parse JSON file: {e}") + elif filename.endswith(".xml"): + try: + from datetime import timedelta + + tree = ET.parse(filename) + root = tree.getroot() + for idx, elem in enumerate(root.findall("SupportTransaction"), start=1): + try: + # Date is an attribute, value is a child, parties are nested + date_val = elem.attrib.get("Date") + # Excel date serial to datetime + try: + excel_epoch = datetime(1899, 12, 30) + date_obj = excel_epoch + timedelta(days=int(float(date_val))) + date_val_fmt = date_obj.strftime("%d/%m/%Y") + except Exception: + date_val_fmt = date_val + amount = float(elem.find("Value").text) + narrative = elem.find("Description").text + parties = elem.find("Parties") + from_account = ( + parties.find("From").text + if parties is not None and parties.find("From") is not None + else None + ) + to_account = ( + parties.find("To").text + if parties is not None and parties.find("To") is not None + else None + ) + + transaction_dict = { + "Date": date_val_fmt, + "date": date_val_fmt, + "From": from_account, + "fromAccount": from_account, + "FromAccount": from_account, + "To": to_account, + "toAccount": to_account, + "ToAccount": to_account, + "Narrative": narrative, + "narrative": narrative, + "Amount": amount, + "amount": amount, + } + transaction = Transaction(transaction_dict) + transactions.append(transaction) + except (ValueError, AttributeError) as e: + logger.warning( + f"Invalid transaction in XML at index {idx}: {elem} — {e}" + ) + print( + f"Skipped invalid transaction in XML at index {idx}. See log for details." + ) + except ET.ParseError as e: + logger.error(f"Failed to parse XML file {filename}: {e}") + print(f"Failed to parse XML file: {e}") else: - print("Unsupported file type. Only .csv and .json are supported.") + print("Unsupported file type. Only .csv, .json, and .xml are supported.") logger.warning(f"Unsupported file type: {filename}") logger.info(f"{filename} read with {len(transactions)} valid transactions") return transactions -def list_available_csv_files(): - # removed duplicate/empty definition - data_dir = "./DataFiles" - try: - files = [ - f for f in os.listdir(data_dir) if f.endswith(".csv") or f.endswith(".json") - ] - if not files: - print("No CSV or JSON files found in ./DataFiles/") - logger.warning("No CSV or JSON files found in ./DataFiles/") - else: - print("Available data files:") - for f in files: - print(f" - {f}") - logger.info("Available data files listed") - return files - except FileNotFoundError: - print("The ./DataFiles directory does not exist") - logger.warning("The ./DataFiles directory does not exist") - return [] - - def List_All(transactions): accountsDict = {} logger.info("showing account balances") for tx in transactions: for name in [tx.From, tx.To]: + if name is None: + continue if name not in accountsDict: accountsDict[name] = Account(name) - accountsDict[tx.From].apply_transaction(tx) - accountsDict[tx.To].apply_transaction(tx) + if tx.From is not None: + accountsDict[tx.From].apply_transaction(tx) + if tx.To is not None: + accountsDict[tx.To].apply_transaction(tx) print("Balances:") for account in accountsDict.values(): @@ -148,11 +185,13 @@ def list_available_data_files(): data_dir = "./DataFiles" try: files = [ - f for f in os.listdir(data_dir) if f.endswith(".csv") or f.endswith(".json") + f + for f in os.listdir(data_dir) + if f.endswith(".csv") or f.endswith(".json") or f.endswith(".xml") ] if not files: print("No CSV or JSON files found in ./DataFiles/") - logger.warning("No CSV or JSON files found in ./DataFiles/") + logger.warning("No CSV, JSON, or XML files found in ./DataFiles/") else: print("Available data files:") for f in files: @@ -193,9 +232,17 @@ def main(): available = list_available_data_files() if not available: continue - filename = input("Enter filename (with extension .csv or .json): ").strip() - if not (filename.endswith(".csv") or filename.endswith(".json")): - print("Invalid file extension. Please enter a .csv or .json file.") + filename = input( + "Enter filename (with extension .csv, .json, or .xml): " + ).strip() + if not ( + filename.endswith(".csv") + or filename.endswith(".json") + or filename.endswith(".xml") + ): + print( + "Invalid file extension. Please enter a .csv, .json, or XML file." + ) continue try: transactions = read_transactions(f"./DataFiles/{filename}") @@ -207,9 +254,17 @@ def main(): available = list_available_data_files() if not available: continue - filename = input("Enter filename (with extension .csv or .json): ").strip() - if not (filename.endswith(".csv") or filename.endswith(".json")): - print("Invalid file extension. Please enter a .csv or .json file.") + filename = input( + "Enter filename (with extension .csv, .json, or .xml): " + ).strip() + if not ( + filename.endswith(".csv") + or filename.endswith(".json") + or filename.endswith(".xml") + ): + print( + "Invalid file extension. Please enter a .csv, .json, or XML file." + ) continue try: transactions = read_transactions(f"./DataFiles/{filename}") From e58f9bdc4b75f698f4cb06340760f6cc9cbd802d Mon Sep 17 00:00:00 2001 From: Bill Haigh Date: Mon, 21 Jul 2025 10:42:03 +0100 Subject: [PATCH 17/17] tidying up --- supportbank/main.py | 384 +++++++++++++++++++------------------------- 1 file changed, 161 insertions(+), 223 deletions(-) diff --git a/supportbank/main.py b/supportbank/main.py index d22c3bb..032aab2 100644 --- a/supportbank/main.py +++ b/supportbank/main.py @@ -1,27 +1,41 @@ -#! I am also aware that I have used a mixture of camel/snake/etc will tidy up at the end. Also why doesnt python have multi-line comments - import csv import json import logging -from datetime import datetime import os +from datetime import datetime import xml.etree.ElementTree as ET -logger = logging.getLogger() +# Logging setup logging.basicConfig(filename="SupportBank.log", filemode="w", level=logging.DEBUG) +logger = logging.getLogger() logger.info("Logging started.") class Transaction: def __init__(self, data): - self.Date = data.get("Date") or data.get("date") - self.From = ( - data.get("From") or data.get("fromAccount") or data.get("FromAccount") - ) - self.To = data.get("To") or data.get("toAccount") or data.get("ToAccount") - self.Narrative = data.get("Narrative") or data.get("narrative") - amount = data.get("Amount") or data.get("amount") - self.Amount = float(amount) + raw_date = data.get("Date") or data.get("date") + self.date = self.normalize_date(raw_date) + self.from_account = data.get("From") or data.get("fromAccount") or data.get("FromAccount") + self.to_account = data.get("To") or data.get("toAccount") or data.get("ToAccount") + self.narrative = data.get("Narrative") or data.get("narrative") or "No Narrative" + amount = data.get("Amount") or data.get("amount") or "0" + self.amount = float(amount) + + @staticmethod + def normalize_date(date_str): + known_formats = [ + "%d/%m/%Y", + "%Y-%m-%d", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%dT%H:%M:%S.%f", + ] + for fmt in known_formats: + try: + dt = datetime.strptime(date_str, fmt) + return dt.strftime("%d/%m/%Y") + except (ValueError, TypeError): + continue + return "01/01/1970" # fallback class Account: @@ -30,250 +44,174 @@ def __init__(self, name): self.balance = 0 self.transactions = [] - def apply_transaction(self, transaction): - if transaction.From == self.name: - self.balance -= int(transaction.Amount * 100) - if transaction.To == self.name: - self.balance += int(transaction.Amount * 100) - self.transactions.append(transaction) + def apply_transaction(self, tx): + if tx.from_account == self.name: + self.balance -= int(tx.amount * 100) + if tx.to_account == self.name: + self.balance += int(tx.amount * 100) + self.transactions.append(tx) -def read_transactions(filename): +def read_transactions(filepath): transactions = [] - if filename.endswith(".csv"): - with open(filename, mode="r") as file: - reader = csv.DictReader(file) - line_number = 2 - for row in reader: - try: - float(row["Amount"]) - datetime.strptime(row["Date"], "%d/%m/%Y") - transaction = Transaction(row) - transactions.append(transaction) - except ValueError as e: - account_name = row.get("From", "UNKNOWN") - logger.warning( - f"Invalid transaction on line {line_number}: {row} — {e}" - ) - print( - f"Skipped invalid transaction on line {line_number} involving account '{account_name}'. See log for details." - ) - line_number += 1 - elif filename.endswith(".json"): - with open(filename, mode="r") as file: - try: - data = json.load(file) - for idx, row in enumerate(data, start=1): + try: + if filepath.endswith(".json"): + with open(filepath, "r") as f: + data = json.load(f) + for item in data: try: - amount = row.get("Amount") or row.get("amount") - float(amount) - date_val = row.get("Date") or row.get("date") - parsed = False - for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%Y-%m-%dT%H:%M:%S"): - try: - datetime.strptime(date_val, fmt) - parsed = True - break - except (ValueError, TypeError): - continue - if not parsed: - raise ValueError(f"Unrecognized date format: {date_val}") - transaction = Transaction(row) - transactions.append(transaction) - except ValueError as e: - account_name = ( - row.get("From") - or row.get("fromAccount") - or row.get("FromAccount") - or "UNKNOWN" - ) - logger.warning( - f"Invalid transaction in JSON at index {idx}: {row} — {e}" - ) - print( - f"Skipped invalid transaction in JSON at index {idx} involving account '{account_name}'. See log for details." - ) - except json.JSONDecodeError as e: - logger.error(f"Failed to parse JSON file {filename}: {e}") - print(f"Failed to parse JSON file: {e}") - elif filename.endswith(".xml"): - try: - from datetime import timedelta - - tree = ET.parse(filename) + transactions.append(Transaction(item)) + except Exception as e: + logger.warning(f"Invalid transaction in JSON: {item} — {e}") + elif filepath.endswith(".csv"): + with open(filepath, "r", newline='') as f: + reader = csv.DictReader(f) + for row in reader: + try: + transactions.append(Transaction(row)) + except Exception as e: + logger.warning(f"Invalid transaction in CSV: {row} — {e}") + elif filepath.endswith(".xml"): + tree = ET.parse(filepath) root = tree.getroot() - for idx, elem in enumerate(root.findall("SupportTransaction"), start=1): + for elem in root.findall(".//SupportTransaction"): try: - # Date is an attribute, value is a child, parties are nested - date_val = elem.attrib.get("Date") - # Excel date serial to datetime - try: - excel_epoch = datetime(1899, 12, 30) - date_obj = excel_epoch + timedelta(days=int(float(date_val))) - date_val_fmt = date_obj.strftime("%d/%m/%Y") - except Exception: - date_val_fmt = date_val - amount = float(elem.find("Value").text) - narrative = elem.find("Description").text - parties = elem.find("Parties") - from_account = ( - parties.find("From").text - if parties is not None and parties.find("From") is not None - else None - ) - to_account = ( - parties.find("To").text - if parties is not None and parties.find("To") is not None - else None - ) - - transaction_dict = { - "Date": date_val_fmt, - "date": date_val_fmt, - "From": from_account, - "fromAccount": from_account, - "FromAccount": from_account, - "To": to_account, - "toAccount": to_account, - "ToAccount": to_account, - "Narrative": narrative, - "narrative": narrative, - "Amount": amount, - "amount": amount, + transaction_data = { + "Date": elem.find("Date").text, + "From": elem.find("Parties/From").text, + "To": elem.find("Parties/To").text, + "Narrative": elem.find("Description").text, + "Amount": elem.find("Value").text, } - transaction = Transaction(transaction_dict) - transactions.append(transaction) - except (ValueError, AttributeError) as e: - logger.warning( - f"Invalid transaction in XML at index {idx}: {elem} — {e}" - ) - print( - f"Skipped invalid transaction in XML at index {idx}. See log for details." - ) - except ET.ParseError as e: - logger.error(f"Failed to parse XML file {filename}: {e}") - print(f"Failed to parse XML file: {e}") - else: - print("Unsupported file type. Only .csv, .json, and .xml are supported.") - logger.warning(f"Unsupported file type: {filename}") - logger.info(f"{filename} read with {len(transactions)} valid transactions") + transactions.append(Transaction(transaction_data)) + except Exception as e: + logger.warning(f"Invalid transaction in XML: {ET.tostring(elem)} — {e}") + else: + logger.warning(f"Unsupported file type: {filepath}") + except Exception as e: + logger.error(f"Failed to read file {filepath}: {e}") return transactions -def List_All(transactions): - accountsDict = {} - logger.info("showing account balances") +def list_all(transactions): + accounts = {} for tx in transactions: - for name in [tx.From, tx.To]: - if name is None: - continue - if name not in accountsDict: - accountsDict[name] = Account(name) - if tx.From is not None: - accountsDict[tx.From].apply_transaction(tx) - if tx.To is not None: - accountsDict[tx.To].apply_transaction(tx) - - print("Balances:") - for account in accountsDict.values(): + for name in [tx.from_account, tx.to_account]: + if name and name not in accounts: + accounts[name] = Account(name) + if tx.from_account: + accounts[tx.from_account].apply_transaction(tx) + if tx.to_account: + accounts[tx.to_account].apply_transaction(tx) + + print("\nAccount Balances:") + for account in accounts.values(): print(f"{account.name}: £{account.balance / 100:.2f}") - logger.info("Accounts and balances displayed successfully") + +def list_transactions(account_name, transactions): + found = False + relevant = [tx for tx in transactions if tx.from_account == account_name or tx.to_account == account_name] + relevant.sort(key=lambda tx: datetime.strptime(tx.date, "%d/%m/%Y")) + + for tx in relevant: + print(f"{tx.date} | From: {tx.from_account} | To: {tx.to_account} | {tx.narrative} | £{tx.amount:.2f}") + found = True + + if not found: + print(f"No transactions found for account: {account_name}") def list_available_data_files(): - data_dir = "./DataFiles" try: - files = [ - f - for f in os.listdir(data_dir) - if f.endswith(".csv") or f.endswith(".json") or f.endswith(".xml") - ] + data_dir = "./DataFiles" + files = [f for f in os.listdir(data_dir) if f.endswith((".csv", ".json", ".xml"))] if not files: - print("No CSV or JSON files found in ./DataFiles/") - logger.warning("No CSV, JSON, or XML files found in ./DataFiles/") - else: - print("Available data files:") - for f in files: - print(f" - {f}") - logger.info("Available data files listed") + print("No data files found.") return files except FileNotFoundError: - print("The ./DataFiles directory does not exist") - logger.warning("The ./DataFiles directory does not exist") + print("The ./DataFiles directory does not exist.") return [] -def List(account, transactions): - account_found = False - logger.info(f"listing transactions for {account}") - for tx in transactions: - if tx.From == account or tx.To == account: - account_found = True - print( - f"{tx.Date} | From: {tx.From} | To: {tx.To} | {tx.Narrative} | £{tx.Amount:.2f}" - ) - if not account_found: - print(f"Account '{account}' not found in any transactions.") - logger.warning(f"Account '{account}' not found in any transactions.") - - def main(): print("Welcome to SupportBank") while True: - print("\nWhat would you like to do?") - print("1. List all balances") - print("2. List transactions for an account") - print("3. Quit") - choice = input("Enter the number of your choice: ").strip() - logger.info(f"User selected choice: {choice}") + print("\n1. List all balances from a single file") + print("2. List transactions for an account from a single file") + print("3. List all balances across all data files") + print("4. List all transactions for an account across all data files") + print("5. Quit") + choice = input("Enter your choice: ").strip() if choice == "1": - available = list_available_data_files() - if not available: - continue - filename = input( - "Enter filename (with extension .csv, .json, or .xml): " - ).strip() - if not ( - filename.endswith(".csv") - or filename.endswith(".json") - or filename.endswith(".xml") - ): - print( - "Invalid file extension. Please enter a .csv, .json, or XML file." - ) + files = list_available_data_files() + if not files: continue - try: - transactions = read_transactions(f"./DataFiles/{filename}") - List_All(transactions) - except FileNotFoundError: - print("File not found.") + filename = input("Enter filename: ").strip() + transactions = read_transactions(f"./DataFiles/{filename}") + list_all(transactions) + elif choice == "2": - account = input("Enter account name: ").strip() - available = list_available_data_files() - if not available: - continue - filename = input( - "Enter filename (with extension .csv, .json, or .xml): " - ).strip() - if not ( - filename.endswith(".csv") - or filename.endswith(".json") - or filename.endswith(".xml") - ): - print( - "Invalid file extension. Please enter a .csv, .json, or XML file." - ) + files = list_available_data_files() + if not files: continue - try: - transactions = read_transactions(f"./DataFiles/{filename}") - List(account, transactions) - except FileNotFoundError: - print("File not found.") + filename = input("Enter filename: ").strip() + account = input("Enter account name: ").strip() + transactions = read_transactions(f"./DataFiles/{filename}") + list_transactions(account, transactions) elif choice == "3": + files = list_available_data_files() + all_transactions = [] + for filename in files: + all_transactions.extend(read_transactions(f"./DataFiles/{filename}")) + list_all(all_transactions) + + elif choice == "4": + files = list_available_data_files() + all_transactions = [] + for filename in files: + all_transactions.extend(read_transactions(f"./DataFiles/{filename}")) + + account_names = set() + for tx in all_transactions: + if tx.from_account: + account_names.add(tx.from_account) + if tx.to_account: + account_names.add(tx.to_account) + + if not account_names: + print("No accounts found.") + continue + + sorted_accounts = sorted(account_names) + for i, name in enumerate(sorted_accounts, 1): + print(f"{i}. {name}") + + selection = input("Select an account by number or name: ").strip() + if selection.isdigit(): + idx = int(selection) + if 1 <= idx <= len(sorted_accounts): + account = sorted_accounts[idx - 1] + else: + print("Invalid number.") + continue + else: + account = selection if selection in account_names else None + if not account: + print("Invalid account.") + continue + + print(f"\nAll transactions for '{account}':") + list_transactions(account, all_transactions) + + elif choice == "5": print("Goodbye!") break else: - print("Invalid choice. Please try again.") + print("Invalid choice. Try again.") + + +if __name__ == "__main__": + main()