From 3aff9e44743313c332a0c47bfbceb35ef109ca53 Mon Sep 17 00:00:00 2001 From: Griffiths Lott Date: Wed, 8 Feb 2023 15:51:50 -0500 Subject: [PATCH] Fully working app. Interface needs work. Could use config to remember last opened location. Debug option not configured --- ILParser.py | 233 +++++++++++++++++++++++++++++++++++++++++++++++++ getcol.py | 2 +- main.py | 246 +++++++++++++++++++++++----------------------------- ui.py | 124 ++++++++++++++++++++++++++ 4 files changed, 468 insertions(+), 137 deletions(-) create mode 100644 ILParser.py create mode 100644 ui.py diff --git a/ILParser.py b/ILParser.py new file mode 100644 index 0000000..40722d0 --- /dev/null +++ b/ILParser.py @@ -0,0 +1,233 @@ +from pandas import DataFrame +import re +from logging import debug, DEBUG, basicConfig, warn +from typing import Optional, Union + + +logConfig = basicConfig(filename='ILFormatter.log', encoding='utf-8', level=DEBUG, filemode='w') +CONTRACT_NO_REGEX = "\d{3}-\d{7}-\d{3}" + +class Column: + def __init__(self, columnName: str, startIndex: int, + length: Optional[int] = None, endIndex: Optional[int] = None, valueRegex: Optional[str] = None) -> None: + + assert length != None or endIndex != None, "You must specify either the length or endIndex of this column" + self.name = columnName + self.start = startIndex + self.end = endIndex if endIndex != None else startIndex + length + self.valueRegex = valueRegex + + def __regex_check(self, value: str) -> bool: + if self.valueRegex == None: return True + return False if re.search(self.valueRegex, value) == None else True + + def extract_column(self, line: str) -> tuple[str, Union[str, float]]: + debug(line) + end = self.end if self.end != -1 else len(line) + try: + dataValue: str = line[self.start : end].replace(',', '').strip() + except: + warn(f"NO DATA VALUE PRESENT ({self.name} | {self.start}-{self.end}): {line}") + if not self.__regex_check(dataValue): + warn(f"Invalid column value: Column: {self.name} value: {dataValue} regex: {self.valueRegex}") + try: + dataValue = float(dataValue) + except: pass + return self.name, dataValue + + +FIN_COLUMNS: list[Column] = [ + Column("CUST.ID", startIndex= 0 ,endIndex = 21, valueRegex = "\d{8}"), + Column("CONTRACT.NO", startIndex= 21 ,endIndex = 37, valueRegex = "CONTRACT_NO_REGEX"), + Column("BUSINESS.TYPE", startIndex= 37 ,endIndex = 51, valueRegex = "\d{2}"), + Column("FED.ID", startIndex= 51 ,endIndex = 72, valueRegex = "\d{9}"), + Column("CUST.CREDIT.ACCT", startIndex= 72 ,endIndex = 89, valueRegex = "\d+"), + Column("CUSTOMER", startIndex= 89 ,endIndex = 120, valueRegex = None), + Column("LEASE.TYPE", startIndex= 120 ,endIndex = 131, valueRegex = None), + Column("EQUIPMENT.COST", startIndex= 131 ,endIndex = 146, valueRegex = None), + Column("CBR.", startIndex= 146 ,endIndex = 161, valueRegex = None), + Column("NET.INVESTMENT", startIndex= 161 ,endIndex = 176, valueRegex = None), + Column("ANNUAL.COMBINED.IRR", startIndex= 176 ,endIndex = 185, valueRegex = None), + Column("CONTRACT.TERM", startIndex= 185 ,endIndex = 199, valueRegex = None), + Column("INCOME.START.DATE", startIndex= 199 ,endIndex = 217, valueRegex = None), + Column("FIRST.PYMT.DATE", startIndex= 217 ,endIndex = 233, valueRegex = None), + Column("FIRST.PYMT.AMT", startIndex= 233 ,endIndex = 248, valueRegex = None), + Column("CONTRACT.PYMT.", startIndex= 248 ,endIndex = 263, valueRegex = None), + Column("INVOICE.CODE", startIndex= 263 ,endIndex = 276, valueRegex = None), + Column("INV.DAYS", startIndex= 276 ,endIndex = 285, valueRegex = None), + Column("INV.DUE.DAY", startIndex= 285 ,endIndex = 297, valueRegex = None), + Column("SEC.DEPOSIT.", startIndex= 297 ,endIndex = 312, valueRegex = None), + Column("IDC.AMOUNTS.", startIndex= 312 ,endIndex = 327, valueRegex = None), + Column("IDC.DATES.", startIndex= 327 ,endIndex = 338, valueRegex = None), + Column("RESIDUAL", startIndex= 338 ,endIndex = 353, valueRegex = None), + Column("MANAGERS.RESIDUAL", startIndex= 353 ,endIndex = 371, valueRegex = None), + Column("PROMOTION", startIndex= 371 ,endIndex = 381, valueRegex = None), + Column("PRODUCT.LINE", startIndex= 381 ,endIndex = 394, valueRegex = None), + Column("REGION", startIndex= 394 ,endIndex = 401, valueRegex = None), + Column("REGION.DESC.", startIndex= 401 ,endIndex = 432, valueRegex = None), + Column("BRANCH", startIndex= 432 ,endIndex = 439, valueRegex = None), + Column("BUSINESS.SEGMENT", startIndex= 439 ,endIndex = 456, valueRegex = None), + Column("LEAD.BANK", startIndex= 456 ,endIndex = 466, valueRegex = None), + Column("MRKTNG.REP", startIndex= 466 ,endIndex = 477, valueRegex = None), + Column("MRKTNG.REGION", startIndex= 477 ,endIndex = 491, valueRegex = None), + Column("REMIT.TO", startIndex= 491 ,endIndex = 500, valueRegex = None), + Column("PYMT.OPTION", startIndex= 500 ,endIndex = 512, valueRegex = None), + Column("BANK.CODE", startIndex= 512 ,endIndex = 522, valueRegex = None), + Column("TAPE.BANK.NUM", startIndex= 522 ,endIndex = 536, valueRegex = None), + Column("TAPE.ACCOUNT.NUM", startIndex= 536 ,endIndex = 557, valueRegex = None), + Column("TAPE.ACCT.TYPE", startIndex= 557 ,endIndex = 572, valueRegex = None), + Column("DEALER", startIndex= 572 ,endIndex = 583, valueRegex = None), + Column("PRIVATE.LABEL", startIndex= 583 ,endIndex = 597, valueRegex = None), + Column("RESID.METHOD", startIndex= 597 ,endIndex = 610, valueRegex = None), + Column("LATE.CHRG.EXMPT", startIndex= 610 ,endIndex = 626, valueRegex = None), + Column("INSURANCE.CODE", startIndex= 626 ,endIndex = 641, valueRegex = None), + Column("VARIABLE.DATE", startIndex= 641 ,endIndex = 655, valueRegex = None), + Column("VARIABLE.RATE", startIndex= 655 ,endIndex = 671, valueRegex = None), + Column("BILLING.CYCLE", startIndex= 671 ,endIndex = 685, valueRegex = None), + Column("UM.USER.DATE2", startIndex= 685 ,endIndex = 699, valueRegex = None), + Column("CR.ATTG.PHONE", startIndex= 699 ,endIndex = 715, valueRegex = None), + Column("GROSS.CONTRACT", startIndex= 715 ,endIndex = 730, valueRegex = None), + Column("ADV", startIndex= 730 ,endIndex = 734, valueRegex = None), + Column("PD.AMT.FINANCED ", startIndex= 735 ,endIndex = 751, valueRegex = None), + Column("PD.INCOME.START.DATE ", startIndex= 751 ,endIndex = 772, valueRegex = None), + Column("INVOICE.DESC", startIndex= 772 ,endIndex = 792, valueRegex = None), + Column("VARIABLE.PYMT.CODE ", startIndex= 792 ,endIndex = 811, valueRegex = None), + Column("PD.PAYMENT.AMT ", startIndex= 811 ,endIndex = 826, valueRegex = None), + Column("QUOTE.BUYOUT ", startIndex= 826 ,endIndex = 839, valueRegex = None), + Column("LATE.CHARGE.CODE ", startIndex= 839 ,endIndex = 856, valueRegex = None), + Column("LATE.CHRG.RATE ", startIndex= 856 ,endIndex = 871, valueRegex = None), + Column("M.DEF.COLLECTOR ", startIndex= 871 ,endIndex = 887, valueRegex = None), + Column("AM.ACH.LEAD.DAYS ", startIndex= 887 ,endIndex = 904, valueRegex = None), + Column("UNL POOL", startIndex= 904 ,endIndex = 915, valueRegex = None), + Column("PD RISK", startIndex= 915 ,endIndex = 926, valueRegex = None), + Column("PD RISK DATE.", startIndex= 926 ,endIndex = 940, valueRegex = None), + Column("LGD RISK", startIndex= 940 ,endIndex = 949, valueRegex = None), + Column("LGD DATE", startIndex= 949 ,endIndex = 960, valueRegex = None), + Column("Service By Others", startIndex= 960 ,endIndex = -1, valueRegex = None) +] + +ASSET_COLS: list[Column] = [ + Column("ASSET.#. ", startIndex= 0 ,endIndex = 9, valueRegex = None), + Column("CUST.ID. ", startIndex= 9 ,endIndex = 30, valueRegex = None), + Column("CONTRACT.NO ", startIndex= 30 ,endIndex = 46, valueRegex = None), + Column("CUST.CREDIT.ACCT ", startIndex= 46 ,endIndex = 63, valueRegex = None), + Column("CUST.NAME. ", startIndex= 63 ,endIndex = 84, valueRegex = None), + Column("EQUIP.DESC ", startIndex= 84 ,endIndex = 125, valueRegex = None), + Column("QUANTITY ", startIndex= 125 ,endIndex = 134, valueRegex = None), + Column("NEW.USED ", startIndex= 134 ,endIndex = 143, valueRegex = None), + Column("MODEL. ", startIndex= 143 ,endIndex = 164, valueRegex = None), + Column("A.MANUFACTURER.YEAR ", startIndex= 164 ,endIndex = 184, valueRegex = None), + Column("SERIAL.NUMBER. ", startIndex= 184 ,endIndex = 205, valueRegex = None), + Column("EQUIP.CODE ", startIndex= 205 ,endIndex = 216, valueRegex = None), + Column("EQUIP.CODE.DESC. ", startIndex= 216 ,endIndex = 247, valueRegex = None), + Column("ASSET.VENDOR ", startIndex= 247 ,endIndex = 260, valueRegex = None), + Column("ASSET.VENDOR.NAME. ", startIndex= 260 ,endIndex = 291, valueRegex = None), + Column("MANUFACTURER ", startIndex= 291 ,endIndex = 304, valueRegex = None), + Column("MANUFACT.NAME. ", startIndex= 304 ,endIndex = 335, valueRegex = None), + Column("UATB.EQUIP.ADDR1.45 ", startIndex= 335 ,endIndex = 381, valueRegex = None), + Column("UATB.EQUIP.ADDR2.45 ", startIndex= 381 ,endIndex = 427, valueRegex = None), + Column("EQUIP.CITY. ", startIndex= 427 ,endIndex = 453, valueRegex = None), + Column("EQUIP.STATE ", startIndex= 453 ,endIndex = 465, valueRegex = None), + Column("EQUIP.ZIP. ", startIndex= 465 ,endIndex = 476, valueRegex = None), + Column("STATE.TAX.CODE ", startIndex= 476 ,endIndex = 491, valueRegex = None), + Column("CNTY.TAX.CODE ", startIndex= 491 ,endIndex = 505, valueRegex = None), + Column("CITY.TAX.CODE ", startIndex= 505 ,endIndex = 519, valueRegex = None), + Column("PROP.STATUS ", startIndex= 519 ,endIndex = 531, valueRegex = None), + Column("EQUIP.COST ", startIndex= 531 ,endIndex = 546, valueRegex = None), + Column("EQUIP.COST.PCT ", startIndex= 546 ,endIndex = 561, valueRegex = None), + Column("PUR.OPTION ", startIndex= 561 ,endIndex = 572, valueRegex = None), + Column("PUR.OPTION. ", startIndex= 572 ,endIndex = 588, valueRegex = None), + Column("AS.RECOURSE.CODE ", startIndex= 588 ,endIndex = 605, valueRegex = None), + Column("RESID.AMT. ", startIndex= 605 ,endIndex = 620, valueRegex = None), + Column("BEG.DEPR.DATE ", startIndex= 620 ,endIndex = 634, valueRegex = None), + Column("OPER.LS.BEGIN.DATE ", startIndex= 634 ,endIndex = 653, valueRegex = None), + Column("OPER.LS.LIM ", startIndex= 653 ,endIndex = 665, valueRegex = None), + Column("OPER.LS.SALVAGE ", startIndex= 665 ,endIndex = -1, valueRegex = None) +] + +CUST_COLS: list[Column] = [ + Column("CONTRACT.NO ", startIndex= 0 ,endIndex = 16, valueRegex = None), + Column("CUST.CREDIT.ACCT ", startIndex= 16 ,endIndex = 33, valueRegex = None), + Column("CUST.ID. ", startIndex= 33 ,endIndex = 54, valueRegex = None), + Column("CUST.NAME. ", startIndex= 54 ,endIndex = 105, valueRegex = None), + Column("UATB.CUST.DBA. ", startIndex= 105 ,endIndex = 136, valueRegex = None), + Column("UATB.CUST.ADDRESS1.45 ", startIndex= 136 ,endIndex = 182, valueRegex = None), + Column("UATB.CUST.ADDRESS2.45 ", startIndex= 182 ,endIndex = 228, valueRegex = None), + Column("UATB.CUST.ADDRESS3.45 ", startIndex= 228 ,endIndex = 274, valueRegex = None), + Column("CUST.CITY. ", startIndex= 274 ,endIndex = 295, valueRegex = None), + Column("CUST.STATE ", startIndex= 295 ,endIndex = 306, valueRegex = None), + Column("CUST.ZIP ", startIndex= 306 ,endIndex = 317, valueRegex = None), + Column("GUAR.CODE.1 ", startIndex= 317 ,endIndex = 329, valueRegex = None), + Column("PRIN1/GUAR.NAME.1. ", startIndex= 329 ,endIndex = 365, valueRegex = None), + Column("PRIN1.ADD1. ", startIndex= 365 ,endIndex = 396, valueRegex = None), + Column("PRIN1.ADD2. ", startIndex= 396 ,endIndex = 427, valueRegex = None), + Column("PRIN1.CITY1. ", startIndex= 427 ,endIndex = 453, valueRegex = None), + Column("PRIN1.ST.1. ", startIndex= 453 ,endIndex = 464, valueRegex = None), + Column("ZIP.1. ", startIndex= 464 ,endIndex = 477, valueRegex = None), + Column("FED.ID/SS#1 ", startIndex= 477 ,endIndex = 503, valueRegex = None), + Column("GUAR.CODE.2.PRIN/GUAR.NAME.2. ", startIndex= 503 ,endIndex = 541, valueRegex = None), + Column("PRIN2.ADD2. ", startIndex= 541 ,endIndex = 572, valueRegex = None), + Column("PRIN2.ADDR2 ", startIndex= 572 ,endIndex = 603, valueRegex = None), + Column("PRIN2.CITY2. ", startIndex= 603 ,endIndex = 629, valueRegex = None), + Column("PRIN2.ST.2ZIP.2. ", startIndex= 629 ,endIndex = 653, valueRegex = None), + Column("FED.ID/SS#2 ", startIndex= 653 ,endIndex = 679, valueRegex = None), + Column("BILLING.NAME ", startIndex= 679 ,endIndex = 720, valueRegex = None), + Column("UATB.AR.ADDRESS1.45 ", startIndex= 720 ,endIndex = 766, valueRegex = None), + Column("UATB.AR.ADDRESS2.45 ", startIndex= 766 ,endIndex = 812, valueRegex = None), + Column("UATB.AR.ADDRESS3.45 ", startIndex= 812 ,endIndex = 858, valueRegex = None), + Column("AR.CITY. ", startIndex= 858 ,endIndex = 879, valueRegex = None), + Column("AR.STATE ", startIndex= 879 ,endIndex = 888, valueRegex = None), + Column("AR.ZIP ", startIndex= 888 ,endIndex = 899, valueRegex = None), + Column("AR.ATTN. ", startIndex= 899 ,endIndex = 920, valueRegex = None), + Column("UATB.CR.ATTG.NAME40. ", startIndex= 920 ,endIndex = 961, valueRegex = None), + Column("CR.SCORING ", startIndex= 961 ,endIndex = 972, valueRegex = None), + Column("FACILITY.SCORE ", startIndex= 972 ,endIndex = 988, valueRegex = None), + Column("SIC.CODE ", startIndex= 988 ,endIndex = -1, valueRegex = None), +] + +DOB_COL: list[Column] = [ + Column("CONTRACT.NO ", startIndex= 0 ,endIndex = 16, valueRegex = None), + Column("CUST.CREDIT.ACCT ", startIndex= 16 ,endIndex = 33, valueRegex = None), + Column("CUST.ID. ", startIndex= 33 ,endIndex = 54, valueRegex = None), + Column("GUAR.CODE.1 ", startIndex= 54 ,endIndex = 66, valueRegex = None), + Column("PRIN/GUAR.NAME.1. ", startIndex= 66 ,endIndex = 102, valueRegex = None), + Column("FED.ID/SS#1 ", startIndex= 102 ,endIndex = 128, valueRegex = None), + Column("DOB1 ", startIndex= 128 ,endIndex = 139, valueRegex = None), + Column("GUAR.CODE.2 ", startIndex= 139 ,endIndex = 151, valueRegex = None), + Column("PRIN/GUAR.NAME.2. ", startIndex= 151 ,endIndex = 177, valueRegex = None), + Column("FED.ID/SS#2 ", startIndex= 177 ,endIndex = -1, valueRegex = None) +] + +def parse(ILOutput: str, columns: list[Column], dataColumnRegex: str = CONTRACT_NO_REGEX) -> DataFrame : + debug(ILOutput) + lines = ILOutput.splitlines() + dataDict = {} + for index, line in enumerate(lines): + debug(f"Index: {index} | {line}") + debug(re.search(dataColumnRegex, line)) + if re.search(dataColumnRegex, line) == None: continue + for col in columns: + name, value = col.extract_column(line) + debug(f"name: {name} | value: {value}") + try: + dataDict[name].append(value) + except: + dataDict[name] = [value] + debug(dataDict) + try: + dataframe = DataFrame(dataDict) + except ValueError as ve: + debug({c: len(dataDict[c]) for c in dataDict.keys()}) + debug(ve) + return dataframe + + +# extracts = [("FIN", FIN_COLUMNS), ("ASSET", ASSET_COLS), ("CUST", CUST_COLS), ("DOB", DOB_COL)] + +# for file, columns in extracts: +# with open(f"Inputs/{file}", errors="replace") as reportFile: +# report: str = reportFile.read() +# # Removes characters that cause errors +# report: str = report.replace("^"," ") +# dataframe: DataFrame = parse(ILOutput=report, columns=columns) +# print(f"{file} dataframe: {dataframe}") \ No newline at end of file diff --git a/getcol.py b/getcol.py index 8682d14..ad526c9 100644 --- a/getcol.py +++ b/getcol.py @@ -10,7 +10,7 @@ for input in ["ASSET", "CUST", "DOB"]: for line in report.splitlines(): print(line.strip()) if len(line.strip()) > 50: - matches = re.finditer('(\w|\.)+\s', line) + matches = re.finditer('(\w|\.|/|#)+\s', line) for match in matches: print(match) colDict["ColName"].append(match.group()) diff --git a/main.py b/main.py index f761462..22ab36f 100644 --- a/main.py +++ b/main.py @@ -1,141 +1,115 @@ -from pandas import DataFrame -import re +from ui import Ui_MainWindow +import ILParser +from PyQt5 import QtWidgets from logging import debug, DEBUG, basicConfig, warn -from typing import Optional, Union +from sys import argv +from typing import Literal, Optional +from pandas import DataFrame, ExcelWriter +from datetime import datetime as dt +logConfig = basicConfig(filename='ILFormatter.log', encoding='utf-8', level=DEBUG, filemode='w') - -logConfig = basicConfig(filename='ILFormatter.log', encoding='utf-8', level=DEBUG) - -TEST_FIN_LOCATION = r"Inputs/FIN" -TEST_ASSET_LOCATION = R"Inputs\ASSET" - -CONTRACT_NO_REGEX = "\d{3}-\d{7}-\d{3}" - -class Column: - def __init__(self, columnName: str, startIndex: int, - length: Optional[int] = None, endIndex: Optional[int] = None, valueRegex: Optional[str] = None) -> None: +class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + def __init__(self, *args, obj=None, **kwargs): + debug("MainWindow class init..") + super(MainWindow, self).__init__(*args, **kwargs) + self.setupUi(self) + self.processButton.setEnabled(False) - assert length != None or endIndex != None, "You must specify either the length or endIndex of this column" - self.name = columnName - self.start = startIndex - self.end = endIndex if endIndex != None else startIndex + length - self.valueRegex = valueRegex - - def __regex_check(self, value: str) -> bool: - if self.valueRegex == None: return True - return False if re.search(self.valueRegex, value) == None else True - - def extract_column(self, line: str) -> tuple[str, Union[str, float]]: - debug(line) - if self.end == -1: - end = len(line) - else: - assert len(line) >= self.end, f"Line is to short to extract value: len: {len(line)} > end : {self.end}" - end = self.end - dataValue: str = line[self.start : end].replace(',', '').strip() - if not self.__regex_check(dataValue): - warn(f"Invalid column value: Column: {self.name} value: {dataValue} regex: {self.valueRegex}") - try: - dataValue = float(dataValue) - except: pass - return self.name, dataValue - - -FIN_COLUMNS: list[Column] = [ - Column("CUST.ID", startIndex= 0 ,endIndex = 21, valueRegex = "\d{8}"), - Column("CONTRACT.NO", startIndex= 21 ,endIndex = 37, valueRegex = "CONTRACT_NO_REGEX"), - Column("BUSINESS.TYPE", startIndex= 37 ,endIndex = 51, valueRegex = "\d{2}"), - Column("FED.ID", startIndex= 51 ,endIndex = 72, valueRegex = "\d{9}"), - Column("CUST.CREDIT.ACCT", startIndex= 72 ,endIndex = 89, valueRegex = "\d+"), - Column("CUSTOMER", startIndex= 89 ,endIndex = 120, valueRegex = None), - Column("LEASE.TYPE", startIndex= 120 ,endIndex = 131, valueRegex = None), - Column("EQUIPMENT.COST", startIndex= 131 ,endIndex = 146, valueRegex = None), - Column("CBR.", startIndex= 146 ,endIndex = 161, valueRegex = None), - Column("NET.INVESTMENT", startIndex= 161 ,endIndex = 176, valueRegex = None), - Column("ANNUAL.COMBINED.IRR", startIndex= 176 ,endIndex = 185, valueRegex = None), - Column("CONTRACT.TERM", startIndex= 185 ,endIndex = 199, valueRegex = None), - Column("INCOME.START.DATE", startIndex= 199 ,endIndex = 217, valueRegex = None), - Column("FIRST.PYMT.DATE", startIndex= 217 ,endIndex = 233, valueRegex = None), - Column("FIRST.PYMT.AMT", startIndex= 233 ,endIndex = 248, valueRegex = None), - Column("CONTRACT.PYMT.", startIndex= 248 ,endIndex = 263, valueRegex = None), - Column("INVOICE.CODE", startIndex= 263 ,endIndex = 276, valueRegex = None), - Column("INV.DAYS", startIndex= 276 ,endIndex = 285, valueRegex = None), - Column("INV.DUE.DAY", startIndex= 285 ,endIndex = 297, valueRegex = None), - Column("SEC.DEPOSIT.", startIndex= 297 ,endIndex = 312, valueRegex = None), - Column("IDC.AMOUNTS.", startIndex= 312 ,endIndex = 327, valueRegex = None), - Column("IDC.DATES.", startIndex= 327 ,endIndex = 338, valueRegex = None), - Column("RESIDUAL", startIndex= 338 ,endIndex = 353, valueRegex = None), - Column("MANAGERS.RESIDUAL", startIndex= 353 ,endIndex = 371, valueRegex = None), - Column("PROMOTION", startIndex= 371 ,endIndex = 381, valueRegex = None), - Column("PRODUCT.LINE", startIndex= 381 ,endIndex = 394, valueRegex = None), - Column("REGION", startIndex= 394 ,endIndex = 401, valueRegex = None), - Column("REGION.DESC.", startIndex= 401 ,endIndex = 432, valueRegex = None), - Column("BRANCH", startIndex= 432 ,endIndex = 439, valueRegex = None), - Column("BUSINESS.SEGMENT", startIndex= 439 ,endIndex = 456, valueRegex = None), - Column("LEAD.BANK", startIndex= 456 ,endIndex = 466, valueRegex = None), - Column("MRKTNG.REP", startIndex= 466 ,endIndex = 477, valueRegex = None), - Column("MRKTNG.REGION", startIndex= 477 ,endIndex = 491, valueRegex = None), - Column("REMIT.TO", startIndex= 491 ,endIndex = 500, valueRegex = None), - Column("PYMT.OPTION", startIndex= 500 ,endIndex = 512, valueRegex = None), - Column("BANK.CODE", startIndex= 512 ,endIndex = 522, valueRegex = None), - Column("TAPE.BANK.NUM", startIndex= 522 ,endIndex = 536, valueRegex = None), - Column("TAPE.ACCOUNT.NUM", startIndex= 536 ,endIndex = 557, valueRegex = None), - Column("TAPE.ACCT.TYPE", startIndex= 557 ,endIndex = 572, valueRegex = None), - Column("DEALER", startIndex= 572 ,endIndex = 583, valueRegex = None), - Column("PRIVATE.LABEL", startIndex= 583 ,endIndex = 597, valueRegex = None), - Column("RESID.METHOD", startIndex= 597 ,endIndex = 610, valueRegex = None), - Column("LATE.CHRG.EXMPT", startIndex= 610 ,endIndex = 626, valueRegex = None), - Column("INSURANCE.CODE", startIndex= 626 ,endIndex = 641, valueRegex = None), - Column("VARIABLE.DATE", startIndex= 641 ,endIndex = 655, valueRegex = None), - Column("VARIABLE.RATE", startIndex= 655 ,endIndex = 671, valueRegex = None), - Column("BILLING.CYCLE", startIndex= 671 ,endIndex = 685, valueRegex = None), - Column("UM.USER.DATE2", startIndex= 685 ,endIndex = 699, valueRegex = None), - Column("CR.ATTG.PHONE", startIndex= 699 ,endIndex = 715, valueRegex = None), - Column("GROSS.CONTRACT", startIndex= 715 ,endIndex = 730, valueRegex = None), - Column("ADV", startIndex= 730 ,endIndex = 734, valueRegex = None), - Column("PD.AMT.FINANCED ", startIndex= 735 ,endIndex = 751, valueRegex = None), - Column("PD.INCOME.START.DATE ", startIndex= 751 ,endIndex = 772, valueRegex = None), - Column("INVOICE.DESC", startIndex= 772 ,endIndex = 792, valueRegex = None), - Column("VARIABLE.PYMT.CODE ", startIndex= 792 ,endIndex = 811, valueRegex = None), - Column("PD.PAYMENT.AMT ", startIndex= 811 ,endIndex = 826, valueRegex = None), - Column("QUOTE.BUYOUT ", startIndex= 826 ,endIndex = 839, valueRegex = None), - Column("LATE.CHARGE.CODE ", startIndex= 839 ,endIndex = 856, valueRegex = None), - Column("LATE.CHRG.RATE ", startIndex= 856 ,endIndex = 871, valueRegex = None), - Column("M.DEF.COLLECTOR ", startIndex= 871 ,endIndex = 887, valueRegex = None), - Column("AM.ACH.LEAD.DAYS ", startIndex= 887 ,endIndex = 904, valueRegex = None), - Column("UNL POOL", startIndex= 904 ,endIndex = 915, valueRegex = None), - Column("PD RISK", startIndex= 915 ,endIndex = 926, valueRegex = None), - Column("PD RISK DATE.", startIndex= 926 ,endIndex = 940, valueRegex = None), - Column("LGD RISK", startIndex= 940 ,endIndex = 949, valueRegex = None), - Column("LGD DATE", startIndex= 949 ,endIndex = 960, valueRegex = None), - Column("Service By Others", startIndex= 960 ,endIndex = -1, valueRegex = None) -] - -def parse(ILOutput: str, columns: list[Column], dataColumnRegex: str = CONTRACT_NO_REGEX) -> DataFrame : - debug(ILOutput) - lines = ILOutput.splitlines() - dataDict = {} - for index, line in enumerate(lines): - debug(f"Index: {index} | {line}") - debug(re.search(dataColumnRegex, line)) - if re.search(dataColumnRegex, line) == None: continue - for col in columns: - name, value = col.extract_column(line) - try: - dataDict[name].append(value) - except: - dataDict[name] = [value] - dataframe = DataFrame(dataDict) - return dataframe - - - - -with open(TEST_FIN_LOCATION, errors="replace") as reportFile: - report: str = reportFile.read() - # Removes characters that cause errors - report: str = report.replace("^"," ") - finDataframe: DataFrame = parse(ILOutput=report, columns=FIN_COLUMNS) - print(f"FIN dataframe: {finDataframe}") \ No newline at end of file + # File Locations + self.assetFile = None + self.custFile = None + self.dobFile = None + self.finFile = None + self.outputLocation = None + + # Button Hooks + self.assetButton.clicked.connect(lambda: self._set_file(lineEdit= self.assetLE, selfFile="ASSET")) + self.custButton.clicked.connect(lambda: self._set_file(lineEdit= self.custLe, selfFile="CUST")) + self.dobButton.clicked.connect(lambda: self._set_file(lineEdit= self.dobLE, selfFile="DOB")) + self.finButton.clicked.connect(lambda: self._set_file(lineEdit= self.finLE, selfFile="FIN")) + self.outputButton.clicked.connect(lambda: self._set_output()) + self.processButton.clicked.connect(lambda: self._process()) + + def _check_files(self): + debug(self.assetFile) + debug(self.custFile) + debug(self.dobFile) + debug(self.finFile) + ready = ( + self.assetFile != None and + self.custFile != None and + self.dobFile != None and + self.finFile != None and + self.outputLocation != None + ) + self.processButton.setEnabled(ready) + + def _set_file(self, lineEdit: QtWidgets.QLineEdit, selfFile: Literal["ASSET", "CUST", "DOB", "FIN"]) -> str : + selectedFile: list[str] = QtWidgets.QFileDialog.getOpenFileName(self, "OpenFile") + debug(f"Selected file: {selectedFile}") + lineEdit.setText(selectedFile[0]) + file = selectedFile[0] if selectedFile[0] != '' else None + if file != None and self.outputLocation == None: + self._auto_output_set(fileRoot='/'.join(file.split('/')[:-1])) + if selfFile == "ASSET": + self.assetFile = file + elif selfFile == "CUST": + self.custFile = file + elif selfFile == "DOB": + self.dobFile = file + elif selfFile == "FIN": + self.finFile = file + self._check_files() + + def _set_output(self): + self.outputLocation = QtWidgets.QFileDialog.getSaveFileName(self, "Output file name") if QtWidgets.QFileDialog.getSaveFileName(self, "Output file name") != '' else None + self.outputLE.setText(self.outputLocation if self.outputLocation != None else '') + debug(f"Output Location: {self.outputLocation}") + + def _auto_output_set(self, fileRoot): + self.outputLocation = fileRoot + f"/Portfolio Contracts - {dt.now().strftime('%Y-%m-%d')}.xlsx" + self.outputLE.setText(self.outputLocation if self.outputLocation != None else '') + debug(f"Auto set output: {self.outputLocation}") + + def _setAssetFile(self): + self.assetFile = self._set_file(self.assetLE) + + def _parse_file(self, filePath: str, parseColumns: list[ILParser.Column]) -> Optional[DataFrame]: + with open(filePath) as file: + report = file.read() + debug(f"Report: {report}") + data: DataFrame = ILParser.parse(report, parseColumns) + debug(f"Data: {data}") + if data.empty: + return None + else: return data + + def _process(self): + assetDf: DataFrame = self._parse_file(self.assetFile, ILParser.ASSET_COLS) + debug(assetDf) + custDf: DataFrame = self._parse_file(self.custFile, ILParser.CUST_COLS) + debug(custDf) + dobDf: DataFrame = self._parse_file(self.dobFile, ILParser.DOB_COL) + debug(dobDf) + finDf: DataFrame = self._parse_file(self.finFile, ILParser.FIN_COLUMNS) + debug(finDf) + with ExcelWriter(self.outputLocation) as writer: + assetDf.to_excel(writer, sheet_name="ASSET") + custDf.to_excel(writer, sheet_name="CUST") + dobDf.to_excel(writer, sheet_name="DOB") + finDf.to_excel(writer, sheet_name="FIN") + debug("Finished writing to excel.") + + +# Defines the app +app = QtWidgets.QApplication(argv) +# Sets the style +app.setStyle("Fusion") +# Builds the main window +window = MainWindow() +window.setWindowTitle("IL Extract") +window.show() +# Starts the app +app.exec() \ No newline at end of file diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..fb7c3ec --- /dev/null +++ b/ui.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'PortfolioILFormatter.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(520, 314) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.widget = QtWidgets.QWidget(self.centralwidget) + self.widget.setGeometry(QtCore.QRect(10, 10, 491, 251)) + self.widget.setObjectName("widget") + self.mainVbox = QtWidgets.QVBoxLayout(self.widget) + self.mainVbox.setContentsMargins(0, 0, 0, 0) + self.mainVbox.setObjectName("mainVbox") + self.assetHbox = QtWidgets.QHBoxLayout() + self.assetHbox.setObjectName("assetHbox") + self.assetButton = QtWidgets.QPushButton(self.widget) + self.assetButton.setMinimumSize(QtCore.QSize(99, 27)) + self.assetButton.setMaximumSize(QtCore.QSize(99, 27)) + self.assetButton.setObjectName("assetButton") + self.assetHbox.addWidget(self.assetButton) + self.assetLE = QtWidgets.QLineEdit(self.widget) + self.assetLE.setObjectName("assetLE") + self.assetHbox.addWidget(self.assetLE) + self.mainVbox.addLayout(self.assetHbox) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.custButton = QtWidgets.QPushButton(self.widget) + self.custButton.setMinimumSize(QtCore.QSize(99, 27)) + self.custButton.setMaximumSize(QtCore.QSize(99, 27)) + self.custButton.setObjectName("custButton") + self.horizontalLayout_2.addWidget(self.custButton) + self.custLe = QtWidgets.QLineEdit(self.widget) + self.custLe.setObjectName("custLe") + self.horizontalLayout_2.addWidget(self.custLe) + self.mainVbox.addLayout(self.horizontalLayout_2) + self.horizontalLayout_6 = QtWidgets.QHBoxLayout() + self.horizontalLayout_6.setObjectName("horizontalLayout_6") + self.dobButton = QtWidgets.QPushButton(self.widget) + self.dobButton.setMinimumSize(QtCore.QSize(99, 27)) + self.dobButton.setMaximumSize(QtCore.QSize(99, 27)) + self.dobButton.setObjectName("dobButton") + self.horizontalLayout_6.addWidget(self.dobButton) + self.dobLE = QtWidgets.QLineEdit(self.widget) + self.dobLE.setObjectName("dobLE") + self.horizontalLayout_6.addWidget(self.dobLE) + self.mainVbox.addLayout(self.horizontalLayout_6) + self.horizontalLayout_7 = QtWidgets.QHBoxLayout() + self.horizontalLayout_7.setObjectName("horizontalLayout_7") + self.finButton = QtWidgets.QPushButton(self.widget) + self.finButton.setMinimumSize(QtCore.QSize(99, 27)) + self.finButton.setMaximumSize(QtCore.QSize(99, 27)) + self.finButton.setObjectName("finButton") + self.horizontalLayout_7.addWidget(self.finButton) + self.finLE = QtWidgets.QLineEdit(self.widget) + self.finLE.setObjectName("finLE") + self.horizontalLayout_7.addWidget(self.finLE) + self.mainVbox.addLayout(self.horizontalLayout_7) + self.horizontalLayout_8 = QtWidgets.QHBoxLayout() + self.horizontalLayout_8.setObjectName("horizontalLayout_8") + self.outputButton = QtWidgets.QPushButton(self.widget) + self.outputButton.setMinimumSize(QtCore.QSize(99, 27)) + self.outputButton.setMaximumSize(QtCore.QSize(99, 27)) + self.outputButton.setObjectName("outputButton") + self.horizontalLayout_8.addWidget(self.outputButton) + self.outputLE = QtWidgets.QLineEdit(self.widget) + self.outputLE.setObjectName("outputLE") + self.horizontalLayout_8.addWidget(self.outputLE) + self.mainVbox.addLayout(self.horizontalLayout_8) + self.processButton = QtWidgets.QPushButton(self.widget) + self.processButton.setObjectName("processButton") + self.mainVbox.addWidget(self.processButton) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 520, 24)) + self.menubar.setObjectName("menubar") + self.menuSettings = QtWidgets.QMenu(self.menubar) + self.menuSettings.setObjectName("menuSettings") + self.menuLog_Level = QtWidgets.QMenu(self.menuSettings) + self.menuLog_Level.setObjectName("menuLog_Level") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + self.actionDebug_2 = QtWidgets.QAction(MainWindow) + self.actionDebug_2.setObjectName("actionDebug_2") + self.actionInfo = QtWidgets.QAction(MainWindow) + self.actionInfo.setObjectName("actionInfo") + self.actionInfo_2 = QtWidgets.QAction(MainWindow) + self.actionInfo_2.setObjectName("actionInfo_2") + self.menuLog_Level.addAction(self.actionInfo) + self.menuLog_Level.addAction(self.actionInfo_2) + self.menuLog_Level.addAction(self.actionDebug_2) + self.menuSettings.addAction(self.menuLog_Level.menuAction()) + self.menubar.addAction(self.menuSettings.menuAction()) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Portfolio IL Output Formatter")) + self.assetButton.setText(_translate("MainWindow", "Select ASSET:")) + self.custButton.setText(_translate("MainWindow", "Select CUST:")) + self.dobButton.setText(_translate("MainWindow", "Select DOB:")) + self.finButton.setText(_translate("MainWindow", "Select FIN:")) + self.outputButton.setText(_translate("MainWindow", "Select Output")) + self.processButton.setText(_translate("MainWindow", "Process Files")) + self.menuSettings.setTitle(_translate("MainWindow", "Settings")) + self.menuLog_Level.setTitle(_translate("MainWindow", "Log Level")) + self.actionDebug_2.setText(_translate("MainWindow", "Debug")) + self.actionInfo.setText(_translate("MainWindow", "Warn")) + self.actionInfo_2.setText(_translate("MainWindow", "Info"))