Parses portfolio related IL outputs to Excel
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PortfolioParser/main.py

216 lines
9.1 KiB

from ui import Ui_MainWindow
from errorDialog import ErrorDialog
import ILParser
from PyQt5 import QtWidgets
from logging import debug, info, warning, exception as logException, error,DEBUG, INFO, WARNING, ERROR, basicConfig, getLogger
from sys import argv
from typing import Literal, Optional
from pandas import DataFrame, ExcelWriter
from datetime import datetime as dt
from os import startfile
from json import load, dump
from time import sleep
# Open the config file, create a dict, and set up logging
with open("config.json") as configFile:
config: dict[Literal["loggingLevel"], Literal["ERROR", "WARNING", "INFO", "DEBUG"]] = load(configFile)
basicConfig(filename='ILFormatter.log', encoding='utf-8', level=config["loggingLevel"], filemode='w', force=True)
info(f"Starting with log level: {getLogger().level}")
# Change the current log level and save the change to config.json
def change_log_level(newLevel: Literal["ERROR", "WARNING", "INFO", "DEBUG"]):
config["loggingLevel"] = newLevel
with open("config.json", 'w') as configFile:
dump(config, configFile)
getLogger().setLevel(newLevel)
print(f"{now()} | New logging level: {getLogger().level}\n")
# Creates an error dialog pop up
# Based on the ui from errorDialog.py
def open_error_dialog(errorLabel: str, errorDescription: str, errorText: str):
dialog = QtWidgets.QDialog()
dialog.ui = ErrorDialog()
dialog.ui.setupUi(dialog)
dialog.ui.setFields(errorLabel, errorDescription, errorText)
dialog.exec_()
# Used to easily record uniform timestamps
now = lambda : dt.now().strftime("%H:%M-%S.%f")
# This class is responable for managing the UI of the application
# and connection it to the functionality of ILParser.py
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)
self.openButton.setEnabled(False)
# Fix log level action buttons
level: QtWidgets.QAction
for level in [self.actionError, self.warnAction, self.infoAction, self.debugAction]:
inUse = level.text().upper() == config["loggingLevel"]
level.setEnabled(not inUse)
level.setChecked(inUse)
if inUse: self.logLevel = level
debug(f"Logging Level: {self.logLevel}")
# 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())
self.openButton.clicked.connect(lambda: self._open_with_default_app(self.outputLocation))
# Action Hooks
self.actionError.triggered.connect(lambda: self._switch_log_levels(self.actionError))
self.warnAction.triggered.connect(lambda: self._switch_log_levels(self.warnAction))
self.infoAction.triggered.connect(lambda: self._switch_log_levels(self.infoAction))
self.debugAction.triggered.connect(lambda: self._switch_log_levels(self.debugAction))
def _check_files(self):
debug(self.assetFile)
debug(self.custFile)
debug(self.dobFile)
debug(self.finFile)
self.openButton.setEnabled(False)
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"]) -> Optional[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 _open_with_default_app(self, item):
"""
Opens the linked item with it's default application (excel)
"""
debug(f"_open_with_default_app: {item}")
startfile(item)
def _switch_log_levels(self, newLevel: QtWidgets.QAction):
oldLevel: QtWidgets.QAction = self.logLevel
print(f"{now()} | Log Level Changed: {oldLevel.text()} -> {newLevel.text()}")
newLevel.setChecked(True)
oldLevel.setChecked(False)
newLevel.setEnabled(False)
oldLevel.setEnabled(True)
self.logLevel = newLevel
change_log_level(newLevel.text().upper())
def _parse_file(self, filePath: str, parseColumns: list[ILParser.Column]) -> Optional[DataFrame]:
with open(filePath) as file:
report = file.read()
debug(f"Report: {report}")
debug(f"Parse Columns:\n{parseColumns}")
try:
data: DataFrame = ILParser.parse(report, parseColumns)
except Exception as e:
logException(f"Failed to parse file-> {filePath} :\n{e}")
open_error_dialog("Parsing Error:",f"Failed to parse file-> {filePath}",repr(e))
return None
debug(f"Data: {data}")
if data.empty:
error(f"Dataframe empty -> {filePath} | Returning none")
open_error_dialog("Data Processing Error:",f"Dataframe empty!",filePath)
return None
else: return data
def _process(self):
assetDf: Optional[DataFrame] = self._parse_file(filePath= self.assetFile, parseColumns= ILParser.ASSET_COLS)
debug(f"AssetDF: {assetDf} | {type(assetDf)} ")
if type(assetDf) != DataFrame:
debug(f"Parse Columns: {ILParser.ASSET_COLS}")
self.assetLE.setText("")
self.assetFile = None
return None
custDf: DataFrame = self._parse_file(self.custFile, ILParser.CUST_COLS)
debug(custDf)
if type(custDf) != DataFrame:
debug(f"Parse Columns: {ILParser.CUST_COLS}")
self.custLe.setText("")
self.custFile = None
return None
dobDf: DataFrame = self._parse_file(self.dobFile, ILParser.DOB_COL)
debug(dobDf)
if type(dobDf) != DataFrame:
debug(f"Parse Columns: {ILParser.DOB_COL}")
self.dobLE.setText("")
self.dobFile = None
return None
finDf: DataFrame = self._parse_file(self.finFile, ILParser.FIN_COLUMNS)
debug(finDf)
if type(finDf) != DataFrame:
debug(f"Parse Columns: {ILParser.FIN_COLUMNS}")
self.finLE.setText("")
self.finFile = None
return None
try:
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")
except Exception as e:
logException(f"{now()} | Failed to write to excel -> {self.outputLocation} :\n{e}")
open_error_dialog("Failed to Create Excel", f"Failed to write to excel -> {self.outputLocation}", repr(e))
return None
debug("Finished writing to excel.")
self.openButton.setEnabled(True)
# 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()