Checking kit integrity on import.

This commit is contained in:
Landon Wark
2023-03-03 15:06:43 -06:00
parent 1c89c31d25
commit 82d5378479
14 changed files with 376 additions and 95 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-20230213.db ; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\DB_backups\submissions-20230213.db
[post_write_hooks] [post_write_hooks]

View File

@@ -0,0 +1,59 @@
"""added versions to ref/kraken
Revision ID: 3d80e4a17a26
Revises: 785bb1140878
Create Date: 2023-03-02 13:09:30.750398
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import sqlite
# revision identifiers, used by Alembic.
revision = '3d80e4a17a26'
down_revision = '785bb1140878'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# op.drop_table('_alembic_tmp__submissions')
with op.batch_alter_table('_control_samples', schema=None) as batch_op:
batch_op.add_column(sa.Column('refseq_version', sa.String(length=16), nullable=True))
batch_op.add_column(sa.Column('kraken2_version', sa.String(length=16), nullable=True))
batch_op.add_column(sa.Column('kraken2_db_version', sa.String(length=32), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('_control_samples', schema=None) as batch_op:
batch_op.drop_column('kraken2_db_version')
batch_op.drop_column('kraken2_version')
batch_op.drop_column('refseq_version')
# op.create_table('_alembic_tmp__submissions',
# sa.Column('id', sa.INTEGER(), nullable=False),
# sa.Column('rsl_plate_num', sa.VARCHAR(length=32), nullable=False),
# sa.Column('submitter_plate_num', sa.VARCHAR(length=127), nullable=True),
# sa.Column('submitted_date', sa.TIMESTAMP(), nullable=True),
# sa.Column('submitting_lab_id', sa.INTEGER(), nullable=True),
# sa.Column('sample_count', sa.INTEGER(), nullable=True),
# sa.Column('extraction_kit_id', sa.INTEGER(), nullable=True),
# sa.Column('submission_type', sa.VARCHAR(length=32), nullable=True),
# sa.Column('technician', sa.VARCHAR(length=64), nullable=True),
# sa.Column('reagents_id', sa.VARCHAR(), nullable=True),
# sa.Column('extraction_info', sqlite.JSON(), nullable=True),
# sa.Column('run_cost', sa.FLOAT(), nullable=True),
# sa.Column('uploaded_by', sa.VARCHAR(length=32), nullable=True),
# sa.ForeignKeyConstraint(['extraction_kit_id'], ['_kits.id'], ondelete='SET NULL'),
# sa.ForeignKeyConstraint(['reagents_id'], ['_reagents.id'], ondelete='SET NULL'),
# sa.ForeignKeyConstraint(['submitting_lab_id'], ['_organizations.id'], ondelete='SET NULL'),
# sa.PrimaryKeyConstraint('id'),
# sa.UniqueConstraint('rsl_plate_num'),
# sa.UniqueConstraint('submitter_plate_num')
# )
# ### end Alembic commands ###

View File

@@ -1,6 +1,6 @@
# __init__.py # __init__.py
# Version of the realpython-reader package # Version of the realpython-reader package
__version__ = "202302.3b" __version__ = "202303.1b"
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"} __author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
__copyright__ = "2022-2023, Government of Canada" __copyright__ = "2022-2023, Government of Canada"

View File

@@ -576,17 +576,23 @@ def get_all_controls_by_type(ctx:dict, con_type:str, start_date:date|None=None,
list: Control instances. list: Control instances.
""" """
# logger.debug(f"Using dates: {start_date} to {end_date}") logger.debug(f"Using dates: {start_date} to {end_date}")
query = ctx['database_session'].query(models.ControlType).filter_by(name=con_type)
try:
output = query.first().instances
except AttributeError:
output = None
# Hacky solution to my not being able to get the sql query to work.
if start_date != None and end_date != None: if start_date != None and end_date != None:
output = [item for item in output if item.submitted_date.date() > start_date and item.submitted_date.date() < end_date] output = ctx['database_session'].query(models.Control).join(models.ControlType).filter_by(name=con_type).filter(models.Control.submitted_date.between(start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d"))).all()
# logger.debug(f"Type {con_type}: {query.first()}") else:
output = ctx['database_session'].query(models.Control).join(models.ControlType).filter_by(name=con_type).all()
logger.debug(f"Returned controls between dates: {output}")
return output return output
# query = ctx['database_session'].query(models.ControlType).filter_by(name=con_type)
# try:
# output = query.first().instances
# except AttributeError:
# output = None
# # Hacky solution to my not being able to get the sql query to work.
# if start_date != None and end_date != None:
# output = [item for item in output if item.submitted_date.date() > start_date and item.submitted_date.date() < end_date]
# # logger.debug(f"Type {con_type}: {query.first()}")
# return output
def get_control_subtypes(ctx:dict, type:str, mode:str) -> list[str]: def get_control_subtypes(ctx:dict, type:str, mode:str) -> list[str]:

View File

@@ -39,9 +39,18 @@ class Control(Base):
# UniqueConstraint('name', name='uq_control_name') # UniqueConstraint('name', name='uq_control_name')
submission_id = Column(INTEGER, ForeignKey("_submissions.id")) #: parent submission id submission_id = Column(INTEGER, ForeignKey("_submissions.id")) #: parent submission id
submission = relationship("BacterialCulture", back_populates="controls", foreign_keys=[submission_id]) #: parent submission submission = relationship("BacterialCulture", back_populates="controls", foreign_keys=[submission_id]) #: parent submission
refseq_version = Column(String(16))
kraken2_version = Column(String(16))
kraken2_db_version = Column(String(32))
def to_sub_dict(self): def to_sub_dict(self) -> dict:
"""
Converts object into convenient dictionary for use in submission summary
Returns:
dict: output dictionary containing: Name, Type, Targets, Top Kraken results
"""
kraken = json.loads(self.kraken) kraken = json.loads(self.kraken)
kraken_cnt_total = sum([kraken[item]['kraken_count'] for item in kraken]) kraken_cnt_total = sum([kraken[item]['kraken_count'] for item in kraken])
new_kraken = [] new_kraken = []
@@ -61,3 +70,46 @@ class Control(Base):
} }
return output return output
def convert_by_mode(self, mode:str) -> list[dict]:
"""
split control object into analysis types
Args:
control (models.Control): control to be parsed into list
mode (str): analysis type
Returns:
list[dict]: list of records
"""
output = []
data = json.loads(getattr(self, mode))
# if len(data) == 0:
# data = self.create_dummy_data(mode)
logger.debug(f"Length of data: {len(data)}")
for genus in data:
_dict = {}
_dict['name'] = self.name
_dict['submitted_date'] = self.submitted_date
_dict['genus'] = genus
_dict['target'] = 'Target' if genus.strip("*") in self.controltype.targets else "Off-target"
for key in data[genus]:
_dict[key] = data[genus][key]
if _dict[key] == {}:
print(self.name, mode)
output.append(_dict)
# logger.debug(output)
return output
def create_dummy_data(self, mode):
match mode:
case "contains":
data = {"Nothing": {"contains_hashes":"0/400", "contains_ratio":0.0}}
case "matches":
data = {"Nothing": {"matches_hashes":"0/400", "matches_ratio":0.0}}
case "kraken":
data = {"Nothing": {"kraken_percent":0.0, "kraken_count":0}}
case _:
data = {}
return data

View File

@@ -4,6 +4,7 @@ from sqlalchemy.orm import relationship
from datetime import datetime as dt from datetime import datetime as dt
import logging import logging
import json import json
from json.decoder import JSONDecodeError
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -74,6 +75,9 @@ class BasicSubmission(Base):
ext_info = json.loads(self.extraction_info) ext_info = json.loads(self.extraction_info)
except TypeError: except TypeError:
ext_info = None ext_info = None
except JSONDecodeError as e:
ext_info = None
logger.debug(f"Json error in {self.rsl_plate_num}: {e}")
try: try:
reagents = [item.to_sub_dict() for item in self.reagents] reagents = [item.to_sub_dict() for item in self.reagents]
except: except:

View File

@@ -1,10 +1,11 @@
from pandas import DataFrame, concat from pandas import DataFrame, concat
from backend.db import models from operator import itemgetter
# from backend.db import models
import json import json
import logging import logging
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from datetime import date from datetime import date, timedelta
import sys import sys
from pathlib import Path from pathlib import Path
@@ -139,31 +140,32 @@ def make_report_html(df:DataFrame, start_date:date, end_date:date) -> str:
# dfs['name'] = df # dfs['name'] = df
# return dfs # return dfs
def convert_control_by_mode(ctx:dict, control:models.Control, mode:str) -> list[dict]: # def convert_control_by_mode(ctx:dict, control:models.Control, mode:str) -> list[dict]:
""" # """
split control object into analysis types # split control object into analysis types... can I move this into the class itself?
# turns out I can
Args: # Args:
ctx (dict): settings passed from gui # ctx (dict): settings passed from gui
control (models.Control): control to be parsed into list # control (models.Control): control to be parsed into list
mode (str): analysis type # mode (str): analysis type
Returns: # Returns:
list[dict]: list of records # list[dict]: list of records
""" # """
output = [] # output = []
data = json.loads(getattr(control, mode)) # data = json.loads(getattr(control, mode))
for genus in data: # for genus in data:
_dict = {} # _dict = {}
_dict['name'] = control.name # _dict['name'] = control.name
_dict['submitted_date'] = control.submitted_date # _dict['submitted_date'] = control.submitted_date
_dict['genus'] = genus # _dict['genus'] = genus
_dict['target'] = 'Target' if genus.strip("*") in control.controltype.targets else "Off-target" # _dict['target'] = 'Target' if genus.strip("*") in control.controltype.targets else "Off-target"
for key in data[genus]: # for key in data[genus]:
_dict[key] = data[genus][key] # _dict[key] = data[genus][key]
output.append(_dict) # output.append(_dict)
# logger.debug(output) # # logger.debug(output)
return output # return output
def convert_data_list_to_df(ctx:dict, input:list[dict], subtype:str|None=None) -> DataFrame: def convert_data_list_to_df(ctx:dict, input:list[dict], subtype:str|None=None) -> DataFrame:
@@ -178,17 +180,81 @@ def convert_data_list_to_df(ctx:dict, input:list[dict], subtype:str|None=None) -
Returns: Returns:
DataFrame: _description_ DataFrame: _description_
""" """
# copy = input
# for item in copy:
# item['submitted_date'] = item['submitted_date'].strftime("%Y-%m-%d")
# with open("controls.json", "w") as f:
# f.write(json.dumps(copy))
# for item in input:
# logger.debug(item.keys())
df = DataFrame.from_records(input) df = DataFrame.from_records(input)
df.to_excel("test.xlsx", engine="openpyxl")
safe = ['name', 'submitted_date', 'genus', 'target'] safe = ['name', 'submitted_date', 'genus', 'target']
# logger.debug(df) # logger.debug(df)
for column in df.columns: for column in df.columns:
if "percent" in column: if "percent" in column:
count_col = [item for item in df.columns if "count" in item][0] count_col = [item for item in df.columns if "count" in item][0]
# The actual percentage from kraken was off due to exclusion of NaN, recalculating. # The actual percentage from kraken was off due to exclusion of NaN, recalculating.
df[column] = 100 * df[count_col] / df.groupby('submitted_date')[count_col].transform('sum') # df[column] = 100 * df[count_col] / df.groupby('submitted_date')[count_col].transform('sum')
df[column] = 100 * df[count_col] / df.groupby('name')[count_col].transform('sum')
if column not in safe: if column not in safe:
if subtype != None and column != subtype: if subtype != None and column != subtype:
del df[column] del df[column]
# logger.debug(df) # logger.debug(df)
# df.sort_values('submitted_date').to_excel("controls.xlsx", engine="openpyxl")
df = displace_date(df)
df.sort_values('submitted_date').to_excel("controls.xlsx", engine="openpyxl")
df = df_column_renamer(df=df)
return df
def df_column_renamer(df:DataFrame) -> DataFrame:
"""
Ad hoc function I created to clarify some fields
Args:
df (DataFrame): input dataframe
Returns:
DataFrame: dataframe with 'clarified' column names
"""
df = df[df.columns.drop(list(df.filter(regex='_hashes')))]
return df.rename(columns = {
"contains_ratio":"contains_shared_hashes_ratio",
"matches_ratio":"matches_shared_hashes_ratio",
"kraken_count":"kraken2_read_count",
"kraken_percent":"kraken2_read_percent"
})
def displace_date(df:DataFrame) -> DataFrame:
"""
This function serves to split samples that were submitted on the same date by incrementing dates.
Args:
df (DataFrame): input dataframe composed of control records
Returns:
DataFrame: output dataframe with dates incremented.
"""
# dict_list = []
# for item in df['name'].unique():
# dict_list.append(dict(name=item, date=df[df.name == item].iloc[0]['submitted_date']))
logger.debug(f"Unique items: {df['name'].unique()}")
# logger.debug(df.to_string())
# the assumption is that closest names will have closest dates...
dict_list = [dict(name=item, date=df[df.name == item].iloc[0]['submitted_date']) for item in sorted(df['name'].unique())]
for ii, item in enumerate(dict_list):
# if ii > 0:
try:
check = item['date'] == dict_list[ii-1]['date']
except IndexError:
check = False
if check:
logger.debug(f"We found one! Increment date!\n{item['date'] - timedelta(days=1)}")
mask = df['name'] == item['name']
# logger.debug(f"We will increment dates in: {df.loc[mask, 'submitted_date']}")
df.loc[mask, 'submitted_date'] = df.loc[mask, 'submitted_date'].apply(lambda x: x + timedelta(days=1))
# logger.debug(f"Do these look incremented: {df.loc[mask, 'submitted_date']}")
return df return df

View File

@@ -20,17 +20,18 @@ from xhtml2pdf import pisa
import yaml import yaml
import pprint import pprint
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_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_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 create_org_from_yaml
) )
from backend.db import lookup_kittype_by_name
from .functions import check_kit_integrity from .functions import check_kit_integrity
from tools import check_not_nan from tools import check_not_nan, create_reagent_list
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
@@ -108,8 +109,8 @@ class App(QMainWindow):
self.importAction = QAction("&Import", self) self.importAction = QAction("&Import", self)
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("Import Kit", self)
self.addOrgAction = QAction("Add Org", self) self.addOrgAction = QAction("Import Org", self)
self.joinControlsAction = QAction("Link Controls") self.joinControlsAction = QAction("Link Controls")
self.joinExtractionAction = QAction("Link Ext Logs") self.joinExtractionAction = QAction("Link Ext Logs")
self.helpAction = QAction("&About", self) self.helpAction = QAction("&About", self)
@@ -137,6 +138,40 @@ class App(QMainWindow):
about = AlertPop(message=output, status="information") about = AlertPop(message=output, status="information")
about.exec() about.exec()
def insert_reagent_import(self, item:str, prsr:SheetParser|None=None) -> QComboBox:
add_widget = QComboBox()
add_widget.setEditable(True)
# Ensure that all reagenttypes have a name that matches the items in the excel parser
query_var = item.replace("lot_", "")
logger.debug(f"Query for: {query_var}")
if prsr != None:
if isinstance(prsr.sub[item], numpy.float64):
logger.debug(f"{prsr.sub[item]['lot']} is a numpy float!")
try:
prsr.sub[item] = int(prsr.sub[item]['lot'])
except ValueError:
pass
# query for reagents using type name from sheet and kit from sheet
logger.debug(f"Attempting lookup of reagents by type: {query_var}")
# below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work.
relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=self.ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])]
output_reg = []
for reagent in relevant_reagents:
if isinstance(reagent, set):
for thing in reagent:
output_reg.append(thing)
elif isinstance(reagent, str):
output_reg.append(reagent)
relevant_reagents = output_reg
# if reagent in sheet is not found insert it into items
if prsr != None:
logger.debug(f"Relevant reagents for {prsr.sub[item]}: {relevant_reagents}")
if str(prsr.sub[item]['lot']) not in relevant_reagents and prsr.sub[item]['lot'] != 'nan':
if check_not_nan(prsr.sub[item]['lot']):
relevant_reagents.insert(0, str(prsr.sub[item]['lot']))
logger.debug(f"New relevant reagents: {relevant_reagents}")
add_widget.addItems(relevant_reagents)
return add_widget
def importSubmission(self): def importSubmission(self):
""" """
@@ -155,6 +190,7 @@ class App(QMainWindow):
try: try:
prsr = SheetParser(fname, **self.ctx) prsr = SheetParser(fname, **self.ctx)
except PermissionError: except PermissionError:
logger.error(f"Couldn't get permission to access file: {fname}")
return return
logger.debug(f"prsr.sub = {prsr.sub}") logger.debug(f"prsr.sub = {prsr.sub}")
# destroy any widgets from previous imports # destroy any widgets from previous imports
@@ -169,6 +205,7 @@ class App(QMainWindow):
(?P<samples>)^samples$ | (?P<samples>)^samples$ |
(?P<reagent>^lot_.*$) (?P<reagent>^lot_.*$)
""", re.VERBOSE) """, re.VERBOSE)
# reagents = []
for item in prsr.sub: for item in prsr.sub:
logger.debug(f"Item: {item}") logger.debug(f"Item: {item}")
# attempt to match variable name to regex group # attempt to match variable name to regex group
@@ -207,6 +244,7 @@ class App(QMainWindow):
add_widget.addItems(uses) add_widget.addItems(uses)
else: else:
add_widget.addItems(['bacterial_culture']) add_widget.addItems(['bacterial_culture'])
self.ext_kit = prsr.sub[item]
case 'submitted_date': case 'submitted_date':
# create label # create label
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title())) self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
@@ -219,40 +257,41 @@ class App(QMainWindow):
except: except:
add_widget.setDate(date.today()) add_widget.setDate(date.today())
case 'reagent': case 'reagent':
# TODO make this a function so I can add in missing reagents below when checking kit integrity.
# create label # create label
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title())) self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
add_widget = QComboBox() # add_widget = QComboBox()
add_widget.setEditable(True) # add_widget.setEditable(True)
# Ensure that all reagenttypes have a name that matches the items in the excel parser # # Ensure that all reagenttypes have a name that matches the items in the excel parser
query_var = item.replace("lot_", "") # query_var = item.replace("lot_", "")
logger.debug(f"Query for: {query_var}") # logger.debug(f"Query for: {query_var}")
if isinstance(prsr.sub[item], numpy.float64): # if isinstance(prsr.sub[item], numpy.float64):
logger.debug(f"{prsr.sub[item]['lot']} is a numpy float!") # logger.debug(f"{prsr.sub[item]['lot']} is a numpy float!")
try: # try:
prsr.sub[item] = int(prsr.sub[item]['lot']) # prsr.sub[item] = int(prsr.sub[item]['lot'])
except ValueError: # except ValueError:
pass # pass
# query for reagents using type name from sheet and kit from sheet # # query for reagents using type name from sheet and kit from sheet
logger.debug(f"Attempting lookup of reagents by type: {query_var}") # logger.debug(f"Attempting lookup of reagents by type: {query_var}")
# below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work. # # below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work.
relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=self.ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])] # relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=self.ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])]
output_reg = [] # output_reg = []
for reagent in relevant_reagents: # for reagent in relevant_reagents:
if isinstance(reagent, set): # if isinstance(reagent, set):
for thing in reagent: # for thing in reagent:
output_reg.append(thing) # output_reg.append(thing)
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 for {prsr.sub[item]}: {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']):
relevant_reagents.insert(0, str(prsr.sub[item]['lot'])) # relevant_reagents.insert(0, str(prsr.sub[item]['lot']))
logger.debug(f"New relevant reagents: {relevant_reagents}") # logger.debug(f"New relevant reagents: {relevant_reagents}")
add_widget.addItems(relevant_reagents) # add_widget.addItems(relevant_reagents)
add_widget = self.insert_reagent_import(item, prsr=prsr)
self.reagents[item] = prsr.sub[item] self.reagents[item] = prsr.sub[item]
# TODO: make samples not appear in frame.
case 'samples': case 'samples':
# hold samples in 'self' until form submitted # hold samples in 'self' until form submitted
logger.debug(f"{item}: {prsr.sub[item]}") logger.debug(f"{item}: {prsr.sub[item]}")
@@ -263,6 +302,17 @@ class App(QMainWindow):
add_widget = QLineEdit() add_widget = QLineEdit()
add_widget.setText(str(prsr.sub[item]).replace("_", " ")) add_widget.setText(str(prsr.sub[item]).replace("_", " "))
self.table_widget.formlayout.addWidget(add_widget) self.table_widget.formlayout.addWidget(add_widget)
# compare self.reagents with expected reagents in kit
if hasattr(self, 'ext_kit'):
kit = lookup_kittype_by_name(ctx=self.ctx, name=self.ext_kit)
kit_integrity = check_kit_integrity(kit, [item.replace("lot_", "") for item in self.reagents])
if kit_integrity != None:
msg = AlertPop(message=kit_integrity['message'], status="critical")
msg.exec()
for item in kit_integrity['missing']:
self.table_widget.formlayout.addWidget(QLabel(f"Lot {item.replace('_', ' ').title()}"))
add_widget =self.insert_reagent_import(item)
self.table_widget.formlayout.addWidget(add_widget)
# create submission button # create submission button
submit_btn = QPushButton("Submit") submit_btn = QPushButton("Submit")
self.table_widget.formlayout.addWidget(submit_btn) self.table_widget.formlayout.addWidget(submit_btn)
@@ -580,11 +630,14 @@ class App(QMainWindow):
# return # return
fig = None fig = None
else: else:
data = [] # data = []
for control in controls: # for control in controls:
# # change each control to list of dicts
# # dicts = convert_control_by_mode(ctx=self.ctx, control=control, mode=self.mode)
# dicts = control.convert_by_mode(mode=self.mode)
# data.append(dicts)
# change each control to list of dicts # change each control to list of dicts
dicts = convert_control_by_mode(ctx=self.ctx, control=control, mode=self.mode) data = [control.convert_by_mode(mode=self.mode) for control in controls]
data.append(dicts)
# flatten data to one dimensional list # flatten data to one dimensional list
data = [item for sublist in data for item in sublist] data = [item for sublist in data for item in sublist]
# logger.debug(data) # logger.debug(data)
@@ -677,6 +730,7 @@ class App(QMainWindow):
with open(fname.__str__(), 'r') as f: with open(fname.__str__(), 'r') as f:
runs = [col.strip().split(",") for col in f.readlines()] runs = [col.strip().split(",") for col in f.readlines()]
# check = [] # check = []
count = 0
for run in runs: for run in runs:
obj = dict( obj = dict(
start_time=run[0].strip(), start_time=run[0].strip(),
@@ -693,21 +747,42 @@ class App(QMainWindow):
sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=obj['rsl_plate_num']) sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=obj['rsl_plate_num'])
try: try:
logger.debug(f"Found submission: {sub.rsl_plate_num}") logger.debug(f"Found submission: {sub.rsl_plate_num}")
count += 1
except AttributeError: except AttributeError:
continue continue
output = json.dumps(obj) # output = json.dumps(obj)
if sub.extraction_info != None:
# try:
# logger.debug(f"Attempting update on ext info: {sub.extraction_info} for {sub.rsl_plate_num}")
existing = json.loads(sub.extraction_info)
# except:
# existing = None
else:
existing = None
try: try:
if output in sub.extraction_info: if json.dumps(obj) in sub.extraction_info:
logger.debug(f"Looks like we already have that info.") logger.debug(f"Looks like we already have that info.")
continue continue
except TypeError: except TypeError:
pass pass
if existing != None:
try: try:
sub.extraction_info += output # sub.extraction_info += output
logger.debug(f"Updating {type(existing)}: {existing} with {type(obj)}: {obj}")
existing.append(obj)
logger.debug(f"Setting: {existing}")
sub.extraction_info = json.dumps(existing)
except TypeError: except TypeError:
sub.extraction_info = output logger.error(f"Error updating!")
sub.extraction_info = json.dumps([obj])
logger.debug(f"Final ext info for {sub.rsl_plate_num}: {sub.extraction_info}")
else:
sub.extraction_info = json.dumps([obj])
self.ctx['database_session'].add(sub) self.ctx['database_session'].add(sub)
self.ctx["database_session"].commit() self.ctx["database_session"].commit()
dlg = AlertPop(message=f"We added {count} logs to the database.", status='information')
dlg.exec()
class AddSubForm(QWidget): class AddSubForm(QWidget):

View File

@@ -260,6 +260,7 @@ class ControlsDatePicker(QWidget):
self.start_date = QDateEdit(calendarPopup=True) self.start_date = QDateEdit(calendarPopup=True)
# start date is three month prior to end date by default # start date is three month prior to end date by default
# edit: 2 month, but the variable name is the same cause I'm lazy
threemonthsago = QDate.currentDate().addDays(-60) threemonthsago = QDate.currentDate().addDays(-60)
self.start_date.setDate(threemonthsago) self.start_date.setDate(threemonthsago)
self.end_date = QDateEdit(calendarPopup=True) self.end_date = QDateEdit(calendarPopup=True)

View File

@@ -1,14 +1,20 @@
# from ..models import * # from ..models import *
from backend.db.models import * from backend.db.models import *
# from backend.db import lookup_kittype_by_name
import logging import logging
import numpy as np import numpy as np
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
def check_kit_integrity(sub:BasicSubmission): def check_kit_integrity(sub:BasicSubmission|KitType, reagenttypes:list|None=None) -> dict|None:
logger.debug(type(sub))
match sub:
case 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}")
reagenttypes = [reagent.type.name for reagent in sub.reagents] reagenttypes = [reagent.type.name for reagent in sub.reagents]
case KitType():
ext_kit_rtypes = [reagenttype.name for reagenttype in sub.reagent_types]
logger.debug(f"Kit reagents: {ext_kit_rtypes}")
logger.debug(f"Submission reagents: {reagenttypes}") 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}")
@@ -17,7 +23,11 @@ def check_kit_integrity(sub:BasicSubmission):
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."} missing = [x for x in ext_kit_rtypes if x not in common]
result = {'message' : f"Couldn't verify reagents match listed kit components.\n\nIt looks like you are missing: {[item.upper() for item in missing]}\n\nAlternatively, you may have set the wrong extraction kit.", 'missing': missing}
return result return result

View File

@@ -25,6 +25,7 @@ def create_charts(ctx:dict, df:pd.DataFrame, ytitle:str|None=None) -> Figure:
genera = [] genera = []
if df.empty: if df.empty:
return None return None
for item in df['genus'].to_list(): for item in df['genus'].to_list():
try: try:
if item[-1] == "*": if item[-1] == "*":
@@ -41,7 +42,7 @@ def create_charts(ctx:dict, df:pd.DataFrame, ytitle:str|None=None) -> Figure:
# sort by and exclude from # sort by and exclude from
sorts = ['submitted_date', "target", "genus"] sorts = ['submitted_date', "target", "genus"]
exclude = ['name', 'genera'] exclude = ['name', 'genera']
modes = [item for item in df.columns if item not in sorts and item not in exclude and "_hashes" not in item] modes = [item for item in df.columns if item not in sorts and item not in exclude]# and "_hashes" not in item]
# Set descending for any columns that have "{mode}" in the header. # Set descending for any columns that have "{mode}" in the header.
ascending = [False if item == "target" else True for item in sorts] ascending = [False if item == "target" else True for item in sorts]
df = df.sort_values(by=sorts, ascending=ascending) df = df.sort_values(by=sorts, ascending=ascending)

View File

@@ -47,8 +47,9 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if sub['ext_info'] %} {% if sub['ext_info'] %}
{% for entry in sub['ext_info'] %}
<h3><u>Extraction Status:</u></h3> <h3><u>Extraction Status:</u></h3>
<p>{% for key, value in sub['ext_info'].items() %} <p>{% for key, value in entry.items() %}
{% if loop.index == 1%} {% if loop.index == 1%}
&nbsp;&nbsp;&nbsp;{{ key|replace('_', ' ')|title() }}: {{ value }}<br> &nbsp;&nbsp;&nbsp;{{ key|replace('_', ' ')|title() }}: {{ value }}<br>
{% else %} {% else %}
@@ -59,6 +60,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %}</p> {% endfor %}</p>
{% endfor %}
{% endif %} {% endif %}
</body> </body>
</html> </html>

View File

@@ -19,8 +19,9 @@ Attached Controls:
{% for genera in item['kraken'] %} {% for genera in item['kraken'] %}
{{ genera['name'] }}: {{ genera['kraken_count'] }} ({{ genera['kraken_percent'] }}){% endfor %}{% endif %} {{ genera['name'] }}: {{ genera['kraken_count'] }} ({{ genera['kraken_percent'] }}){% endfor %}{% endif %}
{% endfor %}{% endif %} {% endfor %}{% endif %}
{% if sub['ext_info'] %} {% if sub['ext_info'] %}{% for entry in sub['ext_info'] %}
Extraction Status: Extraction Status:
{% for key, value in sub['ext_info'].items() %} {% for key, value in entry.items() %}
{{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %} {{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %}
{% endfor %}
{% endif %} {% endif %}

View File

@@ -23,3 +23,7 @@ def check_is_power_user(ctx:dict) -> bool:
logger.debug(f"Check encounteded unknown error: {type(e).__name__} - {e}") logger.debug(f"Check encounteded unknown error: {type(e).__name__} - {e}")
check = False check = False
return check return check
def create_reagent_list(in_dict:dict) -> list[str]:
return [item.strip("lot_") for item in in_dict.keys()]