diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py
index 5dea287..4556eb4 100644
--- a/src/submissions/backend/db/models/__init__.py
+++ b/src/submissions/backend/db/models/__init__.py
@@ -24,28 +24,6 @@ Base: DeclarativeMeta = declarative_base()
logger = logging.getLogger(f"submissions.{__name__}")
-class LogMixin(Base):
-
- tracking_exclusion: ClassVar = ['artic_technician', 'submission_sample_associations',
- 'submission_reagent_associations', 'submission_equipment_associations',
- 'submission_tips_associations', 'contact_id', 'gel_info', 'gel_controls',
- 'source_plates']
-
- __abstract__ = True
-
- @property
- def truncated_name(self):
- name = str(self)
- if len(name) > 64:
- name = name.replace("<", "").replace(">", "")
- if len(name) > 64:
- # NOTE: As if re'agent'
- name = name.replace("agent", "")
- if len(name) > 64:
- name = f"...{name[-61:]}"
- return name
-
-
class BaseClass(Base):
"""
Abstract class to pass ctx values to all SQLAlchemy objects.
@@ -243,8 +221,10 @@ class BaseClass(Base):
Returns:
Any | List[Any]: Single result if limit = 1 or List if other.
"""
+ logger.debug(f"Kwargs: {kwargs}")
if model is None:
model = cls
+ logger.debug(f"Model: {model}")
if query is None:
query: Query = cls.__database_session__.query(model)
singles = model.get_default_info('singles')
@@ -477,6 +457,28 @@ class BaseClass(Base):
return output_date
+class LogMixin(Base):
+
+ tracking_exclusion: ClassVar = ['artic_technician', 'submission_sample_associations',
+ 'submission_reagent_associations', 'submission_equipment_associations',
+ 'submission_tips_associations', 'contact_id', 'gel_info', 'gel_controls',
+ 'source_plates']
+
+ __abstract__ = True
+
+ @property
+ def truncated_name(self):
+ name = str(self)
+ if len(name) > 64:
+ name = name.replace("<", "").replace(">", "")
+ if len(name) > 64:
+ # NOTE: As if re'agent'
+ name = name.replace("agent", "")
+ if len(name) > 64:
+ name = f"...{name[-61:]}"
+ return name
+
+
class ConfigItem(BaseClass):
"""
Key:JSON objects to store config settings in database.
diff --git a/src/submissions/backend/db/models/controls.py b/src/submissions/backend/db/models/controls.py
index d9a017d..6bae5ef 100644
--- a/src/submissions/backend/db/models/controls.py
+++ b/src/submissions/backend/db/models/controls.py
@@ -123,6 +123,9 @@ class Control(BaseClass):
controltype = relationship("ControlType", back_populates="instances",
foreign_keys=[controltype_name]) #: reference to parent control type
name = Column(String(255), unique=True) #: Sample ID
+ sample_id = Column(String, ForeignKey("_basicsample.id", ondelete="SET NULL",
+ name="fk_Cont_sample_id")) #: name of joined submission type
+ sample = relationship("BasicSample", back_populates="control") #: This control's submission sample
submitted_date = Column(TIMESTAMP) #: Date submitted to Robotics
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id")) #: parent submission id
submission = relationship("BasicSubmission", back_populates="controls",
@@ -362,7 +365,6 @@ class IridaControl(Control):
refseq_version = Column(String(16)) #: version of refseq used in fastq parsing
kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing
kraken2_db_version = Column(String(32)) #: folder name of kraken2 db
- sample = relationship("BacterialCultureSample", back_populates="control") #: This control's submission sample
sample_id = Column(INTEGER,
ForeignKey("_basicsample.id", ondelete="SET NULL", name="cont_BCS_id")) #: sample id key
diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py
index d3efa8c..8744dd2 100644
--- a/src/submissions/backend/db/models/kits.py
+++ b/src/submissions/backend/db/models/kits.py
@@ -848,7 +848,7 @@ class SubmissionType(BaseClass):
name = Column(String(128), unique=True) #: name of submission type
info_map = Column(JSON) #: Where parsable information is found in the excel workbook corresponding to this type.
defaults = Column(JSON) #: Basic information about this submission type
- instances = relationship("BasicSubmission") #: Concrete instances of this type.
+ instances = relationship("ClientSubmission", back_populates="submission_type") #: Concrete instances of this type.
template_file = Column(BLOB) #: Blank form for this type stored as binary.
processes = relationship("Process", back_populates="submission_types",
secondary=submissiontypes_processes) #: Relation to equipment processes used for this type.
@@ -1048,9 +1048,9 @@ class SubmissionType(BaseClass):
}
"""
- runtype_kit_associations = relationship(
- "RunTypeKitTypeAssociation",
- back_populates="runtype",
+ submissiontype_kit_associations = relationship(
+ "SubmissionTypeKitTypeAssociation",
+ back_populates="submission_type",
cascade="all, delete-orphan",
) #: Association of kittypes
diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py
index d2adba5..6dc0495 100644
--- a/src/submissions/backend/db/models/submissions.py
+++ b/src/submissions/backend/db/models/submissions.py
@@ -15,8 +15,8 @@ from operator import itemgetter
from pprint import pformat
from pandas import DataFrame
from sqlalchemy.ext.hybrid import hybrid_property
-from . import Base, BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, LogMixin, \
- SubmissionReagentAssociation
+from . import Base, BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, \
+ SubmissionReagentAssociation, LogMixin
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func, Table
from sqlalchemy.orm import relationship, validates, Query
from sqlalchemy.orm.attributes import flag_modified
@@ -55,14 +55,15 @@ class ClientSubmission(BaseClass, LogMixin):
String(64)) #: ["Research", "Diagnostic", "Surveillance", "Validation"], else defaults to submission_type_name
sample_count = Column(INTEGER) #: Number of samples in the submission
- runs = relationship("BasicRun", back_populates="client_submission") #: many-to-one relationship
+ runs = relationship("BasicSubmission", back_populates="client_submission") #: many-to-one relationship
contact = relationship("Contact", back_populates="submissions") #: client org
contact_id = Column(INTEGER, ForeignKey("_contact.id", ondelete="SET NULL",
name="fk_BS_contact_id")) #: client lab id from _organizations
- submission_type = relationship("SubmissionType", back_populates="instances") #: archetype of this submission
submission_type_name = Column(String, ForeignKey("_submissiontype.name", ondelete="SET NULL",
name="fk_BS_subtype_name")) #: name of joined submission type
+ submission_type = relationship("SubmissionType", back_populates="instances") #: archetype of this submission
+
cost_centre = Column(
String(64)) #: Permanent storage of used cost centre in case organization field changed in the future.
@@ -98,7 +99,9 @@ class BasicSubmission(BaseClass, LogMixin):
"""
id = Column(INTEGER, primary_key=True) #: primary key
- rsl_plate_number = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012)
+ rsl_plate_num = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012)
+ client_submission_id = Column(INTEGER, ForeignKey("_clientsubmission.id", ondelete="SET NULL",
+ name="fk_BS_clientsub_id")) #: client lab id from _organizations)
client_submission = relationship("ClientSubmission", back_populates="runs")
# submitter_plate_num = Column(String(127), unique=True) #: The number given to the submission by the submitting lab
started_date = Column(TIMESTAMP) #: Date this run was started.
@@ -304,7 +307,8 @@ class BasicSubmission(BaseClass, LogMixin):
case SubmissionType():
return sub_type
case _:
- return SubmissionType.query(cls.__mapper_args__['polymorphic_identity'])
+ # return SubmissionType.query(cls.__mapper_args__['polymorphic_identity'])
+ return None
@classmethod
def construct_info_map(cls, submission_type: SubmissionType | None = None,
@@ -356,7 +360,7 @@ class BasicSubmission(BaseClass, LogMixin):
"""
# NOTE: get lab from nested organization object
try:
- sub_lab = self.submitting_lab.name
+ sub_lab = self.client_submission.submitting_lab.name
except AttributeError:
sub_lab = None
try:
@@ -364,24 +368,24 @@ class BasicSubmission(BaseClass, LogMixin):
except AttributeError:
pass
# NOTE: get extraction kit name from nested kit object
- try:
- ext_kit = self.extraction_kit.name
- except AttributeError:
- ext_kit = None
+ # try:
+ # ext_kit = self.extraction_kit.name
+ # except AttributeError:
+ # ext_kit = None
# NOTE: load scraped extraction info
- try:
- ext_info = self.extraction_info
- except TypeError:
- ext_info = None
+ # try:
+ # ext_info = self.extraction_info
+ # except TypeError:
+ # ext_info = None
output = {
"id": self.id,
"plate_number": self.rsl_plate_num,
- "submission_type": self.submission_type_name,
- "submitter_plate_number": self.submitter_plate_num,
- "submitted_date": self.submitted_date.strftime("%Y-%m-%d"),
+ "submission_type": self.client_submission.submission_type_name,
+ "submitter_plate_number": self.client_submission.submitter_plate_num,
+ "submitted_date": self.client_submission.submitted_date.strftime("%Y-%m-%d"),
"submitting_lab": sub_lab,
- "sample_count": self.sample_count,
- "extraction_kit": ext_kit,
+ "sample_count": self.client_submission.sample_count,
+ "extraction_kit": "Change submissions.py line 388",
"cost": self.run_cost
}
if report:
@@ -433,11 +437,11 @@ class BasicSubmission(BaseClass, LogMixin):
contact_phone = self.contact.phone
except AttributeError:
contact_phone = "NA"
- output["submission_category"] = self.submission_category
+ output["submission_category"] = self.client_submission.submission_category
output["technician"] = self.technician
output["reagents"] = reagents
output["samples"] = samples
- output["extraction_info"] = ext_info
+ # output["extraction_info"] = ext_info
output["comment"] = comments
output["equipment"] = equipment
output["tips"] = tips
@@ -1236,7 +1240,8 @@ class BasicSubmission(BaseClass, LogMixin):
# # else:
start_date = cls.rectify_query_date(start_date)
end_date = cls.rectify_query_date(end_date, eod=True)
- query = query.filter(model.submitted_date.between(start_date, end_date))
+ logger.debug(f"Start date: {start_date}, end date: {end_date}")
+ query = query.join(ClientSubmission).filter(ClientSubmission.submitted_date.between(start_date, end_date))
# NOTE: by reagent (for some reason)
match reagent:
case str():
@@ -1256,7 +1261,9 @@ class BasicSubmission(BaseClass, LogMixin):
pass
match submission_type_name:
case str():
- query = query.filter(model.submission_type_name == submission_type_name)
+ if not start_date:
+ query = query.join(ClientSubmission)
+ query = query.filter(ClientSubmission.submission_type_name == submission_type_name)
case _:
pass
# NOTE: by id (returns only a single value)
@@ -1269,7 +1276,7 @@ class BasicSubmission(BaseClass, LogMixin):
limit = 1
case _:
pass
- query = query.order_by(cls.submitted_date.desc())
+ # query = query.order_by(cls.submitted_date.desc())
# NOTE: Split query results into pages of size {page_size}
if page_size > 0:
query = query.limit(page_size)
@@ -1471,7 +1478,7 @@ class BasicSubmission(BaseClass, LogMixin):
completed = self.completed_date.date()
except AttributeError:
completed = None
- return self.calculate_turnaround(start_date=self.submitted_date.date(), end_date=completed)
+ return self.calculate_turnaround(start_date=self.client_submission.submitted_date.date(), end_date=completed)
@classmethod
def calculate_turnaround(cls, start_date: date | None = None, end_date: date | None = None) -> int:
@@ -2459,6 +2466,7 @@ class BasicSample(BaseClass, LogMixin):
submitter_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
sample_type = Column(String(32)) #: mode_sub_type of sample
misc_info = Column(JSON)
+ control = relationship("Control", back_populates="sample", uselist=False)
sample_submission_associations = relationship(
"SubmissionSampleAssociation",
@@ -2775,140 +2783,140 @@ class BasicSample(BaseClass, LogMixin):
# NOTE: Below are the custom sample types
-class WastewaterSample(BasicSample):
- """
- Derivative wastewater sample
- """
-
- id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
- ww_processing_num = Column(String(64)) #: wastewater processing number
- ww_full_sample_id = Column(String(64)) #: full id given by entrics
- rsl_number = Column(String(64)) #: rsl plate identification number
- collection_date = Column(TIMESTAMP) #: Date sample collected
- received_date = Column(TIMESTAMP) #: Date sample received
- notes = Column(String(2000)) #: notes from submission form
- sample_location = Column(String(8)) #: location on 24 well plate
- __mapper_args__ = dict(polymorphic_identity="Wastewater Sample",
- polymorphic_load="inline",
- inherit_condition=(id == BasicSample.id))
-
- @classmethod
- def get_default_info(cls, *args):
- """
- Returns default info for a model. Extends BaseClass method.
-
- Returns:
- dict | list | str: Output of key:value dict or single (list, str) desired variable
- """
- dicto = super().get_default_info(*args)
- match dicto:
- case dict():
- dicto['singles'] += ['ww_processing_num']
- output = {}
- for k, v in dicto.items():
- if len(args) > 0 and k not in args:
- continue
- else:
- output[k] = v
- if len(args) == 1:
- return output[args[0]]
- case list():
- if "singles" in args:
- dicto += ['ww_processing_num']
- return dicto
- case _:
- pass
-
- def to_sub_dict(self, full_data: bool = False) -> dict:
- """
- gui friendly dictionary, extends parent method.
-
- Returns:
- dict: sample id, type, received date, collection date
- """
- sample = super().to_sub_dict(full_data=full_data)
- sample['ww_processing_num'] = self.ww_processing_num
- sample['sample_location'] = self.sample_location
- sample['received_date'] = self.received_date
- sample['collection_date'] = self.collection_date
- return sample
-
- @classmethod
- def parse_sample(cls, input_dict: dict) -> dict:
- """
- Custom sample parser. Extends parent
-
- Args:
- input_dict (dict): Basic parser results for this sample.
-
- Returns:
- dict: Updated parser results.
- """
- output_dict = super().parse_sample(input_dict)
- disallowed = ["", None, "None"]
- try:
- check = output_dict['rsl_number'] in disallowed
- except KeyError:
- check = True
- if check:
- output_dict['rsl_number'] = "RSL-WW-" + output_dict['ww_processing_num']
- if output_dict['ww_full_sample_id'] is not None and output_dict["submitter_id"] in disallowed:
- output_dict["submitter_id"] = output_dict['ww_full_sample_id']
- # check = check_key_or_attr("rsl_number", output_dict, check_none=True)
- return output_dict
-
- @classproperty
- def searchables(cls) -> List[dict]:
- """
- Delivers a list of fields that can be used in fuzzy search. Extends parent.
-
- Returns:
- List[str]: List of fields.
- """
- searchables = deepcopy(super().searchables)
- for item in ["ww_processing_num", "ww_full_sample_id", "rsl_number"]:
- label = item.strip("ww_").replace("_", " ").replace("rsl", "RSL").title()
- searchables.append(dict(label=label, field=item))
- return searchables
-
-
-class BacterialCultureSample(BasicSample):
- """
- base of bacterial culture sample
- """
- id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
- organism = Column(String(64)) #: bacterial specimen
- concentration = Column(String(16)) #: sample concentration
- control = relationship("IridaControl", back_populates="sample", uselist=False)
- __mapper_args__ = dict(polymorphic_identity="Bacterial Culture Sample",
- polymorphic_load="inline",
- inherit_condition=(id == BasicSample.id))
-
- def to_sub_dict(self, full_data: bool = False) -> dict:
- """
- gui friendly dictionary, extends parent method.
-
- Returns:
- dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
- """
- sample = super().to_sub_dict(full_data=full_data)
- sample['name'] = self.submitter_id
- sample['organism'] = self.organism
- try:
- sample['concentration'] = f"{float(self.concentration):.2f}"
- except (TypeError, ValueError):
- sample['concentration'] = 0.0
- if self.control is not None:
- sample['colour'] = [0, 128, 0]
- target = next((v for k, v in self.control.controltype.targets.items() if k == self.control.subtype),
- "Not Available")
- try:
- target = ", ".join(target)
- except:
- target = "None"
- sample['tooltip'] = f"\nControl: {self.control.controltype.name} - {target}"
- return sample
-
+# class WastewaterSample(BasicSample):
+# """
+# Derivative wastewater sample
+# """
+#
+# id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
+# ww_processing_num = Column(String(64)) #: wastewater processing number
+# ww_full_sample_id = Column(String(64)) #: full id given by entrics
+# rsl_number = Column(String(64)) #: rsl plate identification number
+# collection_date = Column(TIMESTAMP) #: Date sample collected
+# received_date = Column(TIMESTAMP) #: Date sample received
+# notes = Column(String(2000)) #: notes from submission form
+# sample_location = Column(String(8)) #: location on 24 well plate
+# __mapper_args__ = dict(polymorphic_identity="Wastewater Sample",
+# polymorphic_load="inline",
+# inherit_condition=(id == BasicSample.id))
+#
+# @classmethod
+# def get_default_info(cls, *args):
+# """
+# Returns default info for a model. Extends BaseClass method.
+#
+# Returns:
+# dict | list | str: Output of key:value dict or single (list, str) desired variable
+# """
+# dicto = super().get_default_info(*args)
+# match dicto:
+# case dict():
+# dicto['singles'] += ['ww_processing_num']
+# output = {}
+# for k, v in dicto.items():
+# if len(args) > 0 and k not in args:
+# continue
+# else:
+# output[k] = v
+# if len(args) == 1:
+# return output[args[0]]
+# case list():
+# if "singles" in args:
+# dicto += ['ww_processing_num']
+# return dicto
+# case _:
+# pass
+#
+# def to_sub_dict(self, full_data: bool = False) -> dict:
+# """
+# gui friendly dictionary, extends parent method.
+#
+# Returns:
+# dict: sample id, type, received date, collection date
+# """
+# sample = super().to_sub_dict(full_data=full_data)
+# sample['ww_processing_num'] = self.ww_processing_num
+# sample['sample_location'] = self.sample_location
+# sample['received_date'] = self.received_date
+# sample['collection_date'] = self.collection_date
+# return sample
+#
+# @classmethod
+# def parse_sample(cls, input_dict: dict) -> dict:
+# """
+# Custom sample parser. Extends parent
+#
+# Args:
+# input_dict (dict): Basic parser results for this sample.
+#
+# Returns:
+# dict: Updated parser results.
+# """
+# output_dict = super().parse_sample(input_dict)
+# disallowed = ["", None, "None"]
+# try:
+# check = output_dict['rsl_number'] in disallowed
+# except KeyError:
+# check = True
+# if check:
+# output_dict['rsl_number'] = "RSL-WW-" + output_dict['ww_processing_num']
+# if output_dict['ww_full_sample_id'] is not None and output_dict["submitter_id"] in disallowed:
+# output_dict["submitter_id"] = output_dict['ww_full_sample_id']
+# # check = check_key_or_attr("rsl_number", output_dict, check_none=True)
+# return output_dict
+#
+# @classproperty
+# def searchables(cls) -> List[dict]:
+# """
+# Delivers a list of fields that can be used in fuzzy search. Extends parent.
+#
+# Returns:
+# List[str]: List of fields.
+# """
+# searchables = deepcopy(super().searchables)
+# for item in ["ww_processing_num", "ww_full_sample_id", "rsl_number"]:
+# label = item.strip("ww_").replace("_", " ").replace("rsl", "RSL").title()
+# searchables.append(dict(label=label, field=item))
+# return searchables
+#
+#
+# class BacterialCultureSample(BasicSample):
+# """
+# base of bacterial culture sample
+# """
+# id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
+# organism = Column(String(64)) #: bacterial specimen
+# concentration = Column(String(16)) #: sample concentration
+# control = relationship("IridaControl", back_populates="sample", uselist=False)
+# __mapper_args__ = dict(polymorphic_identity="Bacterial Culture Sample",
+# polymorphic_load="inline",
+# inherit_condition=(id == BasicSample.id))
+#
+# def to_sub_dict(self, full_data: bool = False) -> dict:
+# """
+# gui friendly dictionary, extends parent method.
+#
+# Returns:
+# dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
+# """
+# sample = super().to_sub_dict(full_data=full_data)
+# sample['name'] = self.submitter_id
+# sample['organism'] = self.organism
+# try:
+# sample['concentration'] = f"{float(self.concentration):.2f}"
+# except (TypeError, ValueError):
+# sample['concentration'] = 0.0
+# if self.control is not None:
+# sample['colour'] = [0, 128, 0]
+# target = next((v for k, v in self.control.controltype.targets.items() if k == self.control.subtype),
+# "Not Available")
+# try:
+# target = ", ".join(target)
+# except:
+# target = "None"
+# sample['tooltip'] = f"\nControl: {self.control.controltype.name} - {target}"
+# return sample
+#
# # NOTE: Submission to Sample Associations
@@ -2920,26 +2928,27 @@ class SubmissionSampleAssociation(BaseClass):
id = Column(INTEGER, unique=True, nullable=False) #: id to be used for inheriting purposes
sample_id = Column(INTEGER, ForeignKey("_basicsample.id"), nullable=False) #: id of associated sample
- submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission
+ submission_id = Column(INTEGER, ForeignKey("_clientsubmission.id"), primary_key=True) #: id of associated submission
row = Column(INTEGER, primary_key=True) #: row on the 96 well plate
column = Column(INTEGER, primary_key=True) #: column on the 96 well plate
submission_rank = Column(INTEGER, nullable=False, default=0) #: Location in sample list
+ misc_info = Column(JSON)
# NOTE: reference to the Submission object
- submission = relationship(BasicSubmission,
+ submission = relationship(ClientSubmission,
back_populates="submission_sample_associations") #: associated submission
# NOTE: reference to the Sample object
sample = relationship(BasicSample, back_populates="sample_submission_associations") #: associated sample
- base_sub_type = Column(String) #: string of mode_sub_type name
+ # base_sub_type = Column(String) #: string of mode_sub_type name
# NOTE: Refers to the type of parent.
- __mapper_args__ = {
- "polymorphic_identity": "Basic Association",
- "polymorphic_on": base_sub_type,
- "with_polymorphic": "*",
- }
+ # __mapper_args__ = {
+ # "polymorphic_identity": "Basic Association",
+ # "polymorphic_on": base_sub_type,
+ # "with_polymorphic": "*",
+ # }
def __init__(self, submission: BasicSubmission = None, sample: BasicSample = None, row: int = 1, column: int = 1,
id: int | None = None, submission_rank: int = 0, **kwargs):
@@ -3183,96 +3192,96 @@ class SubmissionSampleAssociation(BaseClass):
raise AttributeError(f"Delete not implemented for {self.__class__}")
-class WastewaterAssociation(SubmissionSampleAssociation):
- """
- table containing wastewater specific submission/sample associations
- DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
- """
- id = Column(INTEGER, ForeignKey("_submissionsampleassociation.id"), primary_key=True)
- ct_n1 = Column(FLOAT(2)) #: AKA ct for N1
- ct_n2 = Column(FLOAT(2)) #: AKA ct for N2
- n1_status = Column(String(32)) #: positive or negative for N1
- n2_status = Column(String(32)) #: positive or negative for N2
- pcr_results = Column(JSON) #: imported PCR status from QuantStudio
-
- __mapper_args__ = dict(polymorphic_identity="Wastewater Association",
- polymorphic_load="inline",
- inherit_condition=(id == SubmissionSampleAssociation.id))
-
- def to_sub_dict(self) -> dict:
- """
- Returns a sample dictionary updated with instance information. Extends parent
-
- Returns:
- dict: Updated dictionary with row, column and well updated
- """
-
- sample = super().to_sub_dict()
- sample['ct'] = f"({self.ct_n1}, {self.ct_n2})"
- try:
- sample['source_row'] = row_keys[self.sample.sample_location[0]]
- sample['source_column'] = int(self.sample.sample_location[1:])
- except (TypeError, AttributeError) as e:
- logger.error(f"Couldn't set sources for {self.sample.rsl_number}. Looks like there isn't data.")
- try:
- sample['positive'] = any(["positive" in item for item in [self.n1_status, self.n2_status]])
- except (TypeError, AttributeError) as e:
- logger.error(f"Couldn't check positives for {self.sample.rsl_number}. Looks like there isn't PCR data.")
- return sample
-
- @property
- def hitpicked(self) -> dict | None:
- """
- Outputs a dictionary usable for html plate maps. Extends parent
-
- Returns:
- dict: dictionary of sample id, row and column in elution plate
- """
- sample = super().hitpicked
- try:
- scaler = max([self.ct_n1, self.ct_n2])
- except TypeError:
- scaler = 0.0
- if scaler == 0.0:
- scaler = 45
- bg = (45 - scaler) * 17
- red = min([64 + bg, 255])
- grn = max([255 - bg, 0])
- blu = 128
- sample['background_color'] = f"rgb({red}, {grn}, {blu})"
- try:
- sample[
- 'tooltip'] += f"
- ct N1: {'{:.2f}'.format(self.ct_n1)}
- ct N2: {'{:.2f}'.format(self.ct_n2)}"
- except (TypeError, AttributeError) as e:
- logger.error(f"Couldn't set tooltip for {self.sample.rsl_number}. Looks like there isn't PCR data.")
- return sample
-
-
-class WastewaterArticAssociation(SubmissionSampleAssociation):
- """
- table containing wastewater artic specific submission/sample associations
- DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
- """
- id = Column(INTEGER, ForeignKey("_submissionsampleassociation.id"), primary_key=True)
- source_plate = Column(String(32))
- source_plate_number = Column(INTEGER)
- source_well = Column(String(8))
- ct = Column(String(8)) #: AKA ct for N1
-
- __mapper_args__ = dict(polymorphic_identity="Wastewater Artic Association",
- polymorphic_load="inline",
- inherit_condition=(id == SubmissionSampleAssociation.id))
-
- def to_sub_dict(self) -> dict:
- """
- Returns a sample dictionary updated with instance information. Extends parent
-
- Returns:
- dict: Updated dictionary with row, column and well updated
- """
- sample = super().to_sub_dict()
- sample['ct'] = self.ct
- sample['source_plate'] = self.source_plate
- sample['source_plate_number'] = self.source_plate_number
- sample['source_well'] = self.source_well
- return sample
+# class WastewaterAssociation(SubmissionSampleAssociation):
+# """
+# table containing wastewater specific submission/sample associations
+# DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
+# """
+# id = Column(INTEGER, ForeignKey("_submissionsampleassociation.id"), primary_key=True)
+# ct_n1 = Column(FLOAT(2)) #: AKA ct for N1
+# ct_n2 = Column(FLOAT(2)) #: AKA ct for N2
+# n1_status = Column(String(32)) #: positive or negative for N1
+# n2_status = Column(String(32)) #: positive or negative for N2
+# pcr_results = Column(JSON) #: imported PCR status from QuantStudio
+#
+# __mapper_args__ = dict(polymorphic_identity="Wastewater Association",
+# polymorphic_load="inline",
+# inherit_condition=(id == SubmissionSampleAssociation.id))
+#
+# def to_sub_dict(self) -> dict:
+# """
+# Returns a sample dictionary updated with instance information. Extends parent
+#
+# Returns:
+# dict: Updated dictionary with row, column and well updated
+# """
+#
+# sample = super().to_sub_dict()
+# sample['ct'] = f"({self.ct_n1}, {self.ct_n2})"
+# try:
+# sample['source_row'] = row_keys[self.sample.sample_location[0]]
+# sample['source_column'] = int(self.sample.sample_location[1:])
+# except (TypeError, AttributeError) as e:
+# logger.error(f"Couldn't set sources for {self.sample.rsl_number}. Looks like there isn't data.")
+# try:
+# sample['positive'] = any(["positive" in item for item in [self.n1_status, self.n2_status]])
+# except (TypeError, AttributeError) as e:
+# logger.error(f"Couldn't check positives for {self.sample.rsl_number}. Looks like there isn't PCR data.")
+# return sample
+#
+# @property
+# def hitpicked(self) -> dict | None:
+# """
+# Outputs a dictionary usable for html plate maps. Extends parent
+#
+# Returns:
+# dict: dictionary of sample id, row and column in elution plate
+# """
+# sample = super().hitpicked
+# try:
+# scaler = max([self.ct_n1, self.ct_n2])
+# except TypeError:
+# scaler = 0.0
+# if scaler == 0.0:
+# scaler = 45
+# bg = (45 - scaler) * 17
+# red = min([64 + bg, 255])
+# grn = max([255 - bg, 0])
+# blu = 128
+# sample['background_color'] = f"rgb({red}, {grn}, {blu})"
+# try:
+# sample[
+# 'tooltip'] += f"
- ct N1: {'{:.2f}'.format(self.ct_n1)}
- ct N2: {'{:.2f}'.format(self.ct_n2)}"
+# except (TypeError, AttributeError) as e:
+# logger.error(f"Couldn't set tooltip for {self.sample.rsl_number}. Looks like there isn't PCR data.")
+# return sample
+#
+#
+# class WastewaterArticAssociation(SubmissionSampleAssociation):
+# """
+# table containing wastewater artic specific submission/sample associations
+# DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
+# """
+# id = Column(INTEGER, ForeignKey("_submissionsampleassociation.id"), primary_key=True)
+# source_plate = Column(String(32))
+# source_plate_number = Column(INTEGER)
+# source_well = Column(String(8))
+# ct = Column(String(8)) #: AKA ct for N1
+#
+# __mapper_args__ = dict(polymorphic_identity="Wastewater Artic Association",
+# polymorphic_load="inline",
+# inherit_condition=(id == SubmissionSampleAssociation.id))
+#
+# def to_sub_dict(self) -> dict:
+# """
+# Returns a sample dictionary updated with instance information. Extends parent
+#
+# Returns:
+# dict: Updated dictionary with row, column and well updated
+# """
+# sample = super().to_sub_dict()
+# sample['ct'] = self.ct
+# sample['source_plate'] = self.source_plate
+# sample['source_plate_number'] = self.source_plate_number
+# sample['source_well'] = self.source_well
+# return sample
diff --git a/src/submissions/backend/excel/reports.py b/src/submissions/backend/excel/reports.py
index ed20510..708e33a 100644
--- a/src/submissions/backend/excel/reports.py
+++ b/src/submissions/backend/excel/reports.py
@@ -47,7 +47,7 @@ class ReportMaker(object):
# NOTE: Set page size to zero to override limiting query size.
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date, page_size=0)
if organizations is not None:
- self.subs = [sub for sub in self.subs if sub.submitting_lab.name in organizations]
+ self.subs = [sub for sub in self.subs if sub.client_submission.submitting_lab.name in organizations]
self.detailed_df, self.summary_df = self.make_report_xlsx()
self.html = self.make_report_html(df=self.summary_df)
@@ -183,7 +183,10 @@ class TurnaroundMaker(ReportArchetype):
except (AttributeError, KeyError):
tat = None
if not tat:
- tat = ctx.TaT_threshold
+ try:
+ tat = ctx.TaT_threshold
+ except AttributeError:
+ tat = 3
try:
tat_ok = days <= tat
except TypeError:
diff --git a/src/submissions/frontend/widgets/gel_checker.py b/src/submissions/frontend/widgets/gel_checker.py
index 1e9a529..54c8f77 100644
--- a/src/submissions/frontend/widgets/gel_checker.py
+++ b/src/submissions/frontend/widgets/gel_checker.py
@@ -12,7 +12,7 @@ import logging, numpy as np
from pprint import pformat
from typing import Tuple, List
from pathlib import Path
-from backend.db.models import WastewaterArtic
+from backend.db.models import BasicSubmission
logger = logging.getLogger(f"submissions.{__name__}")
@@ -20,7 +20,7 @@ logger = logging.getLogger(f"submissions.{__name__}")
# Main window class
class GelBox(QDialog):
- def __init__(self, parent, img_path: str | Path, submission: WastewaterArtic):
+ def __init__(self, parent, img_path: str | Path, submission: BasicSubmission):
super().__init__(parent)
# NOTE: setting title
self.setWindowTitle(f"Gel - {img_path}")
diff --git a/src/submissions/tools/__init__.py b/src/submissions/tools/__init__.py
index b75b98f..e566ad3 100644
--- a/src/submissions/tools/__init__.py
+++ b/src/submissions/tools/__init__.py
@@ -1195,36 +1195,48 @@ class Settings(BaseSettings, extra="allow"):
func = function[1]
# NOTE: assign function based on its name being in config: startup/teardown
# NOTE: scripts must be registered using {name: Null} in the database
- if name in self.startup_scripts.keys():
- self.startup_scripts[name] = func
- if name in self.teardown_scripts.keys():
- self.teardown_scripts[name] = func
+ try:
+ if name in self.startup_scripts.keys():
+ self.startup_scripts[name] = func
+ except AttributeError:
+ pass
+ try:
+ if name in self.teardown_scripts.keys():
+ self.teardown_scripts[name] = func
+ except AttributeError:
+ pass
@timer
def run_startup(self):
"""
Runs startup scripts.
"""
- for script in self.startup_scripts.values():
- try:
- logger.info(f"Running startup script: {script.__name__}")
- thread = Thread(target=script, args=(ctx,))
- thread.start()
- except AttributeError:
- logger.error(f"Couldn't run startup script: {script}")
+ try:
+ for script in self.startup_scripts.values():
+ try:
+ logger.info(f"Running startup script: {script.__name__}")
+ thread = Thread(target=script, args=(ctx,))
+ thread.start()
+ except AttributeError:
+ logger.error(f"Couldn't run startup script: {script}")
+ except AttributeError:
+ pass
@timer
def run_teardown(self):
"""
Runs teardown scripts.
"""
- for script in self.teardown_scripts.values():
- try:
- logger.info(f"Running teardown script: {script.__name__}")
- thread = Thread(target=script, args=(ctx,))
- thread.start()
- except AttributeError:
- logger.error(f"Couldn't run teardown script: {script}")
+ try:
+ for script in self.teardown_scripts.values():
+ try:
+ logger.info(f"Running teardown script: {script.__name__}")
+ thread = Thread(target=script, args=(ctx,))
+ thread.start()
+ except AttributeError:
+ logger.error(f"Couldn't run teardown script: {script}")
+ except AttributeError:
+ pass
@classmethod
def get_alembic_db_path(cls, alembic_path, mode=Literal['path', 'schema', 'user', 'pass']) -> Path | str: