From fc334155ff7761407423a9016f822c968d8904d8 Mon Sep 17 00:00:00 2001 From: Landon Wark Date: Tue, 14 Mar 2023 13:13:10 -0500 Subject: [PATCH] commit before refactor to use pyqt6 input field names --- src/submissions/__init__.py | 2 +- src/submissions/backend/db/__init__.py | 23 +++-- src/submissions/backend/db/models/controls.py | 36 ++++--- src/submissions/backend/db/models/kits.py | 22 ++++- .../backend/db/models/organizations.py | 6 +- src/submissions/backend/db/models/samples.py | 8 +- .../backend/db/models/submissions.py | 12 ++- src/submissions/frontend/__init__.py | 66 ++++--------- .../frontend/custom_widgets/__init__.py | 71 +++++++++++--- .../frontend/custom_widgets/sub_details.py | 2 +- src/submissions/frontend/functions.py | 95 ++++++++++--------- 11 files changed, 193 insertions(+), 150 deletions(-) diff --git a/src/submissions/__init__.py b/src/submissions/__init__.py index 1e42911..6dceed3 100644 --- a/src/submissions/__init__.py +++ b/src/submissions/__init__.py @@ -1,6 +1,6 @@ # __init__.py # Version of the realpython-reader package -__version__ = "202303.1b" +__version__ = "202303.2b" __author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"} __copyright__ = "2022-2023, Government of Canada" diff --git a/src/submissions/backend/db/__init__.py b/src/submissions/backend/db/__init__.py index 30b0de4..48f59e4 100644 --- a/src/submissions/backend/db/__init__.py +++ b/src/submissions/backend/db/__init__.py @@ -92,7 +92,7 @@ def store_reagent(ctx:dict, reagent:models.Reagent) -> None|dict: def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmission: """ - Construct submission obejct from dictionary + Construct submission object from dictionary Args: ctx (dict): settings passed down from gui @@ -110,7 +110,6 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio msg = "A proper RSL plate number is required." return instance, {'code': 2, 'message': "A proper RSL plate number is required."} instance = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.rsl_plate_num==info_dict['rsl_plate_num']).first() - # get model based on submission type converted above logger.debug(f"Looking at models for submission type: {query}") model = getattr(models, query) @@ -121,7 +120,7 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio instance = model() logger.debug(f"Submission doesn't exist yet, creating new instance: {instance}") msg = None - code =0 + code = 0 else: code = 1 msg = "This submission already exists.\nWould you like to overwrite?" @@ -164,7 +163,6 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio logger.debug(f"Looks like that kit doesn't have cost breakdown yet, using full plate cost.") instance.run_cost = instance.extraction_kit.cost_per_run # We need to make sure there's a proper rsl plate number - try: logger.debug(f"Constructed instance: {instance.to_string()}") except AttributeError as e: @@ -196,12 +194,13 @@ def construct_reagent(ctx:dict, info_dict:dict) -> models.Reagent: case "type": reagent.type = lookup_reagenttype_by_name(ctx=ctx, rt_name=info_dict[item].replace(" ", "_").lower()) # add end-of-life extension from reagent type to expiry date - try: - reagent.expiry = reagent.expiry + reagent.type.eol_ext - except TypeError as e: - logger.debug(f"We got a type error: {e}.") - except AttributeError: - pass + # Edit: this will now be done only in the reporting phase to account for potential changes in end-of-life extensions + # try: + # reagent.expiry = reagent.expiry + reagent.type.eol_ext + # except TypeError as e: + # logger.debug(f"We got a type error: {e}.") + # except AttributeError: + # pass return reagent @@ -465,7 +464,7 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> dict: # except KeyError: if not check_is_power_user(ctx=ctx): logger.debug(f"{getuser()} does not have permission to add kits.") - return {'code':1, 'message':"This user does not have permission to add kits."} + return {'code':1, 'message':"This user does not have permission to add kits.", "status":"warning"} for type in exp: if type == "password": continue @@ -486,7 +485,7 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> dict: logger.debug(kit.__dict__) ctx['database_session'].add(kit) ctx['database_session'].commit() - return {'code':0, 'message':'Kit has been added'} + return {'code':0, 'message':'Kit has been added', 'status': 'information'} def create_org_from_yaml(ctx:dict, org:dict) -> dict: """ diff --git a/src/submissions/backend/db/models/controls.py b/src/submissions/backend/db/models/controls.py index 4d99719..5da0191 100644 --- a/src/submissions/backend/db/models/controls.py +++ b/src/submissions/backend/db/models/controls.py @@ -39,9 +39,9 @@ class Control(Base): # UniqueConstraint('name', name='uq_control_name') submission_id = Column(INTEGER, ForeignKey("_submissions.id")) #: parent submission id submission = relationship("BacterialCulture", back_populates="controls", foreign_keys=[submission_id]) #: parent submission - refseq_version = Column(String(16)) - kraken2_version = Column(String(16)) - kraken2_db_version = Column(String(32)) + refseq_version = Column(String(16)) #: version of refseq used in fastq parsing + kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing + kraken2_db_version = Column(String(32)) #: folder name of kraken2 db def to_sub_dict(self) -> dict: @@ -51,17 +51,22 @@ class Control(Base): Returns: dict: output dictionary containing: Name, Type, Targets, Top Kraken results """ + # load json string into dict kraken = json.loads(self.kraken) + # calculate kraken count total to use in percentage kraken_cnt_total = sum([kraken[item]['kraken_count'] for item in kraken]) new_kraken = [] for item in kraken: + # calculate kraken percent (overwrites what's already been scraped) kraken_percent = kraken[item]['kraken_count'] / kraken_cnt_total new_kraken.append({'name': item, 'kraken_count':kraken[item]['kraken_count'], 'kraken_percent':"{0:.0%}".format(kraken_percent)}) new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True) + # set targets if self.controltype.targets == []: targets = ["None"] else: targets = self.controltype.targets + # construct output dictionary output = { "name" : self.name, "type" : self.controltype.name, @@ -72,36 +77,43 @@ class Control(Base): def convert_by_mode(self, mode:str) -> list[dict]: """ - split control object into analysis types + split control object into analysis types for controls graphs Args: control (models.Control): control to be parsed into list - mode (str): analysis type + mode (str): analysis type, 'contains', etc Returns: list[dict]: list of records """ output = [] + # load json string for mode (i.e. contains, matches, kraken2) data = json.loads(getattr(self, mode)) - # if len(data) == 0: - # data = self.create_dummy_data(mode) logger.debug(f"Length of data: {len(data)}") + # dict keys are genera of bacteria, e.g. 'Streptococcus' for genus in data: _dict = {} _dict['name'] = self.name _dict['submitted_date'] = self.submitted_date _dict['genus'] = genus + # get Target or Off-target of genus _dict['target'] = 'Target' if genus.strip("*") in self.controltype.targets else "Off-target" - + # set 'contains_hashes', etc for genus, for key in data[genus]: _dict[key] = data[genus][key] - if _dict[key] == {}: - print(self.name, mode) output.append(_dict) - # logger.debug(output) return output - def create_dummy_data(self, mode): + def create_dummy_data(self, mode:str) -> dict: + """ + Create non-zero length data to maintain entry of zero length 'contains' (depreciated) + + Args: + mode (str): analysis type, 'contains', etc + + Returns: + dict: dictionary of 'Nothing' genus + """ match mode: case "contains": data = {"Nothing": {"contains_hashes":"0/400", "contains_ratio":0.0}} diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index 33fe867..7973539 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -1,6 +1,10 @@ +from copy import deepcopy from . import Base from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT from sqlalchemy.orm import relationship +import logging + +logger = logging.getLogger(f'submissions.{__name__}') # Table containing reagenttype-kittype relationships @@ -44,7 +48,6 @@ class ReagentType(Base): kit_id = Column(INTEGER, ForeignKey("_kits.id", ondelete="SET NULL", use_alter=True, name="fk_RT_kits_id")) #: id of joined kit type kits = relationship("KitType", back_populates="reagent_types", uselist=True, foreign_keys=[kit_id]) #: kits this reagent is used in instances = relationship("Reagent", back_populates="type") #: concrete instances of this reagent type - # instances_id = Column(INTEGER, ForeignKey("_reagents.id", ondelete='SET NULL')) eol_ext = Column(Interval()) #: extension of life interval def __str__(self) -> str: @@ -76,9 +79,11 @@ class Reagent(Base): string representing this object Returns: - str: string representing this object's lot number - """ - return str(self.lot) + str: string representing this object's type and lot number + """ + lot = str(self.lot) + r_type = str(self.type) + return f"{r_type} - {lot}" def to_sub_dict(self) -> dict: """ @@ -91,8 +96,15 @@ class Reagent(Base): type = self.type.name.replace("_", " ").title() except AttributeError: type = "Unknown" + try: + place_holder = self.expiry + self.type.eol_ext + # logger.debug(f"EOL_ext for {self.lot} -- {self.expiry} + {self.type.eol_ext} = {place_holder}") + except TypeError as e: + logger.debug(f"We got a type error setting {self.lot} expiry: {e}.") + except AttributeError as e: + logger.debug(f"We got an attribute error setting {self.lot} expiry: {e}.") return { "type": type, "lot": self.lot, - "expiry": self.expiry.strftime("%Y-%m-%d") + "expiry": place_holder.strftime("%Y-%m-%d") } \ No newline at end of file diff --git a/src/submissions/backend/db/models/organizations.py b/src/submissions/backend/db/models/organizations.py index 9fd3140..75acf27 100644 --- a/src/submissions/backend/db/models/organizations.py +++ b/src/submissions/backend/db/models/organizations.py @@ -1,6 +1,6 @@ from . import Base -from sqlalchemy import Column, String, TIMESTAMP, JSON, Float, INTEGER, ForeignKey, UniqueConstraint, Table -from sqlalchemy.orm import relationship, validates +from sqlalchemy import Column, String, INTEGER, ForeignKey, Table +from sqlalchemy.orm import relationship # table containing organization/contact relationship @@ -32,7 +32,7 @@ class Organization(Base): class Contact(Base): """ - Base of Contace + Base of Contact """ __tablename__ = "_contacts" diff --git a/src/submissions/backend/db/models/samples.py b/src/submissions/backend/db/models/samples.py index cb78e1e..2197c4f 100644 --- a/src/submissions/backend/db/models/samples.py +++ b/src/submissions/backend/db/models/samples.py @@ -10,13 +10,13 @@ class WWSample(Base): __tablename__ = "_ww_samples" id = Column(INTEGER, primary_key=True) #: primary key - ww_processing_num = Column(String(64)) + ww_processing_num = Column(String(64)) #: wastewater processing number ww_sample_full_id = Column(String(64), nullable=False) rsl_number = Column(String(64)) #: rsl plate identification number rsl_plate = relationship("Wastewater", back_populates="samples") #: relationship to parent plate rsl_plate_id = Column(INTEGER, ForeignKey("_submissions.id", ondelete="SET NULL", name="fk_WWS_submission_id")) collection_date = Column(TIMESTAMP) #: Date submission received - testing_type = Column(String(64)) + testing_type = Column(String(64)) site_status = Column(String(64)) notes = Column(String(2000)) ct_n1 = Column(FLOAT(2)) @@ -40,7 +40,7 @@ class WWSample(Base): gui friendly dictionary Returns: - dict: well location and id + dict: well location and id NOTE: keys must sync with BCSample to_sub_dict below """ return { "well": self.well_number, @@ -76,7 +76,7 @@ class BCSample(Base): gui friendly dictionary Returns: - dict: well location and name (sample id, organism) + dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above """ return { "well": self.well_number, diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index 3797d8f..782b0e2 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -22,18 +22,18 @@ class BasicSubmission(Base): submitter_plate_num = Column(String(127), unique=True) #: The number given to the submission by the submitting lab submitted_date = Column(TIMESTAMP) #: Date submission received submitting_lab = relationship("Organization", back_populates="submissions") #: client org - submitting_lab_id = Column(INTEGER, ForeignKey("_organizations.id", ondelete="SET NULL", name="fk_BS_sublab_id")) + submitting_lab_id = Column(INTEGER, ForeignKey("_organizations.id", ondelete="SET NULL", name="fk_BS_sublab_id")) #: client lab id from _organizations sample_count = Column(INTEGER) #: Number of samples in the submission extraction_kit = relationship("KitType", back_populates="submissions") #: The extraction kit used extraction_kit_id = Column(INTEGER, ForeignKey("_kits.id", ondelete="SET NULL", name="fk_BS_extkit_id")) submission_type = Column(String(32)) #: submission type (should be string in D3 of excel sheet) - technician = Column(String(64)) #: initials of processing tech + technician = Column(String(64)) #: initials of processing tech(s) # Move this into custom types? reagents = relationship("Reagent", back_populates="submissions", secondary=reagents_submissions) #: relationship to reagents reagents_id = Column(String, ForeignKey("_reagents.id", ondelete="SET NULL", name="fk_BS_reagents_id")) #: id of used reagents extraction_info = Column(JSON) #: unstructured output from the extraction table logger. - run_cost = Column(FLOAT(2)) - uploaded_by = Column(String(32)) + run_cost = Column(FLOAT(2)) #: total cost of running the plate. Set from kit costs at time of creation. + uploaded_by = Column(String(32)) #: user name of person who submitted the submission to the database. __mapper_args__ = { "polymorphic_identity": "basic_submission", @@ -71,6 +71,7 @@ class BasicSubmission(Base): ext_kit = self.extraction_kit.name except AttributeError: ext_kit = None + # load scraped extraction info try: ext_info = json.loads(self.extraction_info) except TypeError: @@ -80,7 +81,8 @@ class BasicSubmission(Base): logger.debug(f"Json error in {self.rsl_plate_num}: {e}") try: reagents = [item.to_sub_dict() for item in self.reagents] - except: + except Exception as e: + logger.error(f"We got an error retrieving reagents: {e}") reagents = None try: samples = [item.to_sub_dict() for item in self.samples] diff --git a/src/submissions/frontend/__init__.py b/src/submissions/frontend/__init__.py index cf9409c..d3f1bc8 100644 --- a/src/submissions/frontend/__init__.py +++ b/src/submissions/frontend/__init__.py @@ -1,5 +1,6 @@ import json import re +from typing import Tuple from PyQt6.QtWidgets import ( QMainWindow, QLabel, QToolBar, QTabWidget, QWidget, QVBoxLayout, @@ -30,14 +31,14 @@ from backend.db import (construct_submission_info, lookup_reagent, ) from backend.db import lookup_kittype_by_name -from .functions import check_kit_integrity, insert_reagent_import -from tools import check_not_nan, create_reagent_list +from .functions import check_kit_integrity +from tools import check_not_nan from backend.excel.reports import make_report_xlsx, make_report_html import numpy from frontend.custom_widgets.sub_details import SubmissionsSheet from frontend.custom_widgets.pop_ups import AlertPop, QuestionAsker -from frontend.custom_widgets import AddReagentForm, ReportDatePicker, KitAdder, ControlsDatePicker +from frontend.custom_widgets import AddReagentForm, ReportDatePicker, KitAdder, ControlsDatePicker, ImportReagent import logging import difflib from getpass import getuser @@ -172,7 +173,6 @@ class App(QMainWindow): (?P)^samples$ | (?P^lot_.*$) """, re.VERBOSE) - # reagents = [] for item in prsr.sub: logger.debug(f"Item: {item}") # attempt to match variable name to regex group @@ -201,16 +201,16 @@ class App(QMainWindow): self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title())) # if extraction kit not available, all other values fail if not check_not_nan(prsr.sub[item]): - msg = AlertPop(message="Make sure to check your extraction kit!", status="warning") + msg = AlertPop(message="Make sure to check your extraction kit in the excel sheet!", status="warning") msg.exec() # create combobox to hold looked up kits add_widget = QComboBox() # lookup existing kits by 'submission_type' decided on by sheetparser uses = [item.__str__() for item in lookup_kittype_by_use(ctx=self.ctx, used_by=prsr.sub['submission_type'])] - if len(uses) > 0: - add_widget.addItems(uses) - else: - add_widget.addItems(['bacterial_culture']) + # if len(uses) > 0: + add_widget.addItems(uses) + # else: + # add_widget.addItems(['bacterial_culture']) self.ext_kit = prsr.sub[item] case 'submitted_date': # create label @@ -224,40 +224,10 @@ class App(QMainWindow): except: add_widget.setDate(date.today()) case 'reagent': - # create label self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title())) - # add_widget = QComboBox() - # add_widget.setEditable(True) - # # Ensure that all reagenttypes have a name that matches the items in the excel parser - # query_var = item.replace("lot_", "") - # logger.debug(f"Query for: {query_var}") - # if isinstance(prsr.sub[item], numpy.float64): - # logger.debug(f"{prsr.sub[item]['lot']} is a numpy float!") - # try: - # prsr.sub[item] = int(prsr.sub[item]['lot']) - # except ValueError: - # pass - # # query for reagents using type name from sheet and kit from sheet - # logger.debug(f"Attempting lookup of reagents by type: {query_var}") - # # below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work. - # relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=self.ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])] - # output_reg = [] - # for reagent in relevant_reagents: - # if isinstance(reagent, set): - # for thing in reagent: - # output_reg.append(thing) - # elif isinstance(reagent, str): - # output_reg.append(reagent) - # relevant_reagents = output_reg - # logger.debug(f"Relevant reagents for {prsr.sub[item]}: {relevant_reagents}") - # # if reagent in sheet is not found insert it into items - # if str(prsr.sub[item]['lot']) not in relevant_reagents and prsr.sub[item]['lot'] != 'nan': - # if check_not_nan(prsr.sub[item]['lot']): - # relevant_reagents.insert(0, str(prsr.sub[item]['lot'])) - # logger.debug(f"New relevant reagents: {relevant_reagents}") - # add_widget.addItems(relevant_reagents) - add_widget = insert_reagent_import(ctx=self.ctx, item=item, prsr=prsr) + # create reagent choice widget + add_widget = ImportReagent(ctx=self.ctx, item=item, prsr=prsr) self.reagents[item] = prsr.sub[item] case 'samples': # hold samples in 'self' until form submitted @@ -278,7 +248,7 @@ class App(QMainWindow): msg.exec() for item in kit_integrity['missing']: self.table_widget.formlayout.addWidget(QLabel(f"Lot {item.replace('_', ' ').title()}")) - add_widget = insert_reagent_import(ctx=self.ctx, item=item) + add_widget = ImportReagent(ctx=self.ctx, item=item) self.table_widget.formlayout.addWidget(add_widget) # create submission button submit_btn = QPushButton("Submit") @@ -302,7 +272,6 @@ class App(QMainWindow): logger.debug(f"Looked up reagent: {wanted_reagent}") # if reagent not found offer to add to database if wanted_reagent == None: - # dlg = AddReagentQuestion(reagent_type=reagent, reagent_lot=reagents[reagent]) r_lot = reagents[reagent] dlg = QuestionAsker(title=f"Add {r_lot}?", message=f"Couldn't find reagent type {reagent.replace('_', ' ').title().strip('Lot')}: {r_lot} in the database.\n\nWould you like to add it?") if dlg.exec(): @@ -310,10 +279,10 @@ class App(QMainWindow): expiry_date = self.reagents[reagent]['exp'] wanted_reagent = self.add_reagent(reagent_lot=r_lot, reagent_type=reagent.replace("lot_", ""), expiry=expiry_date) 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) - # logger.debug(info) # move samples into preliminary submission dict info['samples'] = self.samples info['uploaded_by'] = getuser() @@ -322,14 +291,14 @@ class App(QMainWindow): base_submission, result = construct_submission_info(ctx=self.ctx, info_dict=info) # check output message for issues match result['code']: + # code 1: ask for overwrite case 1: - # if output['code'] > 0: - # dlg = OverwriteSubQuestion(output['message'], base_submission.rsl_plate_num) dlg = QuestionAsker(title=f"Review {base_submission.rsl_plate_num}?", message=result['message']) if dlg.exec(): base_submission.reagents = [] else: return + # code 2: No RSL plate number given case 2: dlg = AlertPop(message=result['message'], status='critical') dlg.exec() @@ -386,12 +355,12 @@ class App(QMainWindow): return reagent - def extract_form_info(self, object): + def extract_form_info(self, object) -> Tuple[list, list]: """ retrieves arbitrary number of labels, values from form Args: - object (_type_): _description_ + object (_type_): the form widget Returns: _type_: _description_ @@ -399,6 +368,7 @@ class App(QMainWindow): labels = [] values = [] # grab all widgets in form + prev_item = None for item in object.layout.parentWidget().findChildren(QWidget): match item: case QLabel(): diff --git a/src/submissions/frontend/custom_widgets/__init__.py b/src/submissions/frontend/custom_widgets/__init__.py index 6f6f7e4..503ea9e 100644 --- a/src/submissions/frontend/custom_widgets/__init__.py +++ b/src/submissions/frontend/custom_widgets/__init__.py @@ -9,12 +9,15 @@ from PyQt6.QtWidgets import ( ) from PyQt6.QtCore import Qt, QDate, QSize # from PyQt6.QtGui import QFontMetrics, QAction - -from backend.db import get_all_reagenttype_names, lookup_all_sample_types, create_kit_from_yaml +from tools import check_not_nan +from backend.db import get_all_reagenttype_names, lookup_all_sample_types, create_kit_from_yaml, lookup_regent_by_type_name +from backend.excel.parser import SheetParser from jinja2 import Environment, FileSystemLoader import sys from pathlib import Path import logging +import numpy as np +from .pop_ups import AlertPop logger = logging.getLogger(f"submissions.{__name__}") @@ -45,15 +48,18 @@ class AddReagentForm(QDialog): self.buttonBox.rejected.connect(self.reject) # get lot info lot_input = QLineEdit() + lot_input.setObjectName("lot") lot_input.setText(reagent_lot) # get expiry info exp_input = QDateEdit(calendarPopup=True) + exp_input.setObjectName('expiry') if expiry == None: exp_input.setDate(QDate.currentDate()) else: exp_input.setDate(expiry) # 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)]) logger.debug(f"Trying to find index of {reagent_type}") # convert input to user friendly string? @@ -175,17 +181,18 @@ class KitAdder(QWidget): # send to kit constructor result = create_kit_from_yaml(ctx=self.ctx, exp=yml_type) # result = create_kit_from_yaml(ctx=self.ctx, exp=exp) - msg = QMessageBox() - # msg.setIcon(QMessageBox.critical) - match result['code']: - case 0: - msg.setText("Kit added") - msg.setInformativeText(result['message']) - msg.setWindowTitle("Kit added") - case 1: - msg.setText("Permission Error") - msg.setInformativeText(result['message']) - msg.setWindowTitle("Permission Error") + msg = AlertPop(message=result['message'], status=result['status']) + # match result['code']: + # case 0: + # msg = AlertPop(message=result['message'], status="information") + # # msg.setText() + # # msg.setInformativeText(result['message']) + # # msg.setWindowTitle("Kit added") + # case 1: + # msg = AlertPop(m) + # msg.setText("Permission Error") + # msg.setInformativeText(result['message']) + # msg.setWindowTitle("Permission Error") msg.exec() def extract_form_info(self, object): @@ -276,3 +283,41 @@ class ControlsDatePicker(QWidget): def sizeHint(self) -> QSize: return QSize(80,20) + + +class ImportReagent(QComboBox): + + def __init__(self, ctx:dict, item:str, prsr:SheetParser|None=None): + super().__init__() + self.setEditable(True) + # Ensure that all reagenttypes have a name that matches the items in the excel parser + query_var = item.replace("lot_", "") + logger.debug(f"Query for: {query_var}") + if prsr != None: + if isinstance(prsr.sub[item], np.float64): + logger.debug(f"{prsr.sub[item]['lot']} is a numpy float!") + try: + prsr.sub[item] = int(prsr.sub[item]['lot']) + except ValueError: + pass + # query for reagents using type name from sheet and kit from sheet + logger.debug(f"Attempting lookup of reagents by type: {query_var}") + # below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work. + relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])] + output_reg = [] + for reagent in relevant_reagents: + if isinstance(reagent, set): + for thing in reagent: + output_reg.append(thing) + elif isinstance(reagent, str): + output_reg.append(reagent) + relevant_reagents = output_reg + # if reagent in sheet is not found insert it into items + if prsr != None: + logger.debug(f"Relevant reagents for {prsr.sub[item]}: {relevant_reagents}") + if str(prsr.sub[item]['lot']) not in relevant_reagents and prsr.sub[item]['lot'] != 'nan': + if check_not_nan(prsr.sub[item]['lot']): + relevant_reagents.insert(0, str(prsr.sub[item]['lot'])) + logger.debug(f"New relevant reagents: {relevant_reagents}") + self.addItems(relevant_reagents) + \ No newline at end of file diff --git a/src/submissions/frontend/custom_widgets/sub_details.py b/src/submissions/frontend/custom_widgets/sub_details.py index 9eea304..5659347 100644 --- a/src/submissions/frontend/custom_widgets/sub_details.py +++ b/src/submissions/frontend/custom_widgets/sub_details.py @@ -164,7 +164,7 @@ class SubmissionDetails(QDialog): # get submision from db data = lookup_submission_by_id(ctx=ctx, id=id) self.base_dict = data.to_dict() - logger.debug(f"Base dict: {self.base_dict}") + # logger.debug(f"Base dict: {self.base_dict}") # don't want id del self.base_dict['id'] # convert sub objects to dicts diff --git a/src/submissions/frontend/functions.py b/src/submissions/frontend/functions.py index b9771ea..2b07142 100644 --- a/src/submissions/frontend/functions.py +++ b/src/submissions/frontend/functions.py @@ -1,24 +1,23 @@ # from ..models import * from backend.db.models import * -from backend.db import lookup_regent_by_type_name -from tools import check_not_nan -# from backend.db import lookup_kittype_by_name import logging -import numpy as np -from backend.excel.parser import SheetParser -from PyQt6.QtWidgets import ( - QMainWindow, QLabel, QToolBar, - QTabWidget, QWidget, QVBoxLayout, - QPushButton, QFileDialog, - QLineEdit, QMessageBox, QComboBox, QDateEdit, QHBoxLayout, - QSpinBox, QScrollArea -) logger = logging.getLogger(f"submissions.{__name__}") def check_kit_integrity(sub:BasicSubmission|KitType, reagenttypes:list|None=None) -> dict|None: + """ + Ensures all reagents expected in kit are listed in Submission + + Args: + sub (BasicSubmission | KitType): Object containing complete list of reagent types. + reagenttypes (list | None, optional): List to check against complete list. Defaults to None. + + Returns: + dict|None: Result object containing a message and any missing components. + """ logger.debug(type(sub)) + # What type is sub? match sub: case BasicSubmission(): ext_kit_rtypes = [reagenttype.name for reagenttype in sub.extraction_kit.reagent_types] @@ -27,10 +26,13 @@ def check_kit_integrity(sub:BasicSubmission|KitType, reagenttypes:list|None=None ext_kit_rtypes = [reagenttype.name for reagenttype in sub.reagent_types] logger.debug(f"Kit reagents: {ext_kit_rtypes}") logger.debug(f"Submission reagents: {reagenttypes}") + # check if lists are equal check = set(ext_kit_rtypes) == set(reagenttypes) logger.debug(f"Checking if reagents match kit contents: {check}") + # what reagent types are in both lists? common = list(set(ext_kit_rtypes).intersection(reagenttypes)) logger.debug(f"common reagents types: {common}") + # if lists are equal return no problem if check: result = None else: @@ -39,38 +41,39 @@ def check_kit_integrity(sub:BasicSubmission|KitType, reagenttypes:list|None=None return result -def insert_reagent_import(ctx:dict, item:str, prsr:SheetParser|None=None) -> QComboBox: - add_widget = QComboBox() - add_widget.setEditable(True) - # Ensure that all reagenttypes have a name that matches the items in the excel parser - query_var = item.replace("lot_", "") - logger.debug(f"Query for: {query_var}") - if prsr != None: - if isinstance(prsr.sub[item], np.float64): - logger.debug(f"{prsr.sub[item]['lot']} is a numpy float!") - try: - prsr.sub[item] = int(prsr.sub[item]['lot']) - except ValueError: - pass - # query for reagents using type name from sheet and kit from sheet - logger.debug(f"Attempting lookup of reagents by type: {query_var}") - # below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work. - relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])] - output_reg = [] - for reagent in relevant_reagents: - if isinstance(reagent, set): - for thing in reagent: - output_reg.append(thing) - elif isinstance(reagent, str): - output_reg.append(reagent) - relevant_reagents = output_reg - # if reagent in sheet is not found insert it into items - if prsr != None: - logger.debug(f"Relevant reagents for {prsr.sub[item]}: {relevant_reagents}") - if str(prsr.sub[item]['lot']) not in relevant_reagents and prsr.sub[item]['lot'] != 'nan': - if check_not_nan(prsr.sub[item]['lot']): - relevant_reagents.insert(0, str(prsr.sub[item]['lot'])) - logger.debug(f"New relevant reagents: {relevant_reagents}") - add_widget.addItems(relevant_reagents) - return add_widget +# Below is depreciated: +# def insert_reagent_import(ctx:dict, item:str, prsr:SheetParser|None=None) -> QComboBox: +# add_widget = QComboBox() +# add_widget.setEditable(True) +# # Ensure that all reagenttypes have a name that matches the items in the excel parser +# query_var = item.replace("lot_", "") +# logger.debug(f"Query for: {query_var}") +# if prsr != None: +# if isinstance(prsr.sub[item], np.float64): +# logger.debug(f"{prsr.sub[item]['lot']} is a numpy float!") +# try: +# prsr.sub[item] = int(prsr.sub[item]['lot']) +# except ValueError: +# pass +# # query for reagents using type name from sheet and kit from sheet +# logger.debug(f"Attempting lookup of reagents by type: {query_var}") +# # below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work. +# relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])] +# output_reg = [] +# for reagent in relevant_reagents: +# if isinstance(reagent, set): +# for thing in reagent: +# output_reg.append(thing) +# elif isinstance(reagent, str): +# output_reg.append(reagent) +# relevant_reagents = output_reg +# # if reagent in sheet is not found insert it into items +# if prsr != None: +# logger.debug(f"Relevant reagents for {prsr.sub[item]}: {relevant_reagents}") +# if str(prsr.sub[item]['lot']) not in relevant_reagents and prsr.sub[item]['lot'] != 'nan': +# if check_not_nan(prsr.sub[item]['lot']): +# relevant_reagents.insert(0, str(prsr.sub[item]['lot'])) +# logger.debug(f"New relevant reagents: {relevant_reagents}") +# add_widget.addItems(relevant_reagents) +# return add_widget