Automated Control lot retrieval

This commit is contained in:
Landon Wark
2024-04-17 07:53:11 -05:00
parent 9e2c66a865
commit c9bd8d1425
5 changed files with 103 additions and 54 deletions

View File

@@ -1,3 +1,5 @@
- [ ] Fix Parsed/Missing mix ups.
- [x] Have sample parser check for controls and add to reagents?
- [x] Update controls to NestedMutableJson - [x] Update controls to NestedMutableJson
- [x] Appending of qPCR results to WW not saving. Find out why. - [x] Appending of qPCR results to WW not saving. Find out why.
- Possibly due to immutable JSON? But... it's worked before... Right? - Possibly due to immutable JSON? But... it's worked before... Right?

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey
from sqlalchemy.orm import relationship, Query from sqlalchemy.orm import relationship, Query
from sqlalchemy_json import NestedMutableJson from sqlalchemy_json import NestedMutableJson
import logging import logging, re
from operator import itemgetter from operator import itemgetter
from . import BaseClass from . import BaseClass
from tools import setup_lookup from tools import setup_lookup
@@ -25,6 +25,9 @@ class ControlType(BaseClass):
targets = Column(JSON) #: organisms checked for targets = Column(JSON) #: organisms checked for
instances = relationship("Control", back_populates="controltype") #: control samples created of this type. instances = relationship("Control", back_populates="controltype") #: control samples created of this type.
def __repr__(self) -> str:
return f"<ControlType({self.name})>"
@classmethod @classmethod
@setup_lookup @setup_lookup
def query(cls, def query(cls,
@@ -74,6 +77,16 @@ class ControlType(BaseClass):
subtypes = [item for item in jsoner[genera] if "_hashes" not in item and "_ratio" not in item] subtypes = [item for item in jsoner[genera] if "_hashes" not in item and "_ratio" not in item]
return subtypes return subtypes
@classmethod
def get_positive_control_types(cls):
return [item for item in cls.query() if item.targets != []]
@classmethod
def build_positive_regex(cls):
strings = list(set([item.name.split("-")[0] for item in cls.get_positive_control_types()]))
return re.compile(rf"(^{'|^'.join(strings)})-.*", flags=re.IGNORECASE)
class Control(BaseClass): class Control(BaseClass):
""" """
Base class of a control sample. Base class of a control sample.

View File

@@ -1117,6 +1117,39 @@ class BacterialCulture(BasicSubmission):
# input_dict['submitted_date']['missing'] = True # input_dict['submitted_date']['missing'] = True
# return input_dict # return input_dict
@classmethod
def finalize_parse(cls, input_dict: dict, xl: pd.ExcelFile | None = None, info_map: dict | None = None, plate_map: dict | None = None) -> dict:
"""
Extends parent. Currently finds control sample and adds to reagents.
Args:
input_dict (dict): _description_
xl (pd.ExcelFile | None, optional): _description_. Defaults to None.
info_map (dict | None, optional): _description_. Defaults to None.
plate_map (dict | None, optional): _description_. Defaults to None.
Returns:
dict: _description_
"""
from . import ControlType
input_dict = super().finalize_parse(input_dict, xl, info_map, plate_map)
# build regex for all control types that have targets
regex = ControlType.build_positive_regex()
# search samples for match
for sample in input_dict['samples']:
matched = regex.match(sample.submitter_id)
if bool(matched):
logger.debug(f"Control match found: {sample.submitter_id}")
new_lot = matched.group()
try:
pos_control_reg = [reg for reg in input_dict['reagents'] if reg.type=="Bacterial-Positive Control"][0]
except IndexError:
logger.error(f"No positive control reagent listed")
return input_dict
pos_control_reg.lot = new_lot
pos_control_reg.missing = False
return input_dict
@classmethod @classmethod
def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int: def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int:
""" """
@@ -1505,53 +1538,53 @@ class WastewaterArtic(BasicSubmission):
# logger.debug(pformat(input_dict)) # logger.debug(pformat(input_dict))
# logger.debug(pformat(info_map)) # logger.debug(pformat(info_map))
# logger.debug(pformat(plate_map)) # logger.debug(pformat(plate_map))
samples = [] # samples = []
for sample in input_dict['samples']: # for sample in input_dict['samples']:
logger.debug(f"Input sample: {pformat(sample.__dict__)}") # logger.debug(f"Input sample: {pformat(sample.__dict__)}")
if sample.submitter_id == "NTC1": # if sample.submitter_id == "NTC1":
samples.append(dict(sample=sample.submitter_id, destination_row=8, destination_column=2, source_row=0, source_column=0, plate_number='control', plate=None)) # samples.append(dict(sample=sample.submitter_id, destination_row=8, destination_column=2, source_row=0, source_column=0, plate_number='control', plate=None))
continue # continue
elif sample.submitter_id == "NTC2": # elif sample.submitter_id == "NTC2":
samples.append(dict(sample=sample.submitter_id, destination_row=8, destination_column=5, source_row=0, source_column=0, plate_number='control', plate=None)) # samples.append(dict(sample=sample.submitter_id, destination_row=8, destination_column=5, source_row=0, source_column=0, plate_number='control', plate=None))
continue # continue
destination_row = sample.row[0] # destination_row = sample.row[0]
destination_column = sample.column[0] # destination_column = sample.column[0]
# logger.debug(f"Looking up: {sample.submitter_id} friend.") # # logger.debug(f"Looking up: {sample.submitter_id} friend.")
lookup_sample = BasicSample.query(submitter_id=sample.submitter_id) # lookup_sample = BasicSample.query(submitter_id=sample.submitter_id)
lookup_ssa = SubmissionSampleAssociation.query(sample=lookup_sample, exclude_submission_type=cls.__mapper_args__['polymorphic_identity'] , chronologic=True, reverse=True, limit=1) # lookup_ssa = SubmissionSampleAssociation.query(sample=lookup_sample, exclude_submission_type=cls.__mapper_args__['polymorphic_identity'] , chronologic=True, reverse=True, limit=1)
try: # try:
plate = lookup_ssa.submission.rsl_plate_num # plate = lookup_ssa.submission.rsl_plate_num
source_row = lookup_ssa.row # source_row = lookup_ssa.row
source_column = lookup_ssa.column # source_column = lookup_ssa.column
except AttributeError as e: # except AttributeError as e:
logger.error(f"Problem with lookup: {e}") # logger.error(f"Problem with lookup: {e}")
plate = "Error" # plate = "Error"
source_row = 0 # source_row = 0
source_column = 0 # source_column = 0
# continue # # continue
output_sample = dict( # output_sample = dict(
sample=sample.submitter_id, # sample=sample.submitter_id,
destination_column=destination_column, # destination_column=destination_column,
destination_row=destination_row, # destination_row=destination_row,
plate=plate, # plate=plate,
source_column=source_column, # source_column=source_column,
source_row = source_row # source_row = source_row
) # )
logger.debug(f"output sample: {pformat(output_sample)}") # logger.debug(f"output sample: {pformat(output_sample)}")
samples.append(output_sample) # samples.append(output_sample)
plates = sorted(list(set([sample['plate'] for sample in samples if sample['plate'] != None and sample['plate'] != "Error"]))) # plates = sorted(list(set([sample['plate'] for sample in samples if sample['plate'] != None and sample['plate'] != "Error"])))
logger.debug(f"Here's what I got for plates: {plates}") # logger.debug(f"Here's what I got for plates: {plates}")
for iii, plate in enumerate(plates): # for iii, plate in enumerate(plates):
for sample in samples: # for sample in samples:
if sample['plate'] == plate: # if sample['plate'] == plate:
sample['plate_number'] = iii + 1 # sample['plate_number'] = iii + 1
df = pd.DataFrame.from_records(samples).fillna(value="") # df = pd.DataFrame.from_records(samples).fillna(value="")
try: # try:
df.source_row = df.source_row.astype(int) # df.source_row = df.source_row.astype(int)
df.source_column = df.source_column.astype(int) # df.source_column = df.source_column.astype(int)
df.sort_values(by=['destination_column', 'destination_row'], inplace=True) # df.sort_values(by=['destination_column', 'destination_row'], inplace=True)
except AttributeError as e: # except AttributeError as e:
logger.error(f"Couldn't construct df due to {e}") # logger.error(f"Couldn't construct df due to {e}")
# input_dict['csv'] = df # input_dict['csv'] = df
input_dict['csv'] = xl.parse("hitpicks_csv_to_export") input_dict['csv'] = xl.parse("hitpicks_csv_to_export")
return input_dict return input_dict

View File

@@ -809,8 +809,8 @@ class PydSubmission(BaseModel, extra='allow'):
# # what reagent types are in both lists? # # what reagent types are in both lists?
# missing = list(set(ext_kit_rtypes).difference(reagenttypes)) # missing = list(set(ext_kit_rtypes).difference(reagenttypes))
missing = [] missing = []
# output_reagents = self.reagents output_reagents = self.reagents
output_reagents = ext_kit_rtypes # output_reagents = ext_kit_rtypes
logger.debug(f"Already have these reagent types: {reagenttypes}") logger.debug(f"Already have these reagent types: {reagenttypes}")
for rt in ext_kit_rtypes: for rt in ext_kit_rtypes:
if rt.type not in reagenttypes: if rt.type not in reagenttypes:

View File

@@ -369,7 +369,7 @@ class SubmissionFormWidget(QWidget):
match result.code: match result.code:
# code 0: everything is fine. # code 0: everything is fine.
case 0: case 0:
self.report.add_result(None) report.add_result(None)
# code 1: ask for overwrite # code 1: ask for overwrite
case 1: case 1:
dlg = QuestionAsker(title=f"Review {base_submission.rsl_plate_num}?", message=result.msg) dlg = QuestionAsker(title=f"Review {base_submission.rsl_plate_num}?", message=result.msg)
@@ -379,11 +379,11 @@ class SubmissionFormWidget(QWidget):
result = None result = None
else: else:
self.app.ctx.database_session.rollback() self.app.ctx.database_session.rollback()
self.report.add_result(Result(msg="Overwrite cancelled", status="Information")) report.add_result(Result(msg="Overwrite cancelled", status="Information"))
return return
# code 2: No RSL plate number given # code 2: No RSL plate number given
case 2: case 2:
self.report.add_result(result) report.add_result(result)
return return
case _: case _:
pass pass
@@ -457,7 +457,8 @@ class SubmissionFormWidget(QWidget):
for k,v in info.items(): for k,v in info.items():
self.pyd.set_attribute(key=k, value=v) self.pyd.set_attribute(key=k, value=v)
# return submission # return submission
self.report.add_result(report) report.add_result(report)
return report
class InfoItem(QWidget): class InfoItem(QWidget):