Automated Control lot retrieval
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -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?
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user