moved frontend function check_not_nan to tools

This commit is contained in:
Landon Wark
2023-02-15 09:29:03 -06:00
parent a9ce9514fc
commit 85dad791ec
15 changed files with 275 additions and 156 deletions

View File

@@ -55,8 +55,8 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
# are written from script.py.mako # are written from script.py.mako
# output_encoding = utf-8 # output_encoding = utf-8
sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db ; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\DB_backups\submissions-20230130.db sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\DB_backups\submissions-20230213.db
[post_write_hooks] [post_write_hooks]

View File

@@ -7,6 +7,7 @@ from alembic import context
import sys import sys
from pathlib import Path from pathlib import Path
sys.path.append(Path(__file__).parents[1].joinpath("src").resolve().__str__()) sys.path.append(Path(__file__).parents[1].joinpath("src").resolve().__str__())
sys.path.append(Path(__file__).parents[1].joinpath("src", "submissions").resolve().__str__())
print(sys.path) print(sys.path)
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides

View File

@@ -1,4 +1,4 @@
# __init__.py # __init__.py
# Version of the realpython-reader package # Version of the realpython-reader package
__version__ = "1.2.3" __version__ = "1.3.0"

View File

@@ -15,6 +15,7 @@ import json
# from dateutil.relativedelta import relativedelta # from dateutil.relativedelta import relativedelta
from getpass import getuser from getpass import getuser
import numpy as np import numpy as np
from tools import check_not_nan
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -101,8 +102,13 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
# convert submission type into model name # convert submission type into model name
query = info_dict['submission_type'].replace(" ", "") query = info_dict['submission_type'].replace(" ", "")
# check database for existing object # check database for existing object
if info_dict["rsl_plate_num"] == 'nan' or info_dict["rsl_plate_num"] == None or not check_not_nan(info_dict["rsl_plate_num"]):
code = 2
instance = None
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() instance = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.rsl_plate_num==info_dict['rsl_plate_num']).first()
msg = "This submission already exists.\nWould you like to overwrite?"
# get model based on submission type converted above # get model based on submission type converted above
logger.debug(f"Looking at models for submission type: {query}") logger.debug(f"Looking at models for submission type: {query}")
model = getattr(models, query) model = getattr(models, query)
@@ -113,6 +119,10 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
instance = model() instance = model()
logger.debug(f"Submission doesn't exist yet, creating new instance: {instance}") logger.debug(f"Submission doesn't exist yet, creating new instance: {instance}")
msg = None msg = None
code =0
else:
code = 1
msg = "This submission already exists.\nWould you like to overwrite?"
for item in info_dict: for item in info_dict:
logger.debug(f"Setting {item} to {info_dict[item]}") logger.debug(f"Setting {item} to {info_dict[item]}")
# set fields based on keys in dictionary # set fields based on keys in dictionary
@@ -151,9 +161,14 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
except (TypeError, AttributeError): except (TypeError, AttributeError):
logger.debug(f"Looks like that kit doesn't have cost breakdown yet, using full plate cost.") 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 instance.run_cost = instance.extraction_kit.cost_per_run
logger.debug(f"Constructed instance: {instance.to_string()}") # 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:
logger.debug(f"Something went wrong constructing instance {info_dict['rsl_plate_num']}: {e}")
logger.debug(msg) logger.debug(msg)
return instance, {'message':msg} return instance, {'code':code, 'message':msg}
def construct_reagent(ctx:dict, info_dict:dict) -> models.Reagent: def construct_reagent(ctx:dict, info_dict:dict) -> models.Reagent:
@@ -244,7 +259,7 @@ def lookup_kittype_by_use(ctx:dict, used_by:str) -> list[models.KitType]:
Returns: Returns:
list[models.KitType]: list of kittypes that have that sample type in their uses list[models.KitType]: list of kittypes that have that sample type in their uses
""" """
return ctx['database_session'].query(models.KitType).filter(models.KitType.used_for.contains(used_by)) return ctx['database_session'].query(models.KitType).filter(models.KitType.used_for.contains(used_by)).all()
def lookup_kittype_by_name(ctx:dict, name:str) -> models.KitType: def lookup_kittype_by_name(ctx:dict, name:str) -> models.KitType:
""" """
@@ -278,7 +293,7 @@ def lookup_regent_by_type_name(ctx:dict, type_name:str) -> list[models.Reagent]:
def lookup_regent_by_type_name_and_kit_name(ctx:dict, type_name:str, kit_name:str) -> list[models.Reagent]: def lookup_regent_by_type_name_and_kit_name(ctx:dict, type_name:str, kit_name:str) -> list[models.Reagent]:
""" """
Lookup reagents by their type name and kits they belong to (Broken) Lookup reagents by their type name and kits they belong to (Broken... maybe cursed, I'm not sure.)
Args: Args:
ctx (dict): settings pass by gui ctx (dict): settings pass by gui
@@ -292,10 +307,6 @@ def lookup_regent_by_type_name_and_kit_name(ctx:dict, type_name:str, kit_name:st
# Hang on, this is going to be a long one. # Hang on, this is going to be a long one.
# by_type = ctx['database_session'].query(models.Reagent).join(models.Reagent.type, aliased=True).filter(models.ReagentType.name.endswith(type_name)).all() # by_type = ctx['database_session'].query(models.Reagent).join(models.Reagent.type, aliased=True).filter(models.ReagentType.name.endswith(type_name)).all()
rt_types = ctx['database_session'].query(models.ReagentType).filter(models.ReagentType.name.endswith(type_name)) rt_types = ctx['database_session'].query(models.ReagentType).filter(models.ReagentType.name.endswith(type_name))
# add filter for kit name... which I can not get to work. # add filter for kit name... which I can not get to work.
# add_in = by_type.join(models.ReagentType.kits).filter(models.KitType.name==kit_name) # add_in = by_type.join(models.ReagentType.kits).filter(models.KitType.name==kit_name)
try: try:
@@ -315,7 +326,7 @@ def lookup_regent_by_type_name_and_kit_name(ctx:dict, type_name:str, kit_name:st
return output return output
def lookup_all_submissions_by_type(ctx:dict, type:str|None=None) -> list[models.BasicSubmission]: def lookup_all_submissions_by_type(ctx:dict, sub_type:str|None=None) -> list[models.BasicSubmission]:
""" """
Get all submissions, filtering by type if given Get all submissions, filtering by type if given
@@ -326,10 +337,10 @@ def lookup_all_submissions_by_type(ctx:dict, type:str|None=None) -> list[models.
Returns: Returns:
_type_: list of retrieved submissions _type_: list of retrieved submissions
""" """
if type == None: if sub_type == None:
subs = ctx['database_session'].query(models.BasicSubmission).all() subs = ctx['database_session'].query(models.BasicSubmission).all()
else: else:
subs = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.submission_type==type.lower().replace(" ", "_")).all() subs = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.submission_type==sub_type.lower().replace(" ", "_")).all()
return subs return subs
def lookup_all_orgs(ctx:dict) -> list[models.Organization]: def lookup_all_orgs(ctx:dict) -> list[models.Organization]:
@@ -358,7 +369,7 @@ def lookup_org_by_name(ctx:dict, name:str|None) -> models.Organization:
logger.debug(f"Querying organization: {name}") logger.debug(f"Querying organization: {name}")
return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first() return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first()
def submissions_to_df(ctx:dict, type:str|None=None) -> pd.DataFrame: def submissions_to_df(ctx:dict, sub_type:str|None=None) -> pd.DataFrame:
""" """
Convert submissions looked up by type to dataframe Convert submissions looked up by type to dataframe
@@ -369,9 +380,9 @@ def submissions_to_df(ctx:dict, type:str|None=None) -> pd.DataFrame:
Returns: Returns:
pd.DataFrame: dataframe constructed from retrieved submissions pd.DataFrame: dataframe constructed from retrieved submissions
""" """
logger.debug(f"Type: {type}") logger.debug(f"Type: {sub_type}")
# pass to lookup function # pass to lookup function
subs = [item.to_dict() for item in lookup_all_submissions_by_type(ctx=ctx, type=type)] subs = [item.to_dict() for item in lookup_all_submissions_by_type(ctx=ctx, sub_type=sub_type)]
df = pd.DataFrame.from_records(subs) df = pd.DataFrame.from_records(subs)
# logger.debug(f"Pre: {df['Technician']}") # logger.debug(f"Pre: {df['Technician']}")
try: try:
@@ -435,13 +446,16 @@ def get_all_Control_Types_names(ctx:dict) -> list[models.ControlType]:
return conTypes return conTypes
def create_kit_from_yaml(ctx:dict, exp:dict) -> None: def create_kit_from_yaml(ctx:dict, exp:dict) -> dict:
""" """
Create and store a new kit in the database based on a .yml file Create and store a new kit in the database based on a .yml file
Args: Args:
ctx (dict): Context dictionary passed down from frontend ctx (dict): Context dictionary passed down from frontend
exp (dict): Experiment dictionary created from yaml file exp (dict): Experiment dictionary created from yaml file
Returns:
dict: a dictionary containing results of db addition
""" """
try: try:
power_users = ctx['power_users'] power_users = ctx['power_users']
@@ -474,6 +488,44 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> None:
ctx['database_session'].commit() ctx['database_session'].commit()
return {'code':0, 'message':'Kit has been added'} return {'code':0, 'message':'Kit has been added'}
def create_org_from_yaml(ctx:dict, org:dict) -> dict:
"""
Create and store a new organization based on a .yml file
Args:
ctx (dict): Context dictionary passed down from frontend
org (dict): Dictionary containing organization info.
Returns:
dict: dictionary containing results of db addition
"""
try:
power_users = ctx['power_users']
except KeyError:
logger.debug("This user does not have permission to add kits.")
return {'code':1,'message':"This user does not have permission to add organizations."}
logger.debug(f"Adding organization for user: {getuser()}")
if getuser() not in power_users:
logger.debug(f"{getuser()} does not have permission to add kits.")
return {'code':1, 'message':"This user does not have permission to add organizations."}
for client in org:
cli_org = models.Organization(name=client.replace(" ", "_").lower(), cost_centre=org[client]['cost centre'])
for contact in org[client]['contacts']:
cont_name = list(contact.keys())[0]
look_up = ctx['database_session'].query(models.Contact).filter(models.Contact.name==cont_name).first()
if look_up == None:
cli_cont = models.Contact(name=cont_name, phone=contact[cont_name]['phone'], email=contact[cont_name]['email'], organization=[cli_org])
else:
cli_cont = look_up
cli_cont.organization.append(cli_org)
# cli_org.contacts.append(cli_cont)
# cli_org.contact_ids.append_foreign_key(cli_cont.id)
ctx['database_session'].add(cli_cont)
logger.debug(cli_cont.__dict__)
ctx['database_session'].add(cli_org)
ctx["database_session"].commit()
return {"code":0, "message":"Organization has been added."}
def lookup_all_sample_types(ctx:dict) -> list[str]: def lookup_all_sample_types(ctx:dict) -> list[str]:
""" """
@@ -511,7 +563,7 @@ def get_all_available_modes(ctx:dict) -> list[str]:
def get_all_controls_by_type(ctx:dict, con_type:str, start_date:date|None=None, end_date:date|None=None) -> list: def get_all_controls_by_type(ctx:dict, con_type:str, start_date:date|None=None, end_date:date|None=None) -> list[models.Control]:
""" """
Returns a list of control objects that are instances of the input controltype. Returns a list of control objects that are instances of the input controltype.
@@ -571,4 +623,4 @@ def lookup_submission_by_rsl_num(ctx:dict, rsl_num:str):
def lookup_submissions_using_reagent(ctx:dict, reagent:models.Reagent) -> list[models.BasicSubmission]: def lookup_submissions_using_reagent(ctx:dict, reagent:models.Reagent) -> list[models.BasicSubmission]:
return ctx['database_session'].query(models.BasicSubmission).join(reagents_submissions).filter(reagents_submissions.c.reagent_id==reagent.id) return ctx['database_session'].query(models.BasicSubmission).join(reagents_submissions).filter(reagents_submissions.c.reagent_id==reagent.id).all()

View File

@@ -1,21 +1,21 @@
from ..models import * # from ..models import *
import logging # import logging
logger = logging.getLogger(f"submissions.{__name__}") # logger = logging.getLogger(f"submissions.{__name__}")
def check_kit_integrity(sub:BasicSubmission): # def check_kit_integrity(sub:BasicSubmission):
ext_kit_rtypes = [reagenttype.name for reagenttype in sub.extraction_kit.reagent_types] # ext_kit_rtypes = [reagenttype.name for reagenttype in sub.extraction_kit.reagent_types]
logger.debug(f"Kit reagents: {ext_kit_rtypes}") # logger.debug(f"Kit reagents: {ext_kit_rtypes}")
reagenttypes = [reagent.type.name for reagent in sub.reagents] # reagenttypes = [reagent.type.name for reagent in sub.reagents]
logger.debug(f"Submission reagents: {reagenttypes}") # logger.debug(f"Submission reagents: {reagenttypes}")
check = set(ext_kit_rtypes) == set(reagenttypes) # check = set(ext_kit_rtypes) == set(reagenttypes)
logger.debug(f"Checking if reagents match kit contents: {check}") # logger.debug(f"Checking if reagents match kit contents: {check}")
common = list(set(ext_kit_rtypes).intersection(reagenttypes)) # common = list(set(ext_kit_rtypes).intersection(reagenttypes))
logger.debug(f"common reagents types: {common}") # logger.debug(f"common reagents types: {common}")
if check: # if check:
result = None # result = None
else: # else:
result = {'message' : f"Couldn't verify reagents match listed kit components.\n\nIt looks like you are missing: {[x.upper for x in ext_kit_rtypes if x not in common]}\n\nAlternatively, you may have set the wrong extraction kit."} # result = {'message' : f"Couldn't verify reagents match listed kit components.\n\nIt looks like you are missing: {[x.upper for x in ext_kit_rtypes if x not in common]}\n\nAlternatively, you may have set the wrong extraction kit."}
return result # return result

View File

@@ -36,7 +36,7 @@ class Contact(Base):
""" """
__tablename__ = "_contacts" __tablename__ = "_contacts"
id = id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
name = Column(String(64)) #: contact name name = Column(String(64)) #: contact name
email = Column(String(64)) #: contact email email = Column(String(64)) #: contact email
phone = Column(String(32)) #: contact phone number phone = Column(String(32)) #: contact phone number

View File

@@ -1,5 +1,5 @@
from . import Base from . import Base
from sqlalchemy import Column, String, TIMESTAMP, text, JSON, INTEGER, ForeignKey, FLOAT, BOOLEAN from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, FLOAT, BOOLEAN
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship

View File

@@ -17,7 +17,7 @@ class BasicSubmission(Base):
__tablename__ = "_submissions" __tablename__ = "_submissions"
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
rsl_plate_num = Column(String(32), unique=True) #: RSL name (e.g. RSL-22-0012) rsl_plate_num = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012)
submitter_plate_num = Column(String(127), unique=True) #: The number given to the submission by the submitting lab 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 submitted_date = Column(TIMESTAMP) #: Date submission received
submitting_lab = relationship("Organization", back_populates="submissions") #: client org submitting_lab = relationship("Organization", back_populates="submissions") #: client org

View File

@@ -1,13 +1,13 @@
import pandas as pd import pandas as pd
from pathlib import Path from pathlib import Path
from backend.db.models.samples import WWSample, BCSample from backend.db.models import WWSample, BCSample
import logging import logging
from collections import OrderedDict from collections import OrderedDict
import re import re
import numpy as np import numpy as np
from datetime import date from datetime import date
import uuid import uuid
from frontend.functions import check_not_nan from tools import check_not_nan
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -15,7 +15,7 @@ class SheetParser(object):
""" """
object to pull and contain data from excel file object to pull and contain data from excel file
""" """
def __init__(self, filepath:Path|None = None, **kwargs) -> None: def __init__(self, filepath:Path|None = None, **kwargs):
""" """
Args: Args:
filepath (Path | None, optional): file path to excel sheet. Defaults to None. filepath (Path | None, optional): file path to excel sheet. Defaults to None.
@@ -77,12 +77,12 @@ class SheetParser(object):
""" """
submission_info = self.xl.parse(sheet_name=sheet_name, dtype=object) submission_info = self.xl.parse(sheet_name=sheet_name, dtype=object)
self.sub['submitter_plate_num'] = submission_info.iloc[0][1] #if pd.isnull(submission_info.iloc[0][1]) else string_formatter(submission_info.iloc[0][1]) self.sub['submitter_plate_num'] = submission_info.iloc[0][1]
self.sub['rsl_plate_num'] = submission_info.iloc[10][1] #if pd.isnull(submission_info.iloc[10][1]) else string_formatter(submission_info.iloc[10][1]) self.sub['rsl_plate_num'] = submission_info.iloc[10][1]
self.sub['submitted_date'] = submission_info.iloc[1][1] #if pd.isnull(submission_info.iloc[1][1]) else submission_info.iloc[1][1].date()#.strftime("%Y-%m-%d") self.sub['submitted_date'] = submission_info.iloc[1][1]
self.sub['submitting_lab'] = submission_info.iloc[0][3] #if pd.isnull(submission_info.iloc[0][3]) else string_formatter(submission_info.iloc[0][3]) self.sub['submitting_lab'] = submission_info.iloc[0][3]
self.sub['sample_count'] = submission_info.iloc[2][3] #if pd.isnull(submission_info.iloc[2][3]) else string_formatter(submission_info.iloc[2][3]) self.sub['sample_count'] = submission_info.iloc[2][3]
self.sub['extraction_kit'] = submission_info.iloc[3][3] #if #pd.isnull(submission_info.iloc[3][3]) else string_formatter(submission_info.iloc[3][3]) self.sub['extraction_kit'] = submission_info.iloc[3][3]
return submission_info return submission_info
@@ -93,6 +93,12 @@ class SheetParser(object):
""" """
def _parse_reagents(df:pd.DataFrame) -> None: def _parse_reagents(df:pd.DataFrame) -> None:
"""
Pulls reagents from the bacterial sub-dataframe
Args:
df (pd.DataFrame): input sub dataframe
"""
for ii, row in df.iterrows(): for ii, row in df.iterrows():
# skip positive control # skip positive control
if ii == 11: if ii == 11:
@@ -119,16 +125,15 @@ class SheetParser(object):
# self.sub[f"lot_{reagent_type}"] = output_var # self.sub[f"lot_{reagent_type}"] = output_var
# update 2023-02-10 to above allowing generation of expiry date in adding reagent to db. # update 2023-02-10 to above allowing generation of expiry date in adding reagent to db.
logger.debug(f"Expiry date for imported reagent: {row[3]}") logger.debug(f"Expiry date for imported reagent: {row[3]}")
try: # try:
check = not np.isnan(row[3]) # check = not np.isnan(row[3])
except TypeError: # except TypeError:
check = True # check = True
if check: if check_not_nan(row[3]):
expiry = row[3].date() expiry = row[3].date()
else: else:
expiry = date.today() expiry = date.today()
self.sub[f"lot_{reagent_type}"] = {'lot':output_var, 'exp':expiry} self.sub[f"lot_{reagent_type}"] = {'lot':output_var, 'exp':expiry}
submission_info = self._parse_generic("Sample List") submission_info = self._parse_generic("Sample List")
# iloc is [row][column] and the first row is set as header row so -2 # iloc is [row][column] and the first row is set as header row so -2
tech = str(submission_info.iloc[11][1]) tech = str(submission_info.iloc[11][1])
@@ -161,32 +166,38 @@ class SheetParser(object):
self.sub['samples'] = sample_parse() self.sub['samples'] = sample_parse()
def _parse_wastewater(self) -> None: def _parse_wastewater(self) -> None:
""" """
pulls info specific to wastewater sample type pulls info specific to wastewater sample type
""" """
def _parse_reagents(df:pd.DataFrame) -> None: def _parse_reagents(df:pd.DataFrame) -> None:
logger.debug(df) """
Pulls reagents from the bacterial sub-dataframe
Args:
df (pd.DataFrame): input sub dataframe
"""
# logger.debug(df)
for ii, row in df.iterrows(): for ii, row in df.iterrows():
try: # try:
check = not np.isnan(row[5]) # check = not np.isnan(row[5])
except TypeError: # except TypeError:
check = True # check = True
if not isinstance(row[5], float) and check: if not isinstance(row[5], float) and check_not_nan(row[5]):
# must be prefixed with 'lot_' to be recognized by gui # must be prefixed with 'lot_' to be recognized by gui
# regex below will remove 80% from 80% ethanol in the Wastewater kit.
output_key = re.sub(r"\d{1,3}%", "", row[0].lower().strip().replace(' ', '_')) output_key = re.sub(r"\d{1,3}%", "", row[0].lower().strip().replace(' ', '_'))
try: try:
output_var = row[5].upper() output_var = row[5].upper()
except AttributeError: except AttributeError:
logger.debug(f"Couldn't upperize {row[2]}, must be a number") logger.debug(f"Couldn't upperize {row[5]}, must be a number")
output_var = row[5] output_var = row[5]
self.sub[f"lot_{output_key}"] = output_var if check_not_nan(row[7]):
expiry = row[7].date()
# submission_info = self.xl.parse("WW Submissions (ENTER HERE)") else:
expiry = date.today()
self.sub[f"lot_{output_key}"] = {'lot':output_var, 'exp':expiry}
submission_info = self._parse_generic("WW Submissions (ENTER HERE)") submission_info = self._parse_generic("WW Submissions (ENTER HERE)")
enrichment_info = self.xl.parse("Enrichment Worksheet", dtype=object) enrichment_info = self.xl.parse("Enrichment Worksheet", dtype=object)
enr_reagent_range = enrichment_info.iloc[0:4, 9:20] enr_reagent_range = enrichment_info.iloc[0:4, 9:20]
@@ -214,18 +225,12 @@ class SheetParser(object):
# self.sub['lot_pre_mix_2'] = qprc_info.iloc[2][14] #if pd.isnull(qprc_info.iloc[2][14]) else string_formatter(qprc_info.iloc[2][14]) # self.sub['lot_pre_mix_2'] = qprc_info.iloc[2][14] #if pd.isnull(qprc_info.iloc[2][14]) else string_formatter(qprc_info.iloc[2][14])
# self.sub['lot_positive_control'] = qprc_info.iloc[3][14] #if pd.isnull(qprc_info.iloc[3][14]) else string_formatter(qprc_info.iloc[3][14]) # self.sub['lot_positive_control'] = qprc_info.iloc[3][14] #if pd.isnull(qprc_info.iloc[3][14]) else string_formatter(qprc_info.iloc[3][14])
# self.sub['lot_ddh2o'] = qprc_info.iloc[4][14] #if pd.isnull(qprc_info.iloc[4][14]) else string_formatter(qprc_info.iloc[4][14]) # self.sub['lot_ddh2o'] = qprc_info.iloc[4][14] #if pd.isnull(qprc_info.iloc[4][14]) else string_formatter(qprc_info.iloc[4][14])
# gt individual sample info # get individual sample info
sample_parser = SampleParser(submission_info.iloc[16:40]) sample_parser = SampleParser(submission_info.iloc[16:40])
sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples") sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples")
self.sub['samples'] = sample_parse() self.sub['samples'] = sample_parse()
class SampleParser(object): class SampleParser(object):
""" """
object to pull data for samples in excel sheet and construct individual sample objects object to pull data for samples in excel sheet and construct individual sample objects
@@ -242,6 +247,7 @@ class SampleParser(object):
Returns: Returns:
list[BCSample]: list of sample objects list[BCSample]: list of sample objects
""" """
# logger.debug(f"Samples: {self.samples}")
new_list = [] new_list = []
for sample in self.samples: for sample in self.samples:
new = BCSample() new = BCSample()
@@ -297,13 +303,3 @@ class SampleParser(object):
new.well_number = sample['Unnamed: 1'] new.well_number = sample['Unnamed: 1']
new_list.append(new) new_list.append(new)
return new_list return new_list
# def string_formatter(input):
# logger.debug(f"{input} : {type(input)}")
# match input:
# case int() | float() | np.float64:
# return "{:0.0f}".format(input)
# case _:
# return input

View File

@@ -10,6 +10,7 @@ from pathlib import Path
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
# set path of templates depending on pyinstaller/raw python
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
loader_path = Path(sys._MEIPASS).joinpath("files", "templates") loader_path = Path(sys._MEIPASS).joinpath("files", "templates")
else: else:

View File

@@ -19,21 +19,23 @@ from xhtml2pdf import pisa
# import plotly.express as px # import plotly.express as px
import yaml import yaml
import pprint import pprint
import numpy as np
from backend.excel.parser import SheetParser from backend.excel.parser import SheetParser
from backend.excel.reports import convert_control_by_mode, convert_data_list_to_df from backend.excel.reports import convert_control_by_mode, convert_data_list_to_df
from backend.db import (construct_submission_info, lookup_reagent, from backend.db import (construct_submission_info, lookup_reagent,
construct_reagent, store_reagent, store_submission, lookup_kittype_by_use, construct_reagent, store_reagent, store_submission, lookup_kittype_by_use,
lookup_regent_by_type_name, lookup_all_orgs, lookup_submissions_by_date_range, lookup_regent_by_type_name, lookup_all_orgs, lookup_submissions_by_date_range,
get_all_Control_Types_names, create_kit_from_yaml, get_all_available_modes, get_all_controls_by_type, get_all_Control_Types_names, create_kit_from_yaml, get_all_available_modes, get_all_controls_by_type,
get_control_subtypes, lookup_all_submissions_by_type, get_all_controls, lookup_submission_by_rsl_num get_control_subtypes, lookup_all_submissions_by_type, get_all_controls, lookup_submission_by_rsl_num,
create_org_from_yaml
) )
from .functions import check_kit_integrity, check_not_nan
from .functions import check_kit_integrity
from tools import check_not_nan
from backend.excel.reports import make_report_xlsx, make_report_html from backend.excel.reports import make_report_xlsx, make_report_html
import numpy import numpy
from frontend.custom_widgets.sub_details import SubmissionsSheet from frontend.custom_widgets.sub_details import SubmissionsSheet
from frontend.custom_widgets.pop_ups import AddReagentQuestion, OverwriteSubQuestion, AlertPop 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
import logging import logging
import difflib import difflib
@@ -96,6 +98,7 @@ class App(QMainWindow):
self.addToolBar(toolbar) self.addToolBar(toolbar)
toolbar.addAction(self.addReagentAction) toolbar.addAction(self.addReagentAction)
toolbar.addAction(self.addKitAction) toolbar.addAction(self.addKitAction)
toolbar.addAction(self.addOrgAction)
def _createActions(self): def _createActions(self):
""" """
@@ -105,6 +108,7 @@ class App(QMainWindow):
self.addReagentAction = QAction("Add Reagent", self) self.addReagentAction = QAction("Add Reagent", self)
self.generateReportAction = QAction("Make Report", self) self.generateReportAction = QAction("Make Report", self)
self.addKitAction = QAction("Add Kit", self) self.addKitAction = QAction("Add Kit", self)
self.addOrgAction = QAction("Add Org", self)
self.joinControlsAction = QAction("Link Controls") self.joinControlsAction = QAction("Link Controls")
self.joinExtractionAction = QAction("Link Ext Logs") self.joinExtractionAction = QAction("Link Ext Logs")
@@ -117,6 +121,7 @@ class App(QMainWindow):
self.addReagentAction.triggered.connect(self.add_reagent) self.addReagentAction.triggered.connect(self.add_reagent)
self.generateReportAction.triggered.connect(self.generateReport) self.generateReportAction.triggered.connect(self.generateReport)
self.addKitAction.triggered.connect(self.add_kit) self.addKitAction.triggered.connect(self.add_kit)
self.addOrgAction.triggered.connect(self.add_org)
self.table_widget.control_typer.currentIndexChanged.connect(self._controls_getter) self.table_widget.control_typer.currentIndexChanged.connect(self._controls_getter)
self.table_widget.mode_typer.currentIndexChanged.connect(self._controls_getter) self.table_widget.mode_typer.currentIndexChanged.connect(self._controls_getter)
self.table_widget.datepicker.start_date.dateChanged.connect(self._controls_getter) self.table_widget.datepicker.start_date.dateChanged.connect(self._controls_getter)
@@ -183,7 +188,7 @@ class App(QMainWindow):
# create label # create label
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title())) self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
# if extraction kit not available, all other values fail # if extraction kit not available, all other values fail
if np.isnan(prsr.sub[item]): 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!", status="warning")
msg.exec() msg.exec()
# create combobox to hold looked up kits # create combobox to hold looked up kits
@@ -231,7 +236,7 @@ class App(QMainWindow):
elif isinstance(reagent, str): elif isinstance(reagent, str):
output_reg.append(reagent) output_reg.append(reagent)
relevant_reagents = output_reg relevant_reagents = output_reg
logger.debug(f"Relevant reagents: {relevant_reagents}") logger.debug(f"Relevant reagents for {prsr.sub[item]}: {relevant_reagents}")
# if reagent in sheet is not found insert it into items # 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 str(prsr.sub[item]['lot']) not in relevant_reagents and prsr.sub[item]['lot'] != 'nan':
if check_not_nan(prsr.sub[item]['lot']): if check_not_nan(prsr.sub[item]['lot']):
@@ -272,11 +277,13 @@ class App(QMainWindow):
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:
dlg = AddReagentQuestion(reagent_type=reagent, reagent_lot=reagents[reagent]) # 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(): if dlg.exec():
logger.debug(f"checking reagent: {reagent} in self.reagents. Result: {self.reagents[reagent]}") logger.debug(f"checking reagent: {reagent} in self.reagents. Result: {self.reagents[reagent]}")
expiry_date = self.reagents[reagent]['exp'] expiry_date = self.reagents[reagent]['exp']
wanted_reagent = self.add_reagent(reagent_lot=reagents[reagent], reagent_type=reagent.replace("lot_", ""), expiry=expiry_date) wanted_reagent = self.add_reagent(reagent_lot=r_lot, reagent_type=reagent.replace("lot_", ""), expiry=expiry_date)
else: else:
logger.debug("Will not add reagent.") logger.debug("Will not add reagent.")
if wanted_reagent != None: if wanted_reagent != None:
@@ -287,14 +294,23 @@ class App(QMainWindow):
info['uploaded_by'] = getuser() info['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)}")
base_submission, output = construct_submission_info(ctx=self.ctx, info_dict=info) base_submission, result = construct_submission_info(ctx=self.ctx, info_dict=info)
# check output message for issues # check output message for issues
if output['message'] != None: match result['code']:
dlg = OverwriteSubQuestion(output['message'], base_submission.rsl_plate_num) case 1:
if dlg.exec(): # if output['code'] > 0:
base_submission.reagents = [] # dlg = OverwriteSubQuestion(output['message'], base_submission.rsl_plate_num)
else: dlg = QuestionAsker(title=f"Review {base_submission.rsl_plate_num}?", message=result['message'])
if dlg.exec():
base_submission.reagents = []
else:
return
case 2:
dlg = AlertPop(message=result['message'], status='critical')
dlg.exec()
return return
case _:
pass
# add reagents to submission object # add reagents to submission object
for reagent in parsed_reagents: for reagent in parsed_reagents:
base_submission.reagents.append(reagent) base_submission.reagents.append(reagent)
@@ -447,20 +463,59 @@ class App(QMainWindow):
return return
# send to kit creator function # send to kit creator function
result = create_kit_from_yaml(ctx=self.ctx, exp=exp) result = create_kit_from_yaml(ctx=self.ctx, exp=exp)
msg = QMessageBox() # msg = QMessageBox()
# msg.setIcon(QMessageBox.critical) # msg.setIcon(QMessageBox.critical)
match result['code']: match result['code']:
case 0: case 0:
msg.setText("Kit added") msg = AlertPop(message=result['message'], status='info')
msg.setInformativeText(result['message']) # msg.setText("Kit added")
msg.setWindowTitle("Kit added") # msg.setInformativeText(result['message'])
# msg.setWindowTitle("Kit added")
case 1: case 1:
msg.setText("Permission Error") msg = AlertPop(message=result['message'], status='critical')
msg.setInformativeText(result['message']) # msg.setText("Permission Error")
msg.setWindowTitle("Permission Error") # msg.setInformativeText(result['message'])
# msg.setWindowTitle("Permission Error")
msg.exec() msg.exec()
def add_org(self):
"""
Constructs new kit from yaml and adds to DB.
"""
# setup file dialog to find yaml flie
home_dir = str(Path(self.ctx["directory_path"]))
fname = Path(QFileDialog.getOpenFileName(self, 'Open file', home_dir, filter = "yml(*.yml)")[0])
assert fname.exists()
# read yaml file
try:
with open(fname.__str__(), "r") as stream:
try:
org = yaml.load(stream, Loader=yaml.Loader)
except yaml.YAMLError as exc:
logger.error(f'Error reading yaml file {fname}: {exc}')
return {}
except PermissionError:
return
# send to kit creator function
result = create_org_from_yaml(ctx=self.ctx, org=org)
# msg = QMessageBox()
# msg.setIcon(QMessageBox.critical)
match result['code']:
case 0:
msg = AlertPop(message=result['message'], status='information')
# msg.setText("Organization added")
# msg.setInformativeText(result['message'])
# msg.setWindowTitle("Kit added")
case 1:
msg = AlertPop(message=result['message'], status='critical')
# msg.setText("Permission Error")
# msg.setInformativeText(result['message'])
# msg.setWindowTitle("Permission Error")
msg.exec()
def _controls_getter(self): def _controls_getter(self):
""" """

View File

@@ -22,50 +22,67 @@ loader = FileSystemLoader(loader_path)
env = Environment(loader=loader) env = Environment(loader=loader)
class AddReagentQuestion(QDialog): # class AddReagentQuestion(QDialog):
# """
# dialog to ask about adding a new reagne to db
# """
# def __init__(self, reagent_type:str, reagent_lot:str) -> QDialog:
# super().__init__()
# self.setWindowTitle(f"Add {reagent_lot}?")
# QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No
# self.buttonBox = QDialogButtonBox(QBtn)
# self.buttonBox.accepted.connect(self.accept)
# self.buttonBox.rejected.connect(self.reject)
# self.layout = QVBoxLayout()
# message = QLabel(f"Couldn't find reagent type {reagent_type.replace('_', ' ').title().strip('Lot')}: {reagent_lot} in the database.\n\nWould you like to add it?")
# self.layout.addWidget(message)
# self.layout.addWidget(self.buttonBox)
# self.setLayout(self.layout)
# class OverwriteSubQuestion(QDialog):
# """
# dialog to ask about overwriting existing submission
# """
# def __init__(self, message:str, rsl_plate_num:str) -> QDialog:
# super().__init__()
# self.setWindowTitle(f"Overwrite {rsl_plate_num}?")
# QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No
# self.buttonBox = QDialogButtonBox(QBtn)
# self.buttonBox.accepted.connect(self.accept)
# self.buttonBox.rejected.connect(self.reject)
# self.layout = QVBoxLayout()
# message = QLabel(message)
# self.layout.addWidget(message)
# self.layout.addWidget(self.buttonBox)
# self.setLayout(self.layout)
class QuestionAsker(QDialog):
""" """
dialog to ask about adding a new reagne to db dialog to ask yes/no questions
""" """
def __init__(self, reagent_type:str, reagent_lot:str) -> QDialog: def __init__(self, title:str, message:str) -> QDialog:
super().__init__() super().__init__()
self.setWindowTitle(title)
self.setWindowTitle(f"Add {reagent_lot}?")
QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No
self.buttonBox = QDialogButtonBox(QBtn) self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept) self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject) self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
message = QLabel(f"Couldn't find reagent type {reagent_type.replace('_', ' ').title().strip('Lot')}: {reagent_lot} in the database.\n\nWould you like to add it?")
self.layout.addWidget(message)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
class OverwriteSubQuestion(QDialog):
"""
dialog to ask about overwriting existing submission
"""
def __init__(self, message:str, rsl_plate_num:str) -> QDialog:
super().__init__()
self.setWindowTitle(f"Overwrite {rsl_plate_num}?")
QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout() self.layout = QVBoxLayout()
message = QLabel(message) message = QLabel(message)
self.layout.addWidget(message) self.layout.addWidget(message)
self.layout.addWidget(self.buttonBox) self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout) self.setLayout(self.layout)
class AlertPop(QMessageBox): class AlertPop(QMessageBox):
def __init__(self, message:str, status:str) -> QMessageBox: def __init__(self, message:str, status:str) -> QMessageBox:

View File

@@ -21,11 +21,3 @@ def check_kit_integrity(sub:BasicSubmission):
return result return result
def check_not_nan(cell_contents) -> bool:
try:
return not np.isnan(cell_contents)
except ValueError:
return True
except Exception as e:
logger.debug(f"Check encounteded unknown error: {e}")
return False

View File

@@ -0,0 +1,13 @@
import numpy as np
import logging
logger = logging.getLogger(f"submissions.{__name__}")
def check_not_nan(cell_contents) -> bool:
try:
return not np.isnan(cell_contents)
except TypeError:
return True
except Exception as e:
logger.debug(f"Check encounteded unknown error: {type(e).__name__} - {e}")
return False

View File

@@ -1,8 +0,0 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from src.submissions.backend.db.models import *
from src.submissions.backend.db import get_kits_by_use
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True, future=True)
session = Session(engine)
metadata.create_all(engine)