Added importing of PCR results.

This commit is contained in:
Landon Wark
2023-03-28 14:44:13 -05:00
parent d50a9793c7
commit 3c9f095937
16 changed files with 562 additions and 70 deletions

View File

@@ -5,6 +5,7 @@ Convenience functions for interacting with the database.
from . import models
from .models.kits import reagenttypes_kittypes
from .models.submissions import reagents_submissions
from .models.samples import WWSample
import pandas as pd
import sqlalchemy.exc
import sqlite3
@@ -392,7 +393,11 @@ def submissions_to_df(ctx:dict, sub_type:str|None=None) -> pd.DataFrame:
try:
df = df.drop("ext_info", axis=1)
except:
logger.warning(f"Couldn't drop 'controls' column from submissionsheet df.")
logger.warning(f"Couldn't drop 'ext_info' column from submissionsheet df.")
try:
df = df.drop("pcr_info", axis=1)
except:
logger.warning(f"Couldn't drop 'pcr_info' column from submissionsheet df.")
return df
@@ -676,4 +681,39 @@ def delete_submission_by_id(ctx:dict, id:int) -> None:
for sample in sub.samples:
ctx['database_session'].delete(sample)
ctx["database_session"].delete(sub)
ctx["database_session"].commit()
def lookup_ww_sample_by_rsl_sample_number(ctx:dict, rsl_number:str) -> models.WWSample:
"""
Retrieves wastewater sampel from database by rsl sample number
Args:
ctx (dict): settings passed dwon from gui
rsl_number (str): sample number assigned by robotics lab
Returns:
models.WWSample: instance of wastewater sample
"""
return ctx['database_session'].query(models.WWSample).filter(models.WWSample.rsl_number==rsl_number).first()
def update_ww_sample(ctx:dict, sample_obj:dict):
"""
Retrieves wastewater sample by rsl number (sample_obj['sample']) and updates values from constructed dictionary
Args:
ctx (dict): settings passed down from gui
sample_obj (dict): dictionary representing new values for database object
"""
ww_samp = lookup_ww_sample_by_rsl_sample_number(ctx=ctx, rsl_number=sample_obj['sample'])
if ww_samp != None:
for key, value in sample_obj.items():
logger.debug(f"Setting {key} to {value}")
# set attribute 'key' to 'value'
setattr(ww_samp, key, value)
else:
logger.error(f"Unable to find sample {sample_obj['sample']}")
return
ctx['database_session'].add(ww_samp)
ctx["database_session"].commit()

View File

@@ -2,7 +2,7 @@
All models for individual samples.
'''
from . import Base
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, FLOAT, BOOLEAN
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, FLOAT, BOOLEAN, JSON
from sqlalchemy.orm import relationship
@@ -25,11 +25,12 @@ class WWSample(Base):
testing_type = Column(String(64))
site_status = Column(String(64))
notes = Column(String(2000))
ct_n1 = Column(FLOAT(2))
ct_n2 = Column(FLOAT(2))
ct_n1 = Column(FLOAT(2)) #: AKA ct for N1
ct_n2 = Column(FLOAT(2)) #: AKA ct for N2
seq_submitted = Column(BOOLEAN())
ww_seq_run_id = Column(String(64))
sample_type = Column(String(8))
pcr_results = Column(JSON)
def to_string(self) -> str:
@@ -48,9 +49,13 @@ class WWSample(Base):
Returns:
dict: well location and id NOTE: keys must sync with BCSample to_sub_dict below
"""
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)}, ct N2: {'{:.2f}'.format(self.ct_n1)}"
else:
name = self.ww_sample_full_id
return {
"well": self.well_number,
"name": self.ww_sample_full_id,
"name": name,
}

View File

@@ -179,5 +179,20 @@ class Wastewater(BasicSubmission):
derivative submission type from BasicSubmission
"""
samples = relationship("WWSample", back_populates="rsl_plate", uselist=True)
pcr_info = Column(JSON)
# ww_sample_id = Column(String, ForeignKey("_ww_samples.id", ondelete="SET NULL", name="fk_WW_sample_id"))
__mapper_args__ = {"polymorphic_identity": "wastewater", "polymorphic_load": "inline"}
__mapper_args__ = {"polymorphic_identity": "wastewater", "polymorphic_load": "inline"}
def to_dict(self) -> dict:
"""
Extends parent class method to add controls to dict
Returns:
dict: dictionary used in submissions summary
"""
output = super().to_dict()
try:
output['pcr_info'] = json.loads(self.pcr_info)
except TypeError as e:
pass
return output

View File

@@ -1,16 +1,18 @@
'''
contains parser object for pulling values from client generated submission sheets.
'''
from getpass import getuser
import pandas as pd
from pathlib import Path
from backend.db.models import WWSample, BCSample
from backend.db import lookup_ww_sample_by_rsl_sample_number
import logging
from collections import OrderedDict
import re
import numpy as np
from datetime import date
import uuid
from tools import check_not_nan
from tools import check_not_nan, retrieve_rsl_number
logger = logging.getLogger(f"submissions.{__name__}")
@@ -203,6 +205,7 @@ class SheetParser(object):
sample_parser = SampleParser(submission_info.iloc[16:40])
sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples")
self.sub['samples'] = sample_parse()
self.sub['csv'] = self.xl.parse("Copy to import file", dtype=object)
class SampleParser(object):
@@ -284,3 +287,112 @@ class SampleParser(object):
new.well_number = sample['Unnamed: 1']
new_list.append(new)
return new_list
class PCRParser(object):
"""
Object to pull data from Design and Analysis PCR export file.
"""
def __init__(self, ctx:dict, filepath:Path|None = None) -> None:
"""
Initializes object.
Args:
ctx (dict): settings passed down from gui.
filepath (Path | None, optional): file to parse. Defaults to None.
"""
self.ctx = ctx
logger.debug(f"Parsing {filepath.__str__()}")
if filepath == None:
logger.error(f"No filepath given.")
self.xl = None
else:
try:
self.xl = pd.ExcelFile(filepath.__str__())
except ValueError as e:
logger.error(f"Incorrect value: {e}")
self.xl = None
except PermissionError:
logger.error(f"Couldn't get permissions for {filepath.__str__()}. Operation might have been cancelled.")
return
# self.pcr = OrderedDict()
self.pcr = {}
self.plate_num, self.submission_type = retrieve_rsl_number(filepath.__str__())
logger.debug(f"Set plate number to {self.plate_num} and type to {self.submission_type}")
self.samples = []
parser = getattr(self, f"parse_{self.submission_type}")
parser()
def parse_general(self, sheet_name:str):
"""
Parse general info rows for all types of PCR results
Args:
sheet_name (str): Name of sheet in excel workbook that holds info.
"""
df = self.xl.parse(sheet_name=sheet_name, dtype=object).fillna("")
# self.pcr['file'] = df.iloc[1][1]
self.pcr['comment'] = df.iloc[0][1]
self.pcr['operator'] = df.iloc[1][1]
self.pcr['barcode'] = df.iloc[2][1]
self.pcr['instrument'] = df.iloc[3][1]
self.pcr['block_type'] = df.iloc[4][1]
self.pcr['instrument_name'] = df.iloc[5][1]
self.pcr['instrument_serial'] = df.iloc[6][1]
self.pcr['heated_cover_serial'] = df.iloc[7][1]
self.pcr['block_serial'] = df.iloc[8][1]
self.pcr['run-start'] = df.iloc[9][1]
self.pcr['run_end'] = df.iloc[10][1]
self.pcr['run_duration'] = df.iloc[11][1]
self.pcr['sample_volume'] = df.iloc[12][1]
self.pcr['cover_temp'] = df.iloc[13][1]
self.pcr['passive_ref'] = df.iloc[14][1]
self.pcr['pcr_step'] = df.iloc[15][1]
self.pcr['quant_cycle_method'] = df.iloc[16][1]
self.pcr['analysis_time'] = df.iloc[17][1]
self.pcr['software'] = df.iloc[18][1]
self.pcr['plugin'] = df.iloc[19][1]
self.pcr['exported_on'] = df.iloc[20][1]
self.pcr['imported_by'] = getuser()
return df
def parse_wastewater(self):
"""
Parse specific to wastewater samples.
"""
df = self.parse_general(sheet_name="Results")
self.samples_df = df.iloc[23:][0:]
# iloc is [row][column]
for ii, row in self.samples_df.iterrows():
try:
sample_obj = [sample for sample in self.samples if sample['sample'] == row[3]][0]
except IndexError:
sample_obj = dict(
sample = row[3],
)
logger.debug(f"Got sample obj: {sample_obj}")
# logger.debug(f"row: {row}")
# rsl_num = row[3]
# # logger.debug(f"Looking up: {rsl_num}")
# ww_samp = lookup_ww_sample_by_rsl_sample_number(ctx=self.ctx, rsl_number=rsl_num)
# logger.debug(f"Got: {ww_samp}")
match row[4]:
case "N1":
if isinstance(row[12], float):
sample_obj['ct_n1'] = row[12]
else:
sample_obj['ct_n1'] = 0.0
case "N2":
if isinstance(row[12], float):
sample_obj['ct_n2'] = row[12]
else:
sample_obj['ct_n2'] = 0.0
case _:
logger.warning(f"Unexpected input for row[4]: {row[4]}")
self.samples.append(sample_obj)