diff --git a/TODO.md b/TODO.md index 1ce37d7..9b26cee 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ +- [ ] Make the kit verifier make more sense. +- [ ] Slim down the Import and Submit functions in main_window_functions. - [x] Create custom store methods for submission, reagent and sample. - [x] Make pydantic models for other things that use constructors. - [x] Move backend.db.functions.constructor functions into Pydantic models. diff --git a/src/submissions/backend/db/functions/lookups.py b/src/submissions/backend/db/functions/lookups.py index b178193..ff247fd 100644 --- a/src/submissions/backend/db/functions/lookups.py +++ b/src/submissions/backend/db/functions/lookups.py @@ -138,7 +138,7 @@ def lookup_reagent_types(ctx:Settings, assert reagent.type != [] logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}") logger.debug(f"Kit reagent types: {kit_type.reagent_types}") - logger.debug(f"Reagent reagent types: {reagent._sa_instance_state}") + # logger.debug(f"Reagent reagent types: {reagent._sa_instance_state}") result = list(set(kit_type.reagent_types).intersection(reagent.type)) logger.debug(f"Result: {result}") return result[0] diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index 55e0214..01c9465 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import relationship, validates from sqlalchemy.ext.associationproxy import association_proxy from datetime import date import logging +from tools import Settings, check_authorization logger = logging.getLogger(f'submissions.{__name__}') @@ -100,6 +101,11 @@ class KitType(Base): map['info'] = {} return map + @check_authorization + def save(self, ctx:Settings): + ctx.database_session.add(self) + ctx.database_session.commit() + class ReagentType(Base): """ Base of reagent type abstract @@ -265,12 +271,21 @@ class Reagent(Base): rtype = reagent_role.name except AttributeError: rtype = "Unknown" + try: + expiry = self.expiry.strftime("%Y-%m-%d") + except: + expiry = date.today() return { "name":self.name, "type": rtype, "lot": self.lot, "expiry": self.expiry.strftime("%Y-%m-%d") } + + def save(self, ctx:Settings): + ctx.database_session.add(self) + ctx.database_session.commit() + class Discount(Base): """ diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index cb7e0e7..1b7a555 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -853,7 +853,6 @@ class WastewaterSample(BasicSample): output_dict["submitter_id"] = output_dict['ww_full_sample_id'] return output_dict - class BacterialCultureSample(BasicSample): """ base of bacterial culture sample diff --git a/src/submissions/backend/excel/parser.py b/src/submissions/backend/excel/parser.py index 1a108d1..99ce76a 100644 --- a/src/submissions/backend/excel/parser.py +++ b/src/submissions/backend/excel/parser.py @@ -112,7 +112,8 @@ class SheetParser(object): kit = lookup_kit_types(ctx=self.ctx, name=self.sub['extraction_kit']['value']) allowed_reagents = [item.name for item in kit.get_reagents()] logger.debug(f"List of reagents for comparison with allowed_reagents: {pprint.pformat(self.sub['reagents'])}") - self.sub['reagents'] = [reagent for reagent in self.sub['reagents'] if reagent['value'].type in allowed_reagents] + # self.sub['reagents'] = [reagent for reagent in self.sub['reagents'] if reagent['value'].type in allowed_reagents] + self.sub['reagents'] = [reagent for reagent in self.sub['reagents'] if reagent.type in allowed_reagents] def to_pydantic(self) -> PydSubmission: """ @@ -231,8 +232,9 @@ class ReagentParser(object): lot = df.iat[relevant[item]['lot']['row']-1, relevant[item]['lot']['column']-1] expiry = df.iat[relevant[item]['expiry']['row']-1, relevant[item]['expiry']['column']-1] except (KeyError, IndexError): - listo.append(dict(value=PydReagent(ctx=self.ctx, type=item.strip(), lot=None, exp=None, name=None), parsed=False)) + listo.append(PydReagent(ctx=self.ctx, type=item.strip(), lot=None, exp=None, name=None, parsed=False)) continue + # If the cell is blank tell the PydReagent if check_not_nan(lot): parsed = True else: @@ -240,7 +242,7 @@ class ReagentParser(object): # logger.debug(f"Got lot for {item}-{name}: {lot} as {type(lot)}") lot = str(lot) logger.debug(f"Going into pydantic: name: {name}, lot: {lot}, expiry: {expiry}, type: {item.strip()}") - listo.append(dict(value=PydReagent(ctx=self.ctx, type=item.strip(), lot=lot, exp=expiry, name=name), parsed=parsed)) + listo.append(PydReagent(ctx=self.ctx, type=item.strip(), lot=lot, expiry=expiry, name=name, parsed=parsed)) logger.debug(f"Returning listo: {listo}") return listo @@ -387,7 +389,11 @@ class SampleParser(object): # Set row in lookup table to blank values to prevent multipe lookups. try: self.lookup_table.loc[self.lookup_table['Sample #']==addition['Sample #']] = np.nan - except ValueError: + except (ValueError, KeyError): + pass + try: + self.lookup_table.loc[self.lookup_table['Well']==addition['Well']] = np.nan + except (ValueError, KeyError): pass logger.debug(f"Output sample dict: {sample}") logger.debug(f"Final lookup_table: \n\n {self.lookup_table}") diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index a6b37ab..0a16c8a 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -2,6 +2,7 @@ Contains pydantic models and accompanying validators ''' import uuid +from PyQt6 import QtCore from pydantic import BaseModel, field_validator, Field from datetime import date, datetime, timedelta from dateutil.parser import parse @@ -18,6 +19,7 @@ from backend.db.functions import (lookup_submissions, lookup_reagent_types, look ) from backend.db.models import * from sqlalchemy.exc import InvalidRequestError, StatementError +from PyQt6.QtWidgets import QComboBox, QWidget, QLabel, QVBoxLayout logger = logging.getLogger(f"submissions.{__name__}") @@ -25,8 +27,9 @@ class PydReagent(BaseModel): ctx: Settings lot: str|None type: str|None - exp: date|None + expiry: date|None name: str|None + parsed: bool = Field(default=False) @field_validator("type", mode='before') @classmethod @@ -61,7 +64,7 @@ class PydReagent(BaseModel): return value.upper() return value - @field_validator("exp", mode="before") + @field_validator("expiry", mode="before") @classmethod def enforce_date(cls, value): if value != None: @@ -86,7 +89,7 @@ class PydReagent(BaseModel): else: return values.data['type'] - def toSQL(self):# -> Tuple[Reagent, dict]: + def toSQL(self) -> Tuple[Reagent, dict]: result = None logger.debug(f"Reagent SQL constructor is looking up type: {self.type}, lot: {self.lot}") reagent = lookup_reagents(ctx=self.ctx, lot_number=self.lot) @@ -113,6 +116,10 @@ class PydReagent(BaseModel): # NOTE: this will now be done only in the reporting phase to account for potential changes in end-of-life extensions return reagent, result + def toForm(self, parent:QWidget, extraction_kit:str) -> QComboBox: + from frontend.custom_widgets.misc import ReagentFormWidget + return ReagentFormWidget(parent=parent, reagent=self, extraction_kit=extraction_kit) + class PydSample(BaseModel, extra='allow'): submitter_id: str @@ -127,13 +134,6 @@ class PydSample(BaseModel, extra='allow'): return [value] return value - # @field_validator(column) - # @classmethod - # def column_int_to_list(cls, value): - # if isinstance(value, int): - # return [value] - # return value - def toSQL(self, ctx:Settings, submission): result = None self.__dict__.update(self.model_extra) @@ -302,6 +302,22 @@ class PydSubmission(BaseModel, extra='allow'): value['value'] = values.data['submission_type']['value'] return value + def handle_duplicate_samples(self): + submitter_ids = list(set([sample.submitter_id for sample in self.samples])) + output = [] + for id in submitter_ids: + relevants = [item for item in self.samples if item.submitter_id==id] + if len(relevants) <= 1: + output += relevants + else: + rows = [item.row[0] for item in relevants] + columns = [item.column[0] for item in relevants] + dummy = relevants[0] + dummy.row = rows + dummy.column = columns + output.append(dummy) + self.samples = output + def toSQL(self): code = 0 msg = None @@ -380,21 +396,7 @@ class PydSubmission(BaseModel, extra='allow'): logger.debug(f"Constructed submissions message: {msg}") return instance, {'code':code, 'message':msg} - def handle_duplicate_samples(self): - submitter_ids = list(set([sample.submitter_id for sample in self.samples])) - output = [] - for id in submitter_ids: - relevants = [item for item in self.samples if item.submitter_id==id] - if len(relevants) <= 1: - output += relevants - else: - rows = [item.row[0] for item in relevants] - columns = [item.column[0] for item in relevants] - dummy = relevants[0] - dummy.row = rows - dummy.column = columns - output.append(dummy) - self.samples = output + def toForm(self): class PydContact(BaseModel): @@ -447,19 +449,21 @@ class PydReagentType(BaseModel): assoc = None if assoc == None: assoc = KitTypeReagentTypeAssociation(kit_type=kit, reagent_type=instance, uses=self.uses, required=self.required) - kit.kit_reagenttype_associations.append(assoc) + # kit.kit_reagenttype_associations.append(assoc) return instance class PydKit(BaseModel): name: str - reagent_types: List[PydReagentType]|None + reagent_types: List[PydReagentType] = [] def toSQL(self, ctx): + result = dict(message=None, status='Information') instance = lookup_kit_types(ctx=ctx, name=self.name) if instance == None: instance = KitType(name=self.name) - instance.reagent_types = [item.toSQL(ctx, instance) for item in self.reagent_types] - return instance + # instance.reagent_types = [item.toSQL(ctx, instance) for item in self.reagent_types] + [item.toSQL(ctx, instance) for item in self.reagent_types] + return instance, result diff --git a/src/submissions/frontend/__init__.py b/src/submissions/frontend/__init__.py index c1e2176..6ccf3a9 100644 --- a/src/submissions/frontend/__init__.py +++ b/src/submissions/frontend/__init__.py @@ -3,23 +3,22 @@ Constructs main application. ''' from pprint import pformat import sys -from typing import Tuple from PyQt6.QtWidgets import ( QMainWindow, QToolBar, QTabWidget, QWidget, QVBoxLayout, QComboBox, QHBoxLayout, QScrollArea, QLineEdit, QDateEdit ) -from PyQt6.QtCore import Qt, pyqtSignal +from PyQt6.QtCore import pyqtSignal from PyQt6.QtGui import QAction from PyQt6.QtWebEngineWidgets import QWebEngineView from pathlib import Path from backend.db import ( - store_object, lookup_control_types, lookup_modes, #construct_reagent + lookup_control_types, lookup_modes ) from backend.validators import PydSubmission, PydReagent from tools import check_if_app, Settings -from frontend.custom_widgets import SubmissionsSheet, AlertPop, AddReagentForm, KitAdder, ControlsDatePicker, ImportReagent +from frontend.custom_widgets import SubmissionsSheet, AlertPop, AddReagentForm, KitAdder, ControlsDatePicker, ImportReagent, ReagentFormWidget import logging from datetime import date import webbrowser @@ -225,7 +224,9 @@ class App(QMainWindow): reagent = PydReagent(ctx=self.ctx, **info) # send reagent to db # store_reagent(ctx=self.ctx, reagent=reagent) - result = store_object(ctx=self.ctx, object=reagent.toSQL()[0]) + sqlobj, result = reagent.toSQL() + sqlobj.save(ctx=self.ctx) + # result = store_object(ctx=self.ctx, object=reagent.toSQL()[0]) self.result_reporter(result=result) return reagent @@ -320,7 +321,7 @@ class App(QMainWindow): class AddSubForm(QWidget): - def __init__(self, parent): + def __init__(self, parent:QWidget): logger.debug(f"Initializating subform...") super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) @@ -337,7 +338,7 @@ class AddSubForm(QWidget): self.tabs.addTab(self.tab2,"Controls") self.tabs.addTab(self.tab3, "Add Kit") # Create submission adder form - self.formwidget = SubmissionFormWidget(self) + self.formwidget = SubmissionFormContainer(self) self.formlayout = QVBoxLayout(self) self.formwidget.setLayout(self.formlayout) self.formwidget.setFixedWidth(300) @@ -391,7 +392,7 @@ class AddSubForm(QWidget): self.layout.addWidget(self.tabs) self.setLayout(self.layout) -class SubmissionFormWidget(QWidget): +class SubmissionFormContainer(QWidget): import_drag = pyqtSignal(Path) @@ -399,9 +400,7 @@ class SubmissionFormWidget(QWidget): logger.debug(f"Setting form widget...") super().__init__(parent) self.parent = parent - self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer", - "qt_scrollarea_vcontainer", "submit_btn" - ] + self.setAcceptDrops(True) def dragEnterEvent(self, event): @@ -414,6 +413,10 @@ class SubmissionFormWidget(QWidget): fname = Path([u.toLocalFile() for u in event.mimeData().urls()][0]) self.import_drag.emit(fname) + def clear_form(self): + for item in self.findChildren(QWidget): + item.setParent(None) + def parse_form(self) -> PydSubmission: logger.debug(f"Hello from form parser!") info = {} @@ -421,11 +424,15 @@ class SubmissionFormWidget(QWidget): samples = self.parent.parent.samples logger.debug(f"Using samples: {pformat(samples)}") widgets = [widget for widget in self.findChildren(QWidget) if widget.objectName() not in self.ignore] + # widgets = [widget for widget in self.findChildren(QWidget)] for widget in widgets: logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)}") match widget: + case ReagentFormWidget(): + reagent, _ = widget.parse_form() + reagents.append(reagent) case ImportReagent(): - reagent = dict(name=widget.objectName().replace("lot_", ""), lot=widget.currentText(), type=None, exp=None) + reagent = dict(name=widget.objectName().replace("lot_", ""), lot=widget.currentText(), type=None, expiry=None) reagents.append(PydReagent(ctx=self.parent.parent.ctx, **reagent)) case QLineEdit(): info[widget.objectName()] = dict(value=widget.text()) @@ -435,5 +442,6 @@ class SubmissionFormWidget(QWidget): info[widget.objectName()] = dict(value=widget.date().toPyDate()) logger.debug(f"Info: {pformat(info)}") logger.debug(f"Reagents: {pformat(reagents)}") + # sys.exit("Hi Landon. Check the reagents! frontend.__init__ line 442") submission = PydSubmission(ctx=self.parent.parent.ctx, filepath=self.parent.parent.current_file, reagents=reagents, samples=samples, **info) return submission diff --git a/src/submissions/frontend/custom_widgets/misc.py b/src/submissions/frontend/custom_widgets/misc.py index 899f296..9a2bb3b 100644 --- a/src/submissions/frontend/custom_widgets/misc.py +++ b/src/submissions/frontend/custom_widgets/misc.py @@ -20,9 +20,10 @@ from backend.db.models import SubmissionTypeKitTypeAssociation from sqlalchemy import FLOAT, INTEGER import logging import numpy as np -from .pop_ups import AlertPop -from backend.validators import PydReagent +from .pop_ups import AlertPop, QuestionAsker +from backend.validators import PydReagent, PydKit, PydReagentType, PydSubmission from typing import Tuple +from pprint import pformat logger = logging.getLogger(f"submissions.{__name__}") @@ -92,7 +93,7 @@ class AddReagentForm(QDialog): def parse_form(self): return dict(name=self.name_input.currentText(), lot=self.lot_input.text(), - exp=self.exp_input.date().toPyDate(), + expiry=self.exp_input.date().toPyDate(), type=self.type_input.currentText()) def update_names(self): @@ -242,21 +243,22 @@ class KitAdder(QWidget): logger.debug(f"kit info: {pformat(info)}") logger.debug(f"kit reagents: {pformat(reagents)}") info['reagent_types'] = reagents - # for reagent in reagents: - # new_dict = {} - # for k,v in reagent.items(): - # if "_" in k: - # key, sub_key = k.split("_") - # if key not in new_dict.keys(): - # new_dict[key] = {} - # logger.debug(f"Adding key {key}, {sub_key} and value {v} to {new_dict}") - # new_dict[key][sub_key] = v - # else: - # new_dict[k] = v - # info['reagent_types'].append(new_dict) logger.debug(pformat(info)) # send to kit constructor - result = construct_kit_from_yaml(ctx=self.ctx, kit_dict=info) + kit = PydKit(name=info['kit_name']) + for reagent in info['reagent_types']: + uses = { + info['used_for']: + {'sheet':reagent['sheet'], + 'name':reagent['name'], + 'lot':reagent['lot'], + 'expiry':reagent['expiry'] + }} + 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) + sqlobj.save(ctx=self.ctx) msg = AlertPop(message=result['message'], status=result['status']) msg.exec() self.__init__(self.ctx) @@ -521,5 +523,149 @@ class FirstStrandPlateList(QDialog): output.append(plate.currentText()) return output +class ReagentFormWidget(QWidget): + + def __init__(self, parent:QWidget, reagent:PydReagent, extraction_kit:str): + super().__init__() + self.setParent(parent) + logger.debug(f"Reagent form widget parent is: {self.parent()}") + logger.debug(f"It's great grandparent is {self.parent().parent.parent} which has a method [add_reagent]: {hasattr(self.parent().parent.parent, 'add_reagent')}") + self.reagent = reagent + self.extraction_kit = extraction_kit + self.ctx = reagent.ctx + layout = QVBoxLayout() + self.label = self.ReagentParsedLabel(reagent=reagent) + layout.addWidget(self.label) + self.lot = self.ReagentLot(reagent=reagent, extraction_kit=extraction_kit) + layout.addWidget(self.lot) + self.setLayout(layout) + self.setObjectName(reagent.name) + self.missing = not reagent.parsed + + def parse_form(self) -> Tuple[PydReagent, dict]: + lot = self.lot.currentText() + # type = self.label.text().replace("_label") + wanted_reagent = lookup_reagents(ctx=self.ctx, lot_number=lot, reagent_type=self.reagent.type) + 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(): + # logger.debug(f"Looking through {pformat(self.parent.reagents)} for reagent {reagent.name}") + # try: + # picked_reagent = [item for item in obj.reagents if item.type == reagent.name][0] + # except IndexError: + # logger.error(f"Couldn't find {reagent.name} in obj.reagents. Checking missing reagents {pprint.pformat(obj.missing_reagents)}") + # picked_reagent = [item for item in obj.missing_reagents if item.type == reagent.name][0] + # logger.debug(f"checking reagent: {reagent.name} in obj.reagents. Result: {picked_reagent}") + # expiry_date = picked_reagent.expiry + wanted_reagent = self.parent().parent.parent.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 + logger.debug("Will not add reagent.") + return None, dict(message="Failed integrity check", status="critical") + else: + rt = lookup_reagent_types(ctx=self.ctx, kit_type=self.extraction_kit, reagent=wanted_reagent) + return PydReagent(ctx=self.ctx, name=wanted_reagent.name, lot=wanted_reagent.lot, type=rt.name, expiry=wanted_reagent.expiry, parsed=not self.missing), None + class ReagentParsedLabel(QLabel): + + def __init__(self, reagent:PydReagent): + super().__init__() + try: + check = reagent.parsed + except: + return + self.setObjectName(f"{reagent.type}_label") + if check: + self.setText(f"Parsed {reagent.type}") + else: + self.setText(f"MISSING {reagent.type}") + + class ReagentLot(QComboBox): + + def __init__(self, reagent, extraction_kit:str) -> None: + super().__init__() + self.ctx = reagent.ctx + self.setEditable(True) + if reagent.parsed: + pass + logger.debug(f"Attempting lookup of reagents by type: {reagent.type}") + # 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) + relevant_reagents = [item.__str__() for item in lookup] + output_reg = [] + for rel_reagent in relevant_reagents: + # extract strings from any sets. + if isinstance(rel_reagent, set): + for thing in rel_reagent: + output_reg.append(thing) + elif isinstance(rel_reagent, str): + output_reg.append(rel_reagent) + relevant_reagents = output_reg + # if reagent in sheet is not found insert it into the front of relevant reagents so it shows + logger.debug(f"Relevant reagents for {reagent.lot}: {relevant_reagents}") + if str(reagent.lot) not in relevant_reagents: + if check_not_nan(reagent.lot): + relevant_reagents.insert(0, str(reagent.lot)) + else: + # TODO: look up the last used reagent of this type in the database + looked_up_rt = lookup_reagenttype_kittype_association(ctx=self.ctx, reagent_type=reagent.type, kit_type=extraction_kit) + looked_up_reg = lookup_reagents(ctx=self.ctx, lot_number=looked_up_rt.last_used) + logger.debug(f"Because there was no reagent listed for {reagent}, we will insert the last lot used: {looked_up_reg}") + if looked_up_reg != None: + relevant_reagents.remove(str(looked_up_reg.lot)) + relevant_reagents.insert(0, str(looked_up_reg.lot)) + else: + if len(relevant_reagents) > 1: + logger.debug(f"Found {reagent.lot} in relevant reagents: {relevant_reagents}. Moving to front of list.") + idx = relevant_reagents.index(str(reagent.lot)) + logger.debug(f"The index we got for {reagent.lot} in {relevant_reagents} was {idx}") + moved_reag = relevant_reagents.pop(idx) + relevant_reagents.insert(0, moved_reag) + else: + logger.debug(f"Found {reagent.lot} in relevant reagents: {relevant_reagents}. But no need to move due to short list.") + logger.debug(f"New relevant reagents: {relevant_reagents}") + self.setObjectName(f"lot_{reagent.type}") + self.addItems(relevant_reagents) + +class SubmissionFormWidget(QWidget): + + def __init__(self, parent: QWidget) -> None: + super().__init__(parent) + self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer", + "qt_scrollarea_vcontainer", "submit_btn" + ] + + def clear_form(self): + for item in self.findChildren(QWidget): + item.setParent(None) + + def parse_form(self) -> PydSubmission: + logger.debug(f"Hello from form parser!") + info = {} + reagents = [] + samples = self.parent.parent.samples + logger.debug(f"Using samples: {pformat(samples)}") + widgets = [widget for widget in self.findChildren(QWidget) if widget.objectName() not in self.ignore] + # widgets = [widget for widget in self.findChildren(QWidget)] + for widget in widgets: + logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)}") + match widget: + case ReagentFormWidget(): + reagent, _ = widget.parse_form() + reagents.append(reagent) + case ImportReagent(): + reagent = dict(name=widget.objectName().replace("lot_", ""), lot=widget.currentText(), type=None, expiry=None) + reagents.append(PydReagent(ctx=self.parent.parent.ctx, **reagent)) + case QLineEdit(): + info[widget.objectName()] = dict(value=widget.text()) + case QComboBox(): + info[widget.objectName()] = dict(value=widget.currentText()) + case QDateEdit(): + info[widget.objectName()] = dict(value=widget.date().toPyDate()) + logger.debug(f"Info: {pformat(info)}") + logger.debug(f"Reagents: {pformat(reagents)}") + # sys.exit("Hi Landon. Check the reagents! frontend.__init__ line 442") + submission = PydSubmission(ctx=self.parent.parent.ctx, filepath=self.parent.parent.current_file, reagents=reagents, samples=samples, **info) + return submission diff --git a/src/submissions/frontend/custom_widgets/sub_details.py b/src/submissions/frontend/custom_widgets/sub_details.py index 1ff59e1..32332f1 100644 --- a/src/submissions/frontend/custom_widgets/sub_details.py +++ b/src/submissions/frontend/custom_widgets/sub_details.py @@ -393,7 +393,6 @@ class BarcodeWindow(QDialog): painter.drawPixmap(120, -20, image) painter.end() - class SubmissionComment(QDialog): """ a window for adding comment text to a submission diff --git a/src/submissions/frontend/main_window_functions.py b/src/submissions/frontend/main_window_functions.py index bb1ae23..ac2399f 100644 --- a/src/submissions/frontend/main_window_functions.py +++ b/src/submissions/frontend/main_window_functions.py @@ -38,7 +38,7 @@ from .custom_widgets import ReportDatePicker from .custom_widgets.misc import ImportReagent, ParsedQLabel from .visualizations.control_charts import create_charts, construct_html from pathlib import Path -from frontend.custom_widgets.misc import FirstStrandSalvage, FirstStrandPlateList +from frontend.custom_widgets.misc import FirstStrandSalvage, FirstStrandPlateList, ReagentFormWidget logger = logging.getLogger(f"submissions.{__name__}") @@ -78,8 +78,7 @@ def import_submission_function(obj:QMainWindow, fname:Path|None=None) -> Tuple[Q except Exception as e: return obj, dict(message= f"Problem creating pydantic model:\n\n{e}", status="critical") # destroy any widgets from previous imports - for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget): - item.setParent(None) + obj.table_widget.formwidget.clear_form() obj.current_submission_type = pyd.submission_type['value'] obj.current_file = pyd.filepath # Get list of fields from pydantic model. @@ -216,24 +215,26 @@ def kit_integrity_completion_function(obj:QMainWindow) -> Tuple[QMainWindow, dic logger.debug(f"Kit selector: {kit_widget}") # get current kit being used obj.ext_kit = kit_widget.currentText() - for item in obj.reagents: - obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':True}, item.type, title=False, label_name=f"lot_{item.type}")) - reagent = dict(type=item.type, lot=item.lot, exp=item.exp, name=item.name) - add_widget = ImportReagent(ctx=obj.ctx, reagent=reagent, extraction_kit=obj.ext_kit) + for reagent in obj.reagents: + # obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':True}, item.type, title=False, label_name=f"lot_{item.type}")) + # reagent = dict(type=item.type, lot=item.lot, expiry=item.expiry, name=item.name) + # add_widget = ImportReagent(ctx=obj.ctx, reagent=reagent, extraction_kit=obj.ext_kit) + # obj.table_widget.formlayout.addWidget(add_widget) + add_widget = ReagentFormWidget(parent=obj.table_widget.formwidget, reagent=reagent, extraction_kit=obj.ext_kit) obj.table_widget.formlayout.addWidget(add_widget) logger.debug(f"Checking integrity of {obj.ext_kit}") # see if there are any missing reagents if len(obj.missing_reagents) > 0: result = dict(message=f"The submission you are importing is missing some reagents expected by the kit.\n\nIt looks like you are missing: {[item.type.upper() for item in obj.missing_reagents]}\n\nAlternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.\n\nPlease make sure you check the lots carefully!", status="Warning") - for item in obj.missing_reagents: - # Add label that has parsed as False to show "MISSING" label. - obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':False}, item.type, title=False, label_name=f"missing_{item.type}")) - # Set default parameters for the empty reagent. - reagent = dict(type=item.type, lot=None, exp=date.today(), name=None) - # create and add widget - # add_widget = ImportReagent(ctx=obj.ctx, reagent=PydReagent(**reagent), extraction_kit=obj.ext_kit) - add_widget = ImportReagent(ctx=obj.ctx, reagent=reagent, extraction_kit=obj.ext_kit) - obj.table_widget.formlayout.addWidget(add_widget) + # for item in obj.missing_reagents: + # # Add label that has parsed as False to show "MISSING" label. + # obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':False}, item.type, title=False, label_name=f"missing_{item.type}")) + # # Set default parameters for the empty reagent. + # reagent = dict(type=item.type, lot=None, expiry=date.today(), name=None) + # # create and add widget + # # add_widget = ImportReagent(ctx=obj.ctx, reagent=PydReagent(**reagent), extraction_kit=obj.ext_kit) + # add_widget = ImportReagent(ctx=obj.ctx, reagent=reagent, extraction_kit=obj.ext_kit) + # obj.table_widget.formlayout.addWidget(add_widget) # Add submit button to the form. submit_btn = QPushButton("Submit") submit_btn.setObjectName("submit_btn") @@ -264,34 +265,34 @@ def submit_new_sample_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]: # info, reagents = obj.table_widget.formwidget.parse_form() submission: PydSubmission = obj.table_widget.formwidget.parse_form() logger.debug(f"Submission: {pprint.pformat(submission)}") - parsed_reagents = [] + # parsed_reagents = [] # compare reagents in form to reagent database - for reagent in submission.reagents: - # Lookup any existing reagent of this type with this lot number - wanted_reagent = lookup_reagents(ctx=obj.ctx, lot_number=reagent.lot, reagent_type=reagent.name) - logger.debug(f"Looked up reagent: {wanted_reagent}") - # if reagent not found offer to add to database - if wanted_reagent == None: - # r_lot = reagent[reagent] - dlg = QuestionAsker(title=f"Add {reagent.lot}?", message=f"Couldn't find reagent type {reagent.name.strip('Lot')}: {reagent.lot} in the database.\n\nWould you like to add it?") - if dlg.exec(): - logger.debug(f"Looking through {pprint.pformat(obj.reagents)} for reagent {reagent.name}") - try: - picked_reagent = [item for item in obj.reagents if item.type == reagent.name][0] - except IndexError: - logger.error(f"Couldn't find {reagent.name} in obj.reagents. Checking missing reagents {pprint.pformat(obj.missing_reagents)}") - picked_reagent = [item for item in obj.missing_reagents if item.type == reagent.name][0] - logger.debug(f"checking reagent: {reagent.name} in obj.reagents. Result: {picked_reagent}") - expiry_date = picked_reagent.exp - wanted_reagent = obj.add_reagent(reagent_lot=reagent.lot, reagent_type=reagent.name.replace("lot_", ""), expiry=expiry_date, name=picked_reagent.name) - else: - # In this case we will have an empty reagent and the submission will fail kit integrity check - logger.debug("Will not add reagent.") - return obj, dict(message="Failed integrity check", status="critical") - # Append the PydReagent object o be added to the submission - parsed_reagents.append(reagent) - # move samples into preliminary submission dict - submission.reagents = parsed_reagents + # for reagent in submission.reagents: + # # Lookup any existing reagent of this type with this lot number + # wanted_reagent = lookup_reagents(ctx=obj.ctx, lot_number=reagent.lot, reagent_type=reagent.name) + # logger.debug(f"Looked up reagent: {wanted_reagent}") + # # if reagent not found offer to add to database + # if wanted_reagent == None: + # # r_lot = reagent[reagent] + # dlg = QuestionAsker(title=f"Add {reagent.lot}?", message=f"Couldn't find reagent type {reagent.name.strip('Lot')}: {reagent.lot} in the database.\n\nWould you like to add it?") + # if dlg.exec(): + # logger.debug(f"Looking through {pprint.pformat(obj.reagents)} for reagent {reagent.name}") + # try: + # picked_reagent = [item for item in obj.reagents if item.type == reagent.name][0] + # except IndexError: + # logger.error(f"Couldn't find {reagent.name} in obj.reagents. Checking missing reagents {pprint.pformat(obj.missing_reagents)}") + # picked_reagent = [item for item in obj.missing_reagents if item.type == reagent.name][0] + # logger.debug(f"checking reagent: {reagent.name} in obj.reagents. Result: {picked_reagent}") + # expiry_date = picked_reagent.expiry + # wanted_reagent = obj.add_reagent(reagent_lot=reagent.lot, reagent_type=reagent.name.replace("lot_", ""), expiry=expiry_date, name=picked_reagent.name) + # else: + # # In this case we will have an empty reagent and the submission will fail kit integrity check + # logger.debug("Will not add reagent.") + # return obj, dict(message="Failed integrity check", status="critical") + # # Append the PydReagent object o be added to the submission + # parsed_reagents.append(reagent) + # # move samples into preliminary submission dict + # submission.reagents = parsed_reagents # submission.uploaded_by = getuser() # construct submission object # logger.debug(f"Here is the info_dict: {pprint.pformat(info)}") @@ -1020,16 +1021,17 @@ def scrape_reagents(obj:QMainWindow, extraction_kit:str) -> Tuple[QMainWindow, d obj.reagents = [] obj.missing_reagents = [] # Remove previous reagent widgets - [item.setParent(None) for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget) if item.objectName().startswith("lot_") or item.objectName().startswith("missing_")] - [item.setParent(None) for item in obj.table_widget.formlayout.parentWidget().findChildren(QPushButton)] + # [item.setParent(None) for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget) if item.objectName().startswith("lot_") or item.objectName().startswith("missing_")] + # [item.setParent(None) for item in obj.table_widget.formlayout.parentWidget().findChildren(QPushButton)] reagents = obj.prsr.parse_reagents(extraction_kit=extraction_kit) logger.debug(f"Got reagents: {reagents}") - for reagent in obj.prsr.sub['reagents']: - # create label - if reagent['parsed']: - obj.reagents.append(reagent['value']) - else: - obj.missing_reagents.append(reagent['value']) + # for reagent in obj.prsr.sub['reagents']: + # # create label + # if reagent.parsed: + # obj.reagents.append(reagent) + # else: + # obj.missing_reagents.append(reagent) + obj.reagents = obj.prsr.sub['reagents'] logger.debug(f"Imported reagents: {obj.reagents}") logger.debug(f"Missing reagents: {obj.missing_reagents}") return obj, None diff --git a/src/submissions/tools/__init__.py b/src/submissions/tools/__init__.py index b7991a0..874049e 100644 --- a/src/submissions/tools/__init__.py +++ b/src/submissions/tools/__init__.py @@ -65,6 +65,8 @@ def check_not_nan(cell_contents) -> bool: cell_contents = np.nan if cell_contents == None: cell_contents = np.nan + if str(cell_contents).lower() == "none": + cell_contents = np.nan try: if pd.isnull(cell_contents): cell_contents = np.nan