Added report tab with HTML and excel export.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
- Reverted details exports from docx back to pdf.
|
- Reverted details exports from docx back to pdf.
|
||||||
- Large scale speedups for control chart construction.
|
- Large scale speedups for control chart construction.
|
||||||
|
- Reports are now given their own tab and can be updated in real time.
|
||||||
|
|
||||||
## 202409.05
|
## 202409.05
|
||||||
|
|
||||||
|
|||||||
@@ -593,7 +593,7 @@ class BasicSubmission(BaseClass):
|
|||||||
return
|
return
|
||||||
case item if item in self.jsons():
|
case item if item in self.jsons():
|
||||||
match key:
|
match key:
|
||||||
case "custom":
|
case "custom" | "source_plates":
|
||||||
existing = value
|
existing = value
|
||||||
case _:
|
case _:
|
||||||
# logger.debug(f"Setting JSON attribute.")
|
# logger.debug(f"Setting JSON attribute.")
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
'''
|
'''
|
||||||
Contains functions for generating summary reports
|
Contains functions for generating summary reports
|
||||||
'''
|
'''
|
||||||
from PyQt6.QtCore import QMarginsF
|
|
||||||
from PyQt6.QtGui import QPageLayout, QPageSize
|
|
||||||
from pandas import DataFrame, ExcelWriter
|
from pandas import DataFrame, ExcelWriter
|
||||||
import logging, re
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import date, timedelta
|
from datetime import date
|
||||||
from typing import List, Tuple, Any
|
from typing import Tuple
|
||||||
from backend.db.models import BasicSubmission
|
from backend.db.models import BasicSubmission
|
||||||
from tools import jinja_template_loading, get_first_blank_df_row, \
|
from tools import jinja_template_loading, get_first_blank_df_row, \
|
||||||
row_map
|
row_map
|
||||||
@@ -21,10 +19,12 @@ env = jinja_template_loading()
|
|||||||
|
|
||||||
class ReportMaker(object):
|
class ReportMaker(object):
|
||||||
|
|
||||||
def __init__(self, start_date: date, end_date: date):
|
def __init__(self, start_date: date, end_date: date, organizations:list|None=None):
|
||||||
self.start_date = start_date
|
self.start_date = start_date
|
||||||
self.end_date = end_date
|
self.end_date = end_date
|
||||||
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date)
|
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date)
|
||||||
|
if organizations is not None:
|
||||||
|
self.subs = [sub for sub in self.subs if sub.submitting_lab.name in organizations]
|
||||||
self.detailed_df, self.summary_df = self.make_report_xlsx()
|
self.detailed_df, self.summary_df = self.make_report_xlsx()
|
||||||
self.html = self.make_report_html(df=self.summary_df)
|
self.html = self.make_report_html(df=self.summary_df)
|
||||||
|
|
||||||
@@ -35,6 +35,8 @@ class ReportMaker(object):
|
|||||||
Returns:
|
Returns:
|
||||||
DataFrame: output dataframe
|
DataFrame: output dataframe
|
||||||
"""
|
"""
|
||||||
|
if not self.subs:
|
||||||
|
return DataFrame(), DataFrame()
|
||||||
df = DataFrame.from_records([item.to_dict(report=True) for item in self.subs])
|
df = DataFrame.from_records([item.to_dict(report=True) for item in self.subs])
|
||||||
# NOTE: put submissions with the same lab together
|
# NOTE: put submissions with the same lab together
|
||||||
df = df.sort_values("submitting_lab")
|
df = df.sort_values("submitting_lab")
|
||||||
@@ -100,17 +102,6 @@ class ReportMaker(object):
|
|||||||
if isinstance(filename, str):
|
if isinstance(filename, str):
|
||||||
filename = Path(filename)
|
filename = Path(filename)
|
||||||
filename = filename.absolute()
|
filename = filename.absolute()
|
||||||
# NOTE: html_to_pdf doesn't function without a PyQt6 app
|
|
||||||
# if isinstance(obj, QWidget):
|
|
||||||
# logger.info(f"We're in PyQt environment, writing PDF to: {filename}")
|
|
||||||
# page_layout = QPageLayout()
|
|
||||||
# page_layout.setPageSize(QPageSize(QPageSize.PageSizeId.A4))
|
|
||||||
# page_layout.setOrientation(QPageLayout.Orientation.Portrait)
|
|
||||||
# page_layout.setMargins(QMarginsF(25, 25, 25, 25))
|
|
||||||
# self.webview.page().printToPdf(fname.with_suffix(".pdf").__str__(), page_layout)
|
|
||||||
# else:
|
|
||||||
# logger.info("Not in PyQt. Skipping PDF writing.")
|
|
||||||
# logger.debug("Finished writing.")
|
|
||||||
self.writer = ExcelWriter(filename.with_suffix(".xlsx"), engine='openpyxl')
|
self.writer = ExcelWriter(filename.with_suffix(".xlsx"), engine='openpyxl')
|
||||||
self.summary_df.to_excel(self.writer, sheet_name="Report")
|
self.summary_df.to_excel(self.writer, sheet_name="Report")
|
||||||
self.detailed_df.to_excel(self.writer, sheet_name="Details", index=False)
|
self.detailed_df.to_excel(self.writer, sheet_name="Details", index=False)
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ class RSLNamer(object):
|
|||||||
logger.debug(f"Using string method for {filename}.")
|
logger.debug(f"Using string method for {filename}.")
|
||||||
logger.debug(f"Using regex: {regex}")
|
logger.debug(f"Using regex: {regex}")
|
||||||
m = regex.search(filename)
|
m = regex.search(filename)
|
||||||
print(m)
|
|
||||||
try:
|
try:
|
||||||
submission_type = m.lastgroup
|
submission_type = m.lastgroup
|
||||||
logger.debug(f"Got submission type: {submission_type}")
|
logger.debug(f"Got submission type: {submission_type}")
|
||||||
@@ -98,7 +97,6 @@ class RSLNamer(object):
|
|||||||
message="Please select submission type from list below.", obj_type=SubmissionType)
|
message="Please select submission type from list below.", obj_type=SubmissionType)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
submission_type = dlg.parse_form()
|
submission_type = dlg.parse_form()
|
||||||
print(submission_type)
|
|
||||||
submission_type = submission_type.replace("_", " ")
|
submission_type = submission_type.replace("_", " ")
|
||||||
return submission_type
|
return submission_type
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from .controls_chart import ControlsViewer
|
|||||||
from .kit_creator import KitAdder
|
from .kit_creator import KitAdder
|
||||||
from .submission_type_creator import SubmissionTypeAdder, SubmissionType
|
from .submission_type_creator import SubmissionTypeAdder, SubmissionType
|
||||||
from .sample_search import SearchBox
|
from .sample_search import SearchBox
|
||||||
|
from .summary import Summary
|
||||||
|
|
||||||
logger = logging.getLogger(f'submissions.{__name__}')
|
logger = logging.getLogger(f'submissions.{__name__}')
|
||||||
logger.info("Hello, I am a logger")
|
logger.info("Hello, I am a logger")
|
||||||
@@ -69,7 +70,7 @@ class App(QMainWindow):
|
|||||||
fileMenu = menuBar.addMenu("&File")
|
fileMenu = menuBar.addMenu("&File")
|
||||||
# NOTE: Creating menus using a title
|
# NOTE: Creating menus using a title
|
||||||
methodsMenu = menuBar.addMenu("&Methods")
|
methodsMenu = menuBar.addMenu("&Methods")
|
||||||
reportMenu = menuBar.addMenu("&Reports")
|
# reportMenu = menuBar.addMenu("&Reports")
|
||||||
maintenanceMenu = menuBar.addMenu("&Monthly")
|
maintenanceMenu = menuBar.addMenu("&Monthly")
|
||||||
helpMenu = menuBar.addMenu("&Help")
|
helpMenu = menuBar.addMenu("&Help")
|
||||||
helpMenu.addAction(self.helpAction)
|
helpMenu.addAction(self.helpAction)
|
||||||
@@ -80,7 +81,7 @@ class App(QMainWindow):
|
|||||||
fileMenu.addAction(self.yamlImportAction)
|
fileMenu.addAction(self.yamlImportAction)
|
||||||
methodsMenu.addAction(self.searchLog)
|
methodsMenu.addAction(self.searchLog)
|
||||||
methodsMenu.addAction(self.searchSample)
|
methodsMenu.addAction(self.searchSample)
|
||||||
reportMenu.addAction(self.generateReportAction)
|
# reportMenu.addAction(self.generateReportAction)
|
||||||
maintenanceMenu.addAction(self.joinExtractionAction)
|
maintenanceMenu.addAction(self.joinExtractionAction)
|
||||||
maintenanceMenu.addAction(self.joinPCRAction)
|
maintenanceMenu.addAction(self.joinPCRAction)
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ class App(QMainWindow):
|
|||||||
# logger.debug(f"Creating actions...")
|
# logger.debug(f"Creating actions...")
|
||||||
self.importAction = QAction("&Import Submission", self)
|
self.importAction = QAction("&Import Submission", self)
|
||||||
self.addReagentAction = QAction("Add Reagent", self)
|
self.addReagentAction = QAction("Add Reagent", self)
|
||||||
self.generateReportAction = QAction("Make Report", self)
|
# self.generateReportAction = QAction("Make Report", self)
|
||||||
self.addKitAction = QAction("Import Kit", self)
|
self.addKitAction = QAction("Import Kit", self)
|
||||||
self.addOrgAction = QAction("Import Org", self)
|
self.addOrgAction = QAction("Import Org", self)
|
||||||
self.joinExtractionAction = QAction("Link Extraction Logs")
|
self.joinExtractionAction = QAction("Link Extraction Logs")
|
||||||
@@ -122,7 +123,7 @@ class App(QMainWindow):
|
|||||||
# logger.debug(f"Connecting actions...")
|
# logger.debug(f"Connecting actions...")
|
||||||
self.importAction.triggered.connect(self.table_widget.formwidget.importSubmission)
|
self.importAction.triggered.connect(self.table_widget.formwidget.importSubmission)
|
||||||
self.addReagentAction.triggered.connect(self.table_widget.formwidget.add_reagent)
|
self.addReagentAction.triggered.connect(self.table_widget.formwidget.add_reagent)
|
||||||
self.generateReportAction.triggered.connect(self.table_widget.sub_wid.generate_report)
|
# self.generateReportAction.triggered.connect(self.table_widget.sub_wid.generate_report)
|
||||||
self.joinExtractionAction.triggered.connect(self.table_widget.sub_wid.link_extractions)
|
self.joinExtractionAction.triggered.connect(self.table_widget.sub_wid.link_extractions)
|
||||||
self.joinPCRAction.triggered.connect(self.table_widget.sub_wid.link_pcr)
|
self.joinPCRAction.triggered.connect(self.table_widget.sub_wid.link_pcr)
|
||||||
self.helpAction.triggered.connect(self.showAbout)
|
self.helpAction.triggered.connect(self.showAbout)
|
||||||
@@ -254,8 +255,8 @@ class AddSubForm(QWidget):
|
|||||||
# NOTE: Add tabs
|
# NOTE: Add tabs
|
||||||
self.tabs.addTab(self.tab1,"Submissions")
|
self.tabs.addTab(self.tab1,"Submissions")
|
||||||
self.tabs.addTab(self.tab2,"Controls")
|
self.tabs.addTab(self.tab2,"Controls")
|
||||||
self.tabs.addTab(self.tab3, "Add SubmissionType")
|
self.tabs.addTab(self.tab3, "Summary Report")
|
||||||
self.tabs.addTab(self.tab4, "Add Kit")
|
# self.tabs.addTab(self.tab4, "Add Kit")
|
||||||
# NOTE: Create submission adder form
|
# NOTE: Create submission adder form
|
||||||
self.formwidget = SubmissionFormContainer(self)
|
self.formwidget = SubmissionFormContainer(self)
|
||||||
self.formlayout = QVBoxLayout(self)
|
self.formlayout = QVBoxLayout(self)
|
||||||
@@ -282,14 +283,15 @@ class AddSubForm(QWidget):
|
|||||||
self.tab2.layout.addWidget(self.controls_viewer)
|
self.tab2.layout.addWidget(self.controls_viewer)
|
||||||
self.tab2.setLayout(self.tab2.layout)
|
self.tab2.setLayout(self.tab2.layout)
|
||||||
# NOTE: create custom widget to add new tabs
|
# NOTE: create custom widget to add new tabs
|
||||||
ST_adder = SubmissionTypeAdder(self)
|
# ST_adder = SubmissionTypeAdder(self)
|
||||||
|
summary_report = Summary(self)
|
||||||
self.tab3.layout = QVBoxLayout(self)
|
self.tab3.layout = QVBoxLayout(self)
|
||||||
self.tab3.layout.addWidget(ST_adder)
|
self.tab3.layout.addWidget(summary_report)
|
||||||
self.tab3.setLayout(self.tab3.layout)
|
self.tab3.setLayout(self.tab3.layout)
|
||||||
kit_adder = KitAdder(self)
|
# kit_adder = KitAdder(self)
|
||||||
self.tab4.layout = QVBoxLayout(self)
|
# self.tab4.layout = QVBoxLayout(self)
|
||||||
self.tab4.layout.addWidget(kit_adder)
|
# self.tab4.layout.addWidget(kit_adder)
|
||||||
self.tab4.setLayout(self.tab4.layout)
|
# self.tab4.setLayout(self.tab4.layout)
|
||||||
# NOTE: add tabs to main widget
|
# NOTE: add tabs to main widget
|
||||||
self.layout.addWidget(self.tabs)
|
self.layout.addWidget(self.tabs)
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from typing import Tuple
|
|||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QComboBox, QHBoxLayout,
|
QWidget, QVBoxLayout, QComboBox, QHBoxLayout,
|
||||||
QDateEdit, QLabel, QSizePolicy, QPushButton
|
QDateEdit, QLabel, QSizePolicy, QPushButton, QGridLayout
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import QSignalBlocker
|
from PyQt6.QtCore import QSignalBlocker
|
||||||
from backend.db import ControlType, Control
|
from backend.db import ControlType, Control
|
||||||
@@ -17,7 +17,7 @@ import logging
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from tools import Report, Result, get_unique_values_in_df_column, Settings, report_result
|
from tools import Report, Result, get_unique_values_in_df_column, Settings, report_result
|
||||||
from frontend.visualizations.control_charts import CustomFigure
|
from frontend.visualizations.control_charts import CustomFigure
|
||||||
|
from .misc import StartEndDatePicker
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
@@ -28,10 +28,10 @@ class ControlsViewer(QWidget):
|
|||||||
self.app = self.parent().parent()
|
self.app = self.parent().parent()
|
||||||
# logger.debug(f"\n\n{self.app}\n\n")
|
# logger.debug(f"\n\n{self.app}\n\n")
|
||||||
self.report = Report()
|
self.report = Report()
|
||||||
self.datepicker = ControlsDatePicker()
|
self.datepicker = StartEndDatePicker(default_start=-180)
|
||||||
self.webengineview = QWebEngineView()
|
self.webengineview = QWebEngineView()
|
||||||
# NOTE: set tab2 layout
|
# NOTE: set tab2 layout
|
||||||
self.layout = QVBoxLayout(self)
|
self.layout = QGridLayout(self)
|
||||||
self.control_typer = QComboBox()
|
self.control_typer = QComboBox()
|
||||||
# NOTE: fetch types of controls
|
# NOTE: fetch types of controls
|
||||||
con_types = [item.name for item in ControlType.query()]
|
con_types = [item.name for item in ControlType.query()]
|
||||||
@@ -44,18 +44,20 @@ class ControlsViewer(QWidget):
|
|||||||
self.sub_typer = QComboBox()
|
self.sub_typer = QComboBox()
|
||||||
self.sub_typer.setEnabled(False)
|
self.sub_typer.setEnabled(False)
|
||||||
# NOTE: add widgets to tab2 layout
|
# NOTE: add widgets to tab2 layout
|
||||||
self.layout.addWidget(self.datepicker)
|
self.layout.addWidget(self.datepicker, 0,0,1,2)
|
||||||
self.layout.addWidget(self.control_typer)
|
self.save_button = QPushButton("Save Chart", parent=self)
|
||||||
self.layout.addWidget(self.mode_typer)
|
self.layout.addWidget(self.save_button, 0,2,1,1)
|
||||||
self.layout.addWidget(self.sub_typer)
|
self.layout.addWidget(self.control_typer, 1,0,1,3)
|
||||||
self.layout.addWidget(self.webengineview)
|
self.layout.addWidget(self.mode_typer, 2,0,1,3)
|
||||||
|
self.layout.addWidget(self.sub_typer, 3,0,1,3)
|
||||||
|
self.layout.addWidget(self.webengineview, 4,0,1,3)
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
self.controls_getter()
|
self.controls_getter()
|
||||||
self.control_typer.currentIndexChanged.connect(self.controls_getter)
|
self.control_typer.currentIndexChanged.connect(self.controls_getter)
|
||||||
self.mode_typer.currentIndexChanged.connect(self.controls_getter)
|
self.mode_typer.currentIndexChanged.connect(self.controls_getter)
|
||||||
self.datepicker.start_date.dateChanged.connect(self.controls_getter)
|
self.datepicker.start_date.dateChanged.connect(self.controls_getter)
|
||||||
self.datepicker.end_date.dateChanged.connect(self.controls_getter)
|
self.datepicker.end_date.dateChanged.connect(self.controls_getter)
|
||||||
self.datepicker.save_button.pressed.connect(self.save_chart_function)
|
self.save_button.pressed.connect(self.save_chart_function)
|
||||||
|
|
||||||
def save_chart_function(self):
|
def save_chart_function(self):
|
||||||
self.fig.save_figure(parent=self)
|
self.fig.save_figure(parent=self)
|
||||||
@@ -141,7 +143,7 @@ class ControlsViewer(QWidget):
|
|||||||
# NOTE: if no data found from query set fig to none for reporting in webview
|
# NOTE: if no data found from query set fig to none for reporting in webview
|
||||||
if controls is None:
|
if controls is None:
|
||||||
fig = None
|
fig = None
|
||||||
self.datepicker.save_button.setEnabled(False)
|
self.save_button.setEnabled(False)
|
||||||
else:
|
else:
|
||||||
# NOTE: change each control to list of dictionaries
|
# NOTE: change each control to list of dictionaries
|
||||||
data = [control.convert_by_mode(mode=self.mode) for control in controls]
|
data = [control.convert_by_mode(mode=self.mode) for control in controls]
|
||||||
@@ -160,7 +162,7 @@ class ControlsViewer(QWidget):
|
|||||||
# NOTE: send dataframe to chart maker
|
# NOTE: send dataframe to chart maker
|
||||||
df, modes = self.prep_df(ctx=self.app.ctx, df=df)
|
df, modes = self.prep_df(ctx=self.app.ctx, df=df)
|
||||||
fig = CustomFigure(df=df, ytitle=title, modes=modes, parent=self)
|
fig = CustomFigure(df=df, ytitle=title, modes=modes, parent=self)
|
||||||
self.datepicker.save_button.setEnabled(True)
|
self.save_button.setEnabled(True)
|
||||||
# logger.debug(f"Updating figure...")
|
# logger.debug(f"Updating figure...")
|
||||||
self.fig = fig
|
self.fig = fig
|
||||||
# NOTE: construct html for webview
|
# NOTE: construct html for webview
|
||||||
@@ -200,8 +202,6 @@ class ControlsViewer(QWidget):
|
|||||||
continue
|
continue
|
||||||
# NOTE: The actual percentage from kraken was off due to exclusion of NaN, recalculating.
|
# NOTE: The actual percentage from kraken was off due to exclusion of NaN, recalculating.
|
||||||
df[column] = 100 * df[count_col] / df.groupby('name')[count_col].transform('sum')
|
df[column] = 100 * df[count_col] / df.groupby('name')[count_col].transform('sum')
|
||||||
logger.debug(df)
|
|
||||||
logger.debug(safe)
|
|
||||||
df = df[[c for c in df.columns if c in safe]]
|
df = df[[c for c in df.columns if c in safe]]
|
||||||
# NOTE: move date of sample submitted on same date as previous ahead one.
|
# NOTE: move date of sample submitted on same date as previous ahead one.
|
||||||
df = self.displace_date(df=df)
|
df = self.displace_date(df=df)
|
||||||
@@ -340,28 +340,27 @@ class ControlsViewer(QWidget):
|
|||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
class ControlsDatePicker(QWidget):
|
# class ControlsDatePicker(QWidget):
|
||||||
"""
|
# """
|
||||||
custom widget to pick start and end dates for controls graphs
|
# custom widget to pick start and end dates for controls graphs
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
def __init__(self) -> None:
|
# def __init__(self) -> None:
|
||||||
super().__init__()
|
# super().__init__()
|
||||||
self.start_date = QDateEdit(calendarPopup=True)
|
# self.start_date = QDateEdit(calendarPopup=True)
|
||||||
# NOTE: start date is two months prior to end date by default
|
# # NOTE: start date is two months prior to end date by default
|
||||||
sixmonthsago = QDate.currentDate().addDays(-180)
|
# sixmonthsago = QDate.currentDate().addDays(-180)
|
||||||
self.start_date.setDate(sixmonthsago)
|
# self.start_date.setDate(sixmonthsago)
|
||||||
self.end_date = QDateEdit(calendarPopup=True)
|
# self.end_date = QDateEdit(calendarPopup=True)
|
||||||
self.end_date.setDate(QDate.currentDate())
|
# self.end_date.setDate(QDate.currentDate())
|
||||||
self.layout = QHBoxLayout()
|
# self.layout = QHBoxLayout()
|
||||||
self.layout.addWidget(QLabel("Start Date"))
|
# self.layout.addWidget(QLabel("Start Date"))
|
||||||
self.layout.addWidget(self.start_date)
|
# self.layout.addWidget(self.start_date)
|
||||||
self.layout.addWidget(QLabel("End Date"))
|
# self.layout.addWidget(QLabel("End Date"))
|
||||||
self.layout.addWidget(self.end_date)
|
# self.layout.addWidget(self.end_date)
|
||||||
self.setLayout(self.layout)
|
# self.setLayout(self.layout)
|
||||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
# self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.save_button = QPushButton("Save Chart", parent=self)
|
#
|
||||||
self.layout.addWidget(self.save_button)
|
#
|
||||||
|
# def sizeHint(self) -> QSize:
|
||||||
def sizeHint(self) -> QSize:
|
# return QSize(80, 20)
|
||||||
return QSize(80, 20)
|
|
||||||
|
|||||||
@@ -2,17 +2,20 @@
|
|||||||
Contains miscellaneous widgets for frontend functions
|
Contains miscellaneous widgets for frontend functions
|
||||||
'''
|
'''
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
from PyQt6.QtGui import QPageLayout, QPageSize, QStandardItem, QStandardItemModel
|
||||||
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QLabel, QVBoxLayout,
|
QLabel, QVBoxLayout,
|
||||||
QLineEdit, QComboBox, QDialog,
|
QLineEdit, QComboBox, QDialog,
|
||||||
QDialogButtonBox, QDateEdit, QPushButton, QFormLayout
|
QDialogButtonBox, QDateEdit, QPushButton, QFormLayout, QWidget, QHBoxLayout, QSizePolicy
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QDate
|
from PyQt6.QtCore import Qt, QDate, QSize, QMarginsF, pyqtSlot, pyqtSignal, QEvent
|
||||||
from tools import jinja_template_loading
|
from tools import jinja_template_loading
|
||||||
from backend.db.models import *
|
from backend.db.models import *
|
||||||
import logging
|
import logging
|
||||||
from .pop_ups import AlertPop
|
from .pop_ups import AlertPop
|
||||||
from .functions import select_open_file
|
from .functions import select_open_file, select_save_file
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -22,8 +25,10 @@ env = jinja_template_loading()
|
|||||||
class AddReagentForm(QDialog):
|
class AddReagentForm(QDialog):
|
||||||
"""
|
"""
|
||||||
dialog to add gather info about new reagent
|
dialog to add gather info about new reagent
|
||||||
"""
|
"""
|
||||||
def __init__(self, reagent_lot:str|None=None, reagent_role: str | None=None, expiry: date | None=None, reagent_name: str | None=None) -> None:
|
|
||||||
|
def __init__(self, reagent_lot: str | None = None, reagent_role: str | None = None, expiry: date | None = None,
|
||||||
|
reagent_name: str | None = None) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if reagent_lot is None:
|
if reagent_lot is None:
|
||||||
reagent_lot = reagent_role
|
reagent_lot = reagent_role
|
||||||
@@ -71,7 +76,8 @@ class AddReagentForm(QDialog):
|
|||||||
self.layout.addWidget(self.name_input)
|
self.layout.addWidget(self.name_input)
|
||||||
self.layout.addWidget(QLabel("Lot:"))
|
self.layout.addWidget(QLabel("Lot:"))
|
||||||
self.layout.addWidget(self.lot_input)
|
self.layout.addWidget(self.lot_input)
|
||||||
self.layout.addWidget(QLabel("Expiry:\n(use exact date on reagent.\nEOL will be calculated from kit automatically)"))
|
self.layout.addWidget(
|
||||||
|
QLabel("Expiry:\n(use exact date on reagent.\nEOL will be calculated from kit automatically)"))
|
||||||
self.layout.addWidget(self.exp_input)
|
self.layout.addWidget(self.exp_input)
|
||||||
self.layout.addWidget(QLabel("Type:"))
|
self.layout.addWidget(QLabel("Type:"))
|
||||||
self.layout.addWidget(self.type_input)
|
self.layout.addWidget(self.type_input)
|
||||||
@@ -85,57 +91,57 @@ class AddReagentForm(QDialog):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Output info
|
dict: Output info
|
||||||
"""
|
"""
|
||||||
return dict(name=self.name_input.currentText().strip(),
|
return dict(name=self.name_input.currentText().strip(),
|
||||||
lot=self.lot_input.text().strip(),
|
lot=self.lot_input.text().strip(),
|
||||||
expiry=self.exp_input.date().toPyDate(),
|
expiry=self.exp_input.date().toPyDate(),
|
||||||
role=self.type_input.currentText().strip())
|
role=self.type_input.currentText().strip())
|
||||||
|
|
||||||
def update_names(self):
|
def update_names(self):
|
||||||
"""
|
"""
|
||||||
Updates reagent names form field with examples from reagent type
|
Updates reagent names form field with examples from reagent type
|
||||||
"""
|
"""
|
||||||
# logger.debug(self.type_input.currentText())
|
# logger.debug(self.type_input.currentText())
|
||||||
self.name_input.clear()
|
self.name_input.clear()
|
||||||
lookup = Reagent.query(reagent_role=self.type_input.currentText())
|
lookup = Reagent.query(reagent_role=self.type_input.currentText())
|
||||||
self.name_input.addItems(list(set([item.name for item in lookup])))
|
self.name_input.addItems(list(set([item.name for item in lookup])))
|
||||||
|
|
||||||
|
|
||||||
class ReportDatePicker(QDialog):
|
# class ReportDatePicker(QDialog):
|
||||||
"""
|
# """
|
||||||
custom dialog to ask for report start/stop dates
|
# custom dialog to ask for report start/stop dates
|
||||||
"""
|
# """
|
||||||
def __init__(self) -> None:
|
# def __init__(self) -> None:
|
||||||
super().__init__()
|
# super().__init__()
|
||||||
self.setWindowTitle("Select Report Date Range")
|
# self.setWindowTitle("Select Report Date Range")
|
||||||
# NOTE: make confirm/reject buttons
|
# # NOTE: make confirm/reject buttons
|
||||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
# QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
self.buttonBox = QDialogButtonBox(QBtn)
|
# self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
self.buttonBox.accepted.connect(self.accept)
|
# self.buttonBox.accepted.connect(self.accept)
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
# self.buttonBox.rejected.connect(self.reject)
|
||||||
# NOTE: widgets to ask for dates
|
# # NOTE: widgets to ask for dates
|
||||||
self.start_date = QDateEdit(calendarPopup=True)
|
# self.start_date = QDateEdit(calendarPopup=True)
|
||||||
self.start_date.setObjectName("start_date")
|
# self.start_date.setObjectName("start_date")
|
||||||
self.start_date.setDate(QDate.currentDate())
|
# self.start_date.setDate(QDate.currentDate())
|
||||||
self.end_date = QDateEdit(calendarPopup=True)
|
# self.end_date = QDateEdit(calendarPopup=True)
|
||||||
self.end_date.setObjectName("end_date")
|
# self.end_date.setObjectName("end_date")
|
||||||
self.end_date.setDate(QDate.currentDate())
|
# self.end_date.setDate(QDate.currentDate())
|
||||||
self.layout = QVBoxLayout()
|
# self.layout = QVBoxLayout()
|
||||||
self.layout.addWidget(QLabel("Start Date"))
|
# self.layout.addWidget(QLabel("Start Date"))
|
||||||
self.layout.addWidget(self.start_date)
|
# self.layout.addWidget(self.start_date)
|
||||||
self.layout.addWidget(QLabel("End Date"))
|
# self.layout.addWidget(QLabel("End Date"))
|
||||||
self.layout.addWidget(self.end_date)
|
# self.layout.addWidget(self.end_date)
|
||||||
self.layout.addWidget(self.buttonBox)
|
# self.layout.addWidget(self.buttonBox)
|
||||||
self.setLayout(self.layout)
|
# self.setLayout(self.layout)
|
||||||
|
#
|
||||||
def parse_form(self) -> dict:
|
# def parse_form(self) -> dict:
|
||||||
"""
|
# """
|
||||||
Converts information in this object to a dict
|
# Converts information in this object to a dict
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict: output dict.
|
# dict: output dict.
|
||||||
"""
|
# """
|
||||||
return dict(start_date=self.start_date.date().toPyDate(), end_date = self.end_date.date().toPyDate())
|
# return dict(start_date=self.start_date.date().toPyDate(), end_date = self.end_date.date().toPyDate())
|
||||||
|
|
||||||
|
|
||||||
class LogParser(QDialog):
|
class LogParser(QDialog):
|
||||||
@@ -160,13 +166,13 @@ class LogParser(QDialog):
|
|||||||
def filelookup(self):
|
def filelookup(self):
|
||||||
"""
|
"""
|
||||||
Select file to search
|
Select file to search
|
||||||
"""
|
"""
|
||||||
self.fname = select_open_file(self, "tabular")
|
self.fname = select_open_file(self, "tabular")
|
||||||
|
|
||||||
def runsearch(self):
|
def runsearch(self):
|
||||||
"""
|
"""
|
||||||
Gets total/percent occurences of string in tabular file.
|
Gets total/percent occurences of string in tabular file.
|
||||||
"""
|
"""
|
||||||
count: int = 0
|
count: int = 0
|
||||||
total: int = 0
|
total: int = 0
|
||||||
# logger.debug(f"Current search term: {self.phrase_looker.currentText()}")
|
# logger.debug(f"Current search term: {self.phrase_looker.currentText()}")
|
||||||
@@ -177,7 +183,7 @@ class LogParser(QDialog):
|
|||||||
for line in chunk:
|
for line in chunk:
|
||||||
if self.phrase_looker.currentText().lower() in line.lower():
|
if self.phrase_looker.currentText().lower() in line.lower():
|
||||||
count += 1
|
count += 1
|
||||||
percent = (count/total)*100
|
percent = (count / total) * 100
|
||||||
msg = f"I found {count} instances of the search phrase out of {total} = {percent:.2f}%."
|
msg = f"I found {count} instances of the search phrase out of {total} = {percent:.2f}%."
|
||||||
status = "Information"
|
status = "Information"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -186,3 +192,56 @@ class LogParser(QDialog):
|
|||||||
dlg = AlertPop(message=msg, status=status)
|
dlg = AlertPop(message=msg, status=status)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
|
|
||||||
|
|
||||||
|
class StartEndDatePicker(QWidget):
|
||||||
|
"""
|
||||||
|
custom widget to pick start and end dates for controls graphs
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, default_start: int) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.start_date = QDateEdit(calendarPopup=True)
|
||||||
|
# NOTE: start date is two months prior to end date by default
|
||||||
|
default_start = QDate.currentDate().addDays(default_start)
|
||||||
|
self.start_date.setDate(default_start)
|
||||||
|
self.end_date = QDateEdit(calendarPopup=True)
|
||||||
|
self.end_date.setDate(QDate.currentDate())
|
||||||
|
self.layout = QHBoxLayout()
|
||||||
|
self.layout.addWidget(QLabel("Start Date"))
|
||||||
|
self.layout.addWidget(self.start_date)
|
||||||
|
self.layout.addWidget(QLabel("End Date"))
|
||||||
|
self.layout.addWidget(self.end_date)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
|
|
||||||
|
def sizeHint(self) -> QSize:
|
||||||
|
return QSize(80, 20)
|
||||||
|
|
||||||
|
|
||||||
|
def save_pdf(obj: QWebEngineView, filename: Path):
|
||||||
|
page_layout = QPageLayout()
|
||||||
|
page_layout.setPageSize(QPageSize(QPageSize.PageSizeId.A4))
|
||||||
|
page_layout.setOrientation(QPageLayout.Orientation.Portrait)
|
||||||
|
page_layout.setMargins(QMarginsF(25, 25, 25, 25))
|
||||||
|
obj.page().printToPdf(filename.absolute().__str__(), page_layout)
|
||||||
|
|
||||||
|
|
||||||
|
# subclass
|
||||||
|
class CheckableComboBox(QComboBox):
|
||||||
|
# once there is a checkState set, it is rendered
|
||||||
|
# here we assume default Unchecked
|
||||||
|
|
||||||
|
def addItem(self, item, header: bool = False):
|
||||||
|
super(CheckableComboBox, self).addItem(item)
|
||||||
|
item: QStandardItem = self.model().item(self.count() - 1, 0)
|
||||||
|
if not header:
|
||||||
|
item.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled)
|
||||||
|
item.setCheckState(Qt.CheckState.Checked)
|
||||||
|
|
||||||
|
def itemChecked(self, index):
|
||||||
|
item = self.model().item(index, 0)
|
||||||
|
return item.checkState() == Qt.CheckState.Checked
|
||||||
|
|
||||||
|
def changed(self):
|
||||||
|
logger.debug("emitting updated")
|
||||||
|
self.updated.emit()
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ from PyQt6.QtWebChannel import QWebChannel
|
|||||||
from PyQt6.QtCore import Qt, pyqtSlot, QMarginsF
|
from PyQt6.QtCore import Qt, pyqtSlot, QMarginsF
|
||||||
from jinja2 import TemplateNotFound
|
from jinja2 import TemplateNotFound
|
||||||
from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType
|
from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType
|
||||||
from tools import is_power_user, html_to_pdf, jinja_template_loading
|
from tools import is_power_user, jinja_template_loading
|
||||||
from .functions import select_save_file
|
from .functions import select_save_file
|
||||||
|
from .misc import save_pdf
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
from getpass import getuser
|
from getpass import getuser
|
||||||
@@ -177,11 +178,12 @@ class SubmissionDetails(QDialog):
|
|||||||
Renders submission to html, then creates and saves .pdf file to user selected file.
|
Renders submission to html, then creates and saves .pdf file to user selected file.
|
||||||
"""
|
"""
|
||||||
fname = select_save_file(obj=self, default_name=self.export_plate, extension="pdf")
|
fname = select_save_file(obj=self, default_name=self.export_plate, extension="pdf")
|
||||||
page_layout = QPageLayout()
|
# page_layout = QPageLayout()
|
||||||
page_layout.setPageSize(QPageSize(QPageSize.PageSizeId.A4))
|
# page_layout.setPageSize(QPageSize(QPageSize.PageSizeId.A4))
|
||||||
page_layout.setOrientation(QPageLayout.Orientation.Portrait)
|
# page_layout.setOrientation(QPageLayout.Orientation.Portrait)
|
||||||
page_layout.setMargins(QMarginsF(25, 25, 25, 25))
|
# page_layout.setMargins(QMarginsF(25, 25, 25, 25))
|
||||||
self.webview.page().printToPdf(fname.with_suffix(".pdf").__str__(), page_layout)
|
# self.webview.page().printToPdf(fname.with_suffix(".pdf").__str__(), page_layout)
|
||||||
|
save_pdf(obj=self, filename=fname)
|
||||||
|
|
||||||
class SubmissionComment(QDialog):
|
class SubmissionComment(QDialog):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from backend.db.models import BasicSubmission
|
|||||||
from backend.excel import ReportMaker
|
from backend.excel import ReportMaker
|
||||||
from tools import Report, Result, report_result
|
from tools import Report, Result, report_result
|
||||||
from .functions import select_save_file, select_open_file
|
from .functions import select_save_file, select_open_file
|
||||||
from .misc import ReportDatePicker
|
# from .misc import ReportDatePicker
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -226,32 +226,32 @@ class SubmissionsSheet(QTableView):
|
|||||||
report.add_result(Result(msg=f"We added {count} logs to the database.", status='Information'))
|
report.add_result(Result(msg=f"We added {count} logs to the database.", status='Information'))
|
||||||
return report
|
return report
|
||||||
|
|
||||||
@report_result
|
# @report_result
|
||||||
def generate_report(self, *args):
|
# def generate_report(self, *args):
|
||||||
"""
|
# """
|
||||||
Make a report
|
# Make a report
|
||||||
"""
|
# """
|
||||||
report = Report()
|
# report = Report()
|
||||||
result = self.generate_report_function()
|
# result = self.generate_report_function()
|
||||||
report.add_result(result)
|
# report.add_result(result)
|
||||||
return report
|
# return report
|
||||||
|
#
|
||||||
def generate_report_function(self):
|
# def generate_report_function(self):
|
||||||
"""
|
# """
|
||||||
Generate a summary of activities for a time period
|
# Generate a summary of activities for a time period
|
||||||
|
#
|
||||||
Args:
|
# Args:
|
||||||
obj (QMainWindow): original app window
|
# obj (QMainWindow): original app window
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
# Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
||||||
"""
|
# """
|
||||||
report = Report()
|
# report = Report()
|
||||||
# NOTE: ask for date ranges
|
# # NOTE: ask for date ranges
|
||||||
dlg = ReportDatePicker()
|
# dlg = ReportDatePicker()
|
||||||
if dlg.exec():
|
# if dlg.exec():
|
||||||
info = dlg.parse_form()
|
# info = dlg.parse_form()
|
||||||
fname = select_save_file(obj=self, default_name=f"Submissions_Report_{info['start_date']}-{info['end_date']}.xlsx", extension="xlsx")
|
# fname = select_save_file(obj=self, default_name=f"Submissions_Report_{info['start_date']}-{info['end_date']}.xlsx", extension="xlsx")
|
||||||
rp = ReportMaker(start_date=info['start_date'], end_date=info['end_date'])
|
# rp = ReportMaker(start_date=info['start_date'], end_date=info['end_date'])
|
||||||
rp.write_report(filename=fname, obj=self)
|
# rp.write_report(filename=fname, obj=self)
|
||||||
return report
|
# return report
|
||||||
|
|||||||
78
src/submissions/frontend/widgets/summary.py
Normal file
78
src/submissions/frontend/widgets/summary.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
from PyQt6.QtCore import QSignalBlocker
|
||||||
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QComboBox, QLabel
|
||||||
|
from backend.db import Organization
|
||||||
|
from backend.excel import ReportMaker
|
||||||
|
from tools import Report
|
||||||
|
from .misc import StartEndDatePicker, save_pdf, CheckableComboBox
|
||||||
|
from .functions import select_save_file
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
|
class Summary(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent: QWidget) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.app = self.parent().parent()
|
||||||
|
# logger.debug(f"\n\n{self.app}\n\n")
|
||||||
|
self.report = Report()
|
||||||
|
self.datepicker = StartEndDatePicker(default_start=-31)
|
||||||
|
self.webview = QWebEngineView()
|
||||||
|
self.datepicker.start_date.dateChanged.connect(self.get_report)
|
||||||
|
self.datepicker.end_date.dateChanged.connect(self.get_report)
|
||||||
|
self.layout = QGridLayout(self)
|
||||||
|
self.layout.addWidget(self.datepicker, 0, 0, 1, 2)
|
||||||
|
self.save_excel_button = QPushButton("Save Excel", parent=self)
|
||||||
|
self.save_excel_button.pressed.connect(self.save_excel)
|
||||||
|
self.save_pdf_button = QPushButton("Save PDF", parent=self)
|
||||||
|
self.save_pdf_button.pressed.connect(self.save_pdf)
|
||||||
|
self.org_select = CheckableComboBox()
|
||||||
|
self.org_select.setEditable(False)
|
||||||
|
self.org_select.addItem("Select", header=True)
|
||||||
|
for org in [org.name for org in Organization.query()]:
|
||||||
|
self.org_select.addItem(org)
|
||||||
|
self.org_select.model().itemChanged.connect(self.get_report)
|
||||||
|
# self.org_select.itemChecked.connect(self.get_report)
|
||||||
|
self.layout.addWidget(self.save_excel_button, 0, 2, 1, 1)
|
||||||
|
self.layout.addWidget(self.save_pdf_button, 0, 3, 1, 1)
|
||||||
|
self.layout.addWidget(self.webview, 2, 0, 1, 4)
|
||||||
|
self.layout.addWidget(QLabel("Client"), 1, 0, 1, 1)
|
||||||
|
self.layout.addWidget(self.org_select, 1, 1, 1, 3)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
self.get_report()
|
||||||
|
|
||||||
|
|
||||||
|
def get_report(self):
|
||||||
|
orgs = [self.org_select.itemText(i) for i in range(self.org_select.count()) if self.org_select.itemChecked(i)]
|
||||||
|
if self.datepicker.start_date.date() > self.datepicker.end_date.date():
|
||||||
|
logger.warning("Start date after end date is not allowed!")
|
||||||
|
lastmonth = self.datepicker.end_date.date().addDays(-31)
|
||||||
|
# NOTE: block signal that will rerun controls getter and set start date
|
||||||
|
# Without triggering this function again
|
||||||
|
with QSignalBlocker(self.datepicker.start_date) as blocker:
|
||||||
|
self.datepicker.start_date.setDate(lastmonth)
|
||||||
|
self.get_report()
|
||||||
|
return
|
||||||
|
# NOTE: convert to python useable date objects
|
||||||
|
self.start_date = self.datepicker.start_date.date().toPyDate()
|
||||||
|
self.end_date = self.datepicker.end_date.date().toPyDate()
|
||||||
|
self.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs)
|
||||||
|
self.webview.setHtml(self.report_obj.html)
|
||||||
|
if self.report_obj.subs:
|
||||||
|
self.save_pdf_button.setEnabled(True)
|
||||||
|
self.save_excel_button.setEnabled(True)
|
||||||
|
else:
|
||||||
|
self.save_pdf_button.setEnabled(False)
|
||||||
|
self.save_excel_button.setEnabled(False)
|
||||||
|
|
||||||
|
def save_excel(self):
|
||||||
|
fname = select_save_file(self, default_name=f"Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}", extension="xlsx")
|
||||||
|
self.report_obj.write_report(fname, obj=self)
|
||||||
|
|
||||||
|
def save_pdf(self):
|
||||||
|
fname = select_save_file(obj=self,
|
||||||
|
default_name=f"Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}",
|
||||||
|
extension="pdf")
|
||||||
|
save_pdf(obj=self.webview, filename=fname)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Submissions Report for {{ input['start_date'] }} - {{ input['end_date'] }}</title>
|
<title>Submissions Report for {{ input['start_date'] }} to {{ input['end_date'] }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Submissions Report {{ input['start_date'] }} - {{ input['end_date'] }}</h2>
|
<h2>Submissions Report {{ input['start_date'] }} to {{ input['end_date'] }}</h2>
|
||||||
<br>
|
<br>
|
||||||
{{ input['table'] }}
|
{{ input['table'] }}
|
||||||
<br>
|
<br>
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
<h3><u>{{ lab['lab'] }}:</u></h3>
|
<h3><u>{{ lab['lab'] }}:</u></h3>
|
||||||
{% for kit in lab['kits'] %}
|
{% for kit in lab['kits'] %}
|
||||||
<p><b>{{ kit['name'] }}</b></p>
|
<p><b>{{ kit['name'] }}</b></p>
|
||||||
<p> Runs: {{ kit['run_count'] }}, Samples: {{ kit['sample_count'] }}, Cost: {{ "${:,.2f}".format(kit['cost']) }}</p>
|
<p> Runs: {{ kit['run_count'] }}, Samples: {{ kit['sample_count'] }}, Cost: {{ "${:,.2f}".format(kit['cost']) }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<p><b>Lab total:</b></p>
|
<p><b><u>Lab total:</u></b></p>
|
||||||
<p> Runs: {{ lab['total_runs'] }}, Samples: {{ lab['total_samples'] }}, Cost: {{ "${:,.2f}".format(lab['total_cost']) }}</p>
|
<p> Runs: {{ lab['total_runs'] }}, Samples: {{ lab['total_samples'] }}, Cost: {{ "${:,.2f}".format(lab['total_cost']) }}</p>
|
||||||
<br>
|
<br>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import json
|
|||||||
import pprint
|
import pprint
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import logging, re, yaml, sys, os, stat, platform, getpass, inspect, csv
|
import logging, re, yaml, sys, os, stat, platform, getpass, inspect
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from logging import handlers
|
from logging import handlers
|
||||||
@@ -17,22 +17,15 @@ from sqlalchemy import create_engine, text, MetaData
|
|||||||
from pydantic import field_validator, BaseModel, Field
|
from pydantic import field_validator, BaseModel, Field
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from typing import Any, Tuple, Literal, List
|
from typing import Any, Tuple, Literal, List
|
||||||
from PyQt6.QtGui import QPageSize
|
|
||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
|
||||||
from openpyxl.worksheet.worksheet import Worksheet
|
|
||||||
from PyQt6.QtPrintSupport import QPrinter
|
|
||||||
from __init__ import project_path
|
from __init__ import project_path
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from tkinter import Tk # from tkinter import Tk for Python 3.x
|
from tkinter import Tk # from tkinter import Tk for Python 3.x
|
||||||
from tkinter.filedialog import askdirectory
|
from tkinter.filedialog import askdirectory
|
||||||
# from .error_messaging import parse_exception_to_message
|
from sqlalchemy.exc import IntegrityError as sqlalcIntegrityError
|
||||||
from sqlalchemy.exc import ArgumentError, IntegrityError as sqlalcIntegrityError
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
# package_dir = Path(__file__).parents[2].resolve()
|
|
||||||
# package_dir = project_path
|
|
||||||
logger.debug(f"Package dir: {project_path}")
|
logger.debug(f"Package dir: {project_path}")
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
@@ -154,11 +147,11 @@ def check_not_nan(cell_contents) -> bool:
|
|||||||
bool: True if cell has value, else, false.
|
bool: True if cell has value, else, false.
|
||||||
"""
|
"""
|
||||||
# NOTE: check for nan as a string first
|
# NOTE: check for nan as a string first
|
||||||
exclude = ['unnamed:', 'blank', 'void']
|
exclude = ['unnamed:', 'blank', 'void', 'nat', 'nan', ""]
|
||||||
try:
|
try:
|
||||||
if cell_contents.lower() in exclude:
|
if cell_contents.lower() in exclude:
|
||||||
cell_contents = np.nan
|
cell_contents = np.nan
|
||||||
cell_contents = cell_contents.lower()
|
# cell_contents = cell_contents.lower()
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
@@ -166,14 +159,6 @@ def check_not_nan(cell_contents) -> bool:
|
|||||||
cell_contents = np.nan
|
cell_contents = np.nan
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
pass
|
pass
|
||||||
if cell_contents == "nat":
|
|
||||||
cell_contents = np.nan
|
|
||||||
if cell_contents == 'nan':
|
|
||||||
cell_contents = np.nan
|
|
||||||
if cell_contents is None:
|
|
||||||
cell_contents = np.nan
|
|
||||||
if str(cell_contents).lower() == "none":
|
|
||||||
cell_contents = np.nan
|
|
||||||
try:
|
try:
|
||||||
if pd.isnull(cell_contents):
|
if pd.isnull(cell_contents):
|
||||||
cell_contents = np.nan
|
cell_contents = np.nan
|
||||||
@@ -899,7 +884,6 @@ def yaml_regex_creator(loader, node):
|
|||||||
return f"(?P<{name}>RSL(?:-|_)?{abbr}(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\sA-QS-Z]|$)?R?\d?)?)"
|
return f"(?P<{name}>RSL(?:-|_)?{abbr}(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\sA-QS-Z]|$)?R?\d?)?)"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ctx = get_config(None)
|
ctx = get_config(None)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user