Pre-sample/control connect

This commit is contained in:
Landon Wark
2023-12-05 10:20:46 -06:00
parent 283e77fee5
commit cddb947ec8
29 changed files with 1357 additions and 1042 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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

View File

@@ -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.