During code cleanup
This commit is contained in:
@@ -1,3 +1,10 @@
|
|||||||
|
## 202405.04
|
||||||
|
|
||||||
|
## 202405.04
|
||||||
|
|
||||||
|
- Fixed Reagents not being updated on edit.
|
||||||
|
- Fixed data resorting after submitting new run.
|
||||||
|
|
||||||
## 202405.03
|
## 202405.03
|
||||||
|
|
||||||
- Settings can now pull values from the db.
|
- Settings can now pull values from the db.
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ Contains all models for sqlalchemy
|
|||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import sys, logging
|
import sys, logging
|
||||||
|
|
||||||
from sqlalchemy import Column, INTEGER, String, JSON
|
from sqlalchemy import Column, INTEGER, String, JSON
|
||||||
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session
|
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
@@ -81,7 +80,13 @@ class BaseClass(Base):
|
|||||||
return ctx.backup_path
|
return ctx.backup_path
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_info(cls, *args) -> dict | List[str]:
|
def get_default_info(cls, *args) -> dict | list | str:
|
||||||
|
"""
|
||||||
|
Returns default info for a model
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict | list | str: Output of key:value dict or single (list, str) desired variable
|
||||||
|
"""
|
||||||
dicto = dict(singles=['id'])
|
dicto = dict(singles=['id'])
|
||||||
output = {}
|
output = {}
|
||||||
for k, v in dicto.items():
|
for k, v in dicto.items():
|
||||||
@@ -95,7 +100,13 @@ class BaseClass(Base):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query(cls, **kwargs):
|
def query(cls, **kwargs) -> Any | List[Any]:
|
||||||
|
"""
|
||||||
|
Default query function for models
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any | List[Any]: Result of query execution.
|
||||||
|
"""
|
||||||
return cls.execute_query(**kwargs)
|
return cls.execute_query(**kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -119,13 +130,12 @@ class BaseClass(Base):
|
|||||||
singles = model.get_default_info('singles')
|
singles = model.get_default_info('singles')
|
||||||
logger.debug(f"Querying: {model}, with kwargs: {kwargs}")
|
logger.debug(f"Querying: {model}, with kwargs: {kwargs}")
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
logger.debug(f"Using key: {k} with value: {v}")
|
# logger.debug(f"Using key: {k} with value: {v}")
|
||||||
# logger.debug(f"That key found attribute: {attr} with type: {attr}")
|
|
||||||
try:
|
try:
|
||||||
attr = getattr(model, k)
|
attr = getattr(model, k)
|
||||||
query = query.filter(attr == v)
|
query = query.filter(attr == v)
|
||||||
except (ArgumentError, AttributeError) as e:
|
except (ArgumentError, AttributeError) as e:
|
||||||
logger.error(f"Attribute {k} available due to:\n\t{e}\nSkipping.")
|
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
||||||
if k in singles:
|
if k in singles:
|
||||||
limit = 1
|
limit = 1
|
||||||
with query.session.no_autoflush:
|
with query.session.no_autoflush:
|
||||||
@@ -155,6 +165,9 @@ class BaseClass(Base):
|
|||||||
|
|
||||||
|
|
||||||
class ConfigItem(BaseClass):
|
class ConfigItem(BaseClass):
|
||||||
|
"""
|
||||||
|
Key:JSON objects to store config settings in database.
|
||||||
|
"""
|
||||||
id = Column(INTEGER, primary_key=True)
|
id = Column(INTEGER, primary_key=True)
|
||||||
key = Column(String(32))
|
key = Column(String(32))
|
||||||
value = Column(JSON)
|
value = Column(JSON)
|
||||||
@@ -163,8 +176,18 @@ class ConfigItem(BaseClass):
|
|||||||
return f"ConfigItem({self.key} : {self.value})"
|
return f"ConfigItem({self.key} : {self.value})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_config_items(cls):
|
def get_config_items(cls, *args) -> ConfigItem|List[ConfigItem]:
|
||||||
return cls.__database_session__.query(cls).all()
|
"""
|
||||||
|
Get desired config items from database
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ConfigItem|List[ConfigItem]: Config item(s)
|
||||||
|
"""
|
||||||
|
config_items = cls.__database_session__.query(cls).all()
|
||||||
|
config_items = [item for item in config_items if item.key in args]
|
||||||
|
if len(args) == 1:
|
||||||
|
config_items = config_items[0]
|
||||||
|
return config_items
|
||||||
|
|
||||||
|
|
||||||
from .controls import *
|
from .controls import *
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ from typing import List, Literal
|
|||||||
from pandas import ExcelFile
|
from pandas import ExcelFile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from . import Base, BaseClass, Organization
|
from . import Base, BaseClass, Organization
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(f'submissions.{__name__}')
|
logger = logging.getLogger(f'submissions.{__name__}')
|
||||||
|
|
||||||
@@ -300,7 +302,7 @@ class ReagentType(BaseClass):
|
|||||||
# logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}")
|
# logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}")
|
||||||
# logger.debug(f"Kit reagent types: {kit_type.reagent_types}")
|
# logger.debug(f"Kit reagent types: {kit_type.reagent_types}")
|
||||||
result = list(set(kit_type.reagent_types).intersection(reagent.type))
|
result = list(set(kit_type.reagent_types).intersection(reagent.type))
|
||||||
logger.debug(f"Result: {result}")
|
# logger.debug(f"Result: {result}")
|
||||||
try:
|
try:
|
||||||
return result[0]
|
return result[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
@@ -386,7 +388,7 @@ class Reagent(BaseClass):
|
|||||||
place_holder = self.expiry + reagent_role.eol_ext
|
place_holder = self.expiry + reagent_role.eol_ext
|
||||||
except (TypeError, AttributeError) as e:
|
except (TypeError, AttributeError) as e:
|
||||||
place_holder = date.today()
|
place_holder = date.today()
|
||||||
logger.debug(f"We got a type error setting {self.lot} expiry: {e}. setting to today for testing")
|
logger.error(f"We got a type error setting {self.lot} expiry: {e}. setting to today for testing")
|
||||||
if self.expiry.year == 1970:
|
if self.expiry.year == 1970:
|
||||||
place_holder = "NA"
|
place_holder = "NA"
|
||||||
else:
|
else:
|
||||||
@@ -410,14 +412,14 @@ class Reagent(BaseClass):
|
|||||||
Report: Result of operation
|
Report: Result of operation
|
||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
logger.debug(f"Attempting update of reagent type at intersection of ({self}), ({kit})")
|
# logger.debug(f"Attempting update of last used reagent type at intersection of ({self}), ({kit})")
|
||||||
rt = ReagentType.query(kit_type=kit, reagent=self, limit=1)
|
rt = ReagentType.query(kit_type=kit, reagent=self, limit=1)
|
||||||
if rt is not None:
|
if rt is not None:
|
||||||
logger.debug(f"got reagenttype {rt}")
|
# logger.debug(f"got reagenttype {rt}")
|
||||||
assoc = KitTypeReagentTypeAssociation.query(kit_type=kit, reagent_type=rt)
|
assoc = KitTypeReagentTypeAssociation.query(kit_type=kit, reagent_type=rt)
|
||||||
if assoc is not None:
|
if assoc is not None:
|
||||||
if assoc.last_used != self.lot:
|
if assoc.last_used != self.lot:
|
||||||
logger.debug(f"Updating {assoc} last used to {self.lot}")
|
# logger.debug(f"Updating {assoc} last used to {self.lot}")
|
||||||
assoc.last_used = self.lot
|
assoc.last_used = self.lot
|
||||||
result = assoc.save()
|
result = assoc.save()
|
||||||
report.add_result(result)
|
report.add_result(result)
|
||||||
@@ -607,7 +609,7 @@ class SubmissionType(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
List[str]: List of sheet names
|
List[str]: List of sheet names
|
||||||
"""
|
"""
|
||||||
return ExcelFile(self.template_file).sheet_names
|
return ExcelFile(BytesIO(self.template_file)).sheet_names
|
||||||
|
|
||||||
def set_template_file(self, filepath: Path | str):
|
def set_template_file(self, filepath: Path | str):
|
||||||
"""
|
"""
|
||||||
@@ -633,7 +635,7 @@ class SubmissionType(BaseClass):
|
|||||||
|
|
||||||
def construct_info_map(self, mode: Literal['read', 'write']) -> dict:
|
def construct_info_map(self, mode: Literal['read', 'write']) -> dict:
|
||||||
info = self.info_map
|
info = self.info_map
|
||||||
logger.debug(f"Info map: {info}")
|
# logger.debug(f"Info map: {info}")
|
||||||
output = {}
|
output = {}
|
||||||
# for k,v in info.items():
|
# for k,v in info.items():
|
||||||
# info[k]['write'] += info[k]['read']
|
# info[k]['write'] += info[k]['read']
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ class BasicSubmission(BaseClass):
|
|||||||
if report:
|
if report:
|
||||||
return output
|
return output
|
||||||
if full_data:
|
if full_data:
|
||||||
logger.debug(f"Attempting reagents.")
|
# logger.debug(f"Attempting reagents.")
|
||||||
try:
|
try:
|
||||||
reagents = [item.to_sub_dict(extraction_kit=self.extraction_kit) for item in
|
reagents = [item.to_sub_dict(extraction_kit=self.extraction_kit) for item in
|
||||||
self.submission_reagent_associations]
|
self.submission_reagent_associations]
|
||||||
@@ -240,9 +240,9 @@ class BasicSubmission(BaseClass):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"We got an error retrieving reagents: {e}")
|
logger.error(f"We got an error retrieving reagents: {e}")
|
||||||
reagents = None
|
reagents = None
|
||||||
logger.debug(f"Running samples.")
|
# logger.debug(f"Running samples.")
|
||||||
samples = self.adjust_to_dict_samples(backup=backup)
|
samples = self.adjust_to_dict_samples(backup=backup)
|
||||||
logger.debug("Running equipment")
|
# logger.debug("Running equipment")
|
||||||
try:
|
try:
|
||||||
equipment = [item.to_sub_dict() for item in self.submission_equipment_associations]
|
equipment = [item.to_sub_dict() for item in self.submission_equipment_associations]
|
||||||
if len(equipment) == 0:
|
if len(equipment) == 0:
|
||||||
@@ -297,7 +297,7 @@ class BasicSubmission(BaseClass):
|
|||||||
# Get kit associated with this submission
|
# Get kit associated with this submission
|
||||||
assoc = [item for item in self.extraction_kit.kit_submissiontype_associations if
|
assoc = [item for item in self.extraction_kit.kit_submissiontype_associations if
|
||||||
item.submission_type == self.submission_type][0]
|
item.submission_type == self.submission_type][0]
|
||||||
logger.debug(f"Came up with association: {assoc}")
|
# logger.debug(f"Came up with association: {assoc}")
|
||||||
# If every individual cost is 0 this is probably an old plate.
|
# If every individual cost is 0 this is probably an old plate.
|
||||||
if all(item == 0.0 for item in [assoc.constant_cost, assoc.mutable_cost_column, assoc.mutable_cost_sample]):
|
if all(item == 0.0 for item in [assoc.constant_cost, assoc.mutable_cost_column, assoc.mutable_cost_sample]):
|
||||||
try:
|
try:
|
||||||
@@ -369,7 +369,7 @@ class BasicSubmission(BaseClass):
|
|||||||
return [item.role for item in self.submission_equipment_associations]
|
return [item.role for item in self.submission_equipment_associations]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def submissions_to_df(cls, submission_type: str | None = None, limit: int = 0) -> pd.DataFrame:
|
def submissions_to_df(cls, submission_type: str | None = None, limit: int = 0, chronologic:bool=True) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Convert all submissions to dataframe
|
Convert all submissions to dataframe
|
||||||
|
|
||||||
@@ -380,11 +380,11 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
pd.DataFrame: Pandas Dataframe of all relevant submissions
|
pd.DataFrame: Pandas Dataframe of all relevant submissions
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Querying Type: {submission_type}")
|
# logger.debug(f"Querying Type: {submission_type}")
|
||||||
logger.debug(f"Using limit: {limit}")
|
# logger.debug(f"Using limit: {limit}")
|
||||||
# use lookup function to create list of dicts
|
# use lookup function to create list of dicts
|
||||||
subs = [item.to_dict() for item in cls.query(submission_type=submission_type, limit=limit)]
|
subs = [item.to_dict() for item in cls.query(submission_type=submission_type, limit=limit, chronologic=chronologic)]
|
||||||
logger.debug(f"Got {len(subs)} submissions.")
|
# logger.debug(f"Got {len(subs)} submissions.")
|
||||||
df = pd.DataFrame.from_records(subs)
|
df = pd.DataFrame.from_records(subs)
|
||||||
# Exclude sub information
|
# Exclude sub information
|
||||||
for item in ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents',
|
for item in ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents',
|
||||||
@@ -393,6 +393,8 @@ class BasicSubmission(BaseClass):
|
|||||||
df = df.drop(item, axis=1)
|
df = df.drop(item, axis=1)
|
||||||
except:
|
except:
|
||||||
logger.warning(f"Couldn't drop '{item}' column from submissionsheet df.")
|
logger.warning(f"Couldn't drop '{item}' column from submissionsheet df.")
|
||||||
|
if chronologic:
|
||||||
|
df.sort_values(by="Submitted Date", axis=0, inplace=True, ascending=False)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def set_attribute(self, key: str, value):
|
def set_attribute(self, key: str, value):
|
||||||
@@ -466,6 +468,9 @@ class BasicSubmission(BaseClass):
|
|||||||
flag_modified(self, key)
|
flag_modified(self, key)
|
||||||
return
|
return
|
||||||
case _:
|
case _:
|
||||||
|
try:
|
||||||
|
field_value = value.strip()
|
||||||
|
except AttributeError:
|
||||||
field_value = value
|
field_value = value
|
||||||
# insert into field
|
# insert into field
|
||||||
try:
|
try:
|
||||||
@@ -502,7 +507,7 @@ class BasicSubmission(BaseClass):
|
|||||||
"""
|
"""
|
||||||
from backend.validators import PydSubmission, PydSample, PydReagent, PydEquipment
|
from backend.validators import PydSubmission, PydSample, PydReagent, PydEquipment
|
||||||
dicto = self.to_dict(full_data=True, backup=backup)
|
dicto = self.to_dict(full_data=True, backup=backup)
|
||||||
logger.debug("To dict complete")
|
# logger.debug("To dict complete")
|
||||||
new_dict = {}
|
new_dict = {}
|
||||||
for key, value in dicto.items():
|
for key, value in dicto.items():
|
||||||
# start = time()
|
# start = time()
|
||||||
@@ -526,11 +531,11 @@ class BasicSubmission(BaseClass):
|
|||||||
case "id":
|
case "id":
|
||||||
pass
|
pass
|
||||||
case _:
|
case _:
|
||||||
logger.debug(f"Setting dict {key} to {value}")
|
# logger.debug(f"Setting dict {key} to {value}")
|
||||||
new_dict[key.lower().replace(" ", "_")] = dict(value=value, missing=missing)
|
new_dict[key.lower().replace(" ", "_")] = dict(value=value, missing=missing)
|
||||||
# logger.debug(f"{key} complete after {time()-start}")
|
# logger.debug(f"{key} complete after {time()-start}")
|
||||||
new_dict['filepath'] = Path(tempfile.TemporaryFile().name)
|
new_dict['filepath'] = Path(tempfile.TemporaryFile().name)
|
||||||
logger.debug("Done converting fields.")
|
# logger.debug("Done converting fields.")
|
||||||
return PydSubmission(**new_dict)
|
return PydSubmission(**new_dict)
|
||||||
|
|
||||||
def save(self, original: bool = True):
|
def save(self, original: bool = True):
|
||||||
@@ -604,20 +609,20 @@ class BasicSubmission(BaseClass):
|
|||||||
|
|
||||||
# Child class custom functions
|
# Child class custom functions
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def custom_platemap(cls, xl: pd.ExcelFile, plate_map: pd.DataFrame) -> pd.DataFrame:
|
# def custom_platemap(cls, xl: pd.ExcelFile, plate_map: pd.DataFrame) -> pd.DataFrame:
|
||||||
"""
|
# """
|
||||||
Stupid stopgap solution to there being an issue with the Bacterial Culture plate map
|
# Stupid stopgap solution to there being an issue with the Bacterial Culture plate map
|
||||||
|
#
|
||||||
Args:
|
# Args:
|
||||||
xl (pd.ExcelFile): original xl workbook, used for child classes mostly
|
# xl (pd.ExcelFile): original xl workbook, used for child classes mostly
|
||||||
plate_map (pd.DataFrame): original plate map
|
# plate_map (pd.DataFrame): original plate map
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
pd.DataFrame: updated plate map.
|
# pd.DataFrame: updated plate map.
|
||||||
"""
|
# """
|
||||||
logger.info(f"Calling {cls.__mapper_args__['polymorphic_identity']} plate mapper.")
|
# logger.info(f"Calling {cls.__mapper_args__['polymorphic_identity']} plate mapper.")
|
||||||
return plate_map
|
# return plate_map
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None) -> dict:
|
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None) -> dict:
|
||||||
@@ -696,15 +701,15 @@ class BasicSubmission(BaseClass):
|
|||||||
# logger.info(f"Hello from {cls.__mapper_args__['polymorphic_identity']} Enforcer!")
|
# logger.info(f"Hello from {cls.__mapper_args__['polymorphic_identity']} Enforcer!")
|
||||||
# return instr
|
# return instr
|
||||||
from backend.validators import RSLNamer
|
from backend.validators import RSLNamer
|
||||||
logger.debug(f"instr coming into {cls}: {instr}")
|
# logger.debug(f"instr coming into {cls}: {instr}")
|
||||||
logger.debug(f"data coming into {cls}: {data}")
|
# logger.debug(f"data coming into {cls}: {data}")
|
||||||
defaults = cls.get_default_info("abbreviation", "submission_type")
|
defaults = cls.get_default_info("abbreviation", "submission_type")
|
||||||
data['abbreviation'] = defaults['abbreviation']
|
data['abbreviation'] = defaults['abbreviation']
|
||||||
if 'submission_type' not in data.keys() or data['submission_type'] in [None, ""]:
|
if 'submission_type' not in data.keys() or data['submission_type'] in [None, ""]:
|
||||||
data['submission_type'] = defaults['submission_type']
|
data['submission_type'] = defaults['submission_type']
|
||||||
# outstr = super().enforce_name(instr=instr, data=data)
|
# outstr = super().enforce_name(instr=instr, data=data)
|
||||||
if instr in [None, ""]:
|
if instr in [None, ""]:
|
||||||
logger.debug("Sending to RSLNamer to make new plate name.")
|
# logger.debug("Sending to RSLNamer to make new plate name.")
|
||||||
outstr = RSLNamer.construct_new_plate_name(data=data)
|
outstr = RSLNamer.construct_new_plate_name(data=data)
|
||||||
else:
|
else:
|
||||||
outstr = instr
|
outstr = instr
|
||||||
@@ -761,9 +766,9 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
list: _description_
|
list: _description_
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Hello from {cls.__mapper_args__['polymorphic_identity']} PCR parser!")
|
# logger.debug(f"Hello from {cls.__mapper_args__['polymorphic_identity']} PCR parser!")
|
||||||
pcr_sample_map = cls.get_submission_type().sample_map['pcr_samples']
|
pcr_sample_map = cls.get_submission_type().sample_map['pcr_samples']
|
||||||
logger.debug(f'sample map: {pcr_sample_map}')
|
# logger.debug(f'sample map: {pcr_sample_map}')
|
||||||
main_sheet = xl[pcr_sample_map['main_sheet']]
|
main_sheet = xl[pcr_sample_map['main_sheet']]
|
||||||
samples = []
|
samples = []
|
||||||
fields = {k: v for k, v in pcr_sample_map.items() if k not in ['main_sheet', 'start_row']}
|
fields = {k: v for k, v in pcr_sample_map.items() if k not in ['main_sheet', 'start_row']}
|
||||||
@@ -816,7 +821,7 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
List[dict]: Updated dictionaries
|
List[dict]: Updated dictionaries
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.")
|
# logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.")
|
||||||
return [item.to_sub_dict() for item in self.submission_sample_associations]
|
return [item.to_sub_dict() for item in self.submission_sample_associations]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -833,7 +838,7 @@ class BasicSubmission(BaseClass):
|
|||||||
base_dict['excluded'] = cls.get_default_info('details_ignore')
|
base_dict['excluded'] = cls.get_default_info('details_ignore')
|
||||||
env = jinja_template_loading()
|
env = jinja_template_loading()
|
||||||
temp_name = f"{cls.__name__.lower()}_details.html"
|
temp_name = f"{cls.__name__.lower()}_details.html"
|
||||||
logger.debug(f"Returning template: {temp_name}")
|
# logger.debug(f"Returning template: {temp_name}")
|
||||||
try:
|
try:
|
||||||
template = env.get_template(temp_name)
|
template = env.get_template(temp_name)
|
||||||
except TemplateNotFound as e:
|
except TemplateNotFound as e:
|
||||||
@@ -872,7 +877,7 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
models.BasicSubmission | List[models.BasicSubmission]: Submission(s) of interest
|
models.BasicSubmission | List[models.BasicSubmission]: Submission(s) of interest
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Incoming kwargs: {kwargs}")
|
# logger.debug(f"Incoming kwargs: {kwargs}")
|
||||||
# NOTE: if you go back to using 'model' change the appropriate cls to model in the query filters
|
# NOTE: if you go back to using 'model' change the appropriate cls to model in the query filters
|
||||||
if submission_type is not None:
|
if submission_type is not None:
|
||||||
# if isinstance(submission_type, SubmissionType):
|
# if isinstance(submission_type, SubmissionType):
|
||||||
@@ -882,7 +887,7 @@ class BasicSubmission(BaseClass):
|
|||||||
# model = cls.find_subclasses(submission_type=submission_type)
|
# model = cls.find_subclasses(submission_type=submission_type)
|
||||||
elif len(kwargs) > 0:
|
elif len(kwargs) > 0:
|
||||||
# find the subclass containing the relevant attributes
|
# find the subclass containing the relevant attributes
|
||||||
logger.debug(f"Attributes for search: {kwargs}")
|
# logger.debug(f"Attributes for search: {kwargs}")
|
||||||
# model = cls.find_subclasses(attrs=kwargs)
|
# model = cls.find_subclasses(attrs=kwargs)
|
||||||
model = cls.find_polymorphic_subclass(attrs=kwargs)
|
model = cls.find_polymorphic_subclass(attrs=kwargs)
|
||||||
else:
|
else:
|
||||||
@@ -895,7 +900,7 @@ class BasicSubmission(BaseClass):
|
|||||||
logger.warning(f"End date with no start date, using Jan 1, 2023")
|
logger.warning(f"End date with no start date, using Jan 1, 2023")
|
||||||
start_date = date(2023, 1, 1)
|
start_date = date(2023, 1, 1)
|
||||||
if start_date is not None:
|
if start_date is not None:
|
||||||
logger.debug(f"Querying with start date: {start_date} and end date: {end_date}")
|
# logger.debug(f"Querying with start date: {start_date} and end date: {end_date}")
|
||||||
match start_date:
|
match start_date:
|
||||||
case date():
|
case date():
|
||||||
# logger.debug(f"Lookup BasicSubmission by start_date({start_date})")
|
# logger.debug(f"Lookup BasicSubmission by start_date({start_date})")
|
||||||
@@ -919,7 +924,7 @@ class BasicSubmission(BaseClass):
|
|||||||
# logger.debug(f"Lookup BasicSubmission by parsed str end_date {end_date}")
|
# logger.debug(f"Lookup BasicSubmission by parsed str end_date {end_date}")
|
||||||
end_date = parse(end_date).strftime("%Y-%m-%d")
|
end_date = parse(end_date).strftime("%Y-%m-%d")
|
||||||
# logger.debug(f"Looking up BasicSubmissions from start date: {start_date} and end date: {end_date}")
|
# logger.debug(f"Looking up BasicSubmissions from start date: {start_date} and end date: {end_date}")
|
||||||
logger.debug(f"Start date {start_date} == End date {end_date}: {start_date == end_date}")
|
# logger.debug(f"Start date {start_date} == End date {end_date}: {start_date == end_date}")
|
||||||
# logger.debug(f"Compensating for same date by using time")
|
# logger.debug(f"Compensating for same date by using time")
|
||||||
if start_date == end_date:
|
if start_date == end_date:
|
||||||
start_date = datetime.strptime(start_date, "%Y-%m-%d").strftime("%Y-%m-%d %H:%M:%S.%f")
|
start_date = datetime.strptime(start_date, "%Y-%m-%d").strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||||
@@ -999,7 +1004,7 @@ class BasicSubmission(BaseClass):
|
|||||||
f"{key} is not allowed as a query argument as it could lead to creation of duplicate objects. Use .query() instead.")
|
f"{key} is not allowed as a query argument as it could lead to creation of duplicate objects. Use .query() instead.")
|
||||||
instance = cls.query(submission_type=submission_type, limit=1, **kwargs)
|
instance = cls.query(submission_type=submission_type, limit=1, **kwargs)
|
||||||
# logger.debug(f"Retrieved instance: {instance}")
|
# logger.debug(f"Retrieved instance: {instance}")
|
||||||
if instance == None:
|
if instance is None:
|
||||||
used_class = cls.find_polymorphic_subclass(attrs=kwargs, polymorphic_identity=submission_type)
|
used_class = cls.find_polymorphic_subclass(attrs=kwargs, polymorphic_identity=submission_type)
|
||||||
instance = used_class(**kwargs)
|
instance = used_class(**kwargs)
|
||||||
match submission_type:
|
match submission_type:
|
||||||
@@ -1041,11 +1046,11 @@ class BasicSubmission(BaseClass):
|
|||||||
e: _description_
|
e: _description_
|
||||||
"""
|
"""
|
||||||
from frontend.widgets.pop_ups import QuestionAsker
|
from frontend.widgets.pop_ups import QuestionAsker
|
||||||
logger.debug("Hello from delete")
|
# logger.debug("Hello from delete")
|
||||||
fname = self.__backup_path__.joinpath(f"{self.rsl_plate_num}-backup({date.today().strftime('%Y%m%d')})")
|
fname = self.__backup_path__.joinpath(f"{self.rsl_plate_num}-backup({date.today().strftime('%Y%m%d')})")
|
||||||
msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {self.rsl_plate_num}?\n")
|
msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {self.rsl_plate_num}?\n")
|
||||||
if msg.exec():
|
if msg.exec():
|
||||||
# self.backup(fname=fname, full_backup=True)
|
self.backup(fname=fname, full_backup=True)
|
||||||
self.__database_session__.delete(self)
|
self.__database_session__.delete(self)
|
||||||
try:
|
try:
|
||||||
self.__database_session__.commit()
|
self.__database_session__.commit()
|
||||||
@@ -1061,7 +1066,7 @@ class BasicSubmission(BaseClass):
|
|||||||
Args:
|
Args:
|
||||||
obj (_type_): parent widget
|
obj (_type_): parent widget
|
||||||
"""
|
"""
|
||||||
logger.debug("Hello from details")
|
# logger.debug("Hello from details")
|
||||||
from frontend.widgets.submission_details import SubmissionDetails
|
from frontend.widgets.submission_details import SubmissionDetails
|
||||||
dlg = SubmissionDetails(parent=obj, sub=self)
|
dlg = SubmissionDetails(parent=obj, sub=self)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
@@ -1070,7 +1075,7 @@ class BasicSubmission(BaseClass):
|
|||||||
def edit(self, obj):
|
def edit(self, obj):
|
||||||
from frontend.widgets.submission_widget import SubmissionFormWidget
|
from frontend.widgets.submission_widget import SubmissionFormWidget
|
||||||
for widg in obj.app.table_widget.formwidget.findChildren(SubmissionFormWidget):
|
for widg in obj.app.table_widget.formwidget.findChildren(SubmissionFormWidget):
|
||||||
logger.debug(widg)
|
# logger.debug(widg)
|
||||||
widg.setParent(None)
|
widg.setParent(None)
|
||||||
pyd = self.to_pydantic(backup=True)
|
pyd = self.to_pydantic(backup=True)
|
||||||
form = pyd.to_form(parent=obj)
|
form = pyd.to_form(parent=obj)
|
||||||
@@ -1088,7 +1093,7 @@ class BasicSubmission(BaseClass):
|
|||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
comment = dlg.parse_form()
|
comment = dlg.parse_form()
|
||||||
self.set_attribute(key='comment', value=comment)
|
self.set_attribute(key='comment', value=comment)
|
||||||
logger.debug(self.comment)
|
# logger.debug(self.comment)
|
||||||
self.save(original=False)
|
self.save(original=False)
|
||||||
|
|
||||||
def add_equipment(self, obj):
|
def add_equipment(self, obj):
|
||||||
@@ -1102,11 +1107,11 @@ class BasicSubmission(BaseClass):
|
|||||||
dlg = EquipmentUsage(parent=obj, submission=self)
|
dlg = EquipmentUsage(parent=obj, submission=self)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
equipment = dlg.parse_form()
|
equipment = dlg.parse_form()
|
||||||
logger.debug(f"We've got equipment: {equipment}")
|
# logger.debug(f"We've got equipment: {equipment}")
|
||||||
for equip in equipment:
|
for equip in equipment:
|
||||||
logger.debug(f"Processing: {equip}")
|
# logger.debug(f"Processing: {equip}")
|
||||||
_, assoc = equip.toSQL(submission=self)
|
_, assoc = equip.toSQL(submission=self)
|
||||||
logger.debug(f"Appending SubmissionEquipmentAssociation: {assoc}")
|
# logger.debug(f"Appending SubmissionEquipmentAssociation: {assoc}")
|
||||||
assoc.save()
|
assoc.save()
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
@@ -1120,14 +1125,14 @@ class BasicSubmission(BaseClass):
|
|||||||
fname (Path | None, optional): Filename of xlsx file. Defaults to None.
|
fname (Path | None, optional): Filename of xlsx file. Defaults to None.
|
||||||
full_backup (bool, optional): Whether or not to make yaml file. Defaults to False.
|
full_backup (bool, optional): Whether or not to make yaml file. Defaults to False.
|
||||||
"""
|
"""
|
||||||
logger.debug("Hello from backup.")
|
# logger.debug("Hello from backup.")
|
||||||
pyd = self.to_pydantic(backup=True)
|
pyd = self.to_pydantic(backup=True)
|
||||||
if fname == None:
|
if fname is None:
|
||||||
from frontend.widgets.functions import select_save_file
|
from frontend.widgets.functions import select_save_file
|
||||||
fname = select_save_file(default_name=pyd.construct_filename(), extension="xlsx", obj=obj)
|
fname = select_save_file(default_name=pyd.construct_filename(), extension="xlsx", obj=obj)
|
||||||
logger.debug(fname.name)
|
# logger.debug(fname.name)
|
||||||
if fname.name == "":
|
if fname.name == "":
|
||||||
logger.debug(f"export cancelled.")
|
# logger.debug(f"export cancelled.")
|
||||||
return
|
return
|
||||||
# pyd.filepath = fname
|
# pyd.filepath = fname
|
||||||
if full_backup:
|
if full_backup:
|
||||||
@@ -1171,28 +1176,28 @@ class BacterialCulture(BasicSubmission):
|
|||||||
output['controls'] = [item.to_sub_dict() for item in self.controls]
|
output['controls'] = [item.to_sub_dict() for item in self.controls]
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def custom_platemap(cls, xl: pd.ExcelFile, plate_map: pd.DataFrame) -> pd.DataFrame:
|
# def custom_platemap(cls, xl: pd.ExcelFile, plate_map: pd.DataFrame) -> pd.DataFrame:
|
||||||
"""
|
# """
|
||||||
Stupid stopgap solution to there being an issue with the Bacterial Culture plate map. Extends parent.
|
# Stupid stopgap solution to there being an issue with the Bacterial Culture plate map. Extends parent.
|
||||||
|
#
|
||||||
Args:
|
# Args:
|
||||||
xl (pd.ExcelFile): original xl workbook
|
# xl (pd.ExcelFile): original xl workbook
|
||||||
plate_map (pd.DataFrame): original plate map
|
# plate_map (pd.DataFrame): original plate map
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
pd.DataFrame: updated plate map.
|
# pd.DataFrame: updated plate map.
|
||||||
"""
|
# """
|
||||||
plate_map = super().custom_platemap(xl, plate_map)
|
# plate_map = super().custom_platemap(xl, plate_map)
|
||||||
num1 = xl.parse("Sample List").iloc[40, 1]
|
# num1 = xl.parse("Sample List").iloc[40, 1]
|
||||||
num2 = xl.parse("Sample List").iloc[41, 1]
|
# num2 = xl.parse("Sample List").iloc[41, 1]
|
||||||
logger.debug(f"Broken: {plate_map.iloc[5, 0]}, {plate_map.iloc[6, 0]}")
|
# # logger.debug(f"Broken: {plate_map.iloc[5, 0]}, {plate_map.iloc[6, 0]}")
|
||||||
logger.debug(f"Replace: {num1}, {num2}")
|
# # logger.debug(f"Replace: {num1}, {num2}")
|
||||||
if not check_not_nan(plate_map.iloc[5, 0]):
|
# if not check_not_nan(plate_map.iloc[5, 0]):
|
||||||
plate_map.iloc[5, 0] = num1
|
# plate_map.iloc[5, 0] = num1
|
||||||
if not check_not_nan(plate_map.iloc[6, 0]):
|
# if not check_not_nan(plate_map.iloc[6, 0]):
|
||||||
plate_map.iloc[6, 0] = num2
|
# plate_map.iloc[6, 0] = num2
|
||||||
return plate_map
|
# return plate_map
|
||||||
|
|
||||||
# @classmethod
|
# @classmethod
|
||||||
# def custom_writer(cls, input_excel: Workbook, info: dict | None = None, backup: bool = False) -> Workbook:
|
# def custom_writer(cls, input_excel: Workbook, info: dict | None = None, backup: bool = False) -> Workbook:
|
||||||
@@ -1255,7 +1260,7 @@ class BacterialCulture(BasicSubmission):
|
|||||||
for sample in input_dict['samples']:
|
for sample in input_dict['samples']:
|
||||||
matched = regex.match(sample['submitter_id'])
|
matched = regex.match(sample['submitter_id'])
|
||||||
if bool(matched):
|
if bool(matched):
|
||||||
logger.debug(f"Control match found: {sample['submitter_id']}")
|
# logger.debug(f"Control match found: {sample['submitter_id']}")
|
||||||
new_lot = matched.group()
|
new_lot = matched.group()
|
||||||
try:
|
try:
|
||||||
pos_control_reg = \
|
pos_control_reg = \
|
||||||
@@ -1272,8 +1277,8 @@ class BacterialCulture(BasicSubmission):
|
|||||||
"""
|
"""
|
||||||
Extends parent
|
Extends parent
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Checking {sample.well}")
|
# logger.debug(f"Checking {sample.well}")
|
||||||
logger.debug(f"here's the worksheet: {worksheet}")
|
# logger.debug(f"here's the worksheet: {worksheet}")
|
||||||
row = super().custom_sample_autofill_row(sample, worksheet)
|
row = super().custom_sample_autofill_row(sample, worksheet)
|
||||||
df = pd.DataFrame(list(worksheet.values))
|
df = pd.DataFrame(list(worksheet.values))
|
||||||
# logger.debug(f"Here's the dataframe: {df}")
|
# logger.debug(f"Here's the dataframe: {df}")
|
||||||
@@ -1282,7 +1287,7 @@ class BacterialCulture(BasicSubmission):
|
|||||||
new = f"{sample.well[0]}{sample.well[1:].zfill(2)}"
|
new = f"{sample.well[0]}{sample.well[1:].zfill(2)}"
|
||||||
logger.debug(f"Checking: {new}")
|
logger.debug(f"Checking: {new}")
|
||||||
idx = df[df[0] == new]
|
idx = df[df[0] == new]
|
||||||
logger.debug(f"Here is the row: {idx}")
|
# logger.debug(f"Here is the row: {idx}")
|
||||||
row = idx.index.to_list()[0]
|
row = idx.index.to_list()[0]
|
||||||
return row + 1
|
return row + 1
|
||||||
|
|
||||||
@@ -1386,14 +1391,19 @@ class Wastewater(BasicSubmission):
|
|||||||
Parse specific to wastewater samples.
|
Parse specific to wastewater samples.
|
||||||
"""
|
"""
|
||||||
samples = super().parse_pcr(xl=xl, rsl_plate_num=rsl_plate_num)
|
samples = super().parse_pcr(xl=xl, rsl_plate_num=rsl_plate_num)
|
||||||
logger.debug(f'Samples from parent pcr parser: {pformat(samples)}')
|
# logger.debug(f'Samples from parent pcr parser: {pformat(samples)}')
|
||||||
output = []
|
output = []
|
||||||
for sample in samples:
|
for sample in samples:
|
||||||
|
# NOTE: remove '-{target}' from controls
|
||||||
sample['sample'] = re.sub('-N\\d$', '', sample['sample'])
|
sample['sample'] = re.sub('-N\\d$', '', sample['sample'])
|
||||||
|
# NOTE: if sample is already in output skip
|
||||||
if sample['sample'] in [item['sample'] for item in output]:
|
if sample['sample'] in [item['sample'] for item in output]:
|
||||||
continue
|
continue
|
||||||
|
# NOTE: Set ct values
|
||||||
sample[f"ct_{sample['target'].lower()}"] = sample['ct'] if isinstance(sample['ct'], float) else 0.0
|
sample[f"ct_{sample['target'].lower()}"] = sample['ct'] if isinstance(sample['ct'], float) else 0.0
|
||||||
|
# NOTE: Set assessment
|
||||||
sample[f"{sample['target'].lower()}_status"] = sample['assessment']
|
sample[f"{sample['target'].lower()}_status"] = sample['assessment']
|
||||||
|
# NOTE: Get sample having other target
|
||||||
other_targets = [s for s in samples if re.sub('-N\\d$', '', s['sample']) == sample['sample']]
|
other_targets = [s for s in samples if re.sub('-N\\d$', '', s['sample']) == sample['sample']]
|
||||||
for s in other_targets:
|
for s in other_targets:
|
||||||
sample[f"ct_{s['target'].lower()}"] = s['ct'] if isinstance(s['ct'], float) else 0.0
|
sample[f"ct_{s['target'].lower()}"] = s['ct'] if isinstance(s['ct'], float) else 0.0
|
||||||
@@ -1446,13 +1456,13 @@ class Wastewater(BasicSubmission):
|
|||||||
"""
|
"""
|
||||||
Extends parent
|
Extends parent
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Checking {sample.well}")
|
# logger.debug(f"Checking {sample.well}")
|
||||||
logger.debug(f"here's the worksheet: {worksheet}")
|
# logger.debug(f"here's the worksheet: {worksheet}")
|
||||||
row = super().custom_sample_autofill_row(sample, worksheet)
|
row = super().custom_sample_autofill_row(sample, worksheet)
|
||||||
df = pd.DataFrame(list(worksheet.values))
|
df = pd.DataFrame(list(worksheet.values))
|
||||||
logger.debug(f"Here's the dataframe: {df}")
|
# logger.debug(f"Here's the dataframe: {df}")
|
||||||
idx = df[df[1] == sample.sample_location]
|
idx = df[df[1] == sample.sample_location]
|
||||||
logger.debug(f"Here is the row: {idx}")
|
# logger.debug(f"Here is the row: {idx}")
|
||||||
row = idx.index.to_list()[0]
|
row = idx.index.to_list()[0]
|
||||||
return row + 1
|
return row + 1
|
||||||
|
|
||||||
@@ -1468,10 +1478,10 @@ class Wastewater(BasicSubmission):
|
|||||||
parser = PCRParser(filepath=fname)
|
parser = PCRParser(filepath=fname)
|
||||||
self.set_attribute("pcr_info", parser.pcr)
|
self.set_attribute("pcr_info", parser.pcr)
|
||||||
self.save(original=False)
|
self.save(original=False)
|
||||||
logger.debug(f"Got {len(parser.samples)} samples to update!")
|
# logger.debug(f"Got {len(parser.samples)} samples to update!")
|
||||||
logger.debug(f"Parser samples: {parser.samples}")
|
# logger.debug(f"Parser samples: {parser.samples}")
|
||||||
for sample in self.samples:
|
for sample in self.samples:
|
||||||
logger.debug(f"Running update on: {sample}")
|
# logger.debug(f"Running update on: {sample}")
|
||||||
try:
|
try:
|
||||||
sample_dict = [item for item in parser.samples if item['sample'] == sample.rsl_number][0]
|
sample_dict = [item for item in parser.samples if item['sample'] == sample.rsl_number][0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
@@ -1551,19 +1561,18 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
Extends parent
|
Extends parent
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Deal with PCR file.
|
# NOTE: Deal with PCR file.
|
||||||
instr = re.sub(r"Artic", "", instr, flags=re.IGNORECASE)
|
instr = re.sub(r"Artic", "", instr, flags=re.IGNORECASE)
|
||||||
except (AttributeError, TypeError) as e:
|
except (AttributeError, TypeError) as e:
|
||||||
logger.error(f"Problem using regex: {e}")
|
logger.error(f"Problem using regex: {e}")
|
||||||
logger.debug(f"Before RSL addition: {instr}")
|
# logger.debug(f"Before RSL addition: {instr}")
|
||||||
try:
|
try:
|
||||||
instr = instr.replace("-", "")
|
instr = instr.replace("-", "")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
instr = date.today().strftime("%Y%m%d")
|
instr = date.today().strftime("%Y%m%d")
|
||||||
instr = re.sub(r"^(\d{6})", f"RSL-AR-\\1", instr)
|
instr = re.sub(r"^(\d{6})", f"RSL-AR-\\1", instr)
|
||||||
logger.debug(f"name coming out of Artic namer: {instr}")
|
# logger.debug(f"name coming out of Artic namer: {instr}")
|
||||||
outstr = super().enforce_name(instr=instr, data=data)
|
outstr = super().enforce_name(instr=instr, data=data)
|
||||||
|
|
||||||
return outstr
|
return outstr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1611,39 +1620,39 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
Returns:
|
Returns:
|
||||||
str: output name
|
str: output name
|
||||||
"""
|
"""
|
||||||
logger.debug(f"input string raw: {input_str}")
|
# logger.debug(f"input string raw: {input_str}")
|
||||||
# Remove letters.
|
# Remove letters.
|
||||||
processed = input_str.replace("RSL", "")
|
processed = input_str.replace("RSL", "")
|
||||||
processed = re.sub(r"\(.*\)$", "", processed).strip()
|
processed = re.sub(r"\(.*\)$", "", processed).strip()
|
||||||
processed = re.sub(r"[A-QS-Z]+\d*", "", processed)
|
processed = re.sub(r"[A-QS-Z]+\d*", "", processed)
|
||||||
# Remove trailing '-' if any
|
# Remove trailing '-' if any
|
||||||
processed = processed.strip("-")
|
processed = processed.strip("-")
|
||||||
logger.debug(f"Processed after stripping letters: {processed}")
|
# logger.debug(f"Processed after stripping letters: {processed}")
|
||||||
try:
|
try:
|
||||||
en_num = re.search(r"\-\d{1}$", processed).group()
|
en_num = re.search(r"\-\d{1}$", processed).group()
|
||||||
processed = rreplace(processed, en_num, "")
|
processed = rreplace(processed, en_num, "")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
en_num = "1"
|
en_num = "1"
|
||||||
en_num = en_num.strip("-")
|
en_num = en_num.strip("-")
|
||||||
logger.debug(f"Processed after en_num: {processed}")
|
# logger.debug(f"Processed after en_num: {processed}")
|
||||||
try:
|
try:
|
||||||
plate_num = re.search(r"\-\d{1}R?\d?$", processed).group()
|
plate_num = re.search(r"\-\d{1}R?\d?$", processed).group()
|
||||||
processed = rreplace(processed, plate_num, "")
|
processed = rreplace(processed, plate_num, "")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
plate_num = "1"
|
plate_num = "1"
|
||||||
plate_num = plate_num.strip("-")
|
plate_num = plate_num.strip("-")
|
||||||
logger.debug(f"Processed after plate-num: {processed}")
|
# logger.debug(f"Processed after plate-num: {processed}")
|
||||||
day = re.search(r"\d{2}$", processed).group()
|
day = re.search(r"\d{2}$", processed).group()
|
||||||
processed = rreplace(processed, day, "")
|
processed = rreplace(processed, day, "")
|
||||||
logger.debug(f"Processed after day: {processed}")
|
# logger.debug(f"Processed after day: {processed}")
|
||||||
month = re.search(r"\d{2}$", processed).group()
|
month = re.search(r"\d{2}$", processed).group()
|
||||||
processed = rreplace(processed, month, "")
|
processed = rreplace(processed, month, "")
|
||||||
processed = processed.replace("--", "")
|
processed = processed.replace("--", "")
|
||||||
logger.debug(f"Processed after month: {processed}")
|
# logger.debug(f"Processed after month: {processed}")
|
||||||
year = re.search(r'^(?:\d{2})?\d{2}', processed).group()
|
year = re.search(r'^(?:\d{2})?\d{2}', processed).group()
|
||||||
year = f"20{year}"
|
year = f"20{year}"
|
||||||
final_en_name = f"EN{en_num}-{year}{month}{day}"
|
final_en_name = f"EN{en_num}-{year}{month}{day}"
|
||||||
logger.debug(f"Final EN name: {final_en_name}")
|
# logger.debug(f"Final EN name: {final_en_name}")
|
||||||
return final_en_name
|
return final_en_name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1657,14 +1666,14 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
Returns:
|
Returns:
|
||||||
str: output name
|
str: output name
|
||||||
"""
|
"""
|
||||||
logger.debug(f"input string raw: {input_str}")
|
# logger.debug(f"input string raw: {input_str}")
|
||||||
# Remove letters.
|
# Remove letters.
|
||||||
processed = input_str.replace("RSL", "")
|
processed = input_str.replace("RSL", "")
|
||||||
processed = re.sub(r"\(.*\)$", "", processed).strip()
|
processed = re.sub(r"\(.*\)$", "", processed).strip()
|
||||||
processed = re.sub(r"[A-QS-Z]+\d*", "", processed)
|
processed = re.sub(r"[A-QS-Z]+\d*", "", processed)
|
||||||
# Remove trailing '-' if any
|
# Remove trailing '-' if any
|
||||||
processed = processed.strip("-")
|
processed = processed.strip("-")
|
||||||
logger.debug(f"Processed after stripping letters: {processed}")
|
# logger.debug(f"Processed after stripping letters: {processed}")
|
||||||
# try:
|
# try:
|
||||||
# en_num = re.search(r"\-\d{1}$", processed).group()
|
# en_num = re.search(r"\-\d{1}$", processed).group()
|
||||||
# processed = rreplace(processed, en_num, "")
|
# processed = rreplace(processed, en_num, "")
|
||||||
@@ -1678,23 +1687,23 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
plate_num = "1"
|
plate_num = "1"
|
||||||
plate_num = plate_num.strip("-")
|
plate_num = plate_num.strip("-")
|
||||||
logger.debug(f"Plate num: {plate_num}")
|
# logger.debug(f"Plate num: {plate_num}")
|
||||||
repeat_num = re.search(r"R(?P<repeat>\d)?$", "PBS20240426-2R").groups()[0]
|
repeat_num = re.search(r"R(?P<repeat>\d)?$", "PBS20240426-2R").groups()[0]
|
||||||
if repeat_num is None and "R" in plate_num:
|
if repeat_num is None and "R" in plate_num:
|
||||||
repeat_num = "1"
|
repeat_num = "1"
|
||||||
plate_num = re.sub(r"R", rf"R{repeat_num}", plate_num)
|
plate_num = re.sub(r"R", rf"R{repeat_num}", plate_num)
|
||||||
logger.debug(f"Processed after plate-num: {processed}")
|
# logger.debug(f"Processed after plate-num: {processed}")
|
||||||
day = re.search(r"\d{2}$", processed).group()
|
day = re.search(r"\d{2}$", processed).group()
|
||||||
processed = rreplace(processed, day, "")
|
processed = rreplace(processed, day, "")
|
||||||
logger.debug(f"Processed after day: {processed}")
|
# logger.debug(f"Processed after day: {processed}")
|
||||||
month = re.search(r"\d{2}$", processed).group()
|
month = re.search(r"\d{2}$", processed).group()
|
||||||
processed = rreplace(processed, month, "")
|
processed = rreplace(processed, month, "")
|
||||||
processed = processed.replace("--", "")
|
processed = processed.replace("--", "")
|
||||||
logger.debug(f"Processed after month: {processed}")
|
# logger.debug(f"Processed after month: {processed}")
|
||||||
year = re.search(r'^(?:\d{2})?\d{2}', processed).group()
|
year = re.search(r'^(?:\d{2})?\d{2}', processed).group()
|
||||||
year = f"20{year}"
|
year = f"20{year}"
|
||||||
final_en_name = f"PBS{year}{month}{day}-{plate_num}"
|
final_en_name = f"PBS{year}{month}{day}-{plate_num}"
|
||||||
logger.debug(f"Final EN name: {final_en_name}")
|
# logger.debug(f"Final EN name: {final_en_name}")
|
||||||
return final_en_name
|
return final_en_name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1722,12 +1731,12 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
dict: Updated parser product.
|
dict: Updated parser product.
|
||||||
"""
|
"""
|
||||||
input_dict = super().finalize_parse(input_dict, xl, info_map)
|
input_dict = super().finalize_parse(input_dict, xl, info_map)
|
||||||
logger.debug(f"Incoming input_dict: {pformat(input_dict)}")
|
# logger.debug(f"Incoming input_dict: {pformat(input_dict)}")
|
||||||
# TODO: Move to validator?
|
# TODO: Move to validator?
|
||||||
for sample in input_dict['samples']:
|
for sample in input_dict['samples']:
|
||||||
logger.debug(f"Sample: {sample}")
|
# logger.debug(f"Sample: {sample}")
|
||||||
if re.search(r"^NTC", sample['submitter_id']):
|
if re.search(r"^NTC", sample['submitter_id']):
|
||||||
sample['submitter_id'] = sample['submitter_id'] + "-WWG-" + input_dict['rsl_plate_num']['value']
|
sample['submitter_id'] = f"{sample['submitter_id']}-WWG-{input_dict['rsl_plate_num']['value']}"
|
||||||
input_dict['csv'] = xl["hitpicks_csv_to_export"]
|
input_dict['csv'] = xl["hitpicks_csv_to_export"]
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
@@ -1748,13 +1757,13 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
# worksheet = input_excel["First Strand List"]
|
# worksheet = input_excel["First Strand List"]
|
||||||
# samples = cls.query(rsl_number=info['rsl_plate_num']['value']).submission_sample_associations
|
# samples = cls.query(rsl_number=info['rsl_plate_num']['value']).submission_sample_associations
|
||||||
# samples = sorted(samples, key=attrgetter('column', 'row'))
|
# samples = sorted(samples, key=attrgetter('column', 'row'))
|
||||||
logger.debug(f"Info:\n{pformat(info)}")
|
# logger.debug(f"Info:\n{pformat(info)}")
|
||||||
check = 'source_plates' in info.keys() and info['source_plates'] is not None
|
check = 'source_plates' in info.keys() and info['source_plates'] is not None
|
||||||
if check:
|
if check:
|
||||||
worksheet = input_excel['First Strand List']
|
worksheet = input_excel['First Strand List']
|
||||||
start_row = 8
|
start_row = 8
|
||||||
for iii, plate in enumerate(info['source_plates']['value']):
|
for iii, plate in enumerate(info['source_plates']['value']):
|
||||||
logger.debug(f"Plate: {plate}")
|
# logger.debug(f"Plate: {plate}")
|
||||||
row = start_row + iii
|
row = start_row + iii
|
||||||
try:
|
try:
|
||||||
worksheet.cell(row=row, column=3, value=plate['plate'])
|
worksheet.cell(row=row, column=3, value=plate['plate'])
|
||||||
@@ -1782,12 +1791,16 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
# logger.debug(f"vj: {vj}")
|
# logger.debug(f"vj: {vj}")
|
||||||
column = start_column + 2 + jjj
|
column = start_column + 2 + jjj
|
||||||
worksheet.cell(row=start_row, column=column, value=kj['name'])
|
worksheet.cell(row=start_row, column=column, value=kj['name'])
|
||||||
|
# logger.debug(f"Writing {kj['name']} with value {kj['value']} to row {row}, column {column}")
|
||||||
|
try:
|
||||||
worksheet.cell(row=row, column=column, value=kj['value'])
|
worksheet.cell(row=row, column=column, value=kj['value'])
|
||||||
|
except AttributeError:
|
||||||
|
logger.error(f"Failed {kj['name']} with value {kj['value']} to row {row}, column {column}")
|
||||||
check = 'gel_image' in info.keys() and info['gel_image']['value'] is not None
|
check = 'gel_image' in info.keys() and info['gel_image']['value'] is not None
|
||||||
if check:
|
if check:
|
||||||
if info['gel_image'] != None:
|
if info['gel_image'] != None:
|
||||||
worksheet = input_excel['Egel results']
|
worksheet = input_excel['Egel results']
|
||||||
logger.debug(f"We got an image: {info['gel_image']}")
|
# logger.debug(f"We got an image: {info['gel_image']}")
|
||||||
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
|
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
|
||||||
z = zipped.extract(info['gel_image']['value'], Path(TemporaryDirectory().name))
|
z = zipped.extract(info['gel_image']['value'], Path(TemporaryDirectory().name))
|
||||||
img = OpenpyxlImage(z)
|
img = OpenpyxlImage(z)
|
||||||
@@ -1817,7 +1830,7 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
headers = [item['name'] for item in base_dict['gel_info'][0]['values']]
|
headers = [item['name'] for item in base_dict['gel_info'][0]['values']]
|
||||||
base_dict['headers'] = [''] * (4 - len(headers))
|
base_dict['headers'] = [''] * (4 - len(headers))
|
||||||
base_dict['headers'] += headers
|
base_dict['headers'] += headers
|
||||||
logger.debug(f"Gel info: {pformat(base_dict['headers'])}")
|
# logger.debug(f"Gel info: {pformat(base_dict['headers'])}")
|
||||||
check = 'gel_image' in base_dict.keys() and base_dict['gel_image'] != None
|
check = 'gel_image' in base_dict.keys() and base_dict['gel_image'] != None
|
||||||
if check:
|
if check:
|
||||||
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
|
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
|
||||||
@@ -1834,7 +1847,7 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
Returns:
|
Returns:
|
||||||
List[dict]: Updated dictionaries
|
List[dict]: Updated dictionaries
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.")
|
# logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.")
|
||||||
output = []
|
output = []
|
||||||
set_plate = None
|
set_plate = None
|
||||||
for assoc in self.submission_sample_associations:
|
for assoc in self.submission_sample_associations:
|
||||||
@@ -1888,7 +1901,7 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
self.comment.append(com)
|
self.comment.append(com)
|
||||||
else:
|
else:
|
||||||
self.comment = [com]
|
self.comment = [com]
|
||||||
logger.debug(pformat(self.gel_info))
|
# logger.debug(pformat(self.gel_info))
|
||||||
with ZipFile(self.__directory_path__.joinpath("submission_imgs.zip"), 'a') as zipf:
|
with ZipFile(self.__directory_path__.joinpath("submission_imgs.zip"), 'a') as zipf:
|
||||||
# Add a file located at the source_path to the destination within the zip
|
# Add a file located at the source_path to the destination within the zip
|
||||||
# file. It will overwrite existing files if the names collide, but it
|
# file. It will overwrite existing files if the names collide, but it
|
||||||
@@ -1966,7 +1979,7 @@ class BasicSample(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
|
dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Converting {self} to dict.")
|
# logger.debug(f"Converting {self} to dict.")
|
||||||
# start = time()
|
# start = time()
|
||||||
sample = {}
|
sample = {}
|
||||||
sample['Submitter ID'] = self.submitter_id
|
sample['Submitter ID'] = self.submitter_id
|
||||||
@@ -2042,7 +2055,7 @@ class BasicSample(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: Updated parser results.
|
dict: Updated parser results.
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Hello from {cls.__name__} sample parser!")
|
# logger.debug(f"Hello from {cls.__name__} sample parser!")
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -2059,7 +2072,7 @@ class BasicSample(BaseClass):
|
|||||||
base_dict['excluded'] = ['submissions', 'excluded', 'colour', 'tooltip']
|
base_dict['excluded'] = ['submissions', 'excluded', 'colour', 'tooltip']
|
||||||
env = jinja_template_loading()
|
env = jinja_template_loading()
|
||||||
temp_name = f"{cls.__name__.lower()}_details.html"
|
temp_name = f"{cls.__name__.lower()}_details.html"
|
||||||
logger.debug(f"Returning template: {temp_name}")
|
# logger.debug(f"Returning template: {temp_name}")
|
||||||
try:
|
try:
|
||||||
template = env.get_template(temp_name)
|
template = env.get_template(temp_name)
|
||||||
except TemplateNotFound as e:
|
except TemplateNotFound as e:
|
||||||
@@ -2091,7 +2104,7 @@ class BasicSample(BaseClass):
|
|||||||
model = cls.find_polymorphic_subclass(attrs=kwargs)
|
model = cls.find_polymorphic_subclass(attrs=kwargs)
|
||||||
else:
|
else:
|
||||||
model = cls.find_polymorphic_subclass(polymorphic_identity=sample_type)
|
model = cls.find_polymorphic_subclass(polymorphic_identity=sample_type)
|
||||||
logger.debug(f"Length of kwargs: {len(kwargs)}")
|
# logger.debug(f"Length of kwargs: {len(kwargs)}")
|
||||||
# model = models.BasicSample.find_subclasses(ctx=ctx, attrs=kwargs)
|
# model = models.BasicSample.find_subclasses(ctx=ctx, attrs=kwargs)
|
||||||
# query: Query = setup_lookup(ctx=ctx, locals=locals()).query(model)
|
# query: Query = setup_lookup(ctx=ctx, locals=locals()).query(model)
|
||||||
query: Query = cls.__database_session__.query(model)
|
query: Query = cls.__database_session__.query(model)
|
||||||
@@ -2141,7 +2154,7 @@ class BasicSample(BaseClass):
|
|||||||
# f"{key} is not allowed as a query argument as it could lead to creation of duplicate objects.")
|
# f"{key} is not allowed as a query argument as it could lead to creation of duplicate objects.")
|
||||||
sanitized_kwargs = {k:v for k,v in kwargs.items() if k not in disallowed}
|
sanitized_kwargs = {k:v for k,v in kwargs.items() if k not in disallowed}
|
||||||
instance = cls.query(sample_type=sample_type, limit=1, **kwargs)
|
instance = cls.query(sample_type=sample_type, limit=1, **kwargs)
|
||||||
logger.debug(f"Retrieved instance: {instance}")
|
# logger.debug(f"Retrieved instance: {instance}")
|
||||||
if instance is None:
|
if instance is None:
|
||||||
used_class = cls.find_polymorphic_subclass(attrs=sanitized_kwargs, polymorphic_identity=sample_type)
|
used_class = cls.find_polymorphic_subclass(attrs=sanitized_kwargs, polymorphic_identity=sample_type)
|
||||||
instance = used_class(**sanitized_kwargs)
|
instance = used_class(**sanitized_kwargs)
|
||||||
@@ -2219,7 +2232,7 @@ class WastewaterSample(BasicSample):
|
|||||||
dict: Updated parser results.
|
dict: Updated parser results.
|
||||||
"""
|
"""
|
||||||
output_dict = super().parse_sample(input_dict)
|
output_dict = super().parse_sample(input_dict)
|
||||||
logger.debug(f"Initial sample dict: {pformat(output_dict)}")
|
# logger.debug(f"Initial sample dict: {pformat(output_dict)}")
|
||||||
disallowed = ["", None, "None"]
|
disallowed = ["", None, "None"]
|
||||||
try:
|
try:
|
||||||
check = output_dict['rsl_number'] in [None, "None"]
|
check = output_dict['rsl_number'] in [None, "None"]
|
||||||
@@ -2258,7 +2271,7 @@ class WastewaterSample(BasicSample):
|
|||||||
plates = [item.rsl_plate_num for item in
|
plates = [item.rsl_plate_num for item in
|
||||||
self.submissions[:self.submissions.index(current_artic_submission)]]
|
self.submissions[:self.submissions.index(current_artic_submission)]]
|
||||||
subs = [sub for sub in self.submissions if sub.rsl_plate_num in plates]
|
subs = [sub for sub in self.submissions if sub.rsl_plate_num in plates]
|
||||||
logger.debug(f"Submissions: {subs}")
|
# logger.debug(f"Submissions: {subs}")
|
||||||
try:
|
try:
|
||||||
return subs[-1]
|
return subs[-1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
@@ -2339,7 +2352,7 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
self.id = id
|
self.id = id
|
||||||
else:
|
else:
|
||||||
self.id = self.__class__.autoincrement_id()
|
self.id = self.__class__.autoincrement_id()
|
||||||
logger.debug(f"Using id: {self.id}")
|
# logger.debug(f"Using submission sample association id: {self.id}")
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
try:
|
try:
|
||||||
@@ -2356,9 +2369,9 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
dict: Updated dictionary with row, column and well updated
|
dict: Updated dictionary with row, column and well updated
|
||||||
"""
|
"""
|
||||||
# Get sample info
|
# Get sample info
|
||||||
logger.debug(f"Running {self.__repr__()}")
|
# logger.debug(f"Running {self.__repr__()}")
|
||||||
sample = self.sample.to_sub_dict()
|
sample = self.sample.to_sub_dict()
|
||||||
logger.debug("Sample conversion complete.")
|
# logger.debug("Sample conversion complete.")
|
||||||
sample['Name'] = self.sample.submitter_id
|
sample['Name'] = self.sample.submitter_id
|
||||||
sample['Row'] = self.row
|
sample['Row'] = self.row
|
||||||
sample['Column'] = self.column
|
sample['Column'] = self.column
|
||||||
@@ -2382,7 +2395,7 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
"""
|
"""
|
||||||
# Since there is no PCR, negliable result is necessary.
|
# Since there is no PCR, negliable result is necessary.
|
||||||
sample = self.to_sub_dict()
|
sample = self.to_sub_dict()
|
||||||
logger.debug(f"Sample dict to hitpick: {sample}")
|
# logger.debug(f"Sample dict to hitpick: {sample}")
|
||||||
env = jinja_template_loading()
|
env = jinja_template_loading()
|
||||||
template = env.get_template("tooltip.html")
|
template = env.get_template("tooltip.html")
|
||||||
tooltip_text = template.render(fields=sample)
|
tooltip_text = template.render(fields=sample)
|
||||||
@@ -2430,7 +2443,7 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}")
|
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}")
|
||||||
output = cls
|
output = cls
|
||||||
logger.debug(f"Using SubmissionSampleAssociation subclass: {output}")
|
# logger.debug(f"Using SubmissionSampleAssociation subclass: {output}")
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -2519,7 +2532,7 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
SubmissionSampleAssociation: Queried or new association.
|
SubmissionSampleAssociation: Queried or new association.
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Attempting create or query with {kwargs}")
|
# logger.debug(f"Attempting create or query with {kwargs}")
|
||||||
match submission:
|
match submission:
|
||||||
case BasicSubmission():
|
case BasicSubmission():
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from datetime import date
|
|||||||
from dateutil.parser import parse, ParserError
|
from dateutil.parser import parse, ParserError
|
||||||
from tools import check_not_nan, convert_nans_to_nones, row_map, row_keys, is_missing, remove_key_from_list_of_dicts
|
from tools import check_not_nan, convert_nans_to_nones, row_map, row_keys, is_missing, remove_key_from_list_of_dicts
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
@@ -44,26 +45,24 @@ class SheetParser(object):
|
|||||||
logger.error(f"No filepath given.")
|
logger.error(f"No filepath given.")
|
||||||
raise ValueError("No filepath given.")
|
raise ValueError("No filepath given.")
|
||||||
try:
|
try:
|
||||||
# self.xl = pd.ExcelFile(filepath)
|
|
||||||
self.xl = load_workbook(filepath, data_only=True)
|
self.xl = load_workbook(filepath, data_only=True)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.error(f"Incorrect value: {e}")
|
logger.error(f"Incorrect value: {e}")
|
||||||
raise FileNotFoundError(f"Couldn't parse file {self.filepath}")
|
raise FileNotFoundError(f"Couldn't parse file {self.filepath}")
|
||||||
self.sub = OrderedDict()
|
self.sub = OrderedDict()
|
||||||
# make decision about type of sample we have
|
# NOTE: make decision about type of sample we have
|
||||||
self.sub['submission_type'] = dict(value=RSLNamer.retrieve_submission_type(filename=self.filepath),
|
self.sub['submission_type'] = dict(value=RSLNamer.retrieve_submission_type(filename=self.filepath),
|
||||||
missing=True)
|
missing=True)
|
||||||
self.submission_type = SubmissionType.query(name=self.sub['submission_type'])
|
self.submission_type = SubmissionType.query(name=self.sub['submission_type'])
|
||||||
self.sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
self.sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
||||||
# grab the info map from the submission type in database
|
# NOTE: grab the info map from the submission type in database
|
||||||
self.parse_info()
|
self.parse_info()
|
||||||
self.import_kit_validation_check()
|
self.import_kit_validation_check()
|
||||||
self.parse_reagents()
|
self.parse_reagents()
|
||||||
# self.import_reagent_validation_check()
|
|
||||||
self.parse_samples()
|
self.parse_samples()
|
||||||
self.parse_equipment()
|
self.parse_equipment()
|
||||||
self.finalize_parse()
|
self.finalize_parse()
|
||||||
logger.debug(f"Parser.sub after info scrape: {pformat(self.sub)}")
|
# logger.debug(f"Parser.sub after info scrape: {pformat(self.sub)}")
|
||||||
|
|
||||||
def parse_info(self):
|
def parse_info(self):
|
||||||
"""
|
"""
|
||||||
@@ -141,7 +140,7 @@ class SheetParser(object):
|
|||||||
pyd_dict = copy(self.sub)
|
pyd_dict = copy(self.sub)
|
||||||
pyd_dict['samples'] = [PydSample(**sample) for sample in self.sub['samples']]
|
pyd_dict['samples'] = [PydSample(**sample) for sample in self.sub['samples']]
|
||||||
pyd_dict['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']]
|
pyd_dict['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']]
|
||||||
logger.debug(f"Equipment: {self.sub['equipment']}")
|
# logger.debug(f"Equipment: {self.sub['equipment']}")
|
||||||
try:
|
try:
|
||||||
check = len(self.sub['equipment']) == 0
|
check = len(self.sub['equipment']) == 0
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@@ -157,7 +156,7 @@ class SheetParser(object):
|
|||||||
class InfoParser(object):
|
class InfoParser(object):
|
||||||
|
|
||||||
def __init__(self, xl: Workbook, submission_type: str|SubmissionType, sub_object: BasicSubmission|None=None):
|
def __init__(self, xl: Workbook, submission_type: str|SubmissionType, sub_object: BasicSubmission|None=None):
|
||||||
logger.info(f"\n\Hello from InfoParser!\n\n")
|
logger.info(f"\n\nHello from InfoParser!\n\n")
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
submission_type = SubmissionType.query(name=submission_type)
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
if sub_object is None:
|
if sub_object is None:
|
||||||
@@ -166,7 +165,7 @@ class InfoParser(object):
|
|||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
self.map = self.fetch_submission_info_map()
|
self.map = self.fetch_submission_info_map()
|
||||||
self.xl = xl
|
self.xl = xl
|
||||||
logger.debug(f"Info map for InfoParser: {pformat(self.map)}")
|
# logger.debug(f"Info map for InfoParser: {pformat(self.map)}")
|
||||||
|
|
||||||
def fetch_submission_info_map(self) -> dict:
|
def fetch_submission_info_map(self) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -179,13 +178,9 @@ class InfoParser(object):
|
|||||||
dict: Location map of all info for this submission type
|
dict: Location map of all info for this submission type
|
||||||
"""
|
"""
|
||||||
self.submission_type = dict(value=self.submission_type_obj.name, missing=True)
|
self.submission_type = dict(value=self.submission_type_obj.name, missing=True)
|
||||||
logger.debug(f"Looking up submission type: {self.submission_type['value']}")
|
# logger.debug(f"Looking up submission type: {self.submission_type['value']}")
|
||||||
# submission_type = SubmissionType.query(name=self.submission_type['value'])
|
|
||||||
# info_map = submission_type.info_map
|
|
||||||
# self.sub_object: BasicSubmission = \
|
|
||||||
# BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type['value'])
|
|
||||||
info_map = self.sub_object.construct_info_map("read")
|
info_map = self.sub_object.construct_info_map("read")
|
||||||
# Get the parse_info method from the submission type specified
|
# NOTE: Get the parse_info method from the submission type specified
|
||||||
return info_map
|
return info_map
|
||||||
|
|
||||||
def parse_info(self) -> dict:
|
def parse_info(self) -> dict:
|
||||||
@@ -195,30 +190,19 @@ class InfoParser(object):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: key:value of basic info
|
dict: key:value of basic info
|
||||||
"""
|
"""
|
||||||
# if isinstance(self.submission_type, str):
|
|
||||||
# self.submission_type = dict(value=self.submission_type, missing=True)
|
|
||||||
dicto = {}
|
dicto = {}
|
||||||
# exclude_from_generic = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type['value']).get_default_info("parser_ignore")
|
# NOTE: This loop parses generic info
|
||||||
# This loop parses generic info
|
# logger.debug(f"Map: {self.map}")
|
||||||
logger.debug(f"Map: {self.map}")
|
|
||||||
# for sheet in self.xl.sheet_names:
|
|
||||||
for sheet in self.xl.sheetnames:
|
for sheet in self.xl.sheetnames:
|
||||||
# df = self.xl.parse(sheet, header=None)
|
|
||||||
ws = self.xl[sheet]
|
ws = self.xl[sheet]
|
||||||
relevant = []
|
relevant = []
|
||||||
for k, v in self.map.items():
|
for k, v in self.map.items():
|
||||||
# If the value is hardcoded put it in the dictionary directly.
|
# NOTE: If the value is hardcoded put it in the dictionary directly.
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
dicto[k] = dict(value=v, missing=False)
|
dicto[k] = dict(value=v, missing=False)
|
||||||
continue
|
continue
|
||||||
logger.debug(f"Looking for {k} in self.map")
|
# logger.debug(f"Looking for {k} in self.map")
|
||||||
logger.debug(f"Locations: {v}")
|
# logger.debug(f"Locations: {v}")
|
||||||
# try:
|
|
||||||
# check = sheet in self.map[k]['sheets']
|
|
||||||
# except TypeError:
|
|
||||||
# continue
|
|
||||||
# if check:
|
|
||||||
# relevant[k] = v
|
|
||||||
for location in v:
|
for location in v:
|
||||||
try:
|
try:
|
||||||
check = location['sheet'] == sheet
|
check = location['sheet'] == sheet
|
||||||
@@ -235,7 +219,6 @@ class InfoParser(object):
|
|||||||
continue
|
continue
|
||||||
for item in relevant:
|
for item in relevant:
|
||||||
# NOTE: Get cell contents at this location
|
# NOTE: Get cell contents at this location
|
||||||
# value = df.iat[item['row']-1, item['column']-1]
|
|
||||||
value = ws.cell(row=item['row'], column=item['column']).value
|
value = ws.cell(row=item['row'], column=item['column']).value
|
||||||
logger.debug(f"Value for {item['name']} = {value}")
|
logger.debug(f"Value for {item['name']} = {value}")
|
||||||
match item['name']:
|
match item['name']:
|
||||||
@@ -250,10 +233,10 @@ class InfoParser(object):
|
|||||||
dicto[item['name']]['value'] += value
|
dicto[item['name']]['value'] += value
|
||||||
continue
|
continue
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.debug(f"New value for {item['name']}")
|
logger.error(f"New value for {item['name']}")
|
||||||
case _:
|
case _:
|
||||||
value, missing = is_missing(value)
|
value, missing = is_missing(value)
|
||||||
logger.debug(f"Setting {item} on {sheet} to {value}")
|
# logger.debug(f"Setting {item} on {sheet} to {value}")
|
||||||
if item['name'] not in dicto.keys():
|
if item['name'] not in dicto.keys():
|
||||||
try:
|
try:
|
||||||
dicto[item['name']] = dict(value=value, missing=missing)
|
dicto[item['name']] = dict(value=value, missing=missing)
|
||||||
@@ -265,14 +248,14 @@ class InfoParser(object):
|
|||||||
class ReagentParser(object):
|
class ReagentParser(object):
|
||||||
|
|
||||||
def __init__(self, xl: Workbook, submission_type: str, extraction_kit: str, sub_object:BasicSubmission|None=None):
|
def __init__(self, xl: Workbook, submission_type: str, extraction_kit: str, sub_object:BasicSubmission|None=None):
|
||||||
logger.debug("\n\nHello from ReagentParser!\n\n")
|
# logger.debug("\n\nHello from ReagentParser!\n\n")
|
||||||
self.submission_type_obj = submission_type
|
self.submission_type_obj = submission_type
|
||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
if isinstance(extraction_kit, dict):
|
if isinstance(extraction_kit, dict):
|
||||||
extraction_kit = extraction_kit['value']
|
extraction_kit = extraction_kit['value']
|
||||||
self.kit_object = KitType.query(name=extraction_kit)
|
self.kit_object = KitType.query(name=extraction_kit)
|
||||||
self.map = self.fetch_kit_info_map(extraction_kit=extraction_kit, submission_type=submission_type)
|
self.map = self.fetch_kit_info_map(extraction_kit=extraction_kit, submission_type=submission_type)
|
||||||
logger.debug(f"Reagent Parser map: {self.map}")
|
# logger.debug(f"Reagent Parser map: {self.map}")
|
||||||
self.xl = xl
|
self.xl = xl
|
||||||
|
|
||||||
def fetch_kit_info_map(self, extraction_kit: dict, submission_type: str) -> dict:
|
def fetch_kit_info_map(self, extraction_kit: dict, submission_type: str) -> dict:
|
||||||
@@ -305,45 +288,40 @@ class ReagentParser(object):
|
|||||||
"""
|
"""
|
||||||
listo = []
|
listo = []
|
||||||
for sheet in self.xl.sheetnames:
|
for sheet in self.xl.sheetnames:
|
||||||
# df = self.xl.parse(sheet, header=None, dtype=object)
|
|
||||||
ws = self.xl[sheet]
|
ws = self.xl[sheet]
|
||||||
# df.replace({np.nan: None}, inplace = True)
|
|
||||||
relevant = {k.strip(): v for k, v in self.map.items() if sheet in self.map[k]['sheet']}
|
relevant = {k.strip(): v for k, v in self.map.items() if sheet in self.map[k]['sheet']}
|
||||||
logger.debug(f"relevant map for {sheet}: {pformat(relevant)}")
|
# logger.debug(f"relevant map for {sheet}: {pformat(relevant)}")
|
||||||
if relevant == {}:
|
if relevant == {}:
|
||||||
continue
|
continue
|
||||||
for item in relevant:
|
for item in relevant:
|
||||||
logger.debug(f"Attempting to scrape: {item}")
|
# logger.debug(f"Attempting to scrape: {item}")
|
||||||
try:
|
try:
|
||||||
reagent = relevant[item]
|
reagent = relevant[item]
|
||||||
# name = df.iat[relevant[item]['name']['row']-1, relevant[item]['name']['column']-1]
|
|
||||||
# lot = df.iat[relevant[item]['lot']['row']-1, relevant[item]['lot']['column']-1]
|
|
||||||
# expiry = df.iat[relevant[item]['expiry']['row']-1, relevant[item]['expiry']['column']-1]
|
|
||||||
name = ws.cell(row=reagent['name']['row'], column=reagent['name']['column']).value
|
name = ws.cell(row=reagent['name']['row'], column=reagent['name']['column']).value
|
||||||
lot = ws.cell(row=reagent['lot']['row'], column=reagent['lot']['column']).value
|
lot = ws.cell(row=reagent['lot']['row'], column=reagent['lot']['column']).value
|
||||||
expiry = ws.cell(row=reagent['expiry']['row'], column=reagent['expiry']['column']).value
|
expiry = ws.cell(row=reagent['expiry']['row'], column=reagent['expiry']['column']).value
|
||||||
if 'comment' in relevant[item].keys():
|
if 'comment' in relevant[item].keys():
|
||||||
logger.debug(f"looking for {relevant[item]} comment.")
|
# logger.debug(f"looking for {relevant[item]} comment.")
|
||||||
# comment = df.iat[relevant[item]['comment']['row']-1, relevant[item]['comment']['column']-1]
|
comment = ws.cell(row=reagent['comment']['row'], column=reagent['comment']['column']).value
|
||||||
expiry = ws.cell(row=reagent['comment']['row'], column=reagent['comment']['column']).value
|
|
||||||
else:
|
else:
|
||||||
comment = ""
|
comment = ""
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
listo.append(
|
listo.append(
|
||||||
PydReagent(type=item.strip(), lot=None, expiry=None, name=None, comment="", missing=True))
|
PydReagent(type=item.strip(), lot=None, expiry=None, name=None, comment="", missing=True))
|
||||||
continue
|
continue
|
||||||
# If the cell is blank tell the PydReagent
|
# NOTE: If the cell is blank tell the PydReagent
|
||||||
if check_not_nan(lot):
|
if check_not_nan(lot):
|
||||||
missing = False
|
missing = False
|
||||||
else:
|
else:
|
||||||
missing = True
|
missing = True
|
||||||
# logger.debug(f"Got lot for {item}-{name}: {lot} as {type(lot)}")
|
# logger.debug(f"Got lot for {item}-{name}: {lot} as {type(lot)}")
|
||||||
lot = str(lot)
|
lot = str(lot)
|
||||||
logger.debug(
|
# logger.debug(
|
||||||
f"Going into pydantic: name: {name}, lot: {lot}, expiry: {expiry}, type: {item.strip()}, comment: {comment}")
|
# f"Going into pydantic: name: {name}, lot: {lot}, expiry: {expiry}, type: {item.strip()}, comment: {comment}")
|
||||||
try:
|
try:
|
||||||
check = name.lower() != "not applicable"
|
check = name.lower() != "not applicable"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
logger.warning(f"name is not a string.")
|
||||||
check = True
|
check = True
|
||||||
if check:
|
if check:
|
||||||
listo.append(dict(type=item.strip(), lot=lot, expiry=expiry, name=name, comment=comment,
|
listo.append(dict(type=item.strip(), lot=lot, expiry=expiry, name=name, comment=comment,
|
||||||
@@ -364,26 +342,20 @@ class SampleParser(object):
|
|||||||
df (pd.DataFrame): input sample dataframe
|
df (pd.DataFrame): input sample dataframe
|
||||||
elution_map (pd.DataFrame | None, optional): optional map of elution plate. Defaults to None.
|
elution_map (pd.DataFrame | None, optional): optional map of elution plate. Defaults to None.
|
||||||
"""
|
"""
|
||||||
logger.debug("\n\nHello from SampleParser!\n\n")
|
# logger.debug("\n\nHello from SampleParser!\n\n")
|
||||||
self.samples = []
|
self.samples = []
|
||||||
self.xl = xl
|
self.xl = xl
|
||||||
|
if isinstance(submission_type, str):
|
||||||
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
self.submission_type = submission_type.name
|
self.submission_type = submission_type.name
|
||||||
self.submission_type_obj = submission_type
|
self.submission_type_obj = submission_type
|
||||||
if sub_object is None:
|
if sub_object is None:
|
||||||
sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type_obj.name)
|
sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type_obj.name)
|
||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
self.sample_info_map = self.fetch_sample_info_map(submission_type=submission_type, sample_map=sample_map)
|
self.sample_info_map = self.fetch_sample_info_map(submission_type=submission_type, sample_map=sample_map)
|
||||||
logger.debug(f"sample_info_map: {self.sample_info_map}")
|
# logger.debug(f"sample_info_map: {self.sample_info_map}")
|
||||||
# self.plate_map = self.construct_plate_map(plate_map_location=sample_info_map['plate_map'])
|
|
||||||
# logger.debug(f"plate_map: {self.plate_map}")
|
|
||||||
# self.lookup_table = self.construct_lookup_table(lookup_table_location=sample_info_map['lookup_table'])
|
|
||||||
# if "plates" in sample_info_map:
|
|
||||||
# self.plates = sample_info_map['plates']
|
|
||||||
# self.excel_to_db_map = sample_info_map['xl_db_translation']
|
|
||||||
self.plate_map_samples = self.parse_plate_map()
|
self.plate_map_samples = self.parse_plate_map()
|
||||||
self.lookup_samples = self.parse_lookup_table()
|
self.lookup_samples = self.parse_lookup_table()
|
||||||
# if isinstance(self.lookup_table, pd.DataFrame):
|
|
||||||
# self.parse_lookup_table()
|
|
||||||
|
|
||||||
def fetch_sample_info_map(self, submission_type: str, sample_map: dict | None = None) -> dict:
|
def fetch_sample_info_map(self, submission_type: str, sample_map: dict | None = None) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -395,17 +367,12 @@ class SampleParser(object):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: Info locations.
|
dict: Info locations.
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Looking up submission type: {submission_type}")
|
# logger.debug(f"Looking up submission type: {submission_type}")
|
||||||
# submission_type = SubmissionType.query(name=submission_type)
|
|
||||||
# self.sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=submission_type)
|
|
||||||
# self.custom_sub_parser = .parse_samples
|
|
||||||
self.sample_type = self.sub_object.get_default_info("sample_type")
|
self.sample_type = self.sub_object.get_default_info("sample_type")
|
||||||
self.samp_object = BasicSample.find_polymorphic_subclass(polymorphic_identity=self.sample_type)
|
self.samp_object = BasicSample.find_polymorphic_subclass(polymorphic_identity=self.sample_type)
|
||||||
logger.debug(f"Got sample class: {self.samp_object.__name__}")
|
# logger.debug(f"Got sample class: {self.samp_object.__name__}")
|
||||||
# self.custom_sample_parser = .parse_sample
|
|
||||||
# logger.debug(f"info_map: {pformat(se)}")
|
# logger.debug(f"info_map: {pformat(se)}")
|
||||||
if sample_map is None:
|
if sample_map is None:
|
||||||
# sample_info_map = submission_type.info_map['samples']
|
|
||||||
sample_info_map = self.sub_object.construct_sample_map()
|
sample_info_map = self.sub_object.construct_sample_map()
|
||||||
else:
|
else:
|
||||||
sample_info_map = sample_map
|
sample_info_map = sample_map
|
||||||
@@ -459,22 +426,6 @@ class SampleParser(object):
|
|||||||
invalids = [0, "0", "EMPTY"]
|
invalids = [0, "0", "EMPTY"]
|
||||||
smap = self.sample_info_map['plate_map']
|
smap = self.sample_info_map['plate_map']
|
||||||
ws = self.xl[smap['sheet']]
|
ws = self.xl[smap['sheet']]
|
||||||
# ws.protection = SheetProtection()
|
|
||||||
# new_df = self.plate_map.dropna(axis=1, how='all')
|
|
||||||
# columns = new_df.columns.tolist()
|
|
||||||
# for _, iii in new_df.iterrows():
|
|
||||||
# for c in columns:
|
|
||||||
# if check_not_nan(iii[c]):
|
|
||||||
# if iii[c] in invalids:
|
|
||||||
# logger.debug(f"Invalid sample name: {iii[c]}, skipping.")
|
|
||||||
# continue
|
|
||||||
# id = iii[c]
|
|
||||||
# logger.debug(f"Adding sample {iii[c]}")
|
|
||||||
# try:
|
|
||||||
# c = self.plate_map.columns.get_loc(c) + 1
|
|
||||||
# except Exception as e:
|
|
||||||
# logger.error(f"Unable to get column index of {c} due to {e}")
|
|
||||||
# self.samples.append(dict(submitter_id=id, row=row_keys[iii._name], column=c))
|
|
||||||
plate_map_samples = []
|
plate_map_samples = []
|
||||||
for ii, row in enumerate(range(smap['start_row'], smap['end_row'] + 1), start=1):
|
for ii, row in enumerate(range(smap['start_row'], smap['end_row'] + 1), start=1):
|
||||||
# logger.debug(f"Parsing row: {row}")
|
# logger.debug(f"Parsing row: {row}")
|
||||||
@@ -494,42 +445,12 @@ class SampleParser(object):
|
|||||||
pass
|
pass
|
||||||
return plate_map_samples
|
return plate_map_samples
|
||||||
|
|
||||||
def parse_lookup_table(self) -> dict:
|
def parse_lookup_table(self) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
Parse misc info from lookup table.
|
Parse misc info from lookup table.
|
||||||
"""
|
"""
|
||||||
lmap = self.sample_info_map['lookup_table']
|
lmap = self.sample_info_map['lookup_table']
|
||||||
ws = self.xl[lmap['sheet']]
|
ws = self.xl[lmap['sheet']]
|
||||||
# for sample in self.samples:
|
|
||||||
# addition = self.lookup_table[self.lookup_table.isin([sample['submitter_id']]).any(axis=1)].squeeze()
|
|
||||||
# # logger.debug(addition)
|
|
||||||
# if isinstance(addition, pd.DataFrame) and not addition.empty:
|
|
||||||
# addition = addition.iloc[0]
|
|
||||||
# # logger.debug(f"Lookuptable info: {addition.to_dict()}")
|
|
||||||
# for k,v in addition.to_dict().items():
|
|
||||||
# # logger.debug(f"Checking {k} in lookup table.")
|
|
||||||
# if check_not_nan(k) and isinstance(k, str):
|
|
||||||
# if k.lower() not in sample:
|
|
||||||
# k = k.replace(" ", "_").replace("#","num").lower()
|
|
||||||
# # logger.debug(f"Adding {type(v)} - {k}, {v} to the lookuptable output dict")
|
|
||||||
# match v:
|
|
||||||
# case pd.Timestamp():
|
|
||||||
# sample[k] = v.date()
|
|
||||||
# case str():
|
|
||||||
# sample[k] = determine_if_date(v)
|
|
||||||
# case _:
|
|
||||||
# sample[k] = v
|
|
||||||
# # Set row in lookup table to blank values to prevent multipe lookups.
|
|
||||||
# try:
|
|
||||||
# self.lookup_table.loc[self.lookup_table['Sample #']==addition['Sample #']] = np.nan
|
|
||||||
# except (ValueError, KeyError):
|
|
||||||
# pass
|
|
||||||
# try:
|
|
||||||
# self.lookup_table.loc[self.lookup_table['Well']==addition['Well']] = np.nan
|
|
||||||
# except (ValueError, KeyError):
|
|
||||||
# pass
|
|
||||||
# # logger.debug(f"Output sample dict: {sample}")
|
|
||||||
# logger.debug(f"Final lookup_table: \n\n {self.lookup_table}")
|
|
||||||
lookup_samples = []
|
lookup_samples = []
|
||||||
for ii, row in enumerate(range(lmap['start_row'], lmap['end_row']+1), start=1):
|
for ii, row in enumerate(range(lmap['start_row'], lmap['end_row']+1), start=1):
|
||||||
row_dict = {k:ws.cell(row=row, column=v).value for k, v in lmap['sample_columns'].items()}
|
row_dict = {k:ws.cell(row=row, column=v).value for k, v in lmap['sample_columns'].items()}
|
||||||
@@ -549,7 +470,7 @@ class SampleParser(object):
|
|||||||
|
|
||||||
def parse_samples(self) -> Tuple[Report | None, List[dict] | List[PydSample]]:
|
def parse_samples(self) -> Tuple[Report | None, List[dict] | List[PydSample]]:
|
||||||
"""
|
"""
|
||||||
Parse merged platemap\lookup info into dicts/samples
|
Parse merged platemap/lookup info into dicts/samples
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[dict]|List[models.BasicSample]: List of samples
|
List[dict]|List[models.BasicSample]: List of samples
|
||||||
@@ -567,36 +488,14 @@ class SampleParser(object):
|
|||||||
v = convert_nans_to_nones(v)
|
v = convert_nans_to_nones(v)
|
||||||
case _:
|
case _:
|
||||||
v = v
|
v = v
|
||||||
# try:
|
|
||||||
# translated_dict[self.excel_to_db_map[k]] = convert_nans_to_nones(v)
|
|
||||||
# except KeyError:
|
|
||||||
translated_dict[k] = convert_nans_to_nones(v)
|
translated_dict[k] = convert_nans_to_nones(v)
|
||||||
translated_dict['sample_type'] = f"{self.submission_type} Sample"
|
translated_dict['sample_type'] = f"{self.submission_type} Sample"
|
||||||
# translated_dict = self.custom_sub_parser(translated_dict)
|
|
||||||
translated_dict = self.sub_object.parse_samples(translated_dict)
|
translated_dict = self.sub_object.parse_samples(translated_dict)
|
||||||
# translated_dict = self.custom_sample_parser(translated_dict)
|
|
||||||
translated_dict = self.samp_object.parse_sample(translated_dict)
|
translated_dict = self.samp_object.parse_sample(translated_dict)
|
||||||
# logger.debug(f"Here is the output of the custom parser:\n{translated_dict}")
|
# logger.debug(f"Here is the output of the custom parser:\n{translated_dict}")
|
||||||
new_samples.append(PydSample(**translated_dict))
|
new_samples.append(PydSample(**translated_dict))
|
||||||
return result, new_samples
|
return result, new_samples
|
||||||
|
|
||||||
# def grab_plates(self) -> List[str]:
|
|
||||||
# """
|
|
||||||
# Parse plate names from
|
|
||||||
#
|
|
||||||
# Returns:
|
|
||||||
# List[str]: list of plate names.
|
|
||||||
# """
|
|
||||||
# plates = []
|
|
||||||
# for plate in self.plates:
|
|
||||||
# df = self.xl.parse(plate['sheet'], header=None)
|
|
||||||
# if isinstance(df.iat[plate['row'] - 1, plate['column'] - 1], str):
|
|
||||||
# output = RSLNamer.retrieve_rsl_number(filename=df.iat[plate['row'] - 1, plate['column'] - 1])
|
|
||||||
# else:
|
|
||||||
# continue
|
|
||||||
# plates.append(output)
|
|
||||||
# return plates
|
|
||||||
|
|
||||||
def reconcile_samples(self):
|
def reconcile_samples(self):
|
||||||
# TODO: Move to pydantic validator?
|
# TODO: Move to pydantic validator?
|
||||||
if self.plate_map_samples is None or self.lookup_samples is None:
|
if self.plate_map_samples is None or self.lookup_samples is None:
|
||||||
@@ -606,29 +505,17 @@ class SampleParser(object):
|
|||||||
merge_on_id = self.sample_info_map['lookup_table']['merge_on_id']
|
merge_on_id = self.sample_info_map['lookup_table']['merge_on_id']
|
||||||
plate_map_samples = sorted(copy(self.plate_map_samples), key=lambda d: d['id'])
|
plate_map_samples = sorted(copy(self.plate_map_samples), key=lambda d: d['id'])
|
||||||
lookup_samples = sorted(copy(self.lookup_samples), key=lambda d: d[merge_on_id])
|
lookup_samples = sorted(copy(self.lookup_samples), key=lambda d: d[merge_on_id])
|
||||||
# try:
|
|
||||||
# assert len(plate_map_samples) == len(lookup_samples)
|
|
||||||
# except AssertionError:
|
|
||||||
# if len(plate_map_samples) > len(lookup_samples):
|
|
||||||
# logger.error(
|
|
||||||
# f"Plate samples ({len(plate_map_samples)}) is longer than Lookup samples: ({len(lookup_samples)})")
|
|
||||||
# return plate_map_samples
|
|
||||||
# else:
|
|
||||||
# logger.error(
|
|
||||||
# f"Lookup samples ({len(lookup_samples)}) is longer than Plate samples: ({len(plate_map_samples)})")
|
|
||||||
# return lookup_samples
|
|
||||||
for ii, psample in enumerate(plate_map_samples):
|
for ii, psample in enumerate(plate_map_samples):
|
||||||
try:
|
try:
|
||||||
check = psample['id'] == lookup_samples[ii][merge_on_id]
|
check = psample['id'] == lookup_samples[ii][merge_on_id]
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
check = False
|
check = False
|
||||||
if check:
|
if check:
|
||||||
logger.debug(f"Direct match found for {psample['id']}")
|
# logger.debug(f"Direct match found for {psample['id']}")
|
||||||
new = lookup_samples[ii] | psample
|
new = lookup_samples[ii] | psample
|
||||||
lookup_samples[ii] = {}
|
lookup_samples[ii] = {}
|
||||||
# samples.append(new)
|
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Match for {psample['id']} not direct, running search.")
|
# logger.warning(f"Match for {psample['id']} not direct, running search.")
|
||||||
for jj, lsample in enumerate(lookup_samples):
|
for jj, lsample in enumerate(lookup_samples):
|
||||||
try:
|
try:
|
||||||
check = lsample[merge_on_id] == psample['id']
|
check = lsample[merge_on_id] == psample['id']
|
||||||
@@ -637,13 +524,9 @@ class SampleParser(object):
|
|||||||
if check:
|
if check:
|
||||||
new = lsample | psample
|
new = lsample | psample
|
||||||
lookup_samples[jj] = {}
|
lookup_samples[jj] = {}
|
||||||
# self.samples.append(new)
|
|
||||||
# samples.append(new)
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
new = psample
|
new = psample
|
||||||
# samples.append(psample)
|
|
||||||
# new['sample_type'] = f"{self.submission_type} Sample"
|
|
||||||
try:
|
try:
|
||||||
check = new['submitter_id'] is None
|
check = new['submitter_id'] is None
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from backend.validators.pydant import PydSubmission
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import logging, re
|
import logging, re
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from openpyxl import load_workbook
|
from openpyxl import load_workbook
|
||||||
from backend.db.models import BasicSubmission, SubmissionType
|
from backend.db.models import BasicSubmission, SubmissionType
|
||||||
@@ -79,6 +80,8 @@ class RSLNamer(object):
|
|||||||
except UnboundLocalError:
|
except UnboundLocalError:
|
||||||
check = True
|
check = True
|
||||||
if check:
|
if check:
|
||||||
|
if "pytest" in sys.modules:
|
||||||
|
return "Bacterial Culture"
|
||||||
# logger.debug("Final option, ask the user for submission type")
|
# logger.debug("Final option, ask the user for submission type")
|
||||||
from frontend.widgets import ObjectSelector
|
from frontend.widgets import ObjectSelector
|
||||||
dlg = ObjectSelector(title="Couldn't parse submission type.",
|
dlg = ObjectSelector(title="Couldn't parse submission type.",
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
Contains pydantic models and accompanying validators
|
Contains pydantic models and accompanying validators
|
||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
import uuid, re, logging
|
import uuid, re, logging
|
||||||
from pydantic import BaseModel, field_validator, Field, model_validator, PrivateAttr
|
from pydantic import BaseModel, field_validator, Field, model_validator, PrivateAttr
|
||||||
@@ -431,10 +433,14 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
value['value'] = None
|
value['value'] = None
|
||||||
if value['value'] is None:
|
if value['value'] is None:
|
||||||
value['missing'] = True
|
value['missing'] = True
|
||||||
|
if "pytest" in sys.modules:
|
||||||
|
value['value'] = "Nosocomial"
|
||||||
|
return value
|
||||||
from frontend.widgets.pop_ups import ObjectSelector
|
from frontend.widgets.pop_ups import ObjectSelector
|
||||||
dlg = ObjectSelector(title="Missing Submitting Lab",
|
dlg = ObjectSelector(title="Missing Submitting Lab",
|
||||||
message="We need a submitting lab. Please select from the list.",
|
message="We need a submitting lab. Please select from the list.",
|
||||||
obj_type=Organization)
|
obj_type=Organization)
|
||||||
|
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
value['value'] = dlg.parse_form()
|
value['value'] = dlg.parse_form()
|
||||||
else:
|
else:
|
||||||
@@ -651,9 +657,13 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
logger.debug(f"Setting {key} to {value}")
|
logger.debug(f"Setting {key} to {value}")
|
||||||
match key:
|
match key:
|
||||||
case "reagents":
|
case "reagents":
|
||||||
|
if code == 1:
|
||||||
|
instance.submission_reagent_associations = []
|
||||||
|
logger.debug(f"Looking through {self.reagents}")
|
||||||
for reagent in self.reagents:
|
for reagent in self.reagents:
|
||||||
reagent, assoc = reagent.toSQL(submission=instance)
|
reagent, assoc = reagent.toSQL(submission=instance)
|
||||||
if assoc is not None and assoc not in instance.submission_reagent_associations:
|
logger.debug(f"Association: {assoc}")
|
||||||
|
if assoc is not None:# and assoc not in instance.submission_reagent_associations:
|
||||||
instance.submission_reagent_associations.append(assoc)
|
instance.submission_reagent_associations.append(assoc)
|
||||||
# instance.reagents.append(reagent)
|
# instance.reagents.append(reagent)
|
||||||
case "samples":
|
case "samples":
|
||||||
@@ -666,13 +676,13 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
case "equipment":
|
case "equipment":
|
||||||
logger.debug(f"Equipment: {pformat(self.equipment)}")
|
logger.debug(f"Equipment: {pformat(self.equipment)}")
|
||||||
try:
|
try:
|
||||||
if equip == None:
|
if equip is None:
|
||||||
continue
|
continue
|
||||||
except UnboundLocalError:
|
except UnboundLocalError:
|
||||||
continue
|
continue
|
||||||
for equip in self.equipment:
|
for equip in self.equipment:
|
||||||
equip, association = equip.toSQL(submission=instance)
|
equip, association = equip.toSQL(submission=instance)
|
||||||
if association != None:
|
if association is not None:
|
||||||
association.save()
|
association.save()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Equipment association SQL object to be added to submission: {association.__dict__}")
|
f"Equipment association SQL object to be added to submission: {association.__dict__}")
|
||||||
@@ -719,7 +729,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
# We need to make sure there's a proper rsl plate number
|
# 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}")
|
logger.debug(f"We've got a total cost of {instance.run_cost}")
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Constructed instance: {instance.to_string()}")
|
logger.debug(f"Constructed instance: {instance}")
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.debug(f"Something went wrong constructing instance {self.rsl_plate_num}: {e}")
|
logger.debug(f"Something went wrong constructing instance {self.rsl_plate_num}: {e}")
|
||||||
logger.debug(f"Constructed submissions message: {msg}")
|
logger.debug(f"Constructed submissions message: {msg}")
|
||||||
|
|||||||
@@ -263,45 +263,45 @@ class SubmissionFormWidget(QWidget):
|
|||||||
self.app.report.add_result(report)
|
self.app.report.add_result(report)
|
||||||
self.app.result_reporter()
|
self.app.result_reporter()
|
||||||
|
|
||||||
def kit_integrity_completion_function(self, extraction_kit:str|None=None):
|
# def kit_integrity_completion_function(self, extraction_kit:str|None=None):
|
||||||
"""
|
# """
|
||||||
Compare kit contents to parsed contents and creates widgets.
|
# Compare kit contents to parsed contents and creates widgets.
|
||||||
|
#
|
||||||
Args:
|
# Args:
|
||||||
obj (QMainWindow): The original app window
|
# obj (QMainWindow): The original app window
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
# Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
||||||
"""
|
# """
|
||||||
report = Report()
|
# report = Report()
|
||||||
missing_reagents = []
|
# missing_reagents = []
|
||||||
# logger.debug(inspect.currentframe().f_back.f_code.co_name)
|
# # logger.debug(inspect.currentframe().f_back.f_code.co_name)
|
||||||
# find the widget that contains kit info
|
# # find the widget that contains kit info
|
||||||
if extraction_kit is None:
|
# if extraction_kit is None:
|
||||||
kit_widget = self.find_widgets(object_name="extraction_kit")[0].input
|
# kit_widget = self.find_widgets(object_name="extraction_kit")[0].input
|
||||||
logger.debug(f"Kit selector: {kit_widget}")
|
# logger.debug(f"Kit selector: {kit_widget}")
|
||||||
# get current kit being used
|
# # get current kit being used
|
||||||
self.ext_kit = kit_widget.currentText()
|
# self.ext_kit = kit_widget.currentText()
|
||||||
else:
|
# else:
|
||||||
self.ext_kit = extraction_kit
|
# self.ext_kit = extraction_kit
|
||||||
for reagent in self.reagents:
|
# for reagent in self.reagents:
|
||||||
logger.debug(f"Creating widget for {reagent}")
|
# logger.debug(f"Creating widget for {reagent}")
|
||||||
add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.ext_kit)
|
# add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.ext_kit)
|
||||||
# self.form.layout().addWidget(add_widget)
|
# # self.form.layout().addWidget(add_widget)
|
||||||
self.layout.addWidget(add_widget)
|
# self.layout.addWidget(add_widget)
|
||||||
if reagent.missing:
|
# if reagent.missing:
|
||||||
missing_reagents.append(reagent)
|
# missing_reagents.append(reagent)
|
||||||
logger.debug(f"Checking integrity of {self.ext_kit}")
|
# logger.debug(f"Checking integrity of {self.ext_kit}")
|
||||||
# TODO: put check_kit_integrity here instead of what's here?
|
# # TODO: put check_kit_integrity here instead of what's here?
|
||||||
# see if there are any missing reagents
|
# # see if there are any missing reagents
|
||||||
if len(missing_reagents) > 0:
|
# if len(missing_reagents) > 0:
|
||||||
result = Result(msg=f"""The submission you are importing is missing some reagents expected by the kit.\n\n
|
# result = Result(msg=f"""The submission you are importing is missing some reagents expected by the kit.\n\n
|
||||||
It looks like you are missing: {[item.type.upper() for item in missing_reagents]}\n\n
|
# It looks like you are missing: {[item.type.upper() for item in missing_reagents]}\n\n
|
||||||
Alternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.
|
# Alternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.
|
||||||
\n\nPlease make sure you check the lots carefully!""".replace(" ", ""), status="Warning")
|
# \n\nPlease make sure you check the lots carefully!""".replace(" ", ""), status="Warning")
|
||||||
report.add_result(result)
|
# report.add_result(result)
|
||||||
self.report.add_result(report)
|
# self.report.add_result(report)
|
||||||
logger.debug(f"Outgoing report: {self.report.results}")
|
# logger.debug(f"Outgoing report: {self.report.results}")
|
||||||
|
|
||||||
def clear_form(self):
|
def clear_form(self):
|
||||||
"""
|
"""
|
||||||
@@ -374,17 +374,12 @@ class SubmissionFormWidget(QWidget):
|
|||||||
return
|
return
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
# assert base_submission.reagents != []
|
# NOTE: add reagents to submission object
|
||||||
# add reagents to submission object
|
|
||||||
for reagent in base_submission.reagents:
|
for reagent in base_submission.reagents:
|
||||||
# logger.debug(f"Updating: {reagent} with {reagent.lot}")
|
# logger.debug(f"Updating: {reagent} with {reagent.lot}")
|
||||||
reagent.update_last_used(kit=base_submission.extraction_kit)
|
reagent.update_last_used(kit=base_submission.extraction_kit)
|
||||||
# logger.debug(f"Here is the final submission: {pformat(base_submission.__dict__)}")
|
# logger.debug(f"Final reagents: {pformat(base_submission.reagents)}")
|
||||||
# logger.debug(f"Parsed reagents: {pformat(base_submission.reagents)}")
|
# sys.exit("Programmed stop submission_widget.py, line 381")
|
||||||
# logger.debug(f"Sending submission: {base_submission.rsl_plate_num} to database.")
|
|
||||||
# logger.debug(f"Samples from pyd: {pformat(self.pyd.samples)}")
|
|
||||||
# logger.debug(f"Samples SQL: {pformat([item.__dict__ for item in base_submission.samples])}")
|
|
||||||
# logger.debug(f"")
|
|
||||||
base_submission.save()
|
base_submission.save()
|
||||||
# update summary sheet
|
# update summary sheet
|
||||||
self.app.table_widget.sub_wid.setData()
|
self.app.table_widget.sub_wid.setData()
|
||||||
@@ -414,12 +409,12 @@ class SubmissionFormWidget(QWidget):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"No csv file found in the submission at this point.")
|
logger.error(f"No csv file found in the submission at this point.")
|
||||||
|
|
||||||
def parse_form(self) -> PydSubmission:
|
def parse_form(self) -> Report:
|
||||||
"""
|
"""
|
||||||
Transforms form info into PydSubmission
|
Transforms form info into PydSubmission
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PydSubmission: Pydantic submission object
|
Report: Report on status of parse.
|
||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
logger.debug(f"Hello from form parser!")
|
logger.debug(f"Hello from form parser!")
|
||||||
@@ -430,15 +425,16 @@ class SubmissionFormWidget(QWidget):
|
|||||||
match widget:
|
match widget:
|
||||||
case self.ReagentFormWidget():
|
case self.ReagentFormWidget():
|
||||||
reagent, _ = widget.parse_form()
|
reagent, _ = widget.parse_form()
|
||||||
if reagent != None:
|
if reagent is not None:
|
||||||
reagents.append(reagent)
|
reagents.append(reagent)
|
||||||
case self.InfoItem():
|
case self.InfoItem():
|
||||||
field, value = widget.parse_form()
|
field, value = widget.parse_form()
|
||||||
if field != None:
|
if field is not None:
|
||||||
info[field] = value
|
info[field] = value
|
||||||
logger.debug(f"Info: {pformat(info)}")
|
logger.debug(f"Info: {pformat(info)}")
|
||||||
logger.debug(f"Reagents: {pformat(reagents)}")
|
logger.debug(f"Reagents going into pyd: {pformat(reagents)}")
|
||||||
self.pyd.reagents = reagents
|
self.pyd.reagents = reagents
|
||||||
|
|
||||||
# logger.debug(f"Attrs not in info: {[k for k, v in self.__dict__.items() if k not in info.keys()]}")
|
# logger.debug(f"Attrs not in info: {[k for k, v in self.__dict__.items() if k not in info.keys()]}")
|
||||||
for item in self.recover:
|
for item in self.recover:
|
||||||
logger.debug(f"Attempting to recover: {item}")
|
logger.debug(f"Attempting to recover: {item}")
|
||||||
|
|||||||
@@ -238,9 +238,12 @@ class Settings(BaseSettings, extra="allow"):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# self.set_from_db(db_path=kwargs['database_path'])
|
self.set_from_db(db_path=kwargs['database_path'])
|
||||||
|
|
||||||
def set_from_db(self, db_path:Path):
|
def set_from_db(self, db_path:Path):
|
||||||
|
if 'pytest' in sys.modules:
|
||||||
|
config_items = dict(power_users=['lwark', 'styson', 'ruwang'])
|
||||||
|
else:
|
||||||
session = Session(create_engine(f"sqlite:///{db_path}"))
|
session = Session(create_engine(f"sqlite:///{db_path}"))
|
||||||
config_items = session.execute(text("SELECT * FROM _configitem")).all()
|
config_items = session.execute(text("SELECT * FROM _configitem")).all()
|
||||||
session.close()
|
session.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user