Pre- pydsubmission toForm method.

This commit is contained in:
Landon Wark
2023-10-25 14:58:41 -05:00
parent 4b1f88f1d0
commit f3a7d75c6a
11 changed files with 298 additions and 115 deletions

View File

@@ -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] Create custom store methods for submission, reagent and sample.
- [x] Make pydantic models for other things that use constructors. - [x] Make pydantic models for other things that use constructors.
- [x] Move backend.db.functions.constructor functions into Pydantic models. - [x] Move backend.db.functions.constructor functions into Pydantic models.

View File

@@ -138,7 +138,7 @@ def lookup_reagent_types(ctx:Settings,
assert reagent.type != [] assert reagent.type != []
logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}") 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"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)) result = list(set(kit_type.reagent_types).intersection(reagent.type))
logger.debug(f"Result: {result}") logger.debug(f"Result: {result}")
return result[0] return result[0]

View File

@@ -7,6 +7,7 @@ from sqlalchemy.orm import relationship, validates
from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.associationproxy import association_proxy
from datetime import date from datetime import date
import logging import logging
from tools import Settings, check_authorization
logger = logging.getLogger(f'submissions.{__name__}') logger = logging.getLogger(f'submissions.{__name__}')
@@ -100,6 +101,11 @@ class KitType(Base):
map['info'] = {} map['info'] = {}
return map return map
@check_authorization
def save(self, ctx:Settings):
ctx.database_session.add(self)
ctx.database_session.commit()
class ReagentType(Base): class ReagentType(Base):
""" """
Base of reagent type abstract Base of reagent type abstract
@@ -265,6 +271,10 @@ class Reagent(Base):
rtype = reagent_role.name rtype = reagent_role.name
except AttributeError: except AttributeError:
rtype = "Unknown" rtype = "Unknown"
try:
expiry = self.expiry.strftime("%Y-%m-%d")
except:
expiry = date.today()
return { return {
"name":self.name, "name":self.name,
"type": rtype, "type": rtype,
@@ -272,6 +282,11 @@ class Reagent(Base):
"expiry": self.expiry.strftime("%Y-%m-%d") "expiry": self.expiry.strftime("%Y-%m-%d")
} }
def save(self, ctx:Settings):
ctx.database_session.add(self)
ctx.database_session.commit()
class Discount(Base): class Discount(Base):
""" """
Relationship table for client labs for certain kits. Relationship table for client labs for certain kits.

View File

@@ -853,7 +853,6 @@ class WastewaterSample(BasicSample):
output_dict["submitter_id"] = output_dict['ww_full_sample_id'] output_dict["submitter_id"] = output_dict['ww_full_sample_id']
return output_dict return output_dict
class BacterialCultureSample(BasicSample): class BacterialCultureSample(BasicSample):
""" """
base of bacterial culture sample base of bacterial culture sample

View File

@@ -112,7 +112,8 @@ class SheetParser(object):
kit = lookup_kit_types(ctx=self.ctx, name=self.sub['extraction_kit']['value']) kit = lookup_kit_types(ctx=self.ctx, name=self.sub['extraction_kit']['value'])
allowed_reagents = [item.name for item in kit.get_reagents()] 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'])}") 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: 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] 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] expiry = df.iat[relevant[item]['expiry']['row']-1, relevant[item]['expiry']['column']-1]
except (KeyError, IndexError): 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 continue
# If the cell is blank tell the PydReagent
if check_not_nan(lot): if check_not_nan(lot):
parsed = True parsed = True
else: else:
@@ -240,7 +242,7 @@ class ReagentParser(object):
# logger.debug(f"Got lot for {item}-{name}: {lot} as {type(lot)}") # logger.debug(f"Got lot for {item}-{name}: {lot} as {type(lot)}")
lot = str(lot) lot = str(lot)
logger.debug(f"Going into pydantic: name: {name}, lot: {lot}, expiry: {expiry}, type: {item.strip()}") 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}") logger.debug(f"Returning listo: {listo}")
return listo return listo
@@ -387,7 +389,11 @@ class SampleParser(object):
# Set row in lookup table to blank values to prevent multipe lookups. # Set row in lookup table to blank values to prevent multipe lookups.
try: try:
self.lookup_table.loc[self.lookup_table['Sample #']==addition['Sample #']] = np.nan 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 pass
logger.debug(f"Output sample dict: {sample}") logger.debug(f"Output sample dict: {sample}")
logger.debug(f"Final lookup_table: \n\n {self.lookup_table}") logger.debug(f"Final lookup_table: \n\n {self.lookup_table}")

View File

@@ -2,6 +2,7 @@
Contains pydantic models and accompanying validators Contains pydantic models and accompanying validators
''' '''
import uuid import uuid
from PyQt6 import QtCore
from pydantic import BaseModel, field_validator, Field from pydantic import BaseModel, field_validator, Field
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from dateutil.parser import parse 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 backend.db.models import *
from sqlalchemy.exc import InvalidRequestError, StatementError from sqlalchemy.exc import InvalidRequestError, StatementError
from PyQt6.QtWidgets import QComboBox, QWidget, QLabel, QVBoxLayout
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -25,8 +27,9 @@ class PydReagent(BaseModel):
ctx: Settings ctx: Settings
lot: str|None lot: str|None
type: str|None type: str|None
exp: date|None expiry: date|None
name: str|None name: str|None
parsed: bool = Field(default=False)
@field_validator("type", mode='before') @field_validator("type", mode='before')
@classmethod @classmethod
@@ -61,7 +64,7 @@ class PydReagent(BaseModel):
return value.upper() return value.upper()
return value return value
@field_validator("exp", mode="before") @field_validator("expiry", mode="before")
@classmethod @classmethod
def enforce_date(cls, value): def enforce_date(cls, value):
if value != None: if value != None:
@@ -86,7 +89,7 @@ class PydReagent(BaseModel):
else: else:
return values.data['type'] return values.data['type']
def toSQL(self):# -> Tuple[Reagent, dict]: def toSQL(self) -> Tuple[Reagent, dict]:
result = None result = None
logger.debug(f"Reagent SQL constructor is looking up type: {self.type}, lot: {self.lot}") 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) 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 # NOTE: this will now be done only in the reporting phase to account for potential changes in end-of-life extensions
return reagent, result 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'): class PydSample(BaseModel, extra='allow'):
submitter_id: str submitter_id: str
@@ -127,13 +134,6 @@ class PydSample(BaseModel, extra='allow'):
return [value] return [value]
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): def toSQL(self, ctx:Settings, submission):
result = None result = None
self.__dict__.update(self.model_extra) self.__dict__.update(self.model_extra)
@@ -302,6 +302,22 @@ class PydSubmission(BaseModel, extra='allow'):
value['value'] = values.data['submission_type']['value'] value['value'] = values.data['submission_type']['value']
return 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): def toSQL(self):
code = 0 code = 0
msg = None msg = None
@@ -380,21 +396,7 @@ class PydSubmission(BaseModel, extra='allow'):
logger.debug(f"Constructed submissions message: {msg}") logger.debug(f"Constructed submissions message: {msg}")
return instance, {'code':code, 'message':msg} return instance, {'code':code, 'message':msg}
def handle_duplicate_samples(self): def toForm(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
class PydContact(BaseModel): class PydContact(BaseModel):
@@ -447,19 +449,21 @@ class PydReagentType(BaseModel):
assoc = None assoc = None
if assoc == None: if assoc == None:
assoc = KitTypeReagentTypeAssociation(kit_type=kit, reagent_type=instance, uses=self.uses, required=self.required) 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 return instance
class PydKit(BaseModel): class PydKit(BaseModel):
name: str name: str
reagent_types: List[PydReagentType]|None reagent_types: List[PydReagentType] = []
def toSQL(self, ctx): def toSQL(self, ctx):
result = dict(message=None, status='Information')
instance = lookup_kit_types(ctx=ctx, name=self.name) instance = lookup_kit_types(ctx=ctx, name=self.name)
if instance == None: if instance == None:
instance = KitType(name=self.name) instance = KitType(name=self.name)
instance.reagent_types = [item.toSQL(ctx, instance) for item in self.reagent_types] # instance.reagent_types = [item.toSQL(ctx, instance) for item in self.reagent_types]
return instance [item.toSQL(ctx, instance) for item in self.reagent_types]
return instance, result

View File

@@ -3,23 +3,22 @@ Constructs main application.
''' '''
from pprint import pformat from pprint import pformat
import sys import sys
from typing import Tuple
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QMainWindow, QToolBar, QMainWindow, QToolBar,
QTabWidget, QWidget, QVBoxLayout, QTabWidget, QWidget, QVBoxLayout,
QComboBox, QHBoxLayout, QComboBox, QHBoxLayout,
QScrollArea, QLineEdit, QDateEdit QScrollArea, QLineEdit, QDateEdit
) )
from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtCore import pyqtSignal
from PyQt6.QtGui import QAction from PyQt6.QtGui import QAction
from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebEngineWidgets import QWebEngineView
from pathlib import Path from pathlib import Path
from backend.db import ( 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 backend.validators import PydSubmission, PydReagent
from tools import check_if_app, Settings 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 import logging
from datetime import date from datetime import date
import webbrowser import webbrowser
@@ -225,7 +224,9 @@ class App(QMainWindow):
reagent = PydReagent(ctx=self.ctx, **info) reagent = PydReagent(ctx=self.ctx, **info)
# send reagent to db # send reagent to db
# store_reagent(ctx=self.ctx, reagent=reagent) # 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) self.result_reporter(result=result)
return reagent return reagent
@@ -320,7 +321,7 @@ class App(QMainWindow):
class AddSubForm(QWidget): class AddSubForm(QWidget):
def __init__(self, parent): def __init__(self, parent:QWidget):
logger.debug(f"Initializating subform...") logger.debug(f"Initializating subform...")
super(QWidget, self).__init__(parent) super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self) self.layout = QVBoxLayout(self)
@@ -337,7 +338,7 @@ class AddSubForm(QWidget):
self.tabs.addTab(self.tab2,"Controls") self.tabs.addTab(self.tab2,"Controls")
self.tabs.addTab(self.tab3, "Add Kit") self.tabs.addTab(self.tab3, "Add Kit")
# Create submission adder form # Create submission adder form
self.formwidget = SubmissionFormWidget(self) self.formwidget = SubmissionFormContainer(self)
self.formlayout = QVBoxLayout(self) self.formlayout = QVBoxLayout(self)
self.formwidget.setLayout(self.formlayout) self.formwidget.setLayout(self.formlayout)
self.formwidget.setFixedWidth(300) self.formwidget.setFixedWidth(300)
@@ -391,7 +392,7 @@ class AddSubForm(QWidget):
self.layout.addWidget(self.tabs) self.layout.addWidget(self.tabs)
self.setLayout(self.layout) self.setLayout(self.layout)
class SubmissionFormWidget(QWidget): class SubmissionFormContainer(QWidget):
import_drag = pyqtSignal(Path) import_drag = pyqtSignal(Path)
@@ -399,9 +400,7 @@ class SubmissionFormWidget(QWidget):
logger.debug(f"Setting form widget...") logger.debug(f"Setting form widget...")
super().__init__(parent) super().__init__(parent)
self.parent = parent self.parent = parent
self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer",
"qt_scrollarea_vcontainer", "submit_btn"
]
self.setAcceptDrops(True) self.setAcceptDrops(True)
def dragEnterEvent(self, event): def dragEnterEvent(self, event):
@@ -414,6 +413,10 @@ class SubmissionFormWidget(QWidget):
fname = Path([u.toLocalFile() for u in event.mimeData().urls()][0]) fname = Path([u.toLocalFile() for u in event.mimeData().urls()][0])
self.import_drag.emit(fname) self.import_drag.emit(fname)
def clear_form(self):
for item in self.findChildren(QWidget):
item.setParent(None)
def parse_form(self) -> PydSubmission: def parse_form(self) -> PydSubmission:
logger.debug(f"Hello from form parser!") logger.debug(f"Hello from form parser!")
info = {} info = {}
@@ -421,11 +424,15 @@ class SubmissionFormWidget(QWidget):
samples = self.parent.parent.samples samples = self.parent.parent.samples
logger.debug(f"Using samples: {pformat(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) if widget.objectName() not in self.ignore]
# widgets = [widget for widget in self.findChildren(QWidget)]
for widget in widgets: for widget in widgets:
logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)}") logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)}")
match widget: match widget:
case ReagentFormWidget():
reagent, _ = widget.parse_form()
reagents.append(reagent)
case ImportReagent(): 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)) reagents.append(PydReagent(ctx=self.parent.parent.ctx, **reagent))
case QLineEdit(): case QLineEdit():
info[widget.objectName()] = dict(value=widget.text()) info[widget.objectName()] = dict(value=widget.text())
@@ -435,5 +442,6 @@ class SubmissionFormWidget(QWidget):
info[widget.objectName()] = dict(value=widget.date().toPyDate()) info[widget.objectName()] = dict(value=widget.date().toPyDate())
logger.debug(f"Info: {pformat(info)}") logger.debug(f"Info: {pformat(info)}")
logger.debug(f"Reagents: {pformat(reagents)}") 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) submission = PydSubmission(ctx=self.parent.parent.ctx, filepath=self.parent.parent.current_file, reagents=reagents, samples=samples, **info)
return submission return submission

View File

@@ -20,9 +20,10 @@ from backend.db.models import SubmissionTypeKitTypeAssociation
from sqlalchemy import FLOAT, INTEGER from sqlalchemy import FLOAT, INTEGER
import logging import logging
import numpy as np import numpy as np
from .pop_ups import AlertPop from .pop_ups import AlertPop, QuestionAsker
from backend.validators import PydReagent from backend.validators import PydReagent, PydKit, PydReagentType, PydSubmission
from typing import Tuple from typing import Tuple
from pprint import pformat
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -92,7 +93,7 @@ class AddReagentForm(QDialog):
def parse_form(self): def parse_form(self):
return dict(name=self.name_input.currentText(), return dict(name=self.name_input.currentText(),
lot=self.lot_input.text(), lot=self.lot_input.text(),
exp=self.exp_input.date().toPyDate(), expiry=self.exp_input.date().toPyDate(),
type=self.type_input.currentText()) type=self.type_input.currentText())
def update_names(self): def update_names(self):
@@ -242,21 +243,22 @@ class KitAdder(QWidget):
logger.debug(f"kit info: {pformat(info)}") logger.debug(f"kit info: {pformat(info)}")
logger.debug(f"kit reagents: {pformat(reagents)}") logger.debug(f"kit reagents: {pformat(reagents)}")
info['reagent_types'] = 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)) logger.debug(pformat(info))
# send to kit constructor # 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 = AlertPop(message=result['message'], status=result['status'])
msg.exec() msg.exec()
self.__init__(self.ctx) self.__init__(self.ctx)
@@ -521,5 +523,149 @@ class FirstStrandPlateList(QDialog):
output.append(plate.currentText()) output.append(plate.currentText())
return output 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

View File

@@ -393,7 +393,6 @@ class BarcodeWindow(QDialog):
painter.drawPixmap(120, -20, image) painter.drawPixmap(120, -20, image)
painter.end() painter.end()
class SubmissionComment(QDialog): class SubmissionComment(QDialog):
""" """
a window for adding comment text to a submission a window for adding comment text to a submission

View File

@@ -38,7 +38,7 @@ from .custom_widgets import ReportDatePicker
from .custom_widgets.misc import ImportReagent, ParsedQLabel from .custom_widgets.misc import ImportReagent, ParsedQLabel
from .visualizations.control_charts import create_charts, construct_html from .visualizations.control_charts import create_charts, construct_html
from pathlib import Path 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__}") 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: except Exception as e:
return obj, dict(message= f"Problem creating pydantic model:\n\n{e}", status="critical") return obj, dict(message= f"Problem creating pydantic model:\n\n{e}", status="critical")
# destroy any widgets from previous imports # destroy any widgets from previous imports
for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget): obj.table_widget.formwidget.clear_form()
item.setParent(None)
obj.current_submission_type = pyd.submission_type['value'] obj.current_submission_type = pyd.submission_type['value']
obj.current_file = pyd.filepath obj.current_file = pyd.filepath
# Get list of fields from pydantic model. # 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}") logger.debug(f"Kit selector: {kit_widget}")
# get current kit being used # get current kit being used
obj.ext_kit = kit_widget.currentText() obj.ext_kit = kit_widget.currentText()
for item in obj.reagents: for reagent in obj.reagents:
obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':True}, item.type, title=False, label_name=f"lot_{item.type}")) # 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) # 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) # 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) obj.table_widget.formlayout.addWidget(add_widget)
logger.debug(f"Checking integrity of {obj.ext_kit}") logger.debug(f"Checking integrity of {obj.ext_kit}")
# see if there are any missing reagents # see if there are any missing reagents
if len(obj.missing_reagents) > 0: 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") 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: # for item in obj.missing_reagents:
# Add label that has parsed as False to show "MISSING" label. # # 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}")) # 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. # # Set default parameters for the empty reagent.
reagent = dict(type=item.type, lot=None, exp=date.today(), name=None) # reagent = dict(type=item.type, lot=None, expiry=date.today(), name=None)
# create and add widget # # 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=PydReagent(**reagent), extraction_kit=obj.ext_kit)
add_widget = ImportReagent(ctx=obj.ctx, reagent=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) # obj.table_widget.formlayout.addWidget(add_widget)
# Add submit button to the form. # Add submit button to the form.
submit_btn = QPushButton("Submit") submit_btn = QPushButton("Submit")
submit_btn.setObjectName("submit_btn") 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() # info, reagents = obj.table_widget.formwidget.parse_form()
submission: PydSubmission = obj.table_widget.formwidget.parse_form() submission: PydSubmission = obj.table_widget.formwidget.parse_form()
logger.debug(f"Submission: {pprint.pformat(submission)}") logger.debug(f"Submission: {pprint.pformat(submission)}")
parsed_reagents = [] # parsed_reagents = []
# compare reagents in form to reagent database # compare reagents in form to reagent database
for reagent in submission.reagents: # for reagent in submission.reagents:
# Lookup any existing reagent of this type with this lot number # # 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) # wanted_reagent = lookup_reagents(ctx=obj.ctx, lot_number=reagent.lot, reagent_type=reagent.name)
logger.debug(f"Looked up reagent: {wanted_reagent}") # logger.debug(f"Looked up reagent: {wanted_reagent}")
# if reagent not found offer to add to database # # if reagent not found offer to add to database
if wanted_reagent == None: # if wanted_reagent == None:
# r_lot = reagent[reagent] # # 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?") # 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(): # if dlg.exec():
logger.debug(f"Looking through {pprint.pformat(obj.reagents)} for reagent {reagent.name}") # logger.debug(f"Looking through {pprint.pformat(obj.reagents)} for reagent {reagent.name}")
try: # try:
picked_reagent = [item for item in obj.reagents if item.type == reagent.name][0] # picked_reagent = [item for item in obj.reagents if item.type == reagent.name][0]
except IndexError: # except IndexError:
logger.error(f"Couldn't find {reagent.name} in obj.reagents. Checking missing reagents {pprint.pformat(obj.missing_reagents)}") # 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] # 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}") # logger.debug(f"checking reagent: {reagent.name} in obj.reagents. Result: {picked_reagent}")
expiry_date = picked_reagent.exp # 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) # wanted_reagent = obj.add_reagent(reagent_lot=reagent.lot, reagent_type=reagent.name.replace("lot_", ""), expiry=expiry_date, name=picked_reagent.name)
else: # else:
# In this case we will have an empty reagent and the submission will fail kit integrity check # # In this case we will have an empty reagent and the submission will fail kit integrity check
logger.debug("Will not add reagent.") # logger.debug("Will not add reagent.")
return obj, dict(message="Failed integrity check", status="critical") # return obj, dict(message="Failed integrity check", status="critical")
# Append the PydReagent object o be added to the submission # # Append the PydReagent object o be added to the submission
parsed_reagents.append(reagent) # parsed_reagents.append(reagent)
# move samples into preliminary submission dict # # move samples into preliminary submission dict
submission.reagents = parsed_reagents # submission.reagents = parsed_reagents
# submission.uploaded_by = getuser() # submission.uploaded_by = getuser()
# construct submission object # construct submission object
# logger.debug(f"Here is the info_dict: {pprint.pformat(info)}") # 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.reagents = []
obj.missing_reagents = [] obj.missing_reagents = []
# Remove previous reagent widgets # 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(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(QPushButton)]
reagents = obj.prsr.parse_reagents(extraction_kit=extraction_kit) reagents = obj.prsr.parse_reagents(extraction_kit=extraction_kit)
logger.debug(f"Got reagents: {reagents}") logger.debug(f"Got reagents: {reagents}")
for reagent in obj.prsr.sub['reagents']: # for reagent in obj.prsr.sub['reagents']:
# create label # # create label
if reagent['parsed']: # if reagent.parsed:
obj.reagents.append(reagent['value']) # obj.reagents.append(reagent)
else: # else:
obj.missing_reagents.append(reagent['value']) # obj.missing_reagents.append(reagent)
obj.reagents = obj.prsr.sub['reagents']
logger.debug(f"Imported reagents: {obj.reagents}") logger.debug(f"Imported reagents: {obj.reagents}")
logger.debug(f"Missing reagents: {obj.missing_reagents}") logger.debug(f"Missing reagents: {obj.missing_reagents}")
return obj, None return obj, None

View File

@@ -65,6 +65,8 @@ def check_not_nan(cell_contents) -> bool:
cell_contents = np.nan cell_contents = np.nan
if cell_contents == None: if cell_contents == None:
cell_contents = np.nan cell_contents = np.nan
if str(cell_contents).lower() == "none":
cell_contents = np.nan
try: try:
if pd.isnull(cell_contents): if pd.isnull(cell_contents):
cell_contents = np.nan cell_contents = np.nan