hitpicking complete, pre-addition of WW-Arctic parsers and models.

This commit is contained in:
Landon Wark
2023-05-31 09:44:20 -05:00
parent 01d95e80f5
commit 1d6823705c
17 changed files with 334 additions and 247 deletions

View File

@@ -20,7 +20,6 @@ from getpass import getuser
import numpy as np
import yaml
from pathlib import Path
from math import ceil
logger = logging.getLogger(f"submissions.{__name__}")
@@ -136,7 +135,13 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
try:
field_value = lookup_kittype_by_name(ctx=ctx, name=q_str)
except (sqlite3.IntegrityError, sqlalchemy.exc.IntegrityError) as e:
logger.error(f"Hit an integrity error: {e}")
logger.error(f"Hit an integrity error looking up kit type: {e}")
logger.error(f"Details: {e.__dict__}")
if "submitter_plate_num" in e.__dict__['statement']:
msg = "SQL integrity error. Submitter plate id is a duplicate or invalid."
else:
msg = "SQL integrity error of unknown origin."
return instance, dict(code=2, message=msg)
logger.debug(f"Got {field_value} for kit {q_str}")
case "submitting_lab":
q_str = info_dict[item].replace(" ", "_").lower()
@@ -179,7 +184,7 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
discounts = sum(discounts)
instance.run_cost = instance.run_cost - discounts
except Exception as e:
logger.error(f"An unknown exception occurred: {e}")
logger.error(f"An unknown exception occurred when calculating discounts: {e}")
# We need to make sure there's a proper rsl plate number
logger.debug(f"We've got a total cost of {instance.run_cost}")
try:
@@ -748,10 +753,16 @@ def update_ww_sample(ctx:dict, sample_obj:dict):
ww_samp = lookup_ww_sample_by_sub_sample_rsl(ctx=ctx, sample_rsl=sample_obj['sample'], plate_rsl=sample_obj['plate_rsl'])
# ww_samp = lookup_ww_sample_by_sub_sample_well(ctx=ctx, sample_rsl=sample_obj['sample'], well_num=sample_obj['well_num'], plate_rsl=sample_obj['plate_rsl'])
if ww_samp != None:
# del sample_obj['well_number']
for key, value in sample_obj.items():
logger.debug(f"Setting {key} to {value}")
# set attribute 'key' to 'value'
setattr(ww_samp, key, value)
try:
check = getattr(ww_samp, key)
except AttributeError:
continue
if check == None:
logger.debug(f"Setting {key} to {value}")
setattr(ww_samp, key, value)
else:
logger.error(f"Unable to find sample {sample_obj['sample']}")
return
@@ -762,4 +773,29 @@ def lookup_discounts_by_org_and_kit(ctx:dict, kit_id:int, lab_id:int):
return ctx['database_session'].query(models.Discount).join(models.KitType).join(models.Organization).filter(and_(
models.KitType.id==kit_id,
models.Organization.id==lab_id
)).all()
)).all()
def hitpick_plate(submission:models.BasicSubmission, plate_number:int=0) -> list:
plate_dicto = []
for sample in submission.samples:
# have sample report back its info if it's positive, otherwise, None
samp = sample.to_hitpick()
if samp == None:
continue
else:
logger.debug(f"Item name: {samp['name']}")
# plate can handle 88 samples to leave column for controls
# if len(dicto) < 88:
this_sample = dict(
plate_number = plate_number,
sample_name = samp['name'],
column = samp['col'],
row = samp['row'],
plate_name = submission.rsl_plate_num
)
# append to plate samples
plate_dicto.append(this_sample)
# append to all samples
# image = make_plate_map(plate_dicto)
return plate_dicto

View File

@@ -4,6 +4,9 @@ All models for individual samples.
from . import Base
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, FLOAT, BOOLEAN, JSON
from sqlalchemy.orm import relationship
import logging
logger = logging.getLogger(f"submissions.{__name__}")
class WWSample(Base):
@@ -19,7 +22,7 @@ class WWSample(Base):
rsl_plate = relationship("Wastewater", back_populates="samples") #: relationship to parent plate
rsl_plate_id = Column(INTEGER, ForeignKey("_submissions.id", ondelete="SET NULL", name="fk_WWS_submission_id"))
collection_date = Column(TIMESTAMP) #: Date submission received
well_number = Column(String(8)) #: location on plate
well_number = Column(String(8)) #: location on 24 well plate
# The following are fields from the sample tracking excel sheet Ruth put together.
# I have no idea when they will be implemented or how.
testing_type = Column(String(64))
@@ -33,6 +36,7 @@ class WWSample(Base):
ww_seq_run_id = Column(String(64))
sample_type = Column(String(8))
pcr_results = Column(JSON)
elution_well = Column(String(8)) #: location on 96 well plate
def to_string(self) -> str:
@@ -51,6 +55,10 @@ class WWSample(Base):
Returns:
dict: well location and id NOTE: keys must sync with BCSample to_sub_dict below
"""
# well_col = self.well_number[1:]
# well_row = self.well_number[0]
# if well_col > 4:
# well
if self.ct_n1 != None and self.ct_n2 != None:
name = f"{self.ww_sample_full_id}\n\t- ct N1: {'{:.2f}'.format(self.ct_n1)} ({self.n1_status})\n\t- ct N2: {'{:.2f}'.format(self.ct_n2)} ({self.n2_status})"
else:
@@ -59,6 +67,34 @@ class WWSample(Base):
"well": self.well_number,
"name": name,
}
def to_hitpick(self) -> dict|None:
"""
Outputs a dictionary of locations if sample is positive
Returns:
dict: dictionary of sample id, row and column in elution plate
"""
# dictionary to translate row letters into numbers
row_dict = dict(A=1, B=2, C=3, D=4, E=5, F=6, G=7, H=8)
# if either n1 or n2 is positive, include this sample
try:
positive = any(["positive" in item for item in [self.n1_status, self.n2_status]])
except TypeError as e:
logger.error(f"Couldn't check positives for {self.rsl_number}. Looks like there isn't PCR data.")
return None
if positive:
try:
# The first character of the elution well is the row
well_row = row_dict[self.elution_well[0]]
# The remaining charagers are the columns
well_col = self.elution_well[1:]
except TypeError as e:
logger.error(f"This sample doesn't have elution plate info.")
return None
return dict(name=self.ww_sample_full_id, row=well_row, col=well_col)
else:
return None
class BCSample(Base):

View File

@@ -132,7 +132,10 @@ class SheetParser(object):
try:
expiry = row[3].date()
except AttributeError as e:
expiry = datetime.strptime(row[3], "%Y-%m-%d")
try:
expiry = datetime.strptime(row[3], "%Y-%m-%d")
except TypeError as e:
expiry = datetime.fromordinal(datetime(1900, 1, 1).toordinal() + row[3] - 2)
else:
logger.debug(f"Date: {row[3]}")
expiry = date.today()
@@ -378,7 +381,7 @@ class PCRParser(object):
self.samples_df['Assessment'] = well_call_df.values
except ValueError:
logger.error("Well call number doesn't match sample number")
logger.debug(f"Well call dr: {well_call_df}")
logger.debug(f"Well call df: {well_call_df}")
# iloc is [row][column]
for ii, row in self.samples_df.iterrows():
try:
@@ -387,7 +390,7 @@ class PCRParser(object):
sample_obj = dict(
sample = row['Sample'],
plate_rsl = self.plate_num,
well_num = row['Well Position']
elution_well = row['Well Position']
)
logger.debug(f"Got sample obj: {sample_obj}")
# logger.debug(f"row: {row}")

View File

@@ -213,3 +213,7 @@ def drop_reruns_from_df(ctx:dict, df: DataFrame) -> DataFrame:
return df
# else:
# return df
def make_hitpicks(input:list) -> DataFrame:
return DataFrame.from_records(input)