Pre-sample/control connect
This commit is contained in:
@@ -11,9 +11,6 @@ from PyQt6.QtWidgets import (
|
||||
from PyQt6.QtGui import QAction
|
||||
from pathlib import Path
|
||||
from backend.validators import PydReagent
|
||||
# from frontend.functions import (
|
||||
# add_kit_function, add_org_function, link_controls_function, export_csv_function
|
||||
# )
|
||||
from tools import check_if_app, Settings, Report
|
||||
from .pop_ups import AlertPop
|
||||
from .misc import AddReagentForm, LogParser
|
||||
@@ -149,17 +146,12 @@ class App(QMainWindow):
|
||||
webbrowser.get('windows-default').open(f"file://{url.__str__()}")
|
||||
|
||||
def result_reporter(self):
|
||||
# def result_reporter(self, result:TypedDict[]|None=None):
|
||||
"""
|
||||
Report any anomolous results - if any - to the user
|
||||
|
||||
Args:
|
||||
result (dict | None, optional): The result from a function. Defaults to None.
|
||||
"""
|
||||
# logger.info(f"We got the result: {result}")
|
||||
# if result != None:
|
||||
# msg = AlertPop(message=result['message'], status=result['status'])
|
||||
# msg.exec()
|
||||
logger.debug(f"Running results reporter for: {self.report.results}")
|
||||
if len(self.report.results) > 0:
|
||||
logger.debug(f"We've got some results!")
|
||||
@@ -173,43 +165,6 @@ class App(QMainWindow):
|
||||
else:
|
||||
self.statusBar().showMessage("Action completed sucessfully.", 5000)
|
||||
|
||||
# def importSubmission(self, fname:Path|None=None):
|
||||
# """
|
||||
# import submission from excel sheet into form
|
||||
# """
|
||||
# # from .main_window_functions import import_submission_function
|
||||
# self.raise_()
|
||||
# self.activateWindow()
|
||||
# self = import_submission_function(self, fname)
|
||||
# logger.debug(f"Result from result reporter: {self.report.results}")
|
||||
# self.result_reporter()
|
||||
|
||||
# def kit_reload(self):
|
||||
# """
|
||||
# Removes all reagents from form before running kit integrity completion.
|
||||
# """
|
||||
# # from .main_window_functions import kit_reload_function
|
||||
# self = kit_reload_function(self)
|
||||
# self.result_reporter()
|
||||
|
||||
# def kit_integrity_completion(self):
|
||||
# """
|
||||
# Performs check of imported reagents
|
||||
# NOTE: this will not change self.reagents which should be fine
|
||||
# since it's only used when looking up
|
||||
# """
|
||||
# # from .main_window_functions import kit_integrity_completion_function
|
||||
# self = kit_integrity_completion_function(self)
|
||||
# self.result_reporter()
|
||||
|
||||
# def submit_new_sample(self):
|
||||
# """
|
||||
# Attempt to add sample to database when 'submit' button clicked
|
||||
# """
|
||||
# # from .main_window_functions import submit_new_sample_function
|
||||
# self = submit_new_sample_function(self)
|
||||
# self.result_reporter()
|
||||
|
||||
def add_reagent(self, reagent_lot:str|None=None, reagent_type:str|None=None, expiry:date|None=None, name:str|None=None):
|
||||
"""
|
||||
Action to create new reagent in DB.
|
||||
@@ -217,6 +172,8 @@ class App(QMainWindow):
|
||||
Args:
|
||||
reagent_lot (str | None, optional): Parsed reagent from import form. Defaults to None.
|
||||
reagent_type (str | None, optional): Parsed reagent type from import form. Defaults to None.
|
||||
expiry (date | None, optional): Parsed reagent expiry data. Defaults to None.
|
||||
name (str | None, optional): Parsed reagent name. Defaults to None.
|
||||
|
||||
Returns:
|
||||
models.Reagent: the constructed reagent object to add to submission
|
||||
@@ -225,117 +182,20 @@ class App(QMainWindow):
|
||||
if isinstance(reagent_lot, bool):
|
||||
reagent_lot = ""
|
||||
# create form
|
||||
dlg = AddReagentForm(ctx=self.ctx, reagent_lot=reagent_lot, reagent_type=reagent_type, expiry=expiry, reagent_name=name)
|
||||
dlg = AddReagentForm(reagent_lot=reagent_lot, reagent_type=reagent_type, expiry=expiry, reagent_name=name)
|
||||
if dlg.exec():
|
||||
# extract form info
|
||||
# info = extract_form_info(dlg)
|
||||
info = dlg.parse_form()
|
||||
logger.debug(f"Reagent info: {info}")
|
||||
# create reagent object
|
||||
# reagent = construct_reagent(ctx=self.ctx, info_dict=info)
|
||||
reagent = PydReagent(ctx=self.ctx, **info)
|
||||
# send reagent to db
|
||||
# store_reagent(ctx=self.ctx, reagent=reagent)
|
||||
sqlobj, result = reagent.toSQL()
|
||||
sqlobj.save()
|
||||
# result = store_object(ctx=self.ctx, object=reagent.toSQL()[0])
|
||||
report.add_result(result)
|
||||
self.result_reporter()
|
||||
return reagent
|
||||
|
||||
# def generateReport(self):
|
||||
# """
|
||||
# Action to create a summary of sheet data per client
|
||||
# """
|
||||
# # from .main_window_functions import generate_report_function
|
||||
# self, result = generate_report_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
# def add_kit(self):
|
||||
# """
|
||||
# Constructs new kit from yaml and adds to DB.
|
||||
# """
|
||||
# # from .main_window_functions import add_kit_function
|
||||
# self, result = add_kit_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
# def add_org(self):
|
||||
# """
|
||||
# Constructs new kit from yaml and adds to DB.
|
||||
# """
|
||||
# # from .main_window_functions import add_org_function
|
||||
# self, result = add_org_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
# def _controls_getter(self):
|
||||
# """
|
||||
# Lookup controls from database and send to chartmaker
|
||||
# """
|
||||
# # from .main_window_functions import controls_getter_function
|
||||
# self = controls_getter_function(self)
|
||||
# self.result_reporter()
|
||||
|
||||
# def _chart_maker(self):
|
||||
# """
|
||||
# Creates plotly charts for webview
|
||||
# """
|
||||
# # from .main_window_functions import chart_maker_function
|
||||
# self = chart_maker_function(self)
|
||||
# self.result_reporter()
|
||||
|
||||
# def linkControls(self):
|
||||
# """
|
||||
# Adds controls pulled from irida to relevant submissions
|
||||
# NOTE: Depreciated due to improvements in controls scraper.
|
||||
# """
|
||||
# # from .main_window_functions import link_controls_function
|
||||
# self, result = link_controls_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
# def linkExtractions(self):
|
||||
# """
|
||||
# Links extraction logs from .csv files to relevant submissions.
|
||||
# """
|
||||
# # from .main_window_functions import link_extractions_function
|
||||
# self, result = link_extractions_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
# def linkPCR(self):
|
||||
# """
|
||||
# Links PCR logs from .csv files to relevant submissions.
|
||||
# """
|
||||
# # from .main_window_functions import link_pcr_function
|
||||
# self, result = link_pcr_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
# def importPCRResults(self):
|
||||
# """
|
||||
# Imports results exported from Design and Analysis .eds files
|
||||
# """
|
||||
# # from .main_window_functions import import_pcr_results_function
|
||||
# self, result = import_pcr_results_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
# def construct_first_strand(self):
|
||||
# """
|
||||
# Converts first strand excel sheet to Biomek CSV
|
||||
# """
|
||||
# from .main_window_functions import construct_first_strand_function
|
||||
# self, result = construct_first_strand_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
# def scrape_reagents(self, *args, **kwargs):
|
||||
# # from .main_window_functions import scrape_reagents
|
||||
# logger.debug(f"Args: {args}")
|
||||
# logger.debug(F"kwargs: {kwargs}")
|
||||
# self = scrape_reagents(self, args[0])
|
||||
# self.kit_integrity_completion()
|
||||
# self.result_reporter()
|
||||
|
||||
# def export_csv(self, fname:Path|None=None):
|
||||
# # from .main_window_functions import export_csv_function
|
||||
# export_csv_function(self, fname)
|
||||
|
||||
def runSearch(self):
|
||||
dlg = LogParser(self)
|
||||
dlg.exec()
|
||||
@@ -377,32 +237,7 @@ class AddSubForm(QWidget):
|
||||
self.tab1.setLayout(self.tab1.layout)
|
||||
self.tab1.layout.addWidget(self.interior)
|
||||
self.tab1.layout.addWidget(self.sheetwidget)
|
||||
# create widgets for tab 2
|
||||
# self.datepicker = ControlsDatePicker()
|
||||
# self.webengineview = QWebEngineView()
|
||||
# set tab2 layout
|
||||
self.tab2.layout = QVBoxLayout(self)
|
||||
# self.control_typer = QComboBox()
|
||||
# fetch types of controls
|
||||
# con_types = get_all_Control_Types_names(ctx=parent.ctx)
|
||||
# con_types = [item.name for item in lookup_control_types(ctx=parent.ctx)]
|
||||
# con_types = [item.name for item in ControlType.query()]
|
||||
# self.control_typer.addItems(con_types)
|
||||
# create custom widget to get types of analysis
|
||||
# self.mode_typer = QComboBox()
|
||||
# mode_types = get_all_available_modes(ctx=parent.ctx)
|
||||
# mode_types = lookup_modes(ctx=parent.ctx)
|
||||
# mode_types = Control.get_modes()
|
||||
# self.mode_typer.addItems(mode_types)
|
||||
# create custom widget to get subtypes of analysis
|
||||
# self.sub_typer = QComboBox()
|
||||
# self.sub_typer.setEnabled(False)
|
||||
# add widgets to tab2 layout
|
||||
# self.tab2.layout.addWidget(self.datepicker)
|
||||
# self.tab2.layout.addWidget(self.control_typer)
|
||||
# self.tab2.layout.addWidget(self.mode_typer)
|
||||
# self.tab2.layout.addWidget(self.sub_typer)
|
||||
# self.tab2.layout.addWidget(self.webengineview)
|
||||
self.controls_viewer = ControlsViewer(self)
|
||||
self.tab2.layout.addWidget(self.controls_viewer)
|
||||
self.tab2.setLayout(self.tab2.layout)
|
||||
|
||||
@@ -3,10 +3,10 @@ from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QComboBox, QHBoxLayout,
|
||||
QDateEdit, QLabel, QSizePolicy
|
||||
)
|
||||
from PyQt6.QtCore import QSignalBlocker
|
||||
from PyQt6.QtCore import QSignalBlocker, QLoggingCategory
|
||||
from backend.db import ControlType, Control, get_control_subtypes
|
||||
from PyQt6.QtCore import QDate, QSize
|
||||
import logging
|
||||
import logging, sys
|
||||
from tools import Report, Result
|
||||
from backend.excel.reports import convert_data_list_to_df
|
||||
from frontend.visualizations.control_charts import create_charts, construct_html
|
||||
@@ -26,14 +26,10 @@ class ControlsViewer(QWidget):
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.control_typer = QComboBox()
|
||||
# fetch types of controls
|
||||
# con_types = get_all_Control_Types_names(ctx=parent.ctx)
|
||||
# con_types = [item.name for item in lookup_control_types(ctx=parent.ctx)]
|
||||
con_types = [item.name for item in ControlType.query()]
|
||||
self.control_typer.addItems(con_types)
|
||||
# create custom widget to get types of analysis
|
||||
self.mode_typer = QComboBox()
|
||||
# mode_types = get_all_available_modes(ctx=parent.ctx)
|
||||
# mode_types = lookup_modes(ctx=parent.ctx)
|
||||
mode_types = Control.get_modes()
|
||||
self.mode_typer.addItems(mode_types)
|
||||
# create custom widget to get subtypes of analysis
|
||||
@@ -56,27 +52,17 @@ class ControlsViewer(QWidget):
|
||||
"""
|
||||
Lookup controls from database and send to chartmaker
|
||||
"""
|
||||
# from .main_window_functions import controls_getter_function
|
||||
self.controls_getter_function()
|
||||
# self.result_reporter()
|
||||
|
||||
def chart_maker(self):
|
||||
"""
|
||||
Creates plotly charts for webview
|
||||
"""
|
||||
# from .main_window_functions import chart_maker_function
|
||||
self.chart_maker_function()
|
||||
# self.result_reporter()
|
||||
|
||||
def controls_getter_function(self):
|
||||
"""
|
||||
Get controls based on start/end dates
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): original app window
|
||||
|
||||
Returns:
|
||||
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
||||
"""
|
||||
report = Report()
|
||||
# subtype defaults to disabled
|
||||
@@ -136,8 +122,6 @@ class ControlsViewer(QWidget):
|
||||
self.subtype = self.sub_typer.currentText()
|
||||
logger.debug(f"Subtype: {self.subtype}")
|
||||
# query all controls using the type/start and end dates from the gui
|
||||
# controls = get_all_controls_by_type(ctx=obj.ctx, con_type=obj.con_type, start_date=obj.start_date, end_date=obj.end_date)
|
||||
# controls = lookup_controls(ctx=obj.ctx, control_type=obj.con_type, start_date=obj.start_date, end_date=obj.end_date)
|
||||
controls = Control.query(control_type=self.con_type, start_date=self.start_date, end_date=self.end_date)
|
||||
# if no data found from query set fig to none for reporting in webview
|
||||
if controls == None:
|
||||
@@ -174,7 +158,6 @@ class ControlsDatePicker(QWidget):
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.start_date = QDateEdit(calendarPopup=True)
|
||||
# start date is two months prior to end date by default
|
||||
twomonthsago = QDate.currentDate().addDays(-60)
|
||||
|
||||
@@ -20,14 +20,12 @@ def select_open_file(obj:QMainWindow, file_extension:str) -> Path:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
try:
|
||||
# home_dir = Path(obj.ctx.directory_path).resolve().__str__()
|
||||
home_dir = obj.last_dir.resolve().__str__()
|
||||
except FileNotFoundError:
|
||||
home_dir = Path.home().resolve().__str__()
|
||||
except AttributeError:
|
||||
home_dir = obj.app.last_dir.resolve().__str__()
|
||||
fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir, filter = f"{file_extension}(*.{file_extension})")[0])
|
||||
# fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', filter = f"{file_extension}(*.{file_extension})")[0])
|
||||
obj.last_dir = fname.parent
|
||||
return fname
|
||||
|
||||
@@ -44,13 +42,11 @@ def select_save_file(obj:QMainWindow, default_name:str, extension:str) -> Path:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
try:
|
||||
# home_dir = Path(obj.ctx.directory_path).joinpath(default_name).resolve().__str__()
|
||||
home_dir = obj.last_dir.joinpath(default_name).resolve().__str__()
|
||||
except FileNotFoundError:
|
||||
home_dir = Path.home().joinpath(default_name).resolve().__str__()
|
||||
except AttributeError:
|
||||
home_dir = obj.app.last_dir.joinpath(default_name).resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter = f"{extension}(*.{extension})")[0])
|
||||
# fname = Path(QFileDialog.getSaveFileName(obj, "Save File", filter = f"{extension}(*.{extension})")[0])
|
||||
obj.last_dir = fname.parent
|
||||
return fname
|
||||
@@ -9,7 +9,7 @@ from backend.db import SubmissionTypeKitTypeAssociation, SubmissionType, Reagent
|
||||
from backend.validators import PydReagentType, PydKit
|
||||
import logging
|
||||
from pprint import pformat
|
||||
from tools import Report, Result
|
||||
from tools import Report
|
||||
from typing import Tuple
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
@@ -21,7 +21,6 @@ class KitAdder(QWidget):
|
||||
"""
|
||||
def __init__(self, parent) -> None:
|
||||
super().__init__(parent)
|
||||
# self.ctx = parent_ctx
|
||||
self.report = Report()
|
||||
self.app = parent.parent
|
||||
main_box = QVBoxLayout(self)
|
||||
@@ -30,7 +29,6 @@ class KitAdder(QWidget):
|
||||
scroll.setWidgetResizable(True)
|
||||
scrollContent = QWidget(scroll)
|
||||
self.grid = QGridLayout()
|
||||
# self.setLayout(self.grid)
|
||||
scrollContent.setLayout(self.grid)
|
||||
# insert submit button at top
|
||||
self.submit_btn = QPushButton("Submit")
|
||||
@@ -45,7 +43,6 @@ class KitAdder(QWidget):
|
||||
used_for = QComboBox()
|
||||
used_for.setObjectName("used_for")
|
||||
# Insert all existing sample types
|
||||
# used_for.addItems([item.name for item in lookup_submission_type(ctx=parent_ctx)])
|
||||
used_for.addItems([item.name for item in SubmissionType.query()])
|
||||
used_for.setEditable(True)
|
||||
self.grid.addWidget(used_for,3,1)
|
||||
@@ -97,7 +94,6 @@ class KitAdder(QWidget):
|
||||
report = Report()
|
||||
# get form info
|
||||
info, reagents = self.parse_form()
|
||||
# info, reagents = extract_form_info(self)
|
||||
info = {k:v for k,v in info.items() if k in [column.name for column in self.columns] + ['kit_name', 'used_for']}
|
||||
logger.debug(f"kit info: {pformat(info)}")
|
||||
logger.debug(f"kit reagents: {pformat(reagents)}")
|
||||
@@ -115,7 +111,6 @@ class KitAdder(QWidget):
|
||||
}}
|
||||
kit.reagent_types.append(PydReagentType(name=reagent['rtname'], eol_ext=reagent['eol'], uses=uses))
|
||||
logger.debug(f"Output pyd object: {kit.__dict__}")
|
||||
# result = construct_kit_from_yaml(ctx=self.ctx, kit_dict=info)
|
||||
sqlobj, result = kit.toSQL(self.ctx)
|
||||
report.add_result(result=result)
|
||||
sqlobj.save()
|
||||
@@ -153,10 +148,9 @@ class ReagentTypeForm(QWidget):
|
||||
self.reagent_getter = QComboBox()
|
||||
self.reagent_getter.setObjectName("rtname")
|
||||
# lookup all reagent type names from db
|
||||
# lookup = lookup_reagent_types(ctx=ctx)
|
||||
lookup = ReagentType.query()
|
||||
logger.debug(f"Looked up ReagentType names: {lookup}")
|
||||
self.reagent_getter.addItems([item.__str__() for item in lookup])
|
||||
self.reagent_getter.addItems([item.name for item in lookup])
|
||||
self.reagent_getter.setEditable(True)
|
||||
grid.addWidget(self.reagent_getter,0,1)
|
||||
grid.addWidget(QLabel("Extension of Life (months):"),0,2)
|
||||
@@ -221,3 +215,4 @@ class ReagentTypeForm(QWidget):
|
||||
logger.debug(f"Adding key {key}, {sub_key} and value {widget.value()} to {info}")
|
||||
info[key][sub_key] = widget.value()
|
||||
return info
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from backend.db.models import *
|
||||
import logging
|
||||
from .pop_ups import AlertPop
|
||||
from .functions import select_open_file
|
||||
from tools import readInChunks
|
||||
from tools import readInChunks, Settings
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -23,9 +23,9 @@ class AddReagentForm(QDialog):
|
||||
"""
|
||||
dialog to add gather info about new reagent
|
||||
"""
|
||||
def __init__(self, ctx:dict, reagent_lot:str|None=None, reagent_type:str|None=None, expiry:date|None=None, reagent_name:str|None=None) -> None:
|
||||
def __init__(self, reagent_lot:str|None=None, reagent_type:str|None=None, expiry:date|None=None, reagent_name:str|None=None) -> None:
|
||||
super().__init__()
|
||||
self.ctx = ctx
|
||||
# self.ctx = ctx
|
||||
if reagent_lot == None:
|
||||
reagent_lot = reagent_type
|
||||
|
||||
@@ -81,7 +81,13 @@ class AddReagentForm(QDialog):
|
||||
self.setLayout(self.layout)
|
||||
self.type_input.currentTextChanged.connect(self.update_names)
|
||||
|
||||
def parse_form(self):
|
||||
def parse_form(self) -> dict:
|
||||
"""
|
||||
Converts information in form to dict.
|
||||
|
||||
Returns:
|
||||
dict: Output info
|
||||
"""
|
||||
return dict(name=self.name_input.currentText(),
|
||||
lot=self.lot_input.text(),
|
||||
expiry=self.exp_input.date().toPyDate(),
|
||||
@@ -93,7 +99,6 @@ class AddReagentForm(QDialog):
|
||||
"""
|
||||
logger.debug(self.type_input.currentText())
|
||||
self.name_input.clear()
|
||||
# lookup = lookup_reagents(ctx=self.ctx, reagent_type=self.type_input.currentText())
|
||||
lookup = Reagent.query(reagent_type=self.type_input.currentText())
|
||||
self.name_input.addItems(list(set([item.name for item in lookup])))
|
||||
|
||||
@@ -103,7 +108,6 @@ class ReportDatePicker(QDialog):
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.setWindowTitle("Select Report Date Range")
|
||||
# make confirm/reject buttons
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
@@ -125,7 +129,13 @@ class ReportDatePicker(QDialog):
|
||||
self.layout.addWidget(self.buttonBox)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def parse_form(self):
|
||||
def parse_form(self) -> dict:
|
||||
"""
|
||||
Converts information in this object to a dict
|
||||
|
||||
Returns:
|
||||
dict: output dict.
|
||||
"""
|
||||
return dict(start_date=self.start_date.date().toPyDate(), end_date = self.end_date.date().toPyDate())
|
||||
|
||||
class FirstStrandSalvage(QDialog):
|
||||
@@ -162,35 +172,6 @@ class FirstStrandSalvage(QDialog):
|
||||
def parse_form(self):
|
||||
return dict(plate=self.rsl_plate_num.text(), submitter_id=self.submitter_id_input.text(), well=f"{self.row_letter.currentText()}{self.column_number.currentText()}")
|
||||
|
||||
class FirstStrandPlateList(QDialog):
|
||||
|
||||
def __init__(self, ctx:Settings) -> None:
|
||||
super().__init__()
|
||||
self.setWindowTitle("First Strand Plates")
|
||||
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
# ww = [item.rsl_plate_num for item in lookup_submissions(ctx=ctx, submission_type="Wastewater")]
|
||||
ww = [item.rsl_plate_num for item in BasicSubmission.query(submission_type="Wastewater")]
|
||||
self.plate1 = QComboBox()
|
||||
self.plate2 = QComboBox()
|
||||
self.plate3 = QComboBox()
|
||||
self.layout = QFormLayout()
|
||||
for ii, plate in enumerate([self.plate1, self.plate2, self.plate3]):
|
||||
plate.addItems(ww)
|
||||
self.layout.addRow(self.tr(f"&Plate {ii+1}:"), plate)
|
||||
self.layout.addWidget(self.buttonBox)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def parse_form(self):
|
||||
output = []
|
||||
for plate in [self.plate1, self.plate2, self.plate3]:
|
||||
output.append(plate.currentText())
|
||||
return output
|
||||
|
||||
class LogParser(QDialog):
|
||||
|
||||
def __init__(self, parent):
|
||||
|
||||
@@ -53,7 +53,6 @@ class KitSelector(QDialog):
|
||||
super().__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.widget = QComboBox()
|
||||
# kits = [item.__str__() for item in lookup_kit_types(ctx=ctx)]
|
||||
kits = [item.__str__() for item in KitType.query()]
|
||||
self.widget.addItems(kits)
|
||||
self.widget.setEditable(False)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'''
|
||||
Contains widgets specific to the submission summary and submission details.
|
||||
'''
|
||||
import base64
|
||||
import base64, logging, json
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
import pprint
|
||||
from pprint import pformat
|
||||
from PyQt6 import QtPrintSupport
|
||||
from PyQt6.QtWidgets import (
|
||||
QVBoxLayout, QDialog, QTableView,
|
||||
QTextEdit, QPushButton, QScrollArea,
|
||||
QMessageBox, QFileDialog, QMenu, QLabel,
|
||||
QMessageBox, QMenu, QLabel,
|
||||
QDialogButtonBox, QToolBar
|
||||
)
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
@@ -17,19 +17,16 @@ from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
|
||||
from PyQt6.QtGui import QAction, QCursor, QPixmap, QPainter
|
||||
from backend.db.functions import submissions_to_df
|
||||
from backend.db.models import BasicSubmission
|
||||
from backend.excel import make_hitpicks, make_report_html, make_report_xlsx
|
||||
from tools import check_if_app, Report, Result
|
||||
from tools import jinja_template_loading
|
||||
from backend.excel import make_report_html, make_report_xlsx
|
||||
from tools import check_if_app, Report, Result, jinja_template_loading, get_first_blank_df_row, row_map
|
||||
from xhtml2pdf import pisa
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from .pop_ups import QuestionAsker, AlertPop
|
||||
from .pop_ups import QuestionAsker
|
||||
from ..visualizations import make_plate_barcode, make_plate_map, make_plate_map_html
|
||||
from .functions import select_save_file, select_open_file
|
||||
from .misc import ReportDatePicker
|
||||
import pandas as pd
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
from getpass import getuser
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -161,16 +158,19 @@ class SubmissionsSheet(QTableView):
|
||||
detailsAction = QAction('Details', self)
|
||||
# barcodeAction = QAction("Print Barcode", self)
|
||||
commentAction = QAction("Add Comment", self)
|
||||
backupAction = QAction("Backup", self)
|
||||
# hitpickAction = QAction("Hitpicks", self)
|
||||
renameAction.triggered.connect(lambda: self.delete_item(event))
|
||||
detailsAction.triggered.connect(lambda: self.show_details())
|
||||
# barcodeAction.triggered.connect(lambda: self.create_barcode())
|
||||
commentAction.triggered.connect(lambda: self.add_comment())
|
||||
backupAction.triggered.connect(lambda: self.regenerate_submission_form())
|
||||
# hitpickAction.triggered.connect(lambda: self.hit_pick())
|
||||
self.menu.addAction(detailsAction)
|
||||
self.menu.addAction(renameAction)
|
||||
# self.menu.addAction(barcodeAction)
|
||||
self.menu.addAction(commentAction)
|
||||
self.menu.addAction(backupAction)
|
||||
# self.menu.addAction(hitpickAction)
|
||||
# add other required actions
|
||||
self.menu.popup(QCursor.pos())
|
||||
@@ -193,64 +193,64 @@ class SubmissionsSheet(QTableView):
|
||||
return
|
||||
self.setData()
|
||||
|
||||
def hit_pick(self):
|
||||
"""
|
||||
Extract positive samples from submissions with PCR results and export to csv.
|
||||
NOTE: For this to work for arbitrary samples, positive samples must have 'positive' in their name
|
||||
"""
|
||||
# Get all selected rows
|
||||
indices = self.selectionModel().selectedIndexes()
|
||||
# convert to id numbers
|
||||
indices = [index.sibling(index.row(), 0).data() for index in indices]
|
||||
# biomek can handle 4 plates maximum
|
||||
if len(indices) > 4:
|
||||
logger.error(f"Error: Had to truncate number of plates to 4.")
|
||||
indices = indices[:4]
|
||||
# lookup ids in the database
|
||||
# subs = [lookup_submissions(ctx=self.ctx, id=id) for id in indices]
|
||||
subs = [BasicSubmission.query(id=id) for id in indices]
|
||||
# full list of samples
|
||||
dicto = []
|
||||
# list to contain plate images
|
||||
images = []
|
||||
for iii, sub in enumerate(subs):
|
||||
# second check to make sure there aren't too many plates
|
||||
if iii > 3:
|
||||
logger.error(f"Error: Had to truncate number of plates to 4.")
|
||||
continue
|
||||
plate_dicto = sub.hitpick_plate(plate_number=iii+1)
|
||||
if plate_dicto == None:
|
||||
continue
|
||||
image = make_plate_map(plate_dicto)
|
||||
images.append(image)
|
||||
for item in plate_dicto:
|
||||
if len(dicto) < 94:
|
||||
dicto.append(item)
|
||||
else:
|
||||
logger.error(f"We had to truncate the number of samples to 94.")
|
||||
logger.debug(f"We found {len(dicto)} to hitpick")
|
||||
# convert all samples to dataframe
|
||||
df = make_hitpicks(dicto)
|
||||
df = df[df.positive != False]
|
||||
logger.debug(f"Size of the dataframe: {df.shape[0]}")
|
||||
msg = AlertPop(message=f"We found {df.shape[0]} samples to hitpick", status="INFORMATION")
|
||||
msg.exec()
|
||||
if df.size == 0:
|
||||
return
|
||||
date = datetime.strftime(datetime.today(), "%Y-%m-%d")
|
||||
# ask for filename and save as csv.
|
||||
home_dir = Path(self.ctx.directory_path).joinpath(f"Hitpicks_{date}.csv").resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".csv")[0])
|
||||
if fname.__str__() == ".":
|
||||
logger.debug("Saving csv was cancelled.")
|
||||
return
|
||||
df.to_csv(fname.__str__(), index=False)
|
||||
# show plate maps
|
||||
for image in images:
|
||||
try:
|
||||
image.show()
|
||||
except Exception as e:
|
||||
logger.error(f"Could not show image: {e}.")
|
||||
# def hit_pick(self):
|
||||
# """
|
||||
# Extract positive samples from submissions with PCR results and export to csv.
|
||||
# NOTE: For this to work for arbitrary samples, positive samples must have 'positive' in their name
|
||||
# """
|
||||
# # Get all selected rows
|
||||
# indices = self.selectionModel().selectedIndexes()
|
||||
# # convert to id numbers
|
||||
# indices = [index.sibling(index.row(), 0).data() for index in indices]
|
||||
# # biomek can handle 4 plates maximum
|
||||
# if len(indices) > 4:
|
||||
# logger.error(f"Error: Had to truncate number of plates to 4.")
|
||||
# indices = indices[:4]
|
||||
# # lookup ids in the database
|
||||
# # subs = [lookup_submissions(ctx=self.ctx, id=id) for id in indices]
|
||||
# subs = [BasicSubmission.query(id=id) for id in indices]
|
||||
# # full list of samples
|
||||
# dicto = []
|
||||
# # list to contain plate images
|
||||
# images = []
|
||||
# for iii, sub in enumerate(subs):
|
||||
# # second check to make sure there aren't too many plates
|
||||
# if iii > 3:
|
||||
# logger.error(f"Error: Had to truncate number of plates to 4.")
|
||||
# continue
|
||||
# plate_dicto = sub.hitpick_plate(plate_number=iii+1)
|
||||
# if plate_dicto == None:
|
||||
# continue
|
||||
# image = make_plate_map(plate_dicto)
|
||||
# images.append(image)
|
||||
# for item in plate_dicto:
|
||||
# if len(dicto) < 94:
|
||||
# dicto.append(item)
|
||||
# else:
|
||||
# logger.error(f"We had to truncate the number of samples to 94.")
|
||||
# logger.debug(f"We found {len(dicto)} to hitpick")
|
||||
# # convert all samples to dataframe
|
||||
# df = make_hitpicks(dicto)
|
||||
# df = df[df.positive != False]
|
||||
# logger.debug(f"Size of the dataframe: {df.shape[0]}")
|
||||
# msg = AlertPop(message=f"We found {df.shape[0]} samples to hitpick", status="INFORMATION")
|
||||
# msg.exec()
|
||||
# if df.size == 0:
|
||||
# return
|
||||
# date = datetime.strftime(datetime.today(), "%Y-%m-%d")
|
||||
# # ask for filename and save as csv.
|
||||
# home_dir = Path(self.ctx.directory_path).joinpath(f"Hitpicks_{date}.csv").resolve().__str__()
|
||||
# fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".csv")[0])
|
||||
# if fname.__str__() == ".":
|
||||
# logger.debug("Saving csv was cancelled.")
|
||||
# return
|
||||
# df.to_csv(fname.__str__(), index=False)
|
||||
# # show plate maps
|
||||
# for image in images:
|
||||
# try:
|
||||
# image.show()
|
||||
# except Exception as e:
|
||||
# logger.error(f"Could not show image: {e}.")
|
||||
|
||||
def link_extractions(self):
|
||||
self.link_extractions_function()
|
||||
@@ -420,6 +420,7 @@ class SubmissionsSheet(QTableView):
|
||||
subs = BasicSubmission.query(start_date=info['start_date'], end_date=info['end_date'])
|
||||
# convert each object to dict
|
||||
records = [item.report_dict() for item in subs]
|
||||
logger.debug(f"Records: {pformat(records)}")
|
||||
# make dataframe from record dictionaries
|
||||
detailed_df, summary_df = make_report_xlsx(records=records)
|
||||
html = make_report_html(df=summary_df, start_date=info['start_date'], end_date=info['end_date'])
|
||||
@@ -430,23 +431,42 @@ class SubmissionsSheet(QTableView):
|
||||
writer = pd.ExcelWriter(fname.with_suffix(".xlsx"), engine='openpyxl')
|
||||
summary_df.to_excel(writer, sheet_name="Report")
|
||||
detailed_df.to_excel(writer, sheet_name="Details", index=False)
|
||||
worksheet = writer.sheets['Report']
|
||||
for idx, col in enumerate(summary_df): # loop through all columns
|
||||
worksheet: Worksheet = writer.sheets['Report']
|
||||
for idx, col in enumerate(summary_df, start=1): # loop through all columns
|
||||
series = summary_df[col]
|
||||
max_len = max((
|
||||
series.astype(str).map(len).max(), # len of largest item
|
||||
len(str(series.name)) # len of column name/header
|
||||
)) + 20 # adding a little extra space
|
||||
try:
|
||||
worksheet.column_dimensions[get_column_letter(idx)].width = max_len
|
||||
# worksheet.column_dimensions[get_column_letter(idx=idx)].width = max_len
|
||||
# Convert idx to letter
|
||||
col_letter = chr(ord('@') + idx)
|
||||
worksheet.column_dimensions[col_letter].width = max_len
|
||||
except ValueError:
|
||||
pass
|
||||
blank_row = get_first_blank_df_row(summary_df) + 1
|
||||
logger.debug(f"Blank row index = {blank_row}")
|
||||
for col in range(3,6):
|
||||
col_letter = row_map[col]
|
||||
worksheet.cell(row=blank_row, column=col, value=f"=SUM({col_letter}2:{col_letter}{str(blank_row-1)})")
|
||||
for cell in worksheet['D']:
|
||||
if cell.row > 1:
|
||||
cell.style = 'Currency'
|
||||
writer.close()
|
||||
self.report.add_result(report)
|
||||
|
||||
|
||||
def regenerate_submission_form(self):
|
||||
index = (self.selectionModel().currentIndex())
|
||||
value = index.sibling(index.row(),0).data()
|
||||
logger.debug(index)
|
||||
# msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {index.sibling(index.row(),1).data()}?\n")
|
||||
# if msg.exec():
|
||||
# delete_submission(id=value)
|
||||
sub = BasicSubmission.query(id=value)
|
||||
fname = select_save_file(self, default_name=sub.to_pydantic().construct_filename(), extension="xlsx")
|
||||
sub.backup(fname=fname)
|
||||
|
||||
class SubmissionDetails(QDialog):
|
||||
"""
|
||||
a window showing text details of submission
|
||||
@@ -466,7 +486,7 @@ class SubmissionDetails(QDialog):
|
||||
# get submision from db
|
||||
# sub = lookup_submissions(ctx=ctx, id=id)
|
||||
sub = BasicSubmission.query(id=id)
|
||||
logger.debug(f"Submission details data:\n{pprint.pformat(sub.to_dict())}")
|
||||
logger.debug(f"Submission details data:\n{pformat(sub.to_dict())}")
|
||||
self.base_dict = sub.to_dict(full_data=True)
|
||||
# don't want id
|
||||
del self.base_dict['id']
|
||||
@@ -611,8 +631,11 @@ class SubmissionComment(QDialog):
|
||||
|
||||
super().__init__(parent)
|
||||
# self.ctx = ctx
|
||||
self.app = parent.parent().parent().parent().parent().parent().parent
|
||||
print(f"App: {self.app}")
|
||||
try:
|
||||
self.app = parent.parent().parent().parent().parent().parent().parent
|
||||
print(f"App: {self.app}")
|
||||
except AttributeError:
|
||||
pass
|
||||
self.rsl = rsl
|
||||
self.setWindowTitle(f"{self.rsl} Submission Comment")
|
||||
# create text field
|
||||
|
||||
@@ -65,9 +65,6 @@ class SubmissionFormContainer(QWidget):
|
||||
self.app.result_reporter()
|
||||
|
||||
def scrape_reagents(self, *args, **kwargs):
|
||||
# from .main_window_functions import scrape_reagents
|
||||
# logger.debug(f"Args: {args}")
|
||||
# logger.debug(F"kwargs: {kwargs}")
|
||||
print(f"\n\n{inspect.stack()[1].function}\n\n")
|
||||
self.scrape_reagents_function(args[0])
|
||||
self.kit_integrity_completion()
|
||||
@@ -140,7 +137,7 @@ class SubmissionFormContainer(QWidget):
|
||||
return
|
||||
# create sheetparser using excel sheet and context from gui
|
||||
try:
|
||||
self.prsr = SheetParser(ctx=self.ctx, filepath=fname)
|
||||
self.prsr = SheetParser(ctx=self.app.ctx, filepath=fname)
|
||||
except PermissionError:
|
||||
logger.error(f"Couldn't get permission to access file: {fname}")
|
||||
return
|
||||
@@ -519,7 +516,7 @@ class SubmissionFormWidget(QWidget):
|
||||
case 'submitting_lab':
|
||||
add_widget = QComboBox()
|
||||
# lookup organizations suitable for submitting_lab (ctx: self.InfoItem.SubmissionFormWidget.SubmissionFormContainer.AddSubForm )
|
||||
labs = [item.__str__() for item in Organization.query()]
|
||||
labs = [item.name for item in Organization.query()]
|
||||
# try to set closest match to top of list
|
||||
try:
|
||||
labs = difflib.get_close_matches(value, labs, len(labs), 0)
|
||||
@@ -536,7 +533,7 @@ class SubmissionFormWidget(QWidget):
|
||||
add_widget = QComboBox()
|
||||
# lookup existing kits by 'submission_type' decided on by sheetparser
|
||||
logger.debug(f"Looking up kits used for {submission_type}")
|
||||
uses = [item.__str__() for item in KitType.query(used_for=submission_type)]
|
||||
uses = [item.name for item in KitType.query(used_for=submission_type)]
|
||||
obj.uses = uses
|
||||
logger.debug(f"Kits received for {submission_type}: {uses}")
|
||||
if check_not_nan(value):
|
||||
@@ -616,6 +613,8 @@ class ReagentFormWidget(QWidget):
|
||||
def __init__(self, parent:QWidget, reagent:PydReagent, extraction_kit:str):
|
||||
super().__init__(parent)
|
||||
# self.setParent(parent)
|
||||
self.app = self.parent().parent().parent().parent().parent().parent().parent().parent()
|
||||
|
||||
self.reagent = reagent
|
||||
self.extraction_kit = extraction_kit
|
||||
# self.ctx = reagent.ctx
|
||||
@@ -640,7 +639,8 @@ class ReagentFormWidget(QWidget):
|
||||
if wanted_reagent == None:
|
||||
dlg = QuestionAsker(title=f"Add {lot}?", message=f"Couldn't find reagent type {self.reagent.type}: {lot} in the database.\n\nWould you like to add it?")
|
||||
if dlg.exec():
|
||||
wanted_reagent = self.parent().parent().parent().parent().parent().parent().parent().parent().parent.add_reagent(reagent_lot=lot, reagent_type=self.reagent.type, expiry=self.reagent.expiry, name=self.reagent.name)
|
||||
print(self.app)
|
||||
wanted_reagent = self.app.add_reagent(reagent_lot=lot, reagent_type=self.reagent.type, expiry=self.reagent.expiry, name=self.reagent.name)
|
||||
return wanted_reagent, None
|
||||
else:
|
||||
# In this case we will have an empty reagent and the submission will fail kit integrity check
|
||||
@@ -690,7 +690,7 @@ class ReagentFormWidget(QWidget):
|
||||
# below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work.
|
||||
# lookup = lookup_reagents(ctx=self.ctx, reagent_type=reagent.type)
|
||||
lookup = Reagent.query(reagent_type=reagent.type)
|
||||
relevant_reagents = [item.__str__() for item in lookup]
|
||||
relevant_reagents = [str(item.lot) for item in lookup]
|
||||
output_reg = []
|
||||
for rel_reagent in relevant_reagents:
|
||||
# extract strings from any sets.
|
||||
|
||||
Reference in New Issue
Block a user