Before creating info and reagent parser classes.
This commit is contained in:
@@ -10,11 +10,10 @@ from PyQt6.QtWidgets import (
|
||||
QHBoxLayout
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QDate, QSize
|
||||
from tools import check_not_nan
|
||||
from tools import check_not_nan, jinja_template_loading, Settings
|
||||
from ..all_window_functions import extract_form_info
|
||||
from backend.db import get_all_reagenttype_names, lookup_all_sample_types, create_kit_from_yaml, \
|
||||
lookup_regent_by_type_name, lookup_last_used_reagenttype_lot
|
||||
from tools import jinja_template_loading
|
||||
lookup_regent_by_type_name, lookup_last_used_reagenttype_lot, lookup_all_reagent_names_by_role
|
||||
import logging
|
||||
import numpy as np
|
||||
from .pop_ups import AlertPop
|
||||
@@ -28,9 +27,9 @@ class AddReagentForm(QDialog):
|
||||
"""
|
||||
dialog to add gather info about new reagent
|
||||
"""
|
||||
def __init__(self, ctx:dict, reagent_lot:str|None, reagent_type:str|None, expiry:date|None=None) -> None:
|
||||
def __init__(self, ctx:dict, reagent_lot:str|None, reagent_type:str|None, expiry:date|None=None, reagent_name:str|None=None) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.ctx = ctx
|
||||
if reagent_lot == None:
|
||||
reagent_lot = ""
|
||||
|
||||
@@ -42,21 +41,26 @@ class AddReagentForm(QDialog):
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
# widget to get lot info
|
||||
lot_input = QLineEdit()
|
||||
lot_input.setObjectName("lot")
|
||||
lot_input.setText(reagent_lot)
|
||||
self.name_input = QComboBox()
|
||||
self.name_input.setObjectName("name")
|
||||
self.name_input.setEditable(True)
|
||||
self.name_input.setCurrentText(reagent_name)
|
||||
# self.name_input.setText(reagent_name)
|
||||
self.lot_input = QLineEdit()
|
||||
self.lot_input.setObjectName("lot")
|
||||
self.lot_input.setText(reagent_lot)
|
||||
# widget to get expiry info
|
||||
exp_input = QDateEdit(calendarPopup=True)
|
||||
exp_input.setObjectName('expiry')
|
||||
self.exp_input = QDateEdit(calendarPopup=True)
|
||||
self.exp_input.setObjectName('expiry')
|
||||
# if expiry is not passed in from gui, use today
|
||||
if expiry == None:
|
||||
exp_input.setDate(QDate.currentDate())
|
||||
self.exp_input.setDate(QDate.currentDate())
|
||||
else:
|
||||
exp_input.setDate(expiry)
|
||||
self.exp_input.setDate(expiry)
|
||||
# widget to get reagent type info
|
||||
type_input = QComboBox()
|
||||
type_input.setObjectName('type')
|
||||
type_input.addItems([item.replace("_", " ").title() for item in get_all_reagenttype_names(ctx=ctx)])
|
||||
self.type_input = QComboBox()
|
||||
self.type_input.setObjectName('type')
|
||||
self.type_input.addItems([item.replace("_", " ").title() for item in get_all_reagenttype_names(ctx=ctx)])
|
||||
logger.debug(f"Trying to find index of {reagent_type}")
|
||||
# convert input to user friendly string?
|
||||
try:
|
||||
@@ -64,18 +68,26 @@ class AddReagentForm(QDialog):
|
||||
except AttributeError:
|
||||
reagent_type = None
|
||||
# set parsed reagent type to top of list
|
||||
index = type_input.findText(reagent_type, Qt.MatchFlag.MatchEndsWith)
|
||||
index = self.type_input.findText(reagent_type, Qt.MatchFlag.MatchEndsWith)
|
||||
if index >= 0:
|
||||
type_input.setCurrentIndex(index)
|
||||
self.type_input.setCurrentIndex(index)
|
||||
self.layout = QVBoxLayout()
|
||||
self.layout.addWidget(QLabel("Name:"))
|
||||
self.layout.addWidget(self.name_input)
|
||||
self.layout.addWidget(QLabel("Lot:"))
|
||||
self.layout.addWidget(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(exp_input)
|
||||
self.layout.addWidget(self.exp_input)
|
||||
self.layout.addWidget(QLabel("Type:"))
|
||||
self.layout.addWidget(type_input)
|
||||
self.layout.addWidget(self.type_input)
|
||||
self.layout.addWidget(self.buttonBox)
|
||||
self.setLayout(self.layout)
|
||||
self.type_input.currentTextChanged.connect(self.update_names)
|
||||
|
||||
def update_names(self):
|
||||
logger.debug(self.type_input.currentText())
|
||||
self.name_input.clear()
|
||||
self.name_input.addItems(item for item in lookup_all_reagent_names_by_role(ctx=self.ctx, role_name=self.type_input.currentText().replace(" ", "_").lower()))
|
||||
|
||||
|
||||
class ReportDatePicker(QDialog):
|
||||
@@ -111,7 +123,7 @@ class KitAdder(QWidget):
|
||||
"""
|
||||
dialog to get information to add kit
|
||||
"""
|
||||
def __init__(self, parent_ctx:dict) -> None:
|
||||
def __init__(self, parent_ctx:Settings) -> None:
|
||||
super().__init__()
|
||||
self.ctx = parent_ctx
|
||||
self.grid = QGridLayout()
|
||||
@@ -196,6 +208,7 @@ class KitAdder(QWidget):
|
||||
result = create_kit_from_yaml(ctx=self.ctx, exp=yml_type)
|
||||
msg = AlertPop(message=result['message'], status=result['status'])
|
||||
msg.exec()
|
||||
self.__init__(self.ctx)
|
||||
|
||||
|
||||
class ReagentTypeForm(QWidget):
|
||||
|
||||
@@ -7,7 +7,7 @@ from PyQt6.QtWidgets import (
|
||||
)
|
||||
from tools import jinja_template_loading
|
||||
import logging
|
||||
from backend.db.functions import lookup_kittype_by_use
|
||||
from backend.db.functions import lookup_kittype_by_use, lookup_all_sample_types
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -78,4 +78,31 @@ class KitSelector(QDialog):
|
||||
# r = dlg.exec_()
|
||||
# if r:
|
||||
# return dlg.getValues()
|
||||
# return None
|
||||
# return None
|
||||
|
||||
class SubmissionTypeSelector(QDialog):
|
||||
"""
|
||||
dialog to ask yes/no questions
|
||||
"""
|
||||
def __init__(self, ctx:dict, title:str, message:str) -> QDialog:
|
||||
super().__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.widget = QComboBox()
|
||||
sub_type = lookup_all_sample_types(ctx=ctx)
|
||||
self.widget.addItems(sub_type)
|
||||
self.widget.setEditable(False)
|
||||
# set yes/no buttons
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
self.layout = QVBoxLayout()
|
||||
# Text for the yes/no question
|
||||
message = QLabel(message)
|
||||
self.layout.addWidget(message)
|
||||
self.layout.addWidget(self.widget)
|
||||
self.layout.addWidget(self.buttonBox)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def getValues(self):
|
||||
return self.widget.currentText()
|
||||
|
||||
@@ -4,6 +4,7 @@ Contains widgets specific to the submission summary and submission details.
|
||||
import base64
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
import pprint
|
||||
from PyQt6 import QtPrintSupport
|
||||
from PyQt6.QtWidgets import (
|
||||
QVBoxLayout, QDialog, QTableView,
|
||||
@@ -215,7 +216,8 @@ class SubmissionsSheet(QTableView):
|
||||
if iii > 3:
|
||||
logger.error(f"Error: Had to truncate number of plates to 4.")
|
||||
continue
|
||||
plate_dicto = hitpick_plate(submission=sub, plate_number=iii+1)
|
||||
# plate_dicto = hitpick_plate(submission=sub, plate_number=iii+1)
|
||||
plate_dicto = sub.hitpick_plate(plate_number=iii+1)
|
||||
if plate_dicto == None:
|
||||
continue
|
||||
image = make_plate_map(plate_dicto)
|
||||
@@ -236,7 +238,7 @@ class SubmissionsSheet(QTableView):
|
||||
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__()
|
||||
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.")
|
||||
@@ -265,7 +267,7 @@ class SubmissionDetails(QDialog):
|
||||
interior.setParent(self)
|
||||
# get submision from db
|
||||
data = lookup_submission_by_id(ctx=ctx, id=id)
|
||||
logger.debug(f"Submission details data:\n{data.to_dict()}")
|
||||
logger.debug(f"Submission details data:\n{pprint.pformat(data.to_dict())}")
|
||||
self.base_dict = data.to_dict()
|
||||
# don't want id
|
||||
del self.base_dict['id']
|
||||
@@ -291,7 +293,8 @@ class SubmissionDetails(QDialog):
|
||||
# interior.setWidget(txt_editor)
|
||||
self.base_dict['barcode'] = base64.b64encode(make_plate_barcode(self.base_dict['Plate Number'], width=120, height=30)).decode('utf-8')
|
||||
sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=self.base_dict['Plate Number'])
|
||||
plate_dicto = hitpick_plate(sub)
|
||||
# plate_dicto = hitpick_plate(sub)
|
||||
plate_dicto = sub.hitpick_plate()
|
||||
platemap = make_plate_map(plate_dicto)
|
||||
logger.debug(f"platemap: {platemap}")
|
||||
image_io = BytesIO()
|
||||
|
||||
@@ -8,7 +8,8 @@ import inspect
|
||||
import pprint
|
||||
import yaml
|
||||
import json
|
||||
from typing import Tuple
|
||||
from typing import Tuple, List
|
||||
from openpyxl import load_workbook
|
||||
from openpyxl.utils import get_column_letter
|
||||
from xhtml2pdf import pisa
|
||||
import pandas as pd
|
||||
@@ -25,19 +26,16 @@ from backend.db.functions import (
|
||||
construct_submission_info, lookup_reagent, store_submission, lookup_submissions_by_date_range,
|
||||
create_kit_from_yaml, create_org_from_yaml, get_control_subtypes, get_all_controls_by_type,
|
||||
lookup_all_submissions_by_type, get_all_controls, lookup_submission_by_rsl_num, update_ww_sample,
|
||||
check_kit_integrity
|
||||
check_kit_integrity, get_reagents_in_extkit
|
||||
)
|
||||
from backend.excel.parser import SheetParser, PCRParser
|
||||
from backend.excel.reports import make_report_html, make_report_xlsx, convert_data_list_to_df
|
||||
from backend.pydant import PydReagent
|
||||
from tools import check_not_nan
|
||||
from .custom_widgets.pop_ups import AlertPop, QuestionAsker
|
||||
from .custom_widgets.pop_ups import AlertPop, KitSelector, QuestionAsker
|
||||
from .custom_widgets import ReportDatePicker
|
||||
from .custom_widgets.misc import ImportReagent
|
||||
from .visualizations.control_charts import create_charts, construct_html
|
||||
from typing import List
|
||||
from openpyxl import load_workbook
|
||||
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -71,6 +69,7 @@ def import_submission_function(obj:QMainWindow) -> Tuple[QMainWindow, dict|None]
|
||||
except PermissionError:
|
||||
logger.error(f"Couldn't get permission to access file: {fname}")
|
||||
return obj, result
|
||||
# prsr.sub = import_validation_check(ctx=obj.ctx, parser_sub=prsr.sub)
|
||||
# obj.column_count = prsr.column_count
|
||||
try:
|
||||
logger.debug(f"Submission dictionary: {prsr.sub}")
|
||||
@@ -260,7 +259,7 @@ def kit_integrity_completion_function(obj:QMainWindow) -> Tuple[QMainWindow, dic
|
||||
obj.missing_reagents = kit_integrity['missing']
|
||||
for item in kit_integrity['missing']:
|
||||
obj.table_widget.formlayout.addWidget(QLabel(f"Lot {item.replace('_', ' ').title()}"))
|
||||
reagent = dict(type=item, lot=None, exp=None)
|
||||
reagent = dict(type=item, lot=None, exp=None, name=None)
|
||||
add_widget = ImportReagent(ctx=obj.ctx, reagent=PydReagent(**reagent))#item=item)
|
||||
obj.table_widget.formlayout.addWidget(add_widget)
|
||||
submit_btn = QPushButton("Submit")
|
||||
@@ -306,9 +305,11 @@ def submit_new_sample_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
else:
|
||||
# In this case we will have an empty reagent and the submission will fail kit integrity check
|
||||
logger.debug("Will not add reagent.")
|
||||
if wanted_reagent != None:
|
||||
parsed_reagents.append(wanted_reagent)
|
||||
wanted_reagent.type.last_used = reagents[reagent]
|
||||
# obj.ctx.database_session.rollback()
|
||||
return obj, dict(message="Failed integrity check", status="critical")
|
||||
# if wanted_reagent != None:
|
||||
parsed_reagents.append(wanted_reagent)
|
||||
wanted_reagent.type.last_used = reagents[reagent]
|
||||
# move samples into preliminary submission dict
|
||||
info['samples'] = obj.samples
|
||||
info['uploaded_by'] = getuser()
|
||||
@@ -325,6 +326,7 @@ def submit_new_sample_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
# Do not add duplicate reagents.
|
||||
base_submission.reagents = []
|
||||
else:
|
||||
obj.ctx.database_session.rollback()
|
||||
return obj, dict(message="Overwrite cancelled", status="Information")
|
||||
# code 2: No RSL plate number given
|
||||
case 2:
|
||||
@@ -340,7 +342,7 @@ def submit_new_sample_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
if kit_integrity != None:
|
||||
return obj, dict(message=kit_integrity['message'], status="critical")
|
||||
logger.debug(f"Sending submission: {base_submission.rsl_plate_num} to database.")
|
||||
result = store_submission(ctx=obj.ctx, base_submission=base_submission)
|
||||
result = store_submission(ctx=obj.ctx, base_submission=base_submission, samples=obj.samples)
|
||||
# check result of storing for issues
|
||||
# update summary sheet
|
||||
obj.table_widget.sub_wid.setData()
|
||||
@@ -353,7 +355,10 @@ def submit_new_sample_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
extraction_kit = lookup_kittype_by_name(obj.ctx, name=obj.ext_kit)
|
||||
logger.debug(f"We have the extraction kit: {extraction_kit.name}")
|
||||
logger.debug(f"Extraction kit map:\n\n{extraction_kit.used_for[obj.current_submission_type.replace('_', ' ')]}")
|
||||
excel_map = extraction_kit.used_for[obj.current_submission_type.replace('_', ' ')]
|
||||
# TODO replace below with function in KitType object. Update Kittype associations.
|
||||
# excel_map = extraction_kit.used_for[obj.current_submission_type.replace('_', ' ')]
|
||||
excel_map = extraction_kit.construct_xl_map_for_use(obj.current_submission_type.replace('_', ' ').title())
|
||||
excel_map.update(extraction_kit.used_for[obj.current_submission_type.replace('_', ' ').title()])
|
||||
input_reagents = [item.to_reagent_dict() for item in parsed_reagents]
|
||||
autofill_excel(obj=obj, xl_map=excel_map, reagents=input_reagents, missing_reagents=obj.missing_reagents, info=info)
|
||||
if hasattr(obj, 'csv'):
|
||||
@@ -430,7 +435,7 @@ def add_kit_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
result = None
|
||||
# setup file dialog to find yaml flie
|
||||
fname = select_open_file(obj, extension="yml")
|
||||
fname = select_open_file(obj, file_extension="yml")
|
||||
assert fname.exists()
|
||||
# read yaml file
|
||||
try:
|
||||
@@ -587,7 +592,7 @@ def link_controls_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
for bcs in all_bcs:
|
||||
logger.debug(f"Running for {bcs.rsl_plate_num}")
|
||||
logger.debug(f"Here is the current control: {[control.name for control in bcs.controls]}")
|
||||
samples = [sample.sample_id for sample in bcs.samples]
|
||||
samples = [sample.submitter_id for sample in bcs.samples]
|
||||
logger.debug(bcs.controls)
|
||||
for sample in samples:
|
||||
# replace below is a stopgap method because some dingus decided to add spaces in some of the ATCC49... so it looks like "ATCC 49"...
|
||||
@@ -897,6 +902,7 @@ def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_re
|
||||
worksheet.cell(row=item['location']['row'], column=item['location']['column'], value=item['value'])
|
||||
# Hacky way to
|
||||
if info['submission_type'] == "Bacterial Culture":
|
||||
workbook["Sample List"].cell(row=14, column=2, value=getuser())
|
||||
workbook["Sample List"].cell(row=14, column=2, value=getuser()[0:2].upper())
|
||||
fname = select_save_file(obj=obj, default_name=info['rsl_plate_num'], extension="xlsx")
|
||||
workbook.save(filename=fname.__str__())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user