Overhauling database.
This commit is contained in:
@@ -27,7 +27,7 @@ def import_irida(ctx: Settings):
|
|||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.error(f"Error, could not import from irida due to {e}")
|
logger.error(f"Error, could not import from irida due to {e}")
|
||||||
return
|
return
|
||||||
sql = "SELECT name, submitted_date, submission_id, contains, matches, kraken, subtype, refseq_version, " \
|
sql = "SELECT name, submitted_date, run_id, contains, matches, kraken, subtype, refseq_version, " \
|
||||||
"kraken2_version, kraken2_db_version, sample_id FROM _iridacontrol INNER JOIN _control on _control.id " \
|
"kraken2_version, kraken2_db_version, sample_id FROM _iridacontrol INNER JOIN _control on _control.id " \
|
||||||
f"= _iridacontrol.id WHERE _control.name NOT IN ({prm_list})"
|
f"= _iridacontrol.id WHERE _control.name NOT IN ({prm_list})"
|
||||||
cursor = conn.execute(sql)
|
cursor = conn.execute(sql)
|
||||||
@@ -57,7 +57,7 @@ def import_irida(ctx: Settings):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
logger.error(f"Could not get sample for {sample}")
|
logger.error(f"Could not get sample for {sample}")
|
||||||
instance.submission = None
|
instance.submission = None
|
||||||
# instance.submission = sample.submission[0]
|
# instance.run = sample.run[0]
|
||||||
new_session.add(instance)
|
new_session.add(instance)
|
||||||
new_session.commit()
|
new_session.commit()
|
||||||
new_session.close()
|
new_session.close()
|
||||||
|
|||||||
@@ -33,11 +33,13 @@ class BaseClass(Base):
|
|||||||
__table_args__ = {'extend_existing': True} #: NOTE Will only add new columns
|
__table_args__ = {'extend_existing': True} #: NOTE Will only add new columns
|
||||||
|
|
||||||
singles = ['id']
|
singles = ['id']
|
||||||
omni_removes = ["id", 'submissions', "omnigui_class_dict", "omnigui_instance_dict"]
|
omni_removes = ["id", 'runs', "omnigui_class_dict", "omnigui_instance_dict"]
|
||||||
omni_sort = ["name"]
|
omni_sort = ["name"]
|
||||||
omni_inheritable = []
|
omni_inheritable = []
|
||||||
searchables = []
|
searchables = []
|
||||||
|
|
||||||
|
misc_info = Column(JSON)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
try:
|
try:
|
||||||
return f"<{self.__class__.__name__}({self.name})>"
|
return f"<{self.__class__.__name__}({self.name})>"
|
||||||
@@ -120,6 +122,26 @@ class BaseClass(Base):
|
|||||||
from test_settings import ctx
|
from test_settings import ctx
|
||||||
return ctx.backup_path
|
return ctx.backup_path
|
||||||
|
|
||||||
|
@classproperty
|
||||||
|
def jsons(cls) -> List[str]:
|
||||||
|
"""
|
||||||
|
Get list of JSON db columns
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[str]: List of column names
|
||||||
|
"""
|
||||||
|
return [item.name for item in cls.__table__.columns if isinstance(item.type, JSON)]
|
||||||
|
|
||||||
|
@classproperty
|
||||||
|
def timestamps(cls) -> List[str]:
|
||||||
|
"""
|
||||||
|
Get list of TIMESTAMP columns
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[str]: List of column names
|
||||||
|
"""
|
||||||
|
return [item.name for item in cls.__table__.columns if isinstance(item.type, TIMESTAMP)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_info(cls, *args) -> dict | list | str:
|
def get_default_info(cls, *args) -> dict | list | str:
|
||||||
"""
|
"""
|
||||||
@@ -150,7 +172,6 @@ class BaseClass(Base):
|
|||||||
else:
|
else:
|
||||||
return cls.__subclasses__()
|
return cls.__subclasses__()
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fuzzy_search(cls, **kwargs) -> List[Any]:
|
def fuzzy_search(cls, **kwargs) -> List[Any]:
|
||||||
"""
|
"""
|
||||||
@@ -177,7 +198,7 @@ class BaseClass(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def results_to_df(cls, objects: list | None = None, **kwargs) -> DataFrame:
|
def results_to_df(cls, objects: list | None = None, **kwargs) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Converts class sub_dicts into a Dataframe for all instances of the class.
|
Converts class sub_dicts into a Dataframe for all controls of the class.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
objects (list): Objects to be converted to dataframe.
|
objects (list): Objects to be converted to dataframe.
|
||||||
@@ -519,12 +540,13 @@ class ConfigItem(BaseClass):
|
|||||||
|
|
||||||
|
|
||||||
from .controls import *
|
from .controls import *
|
||||||
# NOTE: import order must go: orgs, kit, subs due to circular import issues
|
# NOTE: import order must go: orgs, kit, runs due to circular import issues
|
||||||
from .organizations import *
|
from .organizations import *
|
||||||
|
from .runs import *
|
||||||
from .kits import *
|
from .kits import *
|
||||||
from .submissions import *
|
from .submissions import *
|
||||||
from .audit import AuditLog
|
from .audit import AuditLog
|
||||||
|
|
||||||
# NOTE: Add a creator to the submission for reagent association. Assigned here due to circular import constraints.
|
# NOTE: Add a creator to the run for reagent association. Assigned here due to circular import constraints.
|
||||||
# https://docs.sqlalchemy.org/en/20/orm/extensions/associationproxy.html#sqlalchemy.ext.associationproxy.association_proxy.params.creator
|
# https://docs.sqlalchemy.org/en/20/orm/extensions/associationproxy.html#sqlalchemy.ext.associationproxy.association_proxy.params.creator
|
||||||
BasicSubmission.reagents.creator = lambda reg: SubmissionReagentAssociation(reagent=reg)
|
Procedure.reagents.creator = lambda reg: ProcedureReagentAssociation(reagent=reg)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class ControlType(BaseClass):
|
|||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(255), unique=True) #: controltype name (e.g. Irida Control)
|
name = Column(String(255), unique=True) #: controltype name (e.g. Irida Control)
|
||||||
targets = Column(JSON) #: organisms checked for
|
targets = Column(JSON) #: organisms checked for
|
||||||
instances = relationship("Control", back_populates="controltype") #: control samples created of this type.
|
controls = relationship("Control", back_populates="controltype") #: control samples created of this type.
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
@@ -64,11 +64,11 @@ class ControlType(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
List[str]: list of subtypes available
|
List[str]: list of subtypes available
|
||||||
"""
|
"""
|
||||||
if not self.instances:
|
if not self.controls:
|
||||||
return
|
return
|
||||||
# NOTE: Get first instance since all should have same subtypes
|
# NOTE: Get first instance since all should have same subtypes
|
||||||
# NOTE: Get mode of instance
|
# NOTE: Get mode of instance
|
||||||
jsoner = getattr(self.instances[0], mode)
|
jsoner = getattr(self.controls[0], mode)
|
||||||
try:
|
try:
|
||||||
# NOTE: Pick genera (all should have same subtypes)
|
# NOTE: Pick genera (all should have same subtypes)
|
||||||
genera = list(jsoner.keys())[0]
|
genera = list(jsoner.keys())[0]
|
||||||
@@ -119,17 +119,17 @@ class Control(BaseClass):
|
|||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
controltype_name = Column(String, ForeignKey("_controltype.name", ondelete="SET NULL",
|
controltype_name = Column(String, ForeignKey("_controltype.name", ondelete="SET NULL",
|
||||||
name="fk_BC_subtype_name")) #: name of joined submission type
|
name="fk_BC_subtype_name")) #: name of joined run type
|
||||||
controltype = relationship("ControlType", back_populates="instances",
|
controltype = relationship("ControlType", back_populates="controls",
|
||||||
foreign_keys=[controltype_name]) #: reference to parent control type
|
foreign_keys=[controltype_name]) #: reference to parent control type
|
||||||
name = Column(String(255), unique=True) #: Sample ID
|
name = Column(String(255), unique=True) #: Sample ID
|
||||||
sample_id = Column(String, ForeignKey("_basicsample.id", ondelete="SET NULL",
|
sample_id = Column(String, ForeignKey("_basicsample.id", ondelete="SET NULL",
|
||||||
name="fk_Cont_sample_id")) #: name of joined submission type
|
name="fk_Cont_sample_id")) #: name of joined run type
|
||||||
sample = relationship("BasicSample", back_populates="control") #: This control's submission sample
|
sample = relationship("BasicSample", back_populates="control") #: This control's run sample
|
||||||
submitted_date = Column(TIMESTAMP) #: Date submitted to Robotics
|
submitted_date = Column(TIMESTAMP) #: Date submitted to Robotics
|
||||||
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id")) #: parent submission id
|
procedure_id = Column(INTEGER, ForeignKey("_procedure.id")) #: parent run id
|
||||||
submission = relationship("BasicSubmission", back_populates="controls",
|
procedure = relationship("Procedure", back_populates="controls",
|
||||||
foreign_keys=[submission_id]) #: parent submission
|
foreign_keys=[procedure_id]) #: parent run
|
||||||
|
|
||||||
__mapper_args__ = {
|
__mapper_args__ = {
|
||||||
"polymorphic_identity": "Basic Control",
|
"polymorphic_identity": "Basic Control",
|
||||||
@@ -147,7 +147,7 @@ class Control(BaseClass):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
submissiontype: str | None = None,
|
proceduretype: str | None = None,
|
||||||
subtype: str | None = None,
|
subtype: str | None = None,
|
||||||
start_date: date | datetime | str | int | None = None,
|
start_date: date | datetime | str | int | None = None,
|
||||||
end_date: date | datetime | str | int | None = None,
|
end_date: date | datetime | str | int | None = None,
|
||||||
@@ -158,7 +158,7 @@ class Control(BaseClass):
|
|||||||
Lookup control objects in the database based on a number of parameters.
|
Lookup control objects in the database based on a number of parameters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submissiontype (str | None, optional): Submission type associated with control. Defaults to None.
|
proceduretype (str | None, optional): Submission type associated with control. Defaults to None.
|
||||||
subtype (str | None, optional): Control subtype, eg IridaControl. Defaults to None.
|
subtype (str | None, optional): Control subtype, eg IridaControl. Defaults to None.
|
||||||
start_date (date | str | int | None, optional): Beginning date to search by. Defaults to 2023-01-01 if end_date not None.
|
start_date (date | str | int | None, optional): Beginning date to search by. Defaults to 2023-01-01 if end_date not None.
|
||||||
end_date (date | str | int | None, optional): End date to search by. Defaults to today if start_date not None.
|
end_date (date | str | int | None, optional): End date to search by. Defaults to today if start_date not None.
|
||||||
@@ -168,15 +168,15 @@ class Control(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
Control|List[Control]: Control object of interest.
|
Control|List[Control]: Control object of interest.
|
||||||
"""
|
"""
|
||||||
from backend.db import SubmissionType
|
from backend.db import ProcedureType
|
||||||
query: Query = cls.__database_session__.query(cls)
|
query: Query = cls.__database_session__.query(cls)
|
||||||
match submissiontype:
|
match proceduretype:
|
||||||
case str():
|
case str():
|
||||||
from backend import BasicSubmission, SubmissionType
|
from backend.db import Procedure
|
||||||
query = query.join(BasicSubmission).join(SubmissionType).filter(SubmissionType.name == submissiontype)
|
query = query.join(Procedure).join(ProcedureType).filter(ProcedureType.name == proceduretype)
|
||||||
case SubmissionType():
|
case ProcedureType():
|
||||||
from backend import BasicSubmission
|
from backend import Procedure
|
||||||
query = query.join(BasicSubmission).filter(BasicSubmission.submission_type_name == submissiontype.name)
|
query = query.join(Procedure).filter(Procedure.submission_type_name == proceduretype.name)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
# NOTE: by control type
|
# NOTE: by control type
|
||||||
@@ -234,13 +234,13 @@ class Control(BaseClass):
|
|||||||
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
|
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission")
|
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicRun")
|
||||||
case ControlType():
|
case ControlType():
|
||||||
try:
|
try:
|
||||||
model = cls.__mapper__.polymorphic_map[polymorphic_identity.name].class_
|
model = cls.__mapper__.polymorphic_map[polymorphic_identity.name].class_
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission")
|
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicRun")
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
# NOTE: if attrs passed in and this cls doesn't have all attributes in attr
|
# NOTE: if attrs passed in and this cls doesn't have all attributes in attr
|
||||||
@@ -335,7 +335,7 @@ class PCRControl(Control):
|
|||||||
parent.mode_typer.clear()
|
parent.mode_typer.clear()
|
||||||
parent.mode_typer.setEnabled(False)
|
parent.mode_typer.setEnabled(False)
|
||||||
report = Report()
|
report = Report()
|
||||||
controls = cls.query(submissiontype=chart_settings['sub_type'], start_date=chart_settings['start_date'],
|
controls = cls.query(proceduretype=chart_settings['sub_type'], start_date=chart_settings['start_date'],
|
||||||
end_date=chart_settings['end_date'])
|
end_date=chart_settings['end_date'])
|
||||||
data = [control.to_sub_dict() for control in controls]
|
data = [control.to_sub_dict() for control in controls]
|
||||||
df = DataFrame.from_records(data)
|
df = DataFrame.from_records(data)
|
||||||
@@ -402,7 +402,7 @@ class IridaControl(Control):
|
|||||||
|
|
||||||
def to_sub_dict(self) -> dict:
|
def to_sub_dict(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Converts object into convenient dictionary for use in submission summary
|
Converts object into convenient dictionary for use in run summary
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: output dictionary containing: Name, Type, Targets, Top Kraken results
|
dict: output dictionary containing: Name, Type, Targets, Top Kraken results
|
||||||
@@ -565,7 +565,7 @@ class IridaControl(Control):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
input_df (list[dict]): list of dictionaries containing records
|
input_df (list[dict]): list of dictionaries containing records
|
||||||
sub_mode (str | None, optional): sub_type of submission type. Defaults to None.
|
sub_mode (str | None, optional): sub_type of run type. Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
DataFrame: dataframe of controls
|
DataFrame: dataframe of controls
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from tools import check_authorization, setup_lookup, Report, Result, check_regex
|
|||||||
from typing import List, Literal, Generator, Any, Tuple
|
from typing import List, Literal, Generator, Any, Tuple
|
||||||
from pandas import ExcelFile
|
from pandas import ExcelFile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from . import Base, BaseClass, Organization, LogMixin
|
from . import Base, BaseClass, Organization, LogMixin, ProcedureType
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
logger = logging.getLogger(f'submissions.{__name__}')
|
logger = logging.getLogger(f'submissions.{__name__}')
|
||||||
@@ -94,10 +94,10 @@ equipment_tips = Table(
|
|||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
kittypes_submissions = Table(
|
kittypes_runs = Table(
|
||||||
"_kittypes_submissions",
|
"_kittypes_runs",
|
||||||
Base.metadata,
|
Base.metadata,
|
||||||
Column("_basicsubmission_id", INTEGER, ForeignKey("_basicsubmission.id")),
|
Column("_basicrun_id", INTEGER, ForeignKey("_basicrun.id")),
|
||||||
Column("kittype_id", INTEGER, ForeignKey("_kittype.id")),
|
Column("kittype_id", INTEGER, ForeignKey("_kittype.id")),
|
||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
@@ -105,15 +105,15 @@ kittypes_submissions = Table(
|
|||||||
|
|
||||||
class KitType(BaseClass):
|
class KitType(BaseClass):
|
||||||
"""
|
"""
|
||||||
Base of kits used in submission processing
|
Base of kits used in run processing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
omni_sort = BaseClass.omni_sort + ["kit_submissiontype_associations", "kit_reagentrole_associations", "processes"]
|
omni_sort = BaseClass.omni_sort + ["kit_submissiontype_associations", "kit_reagentrole_associations", "processes"]
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64), unique=True) #: name of kit
|
name = Column(String(64), unique=True) #: name of kit
|
||||||
submissions = relationship("BasicSubmission", back_populates="kittypes",
|
runs = relationship("BasicRun", back_populates="kittypes",
|
||||||
secondary=kittypes_submissions) #: submissions this kit was used for
|
secondary=kittypes_runs) #: runs this kit was used for
|
||||||
processes = relationship("Process", back_populates="kit_types",
|
processes = relationship("Process", back_populates="kit_types",
|
||||||
secondary=kittypes_processes) #: equipment processes used by this kit
|
secondary=kittypes_processes) #: equipment processes used by this kit
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ class KitType(BaseClass):
|
|||||||
# logger.debug(f"Submission type: {submission_type}, Kit: {self}")
|
# logger.debug(f"Submission type: {submission_type}, Kit: {self}")
|
||||||
assocs = [item for item in self.kit_reagentrole_associations if item.submission_type == submission_type]
|
assocs = [item for item in self.kit_reagentrole_associations if item.submission_type == submission_type]
|
||||||
# logger.debug(f"Associations: {assocs}")
|
# logger.debug(f"Associations: {assocs}")
|
||||||
# NOTE: rescue with submission type's default kit.
|
# NOTE: rescue with run type's default kit.
|
||||||
if not assocs:
|
if not assocs:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"No associations found with {self}. Attempting rescue with default kit: {submission_type.default_kit}")
|
f"No associations found with {self}. Attempting rescue with default kit: {submission_type.default_kit}")
|
||||||
@@ -213,7 +213,7 @@ class KitType(BaseClass):
|
|||||||
from frontend.widgets.pop_ups import ObjectSelector
|
from frontend.widgets.pop_ups import ObjectSelector
|
||||||
dlg = ObjectSelector(
|
dlg = ObjectSelector(
|
||||||
title="Select Kit",
|
title="Select Kit",
|
||||||
message="Could not find reagents for this submission type/kit type combo.\nSelect new kit.",
|
message="Could not find reagents for this run type/kit type combo.\nSelect new kit.",
|
||||||
obj_type=self.__class__,
|
obj_type=self.__class__,
|
||||||
values=[kit.name for kit in submission_type.kit_types]
|
values=[kit.name for kit in submission_type.kit_types]
|
||||||
)
|
)
|
||||||
@@ -378,7 +378,7 @@ class KitType(BaseClass):
|
|||||||
# if not new_role:
|
# if not new_role:
|
||||||
# new_role = EquipmentRole(name=role['role'])
|
# new_role = EquipmentRole(name=role['role'])
|
||||||
# for equipment in Equipment.assign_equipment(equipment_role=new_role):
|
# for equipment in Equipment.assign_equipment(equipment_role=new_role):
|
||||||
# new_role.instances.append(equipment)
|
# new_role.controls.append(equipment)
|
||||||
# ster_assoc = SubmissionTypeEquipmentRoleAssociation(submission_type=submission_type,
|
# ster_assoc = SubmissionTypeEquipmentRoleAssociation(submission_type=submission_type,
|
||||||
# equipment_role=new_role)
|
# equipment_role=new_role)
|
||||||
# try:
|
# try:
|
||||||
@@ -425,7 +425,7 @@ class ReagentRole(BaseClass):
|
|||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64)) #: name of role reagent plays
|
name = Column(String(64)) #: name of role reagent plays
|
||||||
instances = relationship("Reagent", back_populates="role",
|
instances = relationship("Reagent", back_populates="role",
|
||||||
secondary=reagentroles_reagents) #: concrete instances of this reagent type
|
secondary=reagentroles_reagents) #: concrete controls of this reagent type
|
||||||
eol_ext = Column(Interval()) #: extension of life interval
|
eol_ext = Column(Interval()) #: extension of life interval
|
||||||
|
|
||||||
reagentrole_kit_associations = relationship(
|
reagentrole_kit_associations = relationship(
|
||||||
@@ -548,7 +548,7 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
role = relationship("ReagentRole", back_populates="instances",
|
role = relationship("ReagentRole", back_populates="controls",
|
||||||
secondary=reagentroles_reagents) #: joined parent reagent type
|
secondary=reagentroles_reagents) #: joined parent reagent type
|
||||||
role_id = Column(INTEGER, ForeignKey("_reagentrole.id", ondelete='SET NULL',
|
role_id = Column(INTEGER, ForeignKey("_reagentrole.id", ondelete='SET NULL',
|
||||||
name="fk_reagent_role_id")) #: id of parent reagent type
|
name="fk_reagent_role_id")) #: id of parent reagent type
|
||||||
@@ -557,13 +557,13 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
|
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
|
||||||
|
|
||||||
reagent_submission_associations = relationship(
|
reagent_submission_associations = relationship(
|
||||||
"SubmissionReagentAssociation",
|
"RunReagentAssociation",
|
||||||
back_populates="reagent",
|
back_populates="reagent",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
) #: Relation to SubmissionSampleAssociation
|
) #: Relation to SubmissionSampleAssociation
|
||||||
|
|
||||||
submissions = association_proxy("reagent_submission_associations", "submission",
|
submissions = association_proxy("reagent_submission_associations", "run",
|
||||||
creator=lambda sub: SubmissionReagentAssociation(
|
creator=lambda sub: RunReagentAssociation(
|
||||||
submission=sub)) #: Association proxy to SubmissionSampleAssociation.samples
|
submission=sub)) #: Association proxy to SubmissionSampleAssociation.samples
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -845,10 +845,10 @@ class SubmissionType(BaseClass):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(128), unique=True) #: name of submission type
|
name = Column(String(128), unique=True) #: name of run type
|
||||||
info_map = Column(JSON) #: Where parsable information is found in the excel workbook corresponding to this 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
|
defaults = Column(JSON) #: Basic information about this run type
|
||||||
instances = relationship("ClientSubmission", back_populates="submission_type") #: Concrete instances of this type.
|
clientsubmissions = relationship("ClientSubmission", back_populates="submission_type") #: Concrete controls of this type.
|
||||||
template_file = Column(BLOB) #: Blank form for this type stored as binary.
|
template_file = Column(BLOB) #: Blank form for this type stored as binary.
|
||||||
processes = relationship("Process", back_populates="submission_types",
|
processes = relationship("Process", back_populates="submission_types",
|
||||||
secondary=submissiontypes_processes) #: Relation to equipment processes used for this type.
|
secondary=submissiontypes_processes) #: Relation to equipment processes used for this type.
|
||||||
@@ -1107,7 +1107,7 @@ class SubmissionType(BaseClass):
|
|||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def omni_removes(cls):
|
def omni_removes(cls):
|
||||||
return super().omni_removes + ["defaults", "instances"]
|
return super().omni_removes + ["defaults", "controls"]
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def basic_template(cls) -> bytes:
|
def basic_template(cls) -> bytes:
|
||||||
@@ -1243,15 +1243,15 @@ class SubmissionType(BaseClass):
|
|||||||
return list(set([item for items in relevant for item in items if item is not None]))
|
return list(set([item for items in relevant for item in items if item is not None]))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def submission_class(self) -> "BasicSubmission":
|
def submission_class(self) -> "BasicRun":
|
||||||
"""
|
"""
|
||||||
Gets submission class associated with this submission type.
|
Gets run class associated with this run type.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
BasicSubmission: Submission class
|
BasicRun: Submission class
|
||||||
"""
|
"""
|
||||||
from .submissions import BasicSubmission
|
from .submissions import BasicRun
|
||||||
return BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.name)
|
return BasicRun.find_polymorphic_subclass(polymorphic_identity=self.name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query_or_create(cls, **kwargs) -> Tuple[SubmissionType, bool]:
|
def query_or_create(cls, **kwargs) -> Tuple[SubmissionType, bool]:
|
||||||
@@ -1264,7 +1264,7 @@ class SubmissionType(BaseClass):
|
|||||||
new = True
|
new = True
|
||||||
for k, v in sanitized_kwargs.items():
|
for k, v in sanitized_kwargs.items():
|
||||||
setattr(instance, k, v)
|
setattr(instance, k, v)
|
||||||
logger.info(f"Instance from submissiontype query or create: {instance}")
|
logger.info(f"Instance from proceduretype query or create: {instance}")
|
||||||
return instance, new
|
return instance, new
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1276,10 +1276,10 @@ class SubmissionType(BaseClass):
|
|||||||
**kwargs
|
**kwargs
|
||||||
) -> SubmissionType | List[SubmissionType]:
|
) -> SubmissionType | List[SubmissionType]:
|
||||||
"""
|
"""
|
||||||
Lookup submission type in the database by a number of parameters
|
Lookup run type in the database by a number of parameters
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str | None, optional): Name of submission type. Defaults to None.
|
name (str | None, optional): Name of run type. Defaults to None.
|
||||||
key (str | None, optional): A key present in the info-map to lookup. Defaults to None.
|
key (str | None, optional): A key present in the info-map to lookup. Defaults to None.
|
||||||
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
||||||
|
|
||||||
@@ -1319,7 +1319,7 @@ class SubmissionType(BaseClass):
|
|||||||
@check_authorization
|
@check_authorization
|
||||||
def save(self):
|
def save(self):
|
||||||
"""
|
"""
|
||||||
Adds this instances to the database and commits.
|
Adds this controls to the database and commits.
|
||||||
"""
|
"""
|
||||||
super().save()
|
super().save()
|
||||||
|
|
||||||
@@ -1399,29 +1399,29 @@ class SubmissionType(BaseClass):
|
|||||||
return dicto
|
return dicto
|
||||||
|
|
||||||
|
|
||||||
class SubmissionTypeKitTypeAssociation(BaseClass):
|
class ProcedureTypeKitTypeAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
Abstract of relationship between kits and their submission type.
|
Abstract of relationship between kits and their run type.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
omni_removes = BaseClass.omni_removes + ["submission_types_id", "kits_id"]
|
omni_removes = BaseClass.omni_removes + ["procedure_type_id", "procedure_id"]
|
||||||
omni_sort = ["submission_type", "kit_type"]
|
omni_sort = ["proceduretype", "kittype"]
|
||||||
level = 2
|
level = 2
|
||||||
|
|
||||||
submission_types_id = Column(INTEGER, ForeignKey("_submissiontype.id"),
|
proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id"),
|
||||||
primary_key=True) #: id of joined submission type
|
primary_key=True) #: id of joined run type
|
||||||
kits_id = Column(INTEGER, ForeignKey("_kittype.id"), primary_key=True) #: id of joined kit
|
kittype_id = Column(INTEGER, ForeignKey("_kittype.id"), primary_key=True) #: id of joined kit
|
||||||
mutable_cost_column = Column(
|
mutable_cost_column = Column(
|
||||||
FLOAT(2)) #: dollar amount per 96 well plate that can change with number of columns (reagents, tips, etc)
|
FLOAT(2)) #: dollar amount per 96 well plate that can change with number of columns (reagents, tips, etc)
|
||||||
mutable_cost_sample = Column(
|
mutable_cost_sample = Column(
|
||||||
FLOAT(2)) #: dollar amount that can change with number of samples (reagents, tips, etc)
|
FLOAT(2)) #: dollar amount that can change with number of samples (reagents, tips, etc)
|
||||||
constant_cost = Column(FLOAT(2)) #: dollar amount per plate that will remain constant (plates, man hours, etc)
|
constant_cost = Column(FLOAT(2)) #: dollar amount per plate that will remain constant (plates, man hours, etc)
|
||||||
|
|
||||||
kit_type = relationship(KitType, back_populates="kit_submissiontype_associations") #: joined kittype
|
kittype = relationship(KitType, back_populates="kittype_proceduretype_associations") #: joined kittype
|
||||||
|
|
||||||
# reference to the "SubmissionType" object
|
# reference to the "SubmissionType" object
|
||||||
submission_type = relationship(SubmissionType,
|
proceduretype = relationship(ProcedureType,
|
||||||
back_populates="submissiontype_kit_associations") #: joined submission type
|
back_populates="proceduretype_kittype_associations") #: joined run type
|
||||||
|
|
||||||
def __init__(self, kit_type=None, submission_type=None,
|
def __init__(self, kit_type=None, submission_type=None,
|
||||||
mutable_cost_column: int = 0.00, mutable_cost_sample: int = 0.00, constant_cost: int = 0.00):
|
mutable_cost_column: int = 0.00, mutable_cost_sample: int = 0.00, constant_cost: int = 0.00):
|
||||||
@@ -1495,7 +1495,7 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
|
|||||||
Lookup SubmissionTypeKitTypeAssociations of interest.
|
Lookup SubmissionTypeKitTypeAssociations of interest.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission_type (SubmissionType | str | int | None, optional): Identifier of submission type. Defaults to None.
|
submission_type (SubmissionType | str | int | None, optional): Identifier of run type. Defaults to None.
|
||||||
kit_type (KitType | str | int | None, optional): Identifier of kit type. Defaults to None.
|
kit_type (KitType | str | int | None, optional): Identifier of kit type. Defaults to None.
|
||||||
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
||||||
|
|
||||||
@@ -1572,7 +1572,7 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
|||||||
primary_key=True) #: id of associated reagent type
|
primary_key=True) #: id of associated reagent type
|
||||||
kits_id = Column(INTEGER, ForeignKey("_kittype.id"), primary_key=True) #: id of associated reagent type
|
kits_id = Column(INTEGER, ForeignKey("_kittype.id"), primary_key=True) #: id of associated reagent type
|
||||||
submission_type_id = Column(INTEGER, ForeignKey("_submissiontype.id"), primary_key=True)
|
submission_type_id = Column(INTEGER, ForeignKey("_submissiontype.id"), primary_key=True)
|
||||||
uses = Column(JSON) #: map to location on excel sheets of different submission types
|
uses = Column(JSON) #: map to location on excel sheets of different run types
|
||||||
required = Column(INTEGER) #: whether the reagent type is required for the kit (Boolean 1 or 0)
|
required = Column(INTEGER) #: whether the reagent type is required for the kit (Boolean 1 or 0)
|
||||||
last_used = Column(String(32)) #: last used lot number of this type of reagent
|
last_used = Column(String(32)) #: last used lot number of this type of reagent
|
||||||
|
|
||||||
@@ -1677,7 +1677,7 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
|||||||
v = KitType.query(name=v)
|
v = KitType.query(name=v)
|
||||||
else:
|
else:
|
||||||
v = v.instance_object
|
v = v.instance_object
|
||||||
case "submissiontype" | "submission_type":
|
case "proceduretype" | "submission_type":
|
||||||
k = "submission_type"
|
k = "submission_type"
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = SubmissionType.query(name=v)
|
v = SubmissionType.query(name=v)
|
||||||
@@ -1742,18 +1742,6 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
# def to_export_dict(self) -> dict:
|
|
||||||
# """
|
|
||||||
# Creates a dictionary of relevant values in this object.
|
|
||||||
#
|
|
||||||
# Returns:
|
|
||||||
# dict: dictionary of Association and related reagent role
|
|
||||||
# """
|
|
||||||
# base_dict = dict(required=self.required)
|
|
||||||
# for k, v in self.reagent_role.to_export_dict().items():
|
|
||||||
# base_dict[k] = v
|
|
||||||
# return base_dict
|
|
||||||
|
|
||||||
def get_all_relevant_reagents(self) -> Generator[Reagent, None, None]:
|
def get_all_relevant_reagents(self) -> Generator[Reagent, None, None]:
|
||||||
"""
|
"""
|
||||||
Creates a generator that will resolve in to a list filling the role associated with this object.
|
Creates a generator that will resolve in to a list filling the role associated with this object.
|
||||||
@@ -1761,7 +1749,7 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
Generator: Generates of reagents.
|
Generator: Generates of reagents.
|
||||||
"""
|
"""
|
||||||
reagents = self.reagent_role.instances
|
reagents = self.reagent_role.controls
|
||||||
try:
|
try:
|
||||||
regex = self.uses['exclude_regex']
|
regex = self.uses['exclude_regex']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -1821,33 +1809,33 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SubmissionReagentAssociation(BaseClass):
|
class ProcedureReagentAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
table containing submission/reagent associations
|
table containing run/reagent associations
|
||||||
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
||||||
"""
|
"""
|
||||||
|
|
||||||
skip_on_edit = True
|
skip_on_edit = True
|
||||||
|
|
||||||
reagent_id = Column(INTEGER, ForeignKey("_reagent.id"), primary_key=True) #: id of associated reagent
|
reagent_id = Column(INTEGER, ForeignKey("_reagent.id"), primary_key=True) #: id of associated reagent
|
||||||
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission
|
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated run
|
||||||
comments = Column(String(1024)) #: Comments about reagents
|
comments = Column(String(1024)) #: Comments about reagents
|
||||||
|
|
||||||
submission = relationship("BasicSubmission",
|
procedure = relationship("Procedure",
|
||||||
back_populates="submission_reagent_associations") #: associated submission
|
back_populates="procedure_reagent_associations") #: associated run
|
||||||
|
|
||||||
reagent = relationship(Reagent, back_populates="reagent_submission_associations") #: associated reagent
|
reagent = relationship(Reagent, back_populates="reagent_procedure_associations") #: associated reagent
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""
|
"""
|
||||||
Returns:
|
Returns:
|
||||||
str: Representation of this SubmissionReagentAssociation
|
str: Representation of this RunReagentAssociation
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return f"<SubmissionReagentAssociation({self.submission.rsl_plate_num} & {self.reagent.lot})>"
|
return f"<RunReagentAssociation({self.procedure.run.rsl_plate_num} & {self.reagent.lot})>"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"Reagent {self.reagent.lot} submission association {self.reagent_id} has no submissions!")
|
logger.error(f"Reagent {self.reagent.lot} run association {self.reagent_id} has no submissions!")
|
||||||
return f"<SubmissionReagentAssociation(Unknown Submission & {self.reagent.lot})>"
|
return f"<RunReagentAssociation(Unknown Submission & {self.reagent.lot})>"
|
||||||
|
|
||||||
def __init__(self, reagent=None, submission=None):
|
def __init__(self, reagent=None, submission=None):
|
||||||
if isinstance(reagent, list):
|
if isinstance(reagent, list):
|
||||||
@@ -1860,21 +1848,21 @@ class SubmissionReagentAssociation(BaseClass):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
submission: "BasicSubmission" | str | int | None = None,
|
run: "BasicRun" | str | int | None = None,
|
||||||
reagent: Reagent | str | None = None,
|
reagent: Reagent | str | None = None,
|
||||||
limit: int = 0) -> SubmissionReagentAssociation | List[SubmissionReagentAssociation]:
|
limit: int = 0) -> RunReagentAssociation | List[RunReagentAssociation]:
|
||||||
"""
|
"""
|
||||||
Lookup SubmissionReagentAssociations of interest.
|
Lookup SubmissionReagentAssociations of interest.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission (BasicSubmission" | str | int | None, optional): Identifier of joined submission. Defaults to None.
|
run (BasicRun | str | int | None, optional): Identifier of joined run. Defaults to None.
|
||||||
reagent (Reagent | str | None, optional): Identifier of joined reagent. Defaults to None.
|
reagent (Reagent | str | None, optional): Identifier of joined reagent. Defaults to None.
|
||||||
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
SubmissionReagentAssociation|List[SubmissionReagentAssociation]: SubmissionReagentAssociation(s) of interest
|
RunReagentAssociation|List[RunReagentAssociation]: SubmissionReagentAssociation(s) of interest
|
||||||
"""
|
"""
|
||||||
from . import BasicSubmission
|
from . import BasicRun
|
||||||
query: Query = cls.__database_session__.query(cls)
|
query: Query = cls.__database_session__.query(cls)
|
||||||
match reagent:
|
match reagent:
|
||||||
case Reagent() | str():
|
case Reagent() | str():
|
||||||
@@ -1883,27 +1871,27 @@ class SubmissionReagentAssociation(BaseClass):
|
|||||||
query = query.filter(cls.reagent == reagent)
|
query = query.filter(cls.reagent == reagent)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
match submission:
|
match run:
|
||||||
case BasicSubmission() | str():
|
case BasicRun() | str():
|
||||||
if isinstance(submission, str):
|
if isinstance(run, str):
|
||||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
run = BasicRun.query(rsl_plate_num=run)
|
||||||
query = query.filter(cls.submission == submission)
|
query = query.filter(cls.run == run)
|
||||||
case int():
|
case int():
|
||||||
submission = BasicSubmission.query(id=submission)
|
run = BasicRun.query(id=run)
|
||||||
query = query.join(BasicSubmission).filter(BasicSubmission.id == submission)
|
query = query.join(BasicRun).filter(BasicRun.id == run)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def to_sub_dict(self, extraction_kit) -> dict:
|
def to_sub_dict(self, extraction_kit) -> dict:
|
||||||
"""
|
"""
|
||||||
Converts this SubmissionReagentAssociation (and associated Reagent) to dict
|
Converts this RunReagentAssociation (and associated Reagent) to dict
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
extraction_kit (_type_): Extraction kit of interest
|
extraction_kit (_type_): Extraction kit of interest
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: This SubmissionReagentAssociation as dict
|
dict: This RunReagentAssociation as dict
|
||||||
"""
|
"""
|
||||||
output = self.reagent.to_sub_dict(extraction_kit)
|
output = self.reagent.to_sub_dict(extraction_kit)
|
||||||
output['comments'] = self.comments
|
output['comments'] = self.comments
|
||||||
@@ -1923,20 +1911,20 @@ class Equipment(BaseClass, LogMixin):
|
|||||||
name = Column(String(64)) #: equipment name
|
name = Column(String(64)) #: equipment name
|
||||||
nickname = Column(String(64)) #: equipment nickname
|
nickname = Column(String(64)) #: equipment nickname
|
||||||
asset_number = Column(String(16)) #: Given asset number (corpo nickname if you will)
|
asset_number = Column(String(16)) #: Given asset number (corpo nickname if you will)
|
||||||
roles = relationship("EquipmentRole", back_populates="instances",
|
roles = relationship("EquipmentRole", back_populates="controls",
|
||||||
secondary=equipmentroles_equipment) #: relation to EquipmentRoles
|
secondary=equipmentroles_equipment) #: relation to EquipmentRoles
|
||||||
processes = relationship("Process", back_populates="equipment",
|
processes = relationship("Process", back_populates="equipment",
|
||||||
secondary=equipment_processes) #: relation to Processes
|
secondary=equipment_processes) #: relation to Processes
|
||||||
tips = relationship("Tips", back_populates="equipment",
|
tips = relationship("Tips", back_populates="equipment",
|
||||||
secondary=equipment_tips) #: relation to Processes
|
secondary=equipment_tips) #: relation to Processes
|
||||||
equipment_submission_associations = relationship(
|
equipment_submission_associations = relationship(
|
||||||
"SubmissionEquipmentAssociation",
|
"RunEquipmentAssociation",
|
||||||
back_populates="equipment",
|
back_populates="equipment",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
) #: Association with BasicSubmission
|
) #: Association with BasicRun
|
||||||
|
|
||||||
submissions = association_proxy("equipment_submission_associations",
|
submissions = association_proxy("equipment_submission_associations",
|
||||||
"submission") #: proxy to equipment_submission_associations.submission
|
"run") #: proxy to equipment_submission_associations.run
|
||||||
|
|
||||||
def to_dict(self, processes: bool = False) -> dict:
|
def to_dict(self, processes: bool = False) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -2135,7 +2123,7 @@ class EquipmentRole(BaseClass):
|
|||||||
id = Column(INTEGER, primary_key=True) #: Role id, primary key
|
id = Column(INTEGER, primary_key=True) #: Role id, primary key
|
||||||
name = Column(String(32)) #: Common name
|
name = Column(String(32)) #: Common name
|
||||||
instances = relationship("Equipment", back_populates="roles",
|
instances = relationship("Equipment", back_populates="roles",
|
||||||
secondary=equipmentroles_equipment) #: Concrete instances (Equipment) of role
|
secondary=equipmentroles_equipment) #: Concrete controls (Equipment) of role
|
||||||
processes = relationship("Process", back_populates='equipment_roles',
|
processes = relationship("Process", back_populates='equipment_roles',
|
||||||
secondary=equipmentroles_processes) #: Associated Processes
|
secondary=equipmentroles_processes) #: Associated Processes
|
||||||
|
|
||||||
@@ -2253,13 +2241,13 @@ class EquipmentRole(BaseClass):
|
|||||||
return OmniEquipmentRole(instance_object=self, name=self.name)
|
return OmniEquipmentRole(instance_object=self, name=self.name)
|
||||||
|
|
||||||
|
|
||||||
class SubmissionEquipmentAssociation(BaseClass):
|
class RunEquipmentAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
Abstract association between BasicSubmission and Equipment
|
Abstract association between BasicRun and Equipment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
equipment_id = Column(INTEGER, ForeignKey("_equipment.id"), primary_key=True) #: id of associated equipment
|
equipment_id = Column(INTEGER, ForeignKey("_equipment.id"), primary_key=True) #: id of associated equipment
|
||||||
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission
|
run_id = Column(INTEGER, ForeignKey("_basicrun.id"), primary_key=True) #: id of associated run
|
||||||
role = Column(String(64), primary_key=True) #: name of the role the equipment fills
|
role = Column(String(64), primary_key=True) #: name of the role the equipment fills
|
||||||
process_id = Column(INTEGER, ForeignKey("_process.id", ondelete="SET NULL",
|
process_id = Column(INTEGER, ForeignKey("_process.id", ondelete="SET NULL",
|
||||||
name="SEA_Process_id")) #: Foreign key of process id
|
name="SEA_Process_id")) #: Foreign key of process id
|
||||||
@@ -2267,16 +2255,16 @@ class SubmissionEquipmentAssociation(BaseClass):
|
|||||||
end_time = Column(TIMESTAMP) #: end time of equipment use
|
end_time = Column(TIMESTAMP) #: end time of equipment use
|
||||||
comments = Column(String(1024)) #: comments about equipment
|
comments = Column(String(1024)) #: comments about equipment
|
||||||
|
|
||||||
submission = relationship("BasicSubmission",
|
run = relationship("BasicRun",
|
||||||
back_populates="submission_equipment_associations") #: associated submission
|
back_populates="run_equipment_associations") #: associated run
|
||||||
|
|
||||||
equipment = relationship(Equipment, back_populates="equipment_submission_associations") #: associated equipment
|
equipment = relationship(Equipment, back_populates="equipment_run_associations") #: associated equipment
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<SubmissionEquipmentAssociation({self.submission.rsl_plate_num} & {self.equipment.name})>"
|
return f"<RunEquipmentAssociation({self.run.rsl_plate_num} & {self.equipment.name})>"
|
||||||
|
|
||||||
def __init__(self, submission, equipment, role: str = "None"):
|
def __init__(self, run, equipment, role: str = "None"):
|
||||||
self.submission = submission
|
self.run = run
|
||||||
self.equipment = equipment
|
self.equipment = equipment
|
||||||
self.role = role
|
self.role = role
|
||||||
|
|
||||||
@@ -2286,10 +2274,10 @@ class SubmissionEquipmentAssociation(BaseClass):
|
|||||||
|
|
||||||
def to_sub_dict(self) -> dict:
|
def to_sub_dict(self) -> dict:
|
||||||
"""
|
"""
|
||||||
This SubmissionEquipmentAssociation as a dictionary
|
This RunEquipmentAssociation as a dictionary
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: This SubmissionEquipmentAssociation as a dictionary
|
dict: This RunEquipmentAssociation as a dictionary
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
process = self.process.name
|
process = self.process.name
|
||||||
@@ -2311,12 +2299,12 @@ class SubmissionEquipmentAssociation(BaseClass):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls, equipment_id: int | None = None, submission_id: int | None = None, role: str | None = None,
|
def query(cls, equipment_id: int | None = None, run_id: int | None = None, role: str | None = None,
|
||||||
limit: int = 0, **kwargs) \
|
limit: int = 0, **kwargs) \
|
||||||
-> Any | List[Any]:
|
-> Any | List[Any]:
|
||||||
query: Query = cls.__database_session__.query(cls)
|
query: Query = cls.__database_session__.query(cls)
|
||||||
query = query.filter(cls.equipment_id == equipment_id)
|
query = query.filter(cls.equipment_id == equipment_id)
|
||||||
query = query.filter(cls.submission_id == submission_id)
|
query = query.filter(cls.run_id == run_id)
|
||||||
if role is not None:
|
if role is not None:
|
||||||
query = query.filter(cls.role == role)
|
query = query.filter(cls.role == role)
|
||||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||||
@@ -2328,13 +2316,13 @@ class SubmissionTypeEquipmentRoleAssociation(BaseClass):
|
|||||||
"""
|
"""
|
||||||
equipmentrole_id = Column(INTEGER, ForeignKey("_equipmentrole.id"), primary_key=True) #: id of associated equipment
|
equipmentrole_id = Column(INTEGER, ForeignKey("_equipmentrole.id"), primary_key=True) #: id of associated equipment
|
||||||
submissiontype_id = Column(INTEGER, ForeignKey("_submissiontype.id"),
|
submissiontype_id = Column(INTEGER, ForeignKey("_submissiontype.id"),
|
||||||
primary_key=True) #: id of associated submission
|
primary_key=True) #: id of associated run
|
||||||
uses = Column(JSON) #: locations of equipment on the submission type excel sheet.
|
uses = Column(JSON) #: locations of equipment on the run type excel sheet.
|
||||||
static = Column(INTEGER,
|
static = Column(INTEGER,
|
||||||
default=1) #: if 1 this piece of equipment will always be used, otherwise it will need to be selected from list?
|
default=1) #: if 1 this piece of equipment will always be used, otherwise it will need to be selected from list?
|
||||||
|
|
||||||
submission_type = relationship(SubmissionType,
|
submission_type = relationship(SubmissionType,
|
||||||
back_populates="submissiontype_equipmentrole_associations") #: associated submission
|
back_populates="submissiontype_equipmentrole_associations") #: associated run
|
||||||
|
|
||||||
equipment_role = relationship(EquipmentRole,
|
equipment_role = relationship(EquipmentRole,
|
||||||
back_populates="equipmentrole_submissiontype_associations") #: associated equipment
|
back_populates="equipmentrole_submissiontype_associations") #: associated equipment
|
||||||
@@ -2386,8 +2374,8 @@ class Process(BaseClass):
|
|||||||
secondary=equipment_processes) #: relation to Equipment
|
secondary=equipment_processes) #: relation to Equipment
|
||||||
equipment_roles = relationship("EquipmentRole", back_populates='processes',
|
equipment_roles = relationship("EquipmentRole", back_populates='processes',
|
||||||
secondary=equipmentroles_processes) #: relation to EquipmentRoles
|
secondary=equipmentroles_processes) #: relation to EquipmentRoles
|
||||||
submissions = relationship("SubmissionEquipmentAssociation",
|
submissions = relationship("RunEquipmentAssociation",
|
||||||
backref='process') #: relation to SubmissionEquipmentAssociation
|
backref='process') #: relation to RunEquipmentAssociation
|
||||||
kit_types = relationship("KitType", back_populates='processes',
|
kit_types = relationship("KitType", back_populates='processes',
|
||||||
secondary=kittypes_processes) #: relation to KitType
|
secondary=kittypes_processes) #: relation to KitType
|
||||||
tip_roles = relationship("TipRole", back_populates='processes',
|
tip_roles = relationship("TipRole", back_populates='processes',
|
||||||
@@ -2545,14 +2533,14 @@ class TipRole(BaseClass):
|
|||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64)) #: name of reagent type
|
name = Column(String(64)) #: name of reagent type
|
||||||
instances = relationship("Tips", back_populates="role",
|
instances = relationship("Tips", back_populates="role",
|
||||||
secondary=tiproles_tips) #: concrete instances of this reagent type
|
secondary=tiproles_tips) #: concrete controls of this reagent type
|
||||||
processes = relationship("Process", back_populates="tip_roles", secondary=process_tiprole)
|
processes = relationship("Process", back_populates="tip_roles", secondary=process_tiprole)
|
||||||
|
|
||||||
tiprole_submissiontype_associations = relationship(
|
tiprole_submissiontype_associations = relationship(
|
||||||
"SubmissionTypeTipRoleAssociation",
|
"SubmissionTypeTipRoleAssociation",
|
||||||
back_populates="tip_role",
|
back_populates="tip_role",
|
||||||
cascade="all, delete-orphan"
|
cascade="all, delete-orphan"
|
||||||
) #: associated submission
|
) #: associated run
|
||||||
|
|
||||||
submission_types = association_proxy("tiprole_submissiontype_associations", "submission_type")
|
submission_types = association_proxy("tiprole_submissiontype_associations", "submission_type")
|
||||||
|
|
||||||
@@ -2608,21 +2596,21 @@ class Tips(BaseClass, LogMixin):
|
|||||||
A concrete instance of tips.
|
A concrete instance of tips.
|
||||||
"""
|
"""
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
role = relationship("TipRole", back_populates="instances",
|
role = relationship("TipRole", back_populates="controls",
|
||||||
secondary=tiproles_tips) #: joined parent reagent type
|
secondary=tiproles_tips) #: joined parent reagent type
|
||||||
role_id = Column(INTEGER, ForeignKey("_tiprole.id", ondelete='SET NULL',
|
role_id = Column(INTEGER, ForeignKey("_tiprole.id", ondelete='SET NULL',
|
||||||
name="fk_tip_role_id")) #: id of parent reagent type
|
name="fk_tip_role_id")) #: id of parent reagent type
|
||||||
name = Column(String(64)) #: tip common name
|
name = Column(String(64)) #: tip common name
|
||||||
lot = Column(String(64)) #: lot number of tips
|
lot = Column(String(64)) #: lot number of tips
|
||||||
equipment = relationship("Equipment", back_populates="tips",
|
equipment = relationship("Equipment", back_populates="tips",
|
||||||
secondary=equipment_tips) #: associated submission
|
secondary=equipment_tips) #: associated run
|
||||||
tips_submission_associations = relationship(
|
tips_submission_associations = relationship(
|
||||||
"SubmissionTipsAssociation",
|
"SubmissionTipsAssociation",
|
||||||
back_populates="tips",
|
back_populates="tips",
|
||||||
cascade="all, delete-orphan"
|
cascade="all, delete-orphan"
|
||||||
) #: associated submission
|
) #: associated run
|
||||||
|
|
||||||
submissions = association_proxy("tips_submission_associations", 'submission')
|
submissions = association_proxy("tips_submission_associations", 'run')
|
||||||
|
|
||||||
@hybrid_property
|
@hybrid_property
|
||||||
def tiprole(self):
|
def tiprole(self):
|
||||||
@@ -2728,12 +2716,12 @@ class SubmissionTypeTipRoleAssociation(BaseClass):
|
|||||||
"""
|
"""
|
||||||
tiprole_id = Column(INTEGER, ForeignKey("_tiprole.id"), primary_key=True) #: id of associated equipment
|
tiprole_id = Column(INTEGER, ForeignKey("_tiprole.id"), primary_key=True) #: id of associated equipment
|
||||||
submissiontype_id = Column(INTEGER, ForeignKey("_submissiontype.id"),
|
submissiontype_id = Column(INTEGER, ForeignKey("_submissiontype.id"),
|
||||||
primary_key=True) #: id of associated submission
|
primary_key=True) #: id of associated run
|
||||||
uses = Column(JSON) #: locations of equipment on the submission type excel sheet.
|
uses = Column(JSON) #: locations of equipment on the run type excel sheet.
|
||||||
static = Column(INTEGER,
|
static = Column(INTEGER,
|
||||||
default=1) #: if 1 this piece of equipment will always be used, otherwise it will need to be selected from list?
|
default=1) #: if 1 this piece of equipment will always be used, otherwise it will need to be selected from list?
|
||||||
submission_type = relationship(SubmissionType,
|
submission_type = relationship(SubmissionType,
|
||||||
back_populates="submissiontype_tiprole_associations") #: associated submission
|
back_populates="submissiontype_tiprole_associations") #: associated run
|
||||||
tip_role = relationship(TipRole,
|
tip_role = relationship(TipRole,
|
||||||
back_populates="tiprole_submissiontype_associations") #: associated equipment
|
back_populates="tiprole_submissiontype_associations") #: associated equipment
|
||||||
|
|
||||||
@@ -2753,16 +2741,16 @@ class SubmissionTypeTipRoleAssociation(BaseClass):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SubmissionTipsAssociation(BaseClass):
|
class RunTipsAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
Association between a concrete submission instance and concrete tips
|
Association between a concrete run instance and concrete tips
|
||||||
"""
|
"""
|
||||||
tip_id = Column(INTEGER, ForeignKey("_tips.id"), primary_key=True) #: id of associated equipment
|
tip_id = Column(INTEGER, ForeignKey("_tips.id"), primary_key=True) #: id of associated equipment
|
||||||
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission
|
run_id = Column(INTEGER, ForeignKey("_basicrun.id"), primary_key=True) #: id of associated run
|
||||||
submission = relationship("BasicSubmission",
|
run = relationship("BasicRun",
|
||||||
back_populates="submission_tips_associations") #: associated submission
|
back_populates="run_tips_associations") #: associated run
|
||||||
tips = relationship(Tips,
|
tips = relationship(Tips,
|
||||||
back_populates="tips_submission_associations") #: associated equipment
|
back_populates="tips_run_associations") #: associated equipment
|
||||||
role_name = Column(String(32), primary_key=True) #, ForeignKey("_tiprole.name"))
|
role_name = Column(String(32), primary_key=True) #, ForeignKey("_tiprole.name"))
|
||||||
|
|
||||||
def to_sub_dict(self) -> dict:
|
def to_sub_dict(self) -> dict:
|
||||||
@@ -2776,21 +2764,21 @@ class SubmissionTipsAssociation(BaseClass):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls, tip_id: int, role: str, submission_id: int | None = None, limit: int = 0, **kwargs) \
|
def query(cls, tip_id: int, role: str, run_id: int | None = None, limit: int = 0, **kwargs) \
|
||||||
-> Any | List[Any]:
|
-> Any | List[Any]:
|
||||||
query: Query = cls.__database_session__.query(cls)
|
query: Query = cls.__database_session__.query(cls)
|
||||||
query = query.filter(cls.tip_id == tip_id)
|
query = query.filter(cls.tip_id == tip_id)
|
||||||
if submission_id is not None:
|
if run_id is not None:
|
||||||
query = query.filter(cls.submission_id == submission_id)
|
query = query.filter(cls.run_id == run_id)
|
||||||
query = query.filter(cls.role_name == role)
|
query = query.filter(cls.role_name == role)
|
||||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query_or_create(cls, tips, submission, role: str, **kwargs):
|
def query_or_create(cls, tips, run, role: str, **kwargs):
|
||||||
kwargs['limit'] = 1
|
kwargs['limit'] = 1
|
||||||
instance = cls.query(tip_id=tips.id, role=role, submission_id=submission.id, **kwargs)
|
instance = cls.query(tip_id=tips.id, role=role, run_id=run.id, **kwargs)
|
||||||
if instance is None:
|
if instance is None:
|
||||||
instance = SubmissionTipsAssociation(submission=submission, tips=tips, role_name=role)
|
instance = cls(run=run, tips=tips, role_name=role)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def to_pydantic(self):
|
def to_pydantic(self):
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,5 +3,6 @@ Contains pandas and openpyxl convenience functions for interacting with excel wo
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
from .parser import *
|
from .parser import *
|
||||||
|
from .submission_parser import *
|
||||||
from .reports import *
|
from .reports import *
|
||||||
from .writer import *
|
from .writer import *
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
contains parser objects for pulling values from client generated submission sheets.
|
contains parser objects for pulling values from client generated run sheets.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from copy import copy
|
from copy import copy
|
||||||
@@ -45,8 +45,8 @@ class SheetParser(object):
|
|||||||
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 = BasicRun.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
||||||
# NOTE: grab the info map from the submission type in database
|
# NOTE: grab the info map from the run 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()
|
||||||
@@ -60,17 +60,17 @@ class SheetParser(object):
|
|||||||
"""
|
"""
|
||||||
parser = InfoParser(xl=self.xl, submission_type=self.submission_type, sub_object=self.sub_object)
|
parser = InfoParser(xl=self.xl, submission_type=self.submission_type, sub_object=self.sub_object)
|
||||||
self.info_map = parser.info_map
|
self.info_map = parser.info_map
|
||||||
# NOTE: in order to accommodate generic submission types we have to check for the type in the excel sheet and rerun accordingly
|
# NOTE: in order to accommodate generic run types we have to check for the type in the excel sheet and rerun accordingly
|
||||||
try:
|
try:
|
||||||
check = parser.parsed_info['submission_type']['value'] not in [None, "None", "", " "]
|
check = parser.parsed_info['submission_type']['value'] not in [None, "None", "", " "]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logger.error(f"Couldn't check submission type due to KeyError: {e}")
|
logger.error(f"Couldn't check run type due to KeyError: {e}")
|
||||||
return
|
return
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Checking for updated submission type: {self.submission_type.name} against new: {parser.parsed_info['submission_type']['value']}")
|
f"Checking for updated run type: {self.submission_type.name} against new: {parser.parsed_info['submission_type']['value']}")
|
||||||
if self.submission_type.name != parser.parsed_info['submission_type']['value']:
|
if self.submission_type.name != parser.parsed_info['submission_type']['value']:
|
||||||
if check:
|
if check:
|
||||||
# NOTE: If initial submission type doesn't match parsed submission type, defer to parsed submission type.
|
# NOTE: If initial run type doesn't match parsed run type, defer to parsed run type.
|
||||||
self.submission_type = SubmissionType.query(name=parser.parsed_info['submission_type']['value'])
|
self.submission_type = SubmissionType.query(name=parser.parsed_info['submission_type']['value'])
|
||||||
logger.info(f"Updated self.submission_type to {self.submission_type}. Rerunning parse.")
|
logger.info(f"Updated self.submission_type to {self.submission_type}. Rerunning parse.")
|
||||||
self.parse_info()
|
self.parse_info()
|
||||||
@@ -145,17 +145,17 @@ class InfoParser(object):
|
|||||||
Object to parse generic info from excel sheet.
|
Object to parse generic info from excel sheet.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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: BasicRun | None = None):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (str | SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (str | SubmissionType): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
|
sub_object (BasicRun | None, optional): Submission object holding methods. Defaults to None.
|
||||||
"""
|
"""
|
||||||
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:
|
||||||
sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=submission_type.name)
|
sub_object = BasicRun.find_polymorphic_subclass(polymorphic_identity=submission_type.name)
|
||||||
self.submission_type_obj = submission_type
|
self.submission_type_obj = 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)
|
||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
@@ -167,9 +167,9 @@ class InfoParser(object):
|
|||||||
Gets location of basic info from the submission_type object in the database.
|
Gets location of basic info from the submission_type object in the database.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Location map of all info for this submission type
|
dict: Location map of all info for this run type
|
||||||
"""
|
"""
|
||||||
# NOTE: Get the parse_info method from the submission type specified
|
# NOTE: Get the parse_info method from the run type specified
|
||||||
return self.sub_object.construct_info_map(submission_type=self.submission_type_obj, mode="read")
|
return self.sub_object.construct_info_map(submission_type=self.submission_type_obj, mode="read")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -232,7 +232,7 @@ class InfoParser(object):
|
|||||||
dicto[item['name']] = dict(value=value, missing=missing)
|
dicto[item['name']] = dict(value=value, missing=missing)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
continue
|
continue
|
||||||
# NOTE: Return after running the parser components held in submission object.
|
# NOTE: Return after running the parser components held in run object.
|
||||||
return self.sub_object.custom_info_parser(input_dict=dicto, xl=self.xl, custom_fields=self.info_map['custom'])
|
return self.sub_object.custom_info_parser(input_dict=dicto, xl=self.xl, custom_fields=self.info_map['custom'])
|
||||||
|
|
||||||
|
|
||||||
@@ -242,20 +242,20 @@ class ReagentParser(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, xl: Workbook, submission_type: str | SubmissionType, extraction_kit: str,
|
def __init__(self, xl: Workbook, submission_type: str | SubmissionType, extraction_kit: str,
|
||||||
sub_object: BasicSubmission | None = None):
|
run_object: BasicRun | None = None):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (str|SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (str|SubmissionType): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
extraction_kit (str): Extraction kit used.
|
extraction_kit (str): Extraction kit used.
|
||||||
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
|
run_object (BasicRun | None, optional): Submission object holding methods. Defaults to None.
|
||||||
"""
|
"""
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
submission_type = SubmissionType.query(name=submission_type)
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
self.submission_type_obj = submission_type
|
self.submission_type_obj = submission_type
|
||||||
if not sub_object:
|
if not run_object:
|
||||||
sub_object = submission_type.submission_class
|
run_object = submission_type.submission_class
|
||||||
self.sub_object = sub_object
|
self.run_object = run_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)
|
||||||
@@ -267,7 +267,7 @@ class ReagentParser(object):
|
|||||||
Gets location of kit reagents from database
|
Gets location of kit reagents from database
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission_type (str): Name of submission type.
|
submission_type (str): Name of run type.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: locations of reagent info for the kit.
|
dict: locations of reagent info for the kit.
|
||||||
@@ -327,13 +327,13 @@ class SampleParser(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, xl: Workbook, submission_type: SubmissionType, sample_map: dict | None = None,
|
def __init__(self, xl: Workbook, submission_type: SubmissionType, sample_map: dict | None = None,
|
||||||
sub_object: BasicSubmission | None = None) -> None:
|
sub_object: BasicRun | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (SubmissionType): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
sample_map (dict | None, optional): Locations in database where samples are found. Defaults to None.
|
sample_map (dict | None, optional): Locations in database where samples are found. Defaults to None.
|
||||||
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
|
sub_object (BasicRun | None, optional): Submission object holding methods. Defaults to None.
|
||||||
"""
|
"""
|
||||||
self.samples = []
|
self.samples = []
|
||||||
self.xl = xl
|
self.xl = xl
|
||||||
@@ -343,8 +343,8 @@ class SampleParser(object):
|
|||||||
self.submission_type_obj = submission_type
|
self.submission_type_obj = submission_type
|
||||||
if sub_object is None:
|
if sub_object is None:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Sample parser attempting to fetch submission class with polymorphic identity: {self.submission_type}")
|
f"Sample parser attempting to fetch run class with polymorphic identity: {self.submission_type}")
|
||||||
sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
sub_object = BasicRun.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
self.sample_type = self.sub_object.get_default_info("sample_type", submission_type=submission_type)
|
self.sample_type = self.sub_object.get_default_info("sample_type", submission_type=submission_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)
|
||||||
@@ -352,10 +352,10 @@ class SampleParser(object):
|
|||||||
@property
|
@property
|
||||||
def sample_map(self) -> dict:
|
def sample_map(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Gets info locations in excel book for submission type.
|
Gets info locations in excel book for run type.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission_type (str): submission type
|
submission_type (str): run type
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Info locations.
|
dict: Info locations.
|
||||||
@@ -478,7 +478,7 @@ class EquipmentParser(object):
|
|||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (str | SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (str | SubmissionType): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
"""
|
"""
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
submission_type = SubmissionType.query(name=submission_type)
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
@@ -488,7 +488,7 @@ class EquipmentParser(object):
|
|||||||
@property
|
@property
|
||||||
def equipment_map(self) -> dict:
|
def equipment_map(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Gets the map of equipment locations in the submission type's spreadsheet
|
Gets the map of equipment locations in the run type's spreadsheet
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[dict]: List of locations
|
List[dict]: List of locations
|
||||||
@@ -556,7 +556,7 @@ class TipParser(object):
|
|||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (str | SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (str | SubmissionType): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
"""
|
"""
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
submission_type = SubmissionType.query(name=submission_type)
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
@@ -566,7 +566,7 @@ class TipParser(object):
|
|||||||
@property
|
@property
|
||||||
def tip_map(self) -> dict:
|
def tip_map(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Gets the map of equipment locations in the submission type's spreadsheet
|
Gets the map of equipment locations in the run type's spreadsheet
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[dict]: List of locations
|
List[dict]: List of locations
|
||||||
@@ -609,11 +609,11 @@ class TipParser(object):
|
|||||||
class PCRParser(object):
|
class PCRParser(object):
|
||||||
"""Object to pull data from Design and Analysis PCR export file."""
|
"""Object to pull data from Design and Analysis PCR export file."""
|
||||||
|
|
||||||
def __init__(self, filepath: Path | None = None, submission: BasicSubmission | None = None) -> None:
|
def __init__(self, filepath: Path | None = None, submission: BasicRun | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
filepath (Path | None, optional): file to parse. Defaults to None.
|
filepath (Path | None, optional): file to parse. Defaults to None.
|
||||||
submission (BasicSubmission | None, optional): Submission parsed data to be added to.
|
submission (BasicRun | None, optional): Submission parsed data to be added to.
|
||||||
"""
|
"""
|
||||||
if filepath is None:
|
if filepath is None:
|
||||||
logger.error('No filepath given.')
|
logger.error('No filepath given.')
|
||||||
@@ -659,7 +659,7 @@ class PCRParser(object):
|
|||||||
|
|
||||||
class ConcentrationParser(object):
|
class ConcentrationParser(object):
|
||||||
|
|
||||||
def __init__(self, filepath: Path | None = None, submission: BasicSubmission | None = None) -> None:
|
def __init__(self, filepath: Path | None = None, run: BasicRun | None = None) -> None:
|
||||||
if filepath is None:
|
if filepath is None:
|
||||||
logger.error('No filepath given.')
|
logger.error('No filepath given.')
|
||||||
self.xl = None
|
self.xl = None
|
||||||
@@ -672,11 +672,11 @@ class ConcentrationParser(object):
|
|||||||
except PermissionError:
|
except PermissionError:
|
||||||
logger.error(f"Couldn't get permissions for {filepath.__str__()}. Operation might have been cancelled.")
|
logger.error(f"Couldn't get permissions for {filepath.__str__()}. Operation might have been cancelled.")
|
||||||
return None
|
return None
|
||||||
if submission is None:
|
if run is None:
|
||||||
self.submission_obj = BacterialCulture
|
self.submission_obj = BasicRun()
|
||||||
rsl_plate_num = None
|
rsl_plate_num = None
|
||||||
else:
|
else:
|
||||||
self.submission_obj = submission
|
self.submission_obj = run
|
||||||
rsl_plate_num = self.submission_obj.rsl_plate_num
|
rsl_plate_num = self.submission_obj.rsl_plate_num
|
||||||
self.samples = self.submission_obj.parse_concentration(xl=self.xl, rsl_plate_num=rsl_plate_num)
|
self.samples = self.submission_obj.parse_concentration(xl=self.xl, rsl_plate_num=rsl_plate_num)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from pandas import DataFrame, ExcelWriter
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
from backend.db.models import BasicSubmission
|
from backend.db.models import BasicRun
|
||||||
from tools import jinja_template_loading, get_first_blank_df_row, row_map, flatten_list
|
from tools import jinja_template_loading, get_first_blank_df_row, row_map, flatten_list
|
||||||
from PyQt6.QtWidgets import QWidget
|
from PyQt6.QtWidgets import QWidget
|
||||||
from openpyxl.worksheet.worksheet import Worksheet
|
from openpyxl.worksheet.worksheet import Worksheet
|
||||||
@@ -45,9 +45,9 @@ class ReportMaker(object):
|
|||||||
self.start_date = start_date
|
self.start_date = start_date
|
||||||
self.end_date = end_date
|
self.end_date = end_date
|
||||||
# NOTE: Set page size to zero to override limiting query size.
|
# 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)
|
self.runs = BasicRun.query(start_date=start_date, end_date=end_date, page_size=0)
|
||||||
if organizations is not None:
|
if organizations is not None:
|
||||||
self.subs = [sub for sub in self.subs if sub.client_submission.submitting_lab.name in organizations]
|
self.runs = [run for run in self.runs if run.client_submission.submitting_lab.name in organizations]
|
||||||
self.detailed_df, self.summary_df = self.make_report_xlsx()
|
self.detailed_df, self.summary_df = self.make_report_xlsx()
|
||||||
self.html = self.make_report_html(df=self.summary_df)
|
self.html = self.make_report_html(df=self.summary_df)
|
||||||
|
|
||||||
@@ -58,9 +58,9 @@ class ReportMaker(object):
|
|||||||
Returns:
|
Returns:
|
||||||
DataFrame: output dataframe
|
DataFrame: output dataframe
|
||||||
"""
|
"""
|
||||||
if not self.subs:
|
if not self.runs:
|
||||||
return DataFrame(), DataFrame()
|
return DataFrame(), DataFrame()
|
||||||
df = DataFrame.from_records([item.to_dict(report=True) for item in self.subs])
|
df = DataFrame.from_records([item.to_dict(report=True) for item in self.runs])
|
||||||
# NOTE: put submissions with the same lab together
|
# NOTE: put submissions with the same lab together
|
||||||
df = df.sort_values("submitting_lab")
|
df = df.sort_values("submitting_lab")
|
||||||
# NOTE: aggregate cost and sample count columns
|
# NOTE: aggregate cost and sample count columns
|
||||||
@@ -156,19 +156,19 @@ class TurnaroundMaker(ReportArchetype):
|
|||||||
self.start_date = start_date
|
self.start_date = start_date
|
||||||
self.end_date = end_date
|
self.end_date = end_date
|
||||||
# NOTE: Set page size to zero to override limiting query size.
|
# NOTE: Set page size to zero to override limiting query size.
|
||||||
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date,
|
self.subs = BasicRun.query(start_date=start_date, end_date=end_date,
|
||||||
submission_type_name=submission_type, page_size=0)
|
submission_type_name=submission_type, page_size=0)
|
||||||
records = [self.build_record(sub) for sub in self.subs]
|
records = [self.build_record(sub) for sub in self.subs]
|
||||||
self.df = DataFrame.from_records(records)
|
self.df = DataFrame.from_records(records)
|
||||||
self.sheet_name = "Turnaround"
|
self.sheet_name = "Turnaround"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build_record(cls, sub: BasicSubmission) -> dict:
|
def build_record(cls, sub: BasicRun) -> dict:
|
||||||
"""
|
"""
|
||||||
Build a turnaround dictionary from a submission
|
Build a turnaround dictionary from a run
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sub (BasicSubmission): The submission to be processed.
|
sub (BasicRun): The run to be processed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
@@ -203,9 +203,9 @@ class ConcentrationMaker(ReportArchetype):
|
|||||||
self.start_date = start_date
|
self.start_date = start_date
|
||||||
self.end_date = end_date
|
self.end_date = end_date
|
||||||
# NOTE: Set page size to zero to override limiting query size.
|
# NOTE: Set page size to zero to override limiting query size.
|
||||||
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date,
|
self.subs = BasicRun.query(start_date=start_date, end_date=end_date,
|
||||||
submission_type_name=submission_type, page_size=0)
|
submission_type_name=submission_type, page_size=0)
|
||||||
# self.samples = flatten_list([sub.get_provisional_controls(controls_only=controls_only) for sub in self.subs])
|
# self.samples = flatten_list([sub.get_provisional_controls(controls_only=controls_only) for sub in self.runs])
|
||||||
self.samples = flatten_list([sub.get_provisional_controls(include=include) for sub in self.subs])
|
self.samples = flatten_list([sub.get_provisional_controls(include=include) for sub in self.subs])
|
||||||
self.records = [self.build_record(sample) for sample in self.samples]
|
self.records = [self.build_record(sample) for sample in self.samples]
|
||||||
self.df = DataFrame.from_records(self.records)
|
self.df = DataFrame.from_records(self.records)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
contains writer objects for pushing values to submission sheet templates.
|
contains writer objects for pushing values to run sheet templates.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from copy import copy
|
from copy import copy
|
||||||
@@ -8,7 +8,7 @@ from operator import itemgetter
|
|||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import List, Generator, Tuple
|
from typing import List, Generator, Tuple
|
||||||
from openpyxl import load_workbook, Workbook
|
from openpyxl import load_workbook, Workbook
|
||||||
from backend.db.models import SubmissionType, KitType, BasicSubmission
|
from backend.db.models import SubmissionType, KitType, BasicRun
|
||||||
from backend.validators.pydant import PydSubmission
|
from backend.validators.pydant import PydSubmission
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@@ -24,7 +24,7 @@ class SheetWriter(object):
|
|||||||
def __init__(self, submission: PydSubmission):
|
def __init__(self, submission: PydSubmission):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
submission (PydSubmission): Object containing submission information.
|
submission (PydSubmission): Object containing run information.
|
||||||
"""
|
"""
|
||||||
self.sub = OrderedDict(submission.improved_dict())
|
self.sub = OrderedDict(submission.improved_dict())
|
||||||
# NOTE: Set values from pydantic object.
|
# NOTE: Set values from pydantic object.
|
||||||
@@ -35,7 +35,7 @@ class SheetWriter(object):
|
|||||||
case 'submission_type':
|
case 'submission_type':
|
||||||
self.sub[k] = v['value']
|
self.sub[k] = v['value']
|
||||||
self.submission_type = SubmissionType.query(name=v['value'])
|
self.submission_type = SubmissionType.query(name=v['value'])
|
||||||
self.sub_object = BasicSubmission.find_polymorphic_subclass(
|
self.run_object = BasicRun.find_polymorphic_subclass(
|
||||||
polymorphic_identity=self.submission_type)
|
polymorphic_identity=self.submission_type)
|
||||||
case _:
|
case _:
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
@@ -99,22 +99,22 @@ class SheetWriter(object):
|
|||||||
|
|
||||||
class InfoWriter(object):
|
class InfoWriter(object):
|
||||||
"""
|
"""
|
||||||
object to write general submission info into excel file
|
object to write general run info into excel file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, info_dict: dict,
|
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, info_dict: dict,
|
||||||
sub_object: BasicSubmission | None = None):
|
sub_object: BasicRun | None = None):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (SubmissionType | str): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
info_dict (dict): Dictionary of information to write.
|
info_dict (dict): Dictionary of information to write.
|
||||||
sub_object (BasicSubmission | None, optional): Submission object containing methods. Defaults to None.
|
sub_object (BasicRun | None, optional): Submission object containing methods. Defaults to None.
|
||||||
"""
|
"""
|
||||||
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:
|
||||||
sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=submission_type.name)
|
sub_object = BasicRun.find_polymorphic_subclass(polymorphic_identity=submission_type.name)
|
||||||
self.submission_type = submission_type
|
self.submission_type = submission_type
|
||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
self.xl = xl
|
self.xl = xl
|
||||||
@@ -196,7 +196,7 @@ class ReagentWriter(object):
|
|||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (SubmissionType | str): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
extraction_kit (KitType | str): Extraction kit used.
|
extraction_kit (KitType | str): Extraction kit used.
|
||||||
reagent_list (list): List of reagent dicts to be written to excel.
|
reagent_list (list): List of reagent dicts to be written to excel.
|
||||||
"""
|
"""
|
||||||
@@ -273,7 +273,7 @@ class SampleWriter(object):
|
|||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (SubmissionType | str): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
sample_list (list): List of sample dictionaries to be written to excel file.
|
sample_list (list): List of sample dictionaries to be written to excel file.
|
||||||
"""
|
"""
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
@@ -281,7 +281,7 @@ class SampleWriter(object):
|
|||||||
self.submission_type = submission_type
|
self.submission_type = submission_type
|
||||||
self.xl = xl
|
self.xl = xl
|
||||||
self.sample_map = submission_type.sample_map['lookup_table']
|
self.sample_map = submission_type.sample_map['lookup_table']
|
||||||
# NOTE: exclude any samples without a submission rank.
|
# NOTE: exclude any samples without a run rank.
|
||||||
samples = [item for item in self.reconcile_map(sample_list) if item['submission_rank'] > 0]
|
samples = [item for item in self.reconcile_map(sample_list) if item['submission_rank'] > 0]
|
||||||
self.samples = sorted(samples, key=itemgetter('submission_rank'))
|
self.samples = sorted(samples, key=itemgetter('submission_rank'))
|
||||||
self.blank_lookup_table()
|
self.blank_lookup_table()
|
||||||
@@ -351,7 +351,7 @@ class EquipmentWriter(object):
|
|||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (SubmissionType | str): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
equipment_list (list): List of equipment dictionaries to write to excel file.
|
equipment_list (list): List of equipment dictionaries to write to excel file.
|
||||||
"""
|
"""
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
@@ -433,7 +433,7 @@ class TipWriter(object):
|
|||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (SubmissionType | str): Type of run expected (Wastewater, Bacterial Culture, etc.)
|
||||||
tips_list (list): List of tip dictionaries to write to the excel file.
|
tips_list (list): List of tip dictionaries to write to the excel file.
|
||||||
"""
|
"""
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import logging, re
|
|||||||
import sys
|
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 BasicRun, SubmissionType
|
||||||
from tools import jinja_template_loading
|
from tools import jinja_template_loading
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
@@ -25,9 +25,9 @@ class RSLNamer(object):
|
|||||||
self.submission_type = submission_type
|
self.submission_type = submission_type
|
||||||
if not self.submission_type:
|
if not self.submission_type:
|
||||||
self.submission_type = self.retrieve_submission_type(filename=filename)
|
self.submission_type = self.retrieve_submission_type(filename=filename)
|
||||||
logger.info(f"got submission type: {self.submission_type}")
|
logger.info(f"got run type: {self.submission_type}")
|
||||||
if self.submission_type:
|
if self.submission_type:
|
||||||
self.sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
self.sub_object = BasicRun.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
||||||
self.parsed_name = self.retrieve_rsl_number(filename=filename, regex=self.sub_object.get_regex(
|
self.parsed_name = self.retrieve_rsl_number(filename=filename, regex=self.sub_object.get_regex(
|
||||||
submission_type=submission_type))
|
submission_type=submission_type))
|
||||||
if not data:
|
if not data:
|
||||||
@@ -40,7 +40,7 @@ class RSLNamer(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def retrieve_submission_type(cls, filename: str | Path) -> str:
|
def retrieve_submission_type(cls, filename: str | Path) -> str:
|
||||||
"""
|
"""
|
||||||
Gets submission type from excel file properties or sheet names or regex pattern match or user input
|
Gets run type from excel file properties or sheet names or regex pattern match or user input
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename (str | Path): filename
|
filename (str | Path): filename
|
||||||
@@ -49,12 +49,12 @@ class RSLNamer(object):
|
|||||||
TypeError: Raised if unsupported variable type for filename given.
|
TypeError: Raised if unsupported variable type for filename given.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: parsed submission type
|
str: parsed run type
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def st_from_path(filepath: Path) -> str:
|
def st_from_path(filepath: Path) -> str:
|
||||||
"""
|
"""
|
||||||
Sub def to get submissiontype from a file path
|
Sub def to get proceduretype from a file path
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filepath ():
|
filepath ():
|
||||||
@@ -83,13 +83,13 @@ class RSLNamer(object):
|
|||||||
def st_from_str(file_name: str) -> str:
|
def st_from_str(file_name: str) -> str:
|
||||||
if file_name.startswith("tmp"):
|
if file_name.startswith("tmp"):
|
||||||
return "Bacterial Culture"
|
return "Bacterial Culture"
|
||||||
regex = BasicSubmission.regex
|
regex = BasicRun.regex
|
||||||
m = regex.search(file_name)
|
m = regex.search(file_name)
|
||||||
try:
|
try:
|
||||||
sub_type = m.lastgroup
|
sub_type = m.lastgroup
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
sub_type = None
|
sub_type = None
|
||||||
logger.critical(f"No submission type found or submission type found!: {e}")
|
logger.critical(f"No run type found or run type found!: {e}")
|
||||||
return sub_type
|
return sub_type
|
||||||
|
|
||||||
match filename:
|
match filename:
|
||||||
@@ -107,8 +107,8 @@ class RSLNamer(object):
|
|||||||
if "pytest" in sys.modules:
|
if "pytest" in sys.modules:
|
||||||
raise ValueError("Submission Type came back as None.")
|
raise ValueError("Submission Type came back as None.")
|
||||||
from frontend.widgets import ObjectSelector
|
from frontend.widgets import ObjectSelector
|
||||||
dlg = ObjectSelector(title="Couldn't parse submission type.",
|
dlg = ObjectSelector(title="Couldn't parse run type.",
|
||||||
message="Please select submission type from list below.",
|
message="Please select run type from list below.",
|
||||||
obj_type=SubmissionType)
|
obj_type=SubmissionType)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
submission_type = dlg.parse_form()
|
submission_type = dlg.parse_form()
|
||||||
@@ -118,14 +118,14 @@ class RSLNamer(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def retrieve_rsl_number(cls, filename: str | Path, regex: re.Pattern | None = None):
|
def retrieve_rsl_number(cls, filename: str | Path, regex: re.Pattern | None = None):
|
||||||
"""
|
"""
|
||||||
Uses regex to retrieve the plate number and submission type from an input string
|
Uses regex to retrieve the plate number and run type from an input string
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
regex (str): string to construct pattern
|
regex (str): string to construct pattern
|
||||||
filename (str): string to be parsed
|
filename (str): string to be parsed
|
||||||
"""
|
"""
|
||||||
if regex is None:
|
if regex is None:
|
||||||
regex = BasicSubmission.regex
|
regex = BasicRun.regex
|
||||||
match filename:
|
match filename:
|
||||||
case Path():
|
case Path():
|
||||||
m = regex.search(filename.stem)
|
m = regex.search(filename.stem)
|
||||||
@@ -145,10 +145,10 @@ class RSLNamer(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def construct_new_plate_name(cls, data: dict) -> str:
|
def construct_new_plate_name(cls, data: dict) -> str:
|
||||||
"""
|
"""
|
||||||
Make a brand-new plate name from submission data.
|
Make a brand-new plate name from run data.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (dict): incoming submission data
|
data (dict): incoming run data
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Output filename
|
str: Output filename
|
||||||
@@ -170,7 +170,7 @@ class RSLNamer(object):
|
|||||||
if "rsl_plate_num" in data.keys():
|
if "rsl_plate_num" in data.keys():
|
||||||
plate_number = data['rsl_plate_num'].split("-")[-1][0]
|
plate_number = data['rsl_plate_num'].split("-")[-1][0]
|
||||||
else:
|
else:
|
||||||
previous = BasicSubmission.query(start_date=today, end_date=today, submissiontype=data['submission_type'])
|
previous = BasicRun.query(start_date=today, end_date=today, submissiontype=data['submission_type'])
|
||||||
plate_number = len(previous) + 1
|
plate_number = len(previous) + 1
|
||||||
return f"RSL-{data['abbreviation']}-{today.year}{str(today.month).zfill(2)}{str(today.day).zfill(2)}-{plate_number}"
|
return f"RSL-{data['abbreviation']}-{today.year}{str(today.month).zfill(2)}{str(today.day).zfill(2)}-{plate_number}"
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ class RSLNamer(object):
|
|||||||
Make export file name from jinja template. (currently unused)
|
Make export file name from jinja template. (currently unused)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
template (jinja2.Template): Template stored in BasicSubmission
|
template (jinja2.Template): Template stored in BasicRun
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: output file name.
|
str: output file name.
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ class OmniSubmissionTypeKitTypeAssociation(BaseOmni):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return f"<{self.__class__.__name__}(NO NAME)>"
|
return f"<{self.__class__.__name__}(NO NAME)>"
|
||||||
|
|
||||||
@field_validator("submissiontype", mode="before")
|
@field_validator("proceduretype", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def rescue_submissiontype_none(cls, value):
|
def rescue_submissiontype_none(cls, value):
|
||||||
if not value:
|
if not value:
|
||||||
@@ -324,7 +324,7 @@ class OmniSubmissionTypeKitTypeAssociation(BaseOmni):
|
|||||||
Convert this object to an instance of its class object.
|
Convert this object to an instance of its class object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# logger.debug(f"Self kittype: {self.submissiontype}")
|
# logger.debug(f"Self kittype: {self.proceduretype}")
|
||||||
if issubclass(self.submissiontype.__class__, BaseOmni):
|
if issubclass(self.submissiontype.__class__, BaseOmni):
|
||||||
submissiontype = SubmissionType.query(name=self.submissiontype.name)
|
submissiontype = SubmissionType.query(name=self.submissiontype.name)
|
||||||
else:
|
else:
|
||||||
@@ -334,7 +334,7 @@ class OmniSubmissionTypeKitTypeAssociation(BaseOmni):
|
|||||||
else:
|
else:
|
||||||
kittype = KitType.query(name=self.kittype)
|
kittype = KitType.query(name=self.kittype)
|
||||||
# logger.debug(f"Self kittype: {self.kittype}")
|
# logger.debug(f"Self kittype: {self.kittype}")
|
||||||
# logger.debug(f"Query or create with {kittype}, {submissiontype}")
|
# logger.debug(f"Query or create with {kittype}, {proceduretype}")
|
||||||
instance, is_new = self.class_object.query_or_create(kittype=kittype, submissiontype=submissiontype)
|
instance, is_new = self.class_object.query_or_create(kittype=kittype, submissiontype=submissiontype)
|
||||||
instance.mutable_cost_column = self.mutable_cost_column
|
instance.mutable_cost_column = self.mutable_cost_column
|
||||||
instance.mutable_cost_sample = self.mutable_cost_sample
|
instance.mutable_cost_sample = self.mutable_cost_sample
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class PydReagent(BaseModel):
|
|||||||
return {k: getattr(self, k) for k in fields}
|
return {k: getattr(self, k) for k in fields}
|
||||||
|
|
||||||
@report_result
|
@report_result
|
||||||
def to_sql(self, submission: BasicSubmission | str = None) -> Tuple[Reagent, Report]:
|
def to_sql(self, submission: BasicRun | str = None) -> Tuple[Reagent, Report]:
|
||||||
"""
|
"""
|
||||||
Converts this instance into a backend.db.models.kit.Reagent instance
|
Converts this instance into a backend.db.models.kit.Reagent instance
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ class PydReagent(BaseModel):
|
|||||||
# NOTE: reagent method sets fields based on keys in dictionary
|
# NOTE: reagent method sets fields based on keys in dictionary
|
||||||
reagent.set_attribute(key, value)
|
reagent.set_attribute(key, value)
|
||||||
if submission is not None and reagent not in submission.reagents:
|
if submission is not None and reagent not in submission.reagents:
|
||||||
assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission)
|
assoc = RunReagentAssociation(reagent=reagent, submission=submission)
|
||||||
assoc.comments = self.comment
|
assoc.comments = self.comment
|
||||||
else:
|
else:
|
||||||
assoc = None
|
assoc = None
|
||||||
@@ -198,13 +198,13 @@ class PydSample(BaseModel, extra='allow'):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
@report_result
|
@report_result
|
||||||
def to_sql(self, submission: BasicSubmission | str = None) -> Tuple[
|
def to_sql(self, run: BasicRun | str = None) -> Tuple[
|
||||||
BasicSample, List[SubmissionSampleAssociation], Result | None]:
|
BasicSample, List[SubmissionSampleAssociation], Result | None]:
|
||||||
"""
|
"""
|
||||||
Converts this instance into a backend.db.models.submissions.Sample object
|
Converts this instance into a backend.db.models.submissions.Sample object
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission (BasicSubmission | str, optional): Submission joined to this sample. Defaults to None.
|
run (BasicRun | str, optional): Submission joined to this sample. Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[BasicSample, Result]: Sample object and result object.
|
Tuple[BasicSample, Result]: Sample object and result object.
|
||||||
@@ -220,13 +220,13 @@ class PydSample(BaseModel, extra='allow'):
|
|||||||
case _:
|
case _:
|
||||||
instance.__setattr__(key, value)
|
instance.__setattr__(key, value)
|
||||||
out_associations = []
|
out_associations = []
|
||||||
if submission is not None:
|
if run is not None:
|
||||||
if isinstance(submission, str):
|
if isinstance(run, str):
|
||||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
run = BasicRun.query(rsl_plate_num=run)
|
||||||
assoc_type = submission.submission_type_name
|
assoc_type = run.submission_type_name
|
||||||
for row, column, aid, submission_rank in zip(self.row, self.column, self.assoc_id, self.submission_rank):
|
for row, column, aid, submission_rank in zip(self.row, self.column, self.assoc_id, self.submission_rank):
|
||||||
association = SubmissionSampleAssociation.query_or_create(association_type=f"{assoc_type} Association",
|
association = SubmissionSampleAssociation.query_or_create(association_type=f"{assoc_type} Association",
|
||||||
submission=submission,
|
submission=run,
|
||||||
sample=instance,
|
sample=instance,
|
||||||
row=row, column=column, id=aid,
|
row=row, column=column, id=aid,
|
||||||
submission_rank=submission_rank,
|
submission_rank=submission_rank,
|
||||||
@@ -234,7 +234,7 @@ class PydSample(BaseModel, extra='allow'):
|
|||||||
try:
|
try:
|
||||||
out_associations.append(association)
|
out_associations.append(association)
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
logger.error(f"Could not attach submission sample association due to: {e}")
|
logger.error(f"Could not attach run sample association due to: {e}")
|
||||||
instance.metadata.session.rollback()
|
instance.metadata.session.rollback()
|
||||||
return instance, out_associations, report
|
return instance, out_associations, report
|
||||||
|
|
||||||
@@ -267,20 +267,20 @@ class PydTips(BaseModel):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
@report_result
|
@report_result
|
||||||
def to_sql(self, submission: BasicSubmission) -> SubmissionTipsAssociation:
|
def to_sql(self, submission: BasicRun) -> SubmissionTipsAssociation:
|
||||||
"""
|
"""
|
||||||
Convert this object to the SQL version for database storage.
|
Convert this object to the SQL version for database storage.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission (BasicSubmission): A submission object to associate tips represented here.
|
submission (BasicRun): A run object to associate tips represented here.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
SubmissionTipsAssociation: Association between queried tips and submission
|
SubmissionTipsAssociation: Association between queried tips and run
|
||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
tips = Tips.query(name=self.name, limit=1)
|
tips = Tips.query(name=self.name, limit=1)
|
||||||
# logger.debug(f"Tips query has yielded: {tips}")
|
# logger.debug(f"Tips query has yielded: {tips}")
|
||||||
assoc = SubmissionTipsAssociation.query_or_create(tips=tips, submission=submission, role=self.role, limit=1)
|
assoc = SubmissionTipsAssociation.query_or_create(tips=tips, run=submission, role=self.role, limit=1)
|
||||||
return assoc, report
|
return assoc, report
|
||||||
|
|
||||||
|
|
||||||
@@ -315,19 +315,19 @@ class PydEquipment(BaseModel, extra='ignore'):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
@report_result
|
@report_result
|
||||||
def to_sql(self, submission: BasicSubmission | str = None, extraction_kit: KitType | str = None) -> Tuple[Equipment, SubmissionEquipmentAssociation]:
|
def to_sql(self, submission: BasicRun | str = None, extraction_kit: KitType | str = None) -> Tuple[Equipment, RunEquipmentAssociation]:
|
||||||
"""
|
"""
|
||||||
Creates Equipment and SubmssionEquipmentAssociations for this PydEquipment
|
Creates Equipment and SubmssionEquipmentAssociations for this PydEquipment
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission ( BasicSubmission | str ): BasicSubmission of interest
|
submission ( BasicRun | str ): BasicRun of interest
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[Equipment, SubmissionEquipmentAssociation]: SQL objects
|
Tuple[Equipment, RunEquipmentAssociation]: SQL objects
|
||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
if isinstance(submission, str):
|
if isinstance(submission, str):
|
||||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
submission = BasicRun.query(rsl_plate_num=submission)
|
||||||
if isinstance(extraction_kit, str):
|
if isinstance(extraction_kit, str):
|
||||||
extraction_kit = KitType.query(name=extraction_kit)
|
extraction_kit = KitType.query(name=extraction_kit)
|
||||||
equipment = Equipment.query(asset_number=self.asset_number)
|
equipment = Equipment.query(asset_number=self.asset_number)
|
||||||
@@ -335,15 +335,15 @@ class PydEquipment(BaseModel, extra='ignore'):
|
|||||||
logger.error("No equipment found. Returning None.")
|
logger.error("No equipment found. Returning None.")
|
||||||
return
|
return
|
||||||
if submission is not None:
|
if submission is not None:
|
||||||
# NOTE: Need to make sure the same association is not added to the submission
|
# NOTE: Need to make sure the same association is not added to the run
|
||||||
try:
|
try:
|
||||||
assoc = SubmissionEquipmentAssociation.query(equipment_id=equipment.id, submission_id=submission.id,
|
assoc = RunEquipmentAssociation.query(equipment_id=equipment.id, submission_id=submission.id,
|
||||||
role=self.role, limit=1)
|
role=self.role, limit=1)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
logger.error(f"Couldn't get association due to {e}, returning...")
|
logger.error(f"Couldn't get association due to {e}, returning...")
|
||||||
assoc = None
|
assoc = None
|
||||||
if assoc is None:
|
if assoc is None:
|
||||||
assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment)
|
assoc = RunEquipmentAssociation(submission=submission, equipment=equipment)
|
||||||
# TODO: This seems precarious. What if there is more than one process?
|
# TODO: This seems precarious. What if there is more than one process?
|
||||||
# NOTE: It looks like the way fetching the processes is done in the SQL model, this shouldn't be a problem, but I'll include a failsafe.
|
# NOTE: It looks like the way fetching the processes is done in the SQL model, this shouldn't be a problem, but I'll include a failsafe.
|
||||||
# NOTE: I need to find a way to filter this by the kit involved.
|
# NOTE: I need to find a way to filter this by the kit involved.
|
||||||
@@ -360,7 +360,7 @@ class PydEquipment(BaseModel, extra='ignore'):
|
|||||||
logger.warning(f"Found already existing association: {assoc}")
|
logger.warning(f"Found already existing association: {assoc}")
|
||||||
assoc = None
|
assoc = None
|
||||||
else:
|
else:
|
||||||
logger.warning(f"No submission found")
|
logger.warning(f"No run found")
|
||||||
assoc = None
|
assoc = None
|
||||||
return equipment, assoc, report
|
return equipment, assoc, report
|
||||||
|
|
||||||
@@ -519,7 +519,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
value['value'] = value['value'].strip()
|
value['value'] = value['value'].strip()
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
if "pytest" in sys.modules and sub_type.replace(" ", "") == "BasicSubmission":
|
if "pytest" in sys.modules and sub_type.replace(" ", "") == "BasicRun":
|
||||||
output = "RSL-BS-Test001"
|
output = "RSL-BS-Test001"
|
||||||
else:
|
else:
|
||||||
output = RSLNamer(filename=values.data['filepath'].__str__(), submission_type=sub_type,
|
output = RSLNamer(filename=values.data['filepath'].__str__(), submission_type=sub_type,
|
||||||
@@ -674,7 +674,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
def __init__(self, run_custom: bool = False, **data):
|
def __init__(self, run_custom: bool = False, **data):
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
# NOTE: this could also be done with default_factory
|
# NOTE: this could also be done with default_factory
|
||||||
self.submission_object = BasicSubmission.find_polymorphic_subclass(
|
self.submission_object = BasicRun.find_polymorphic_subclass(
|
||||||
polymorphic_identity=self.submission_type['value'])
|
polymorphic_identity=self.submission_type['value'])
|
||||||
self.namer = RSLNamer(self.rsl_plate_num['value'], submission_type=self.submission_type['value'])
|
self.namer = RSLNamer(self.rsl_plate_num['value'], submission_type=self.submission_type['value'])
|
||||||
if run_custom:
|
if run_custom:
|
||||||
@@ -771,18 +771,18 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
return missing_info, missing_reagents
|
return missing_info, missing_reagents
|
||||||
|
|
||||||
@report_result
|
@report_result
|
||||||
def to_sql(self) -> Tuple[BasicSubmission | None, Report]:
|
def to_sql(self) -> Tuple[BasicRun | None, Report]:
|
||||||
"""
|
"""
|
||||||
Converts this instance into a backend.db.models.submissions.BasicSubmission instance
|
Converts this instance into a backend.db.models.submissions.BasicRun instance
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[BasicSubmission, Result]: BasicSubmission instance, result object
|
Tuple[BasicRun, Result]: BasicRun instance, result object
|
||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
dicto = self.improved_dict()
|
dicto = self.improved_dict()
|
||||||
# logger.debug(f"Pydantic submission type: {self.submission_type['value']}")
|
# logger.debug(f"Pydantic run type: {self.submission_type['value']}")
|
||||||
# logger.debug(f"Pydantic improved_dict: {pformat(dicto)}")
|
# logger.debug(f"Pydantic improved_dict: {pformat(dicto)}")
|
||||||
instance, result = BasicSubmission.query_or_create(submission_type=self.submission_type['value'],
|
instance, result = BasicRun.query_or_create(submission_type=self.submission_type['value'],
|
||||||
rsl_plate_num=self.rsl_plate_num['value'])
|
rsl_plate_num=self.rsl_plate_num['value'])
|
||||||
# logger.debug(f"Created or queried instance: {instance}")
|
# logger.debug(f"Created or queried instance: {instance}")
|
||||||
if instance is None:
|
if instance is None:
|
||||||
@@ -808,7 +808,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
reagent = reagent.to_sql(submission=instance)
|
reagent = reagent.to_sql(submission=instance)
|
||||||
case "samples":
|
case "samples":
|
||||||
for sample in self.samples:
|
for sample in self.samples:
|
||||||
sample, associations, _ = sample.to_sql(submission=instance)
|
sample, associations, _ = sample.to_sql(run=instance)
|
||||||
for assoc in associations:
|
for assoc in associations:
|
||||||
if assoc is not None:
|
if assoc is not None:
|
||||||
if assoc not in instance.submission_sample_associations:
|
if assoc not in instance.submission_sample_associations:
|
||||||
@@ -1186,7 +1186,7 @@ class PydEquipmentRole(BaseModel):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
parent (_type_): parent widget
|
parent (_type_): parent widget
|
||||||
used (list): list of equipment already added to submission
|
used (list): list of equipment already added to run
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
RoleComboBox: widget
|
RoleComboBox: widget
|
||||||
@@ -1203,7 +1203,7 @@ class PydPCRControl(BaseModel):
|
|||||||
ct: float
|
ct: float
|
||||||
reagent_lot: str
|
reagent_lot: str
|
||||||
submitted_date: datetime #: Date submitted to Robotics
|
submitted_date: datetime #: Date submitted to Robotics
|
||||||
submission_id: int
|
run_id: int
|
||||||
controltype_name: str
|
controltype_name: str
|
||||||
|
|
||||||
@report_result
|
@report_result
|
||||||
@@ -1231,7 +1231,7 @@ class PydIridaControl(BaseModel, extra='ignore'):
|
|||||||
kraken2_db_version: str
|
kraken2_db_version: str
|
||||||
sample_id: int
|
sample_id: int
|
||||||
submitted_date: datetime #: Date submitted to Robotics
|
submitted_date: datetime #: Date submitted to Robotics
|
||||||
submission_id: int
|
run_id: int
|
||||||
controltype_name: str
|
controltype_name: str
|
||||||
|
|
||||||
@field_validator("refseq_version", "kraken2_version", "kraken2_db_version", mode='before')
|
@field_validator("refseq_version", "kraken2_version", "kraken2_db_version", mode='before')
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ class ConcentrationsChart(CustomFigure):
|
|||||||
self.df = df
|
self.df = df
|
||||||
try:
|
try:
|
||||||
self.df = self.df[self.df.concentration.notnull()]
|
self.df = self.df[self.df.concentration.notnull()]
|
||||||
self.df = self.df.sort_values(['submitted_date', 'submission'], ascending=[True, True]).reset_index(
|
self.df = self.df.sort_values(['submitted_date', 'run'], ascending=[True, True]).reset_index(
|
||||||
drop=True)
|
drop=True)
|
||||||
self.df = self.df.reset_index().rename(columns={"index": "idx"})
|
self.df = self.df.reset_index().rename(columns={"index": "idx"})
|
||||||
# logger.debug(f"DF after changes:\n{self.df}")
|
# logger.debug(f"DF after changes:\n{self.df}")
|
||||||
scatter = px.scatter(data_frame=self.df, x='submission', y="concentration",
|
scatter = px.scatter(data_frame=self.df, x='run', y="concentration",
|
||||||
hover_data=["name", "submission", "submitted_date", "concentration"],
|
hover_data=["name", "run", "submitted_date", "concentration"],
|
||||||
color="positive", color_discrete_map={"positive": "red", "negative": "green", "sample":"orange"}
|
color="positive", color_discrete_map={"positive": "red", "negative": "green", "sample":"orange"}
|
||||||
)
|
)
|
||||||
except (ValueError, AttributeError) as e:
|
except (ValueError, AttributeError) as e:
|
||||||
@@ -44,11 +44,11 @@ class ConcentrationsChart(CustomFigure):
|
|||||||
for trace in traces:
|
for trace in traces:
|
||||||
self.add_trace(trace)
|
self.add_trace(trace)
|
||||||
try:
|
try:
|
||||||
tickvals = self.df['submission'].tolist()
|
tickvals = self.df['run'].tolist()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
tickvals = []
|
tickvals = []
|
||||||
try:
|
try:
|
||||||
ticklabels = self.df['submission'].tolist()
|
ticklabels = self.df['run'].tolist()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
ticklabels = []
|
ticklabels = []
|
||||||
self.update_layout(
|
self.update_layout(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from PyQt6.QtGui import QAction
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
from pandas import ExcelWriter
|
from pandas import ExcelWriter
|
||||||
from backend import Reagent, BasicSample, Organization, KitType, BasicSubmission
|
from backend import Reagent, BasicSample, Organization, KitType, BasicRun
|
||||||
from tools import (
|
from tools import (
|
||||||
check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size, is_power_user,
|
check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size, is_power_user,
|
||||||
under_development
|
under_development
|
||||||
@@ -211,7 +211,7 @@ class App(QMainWindow):
|
|||||||
dlg = DateTypePicker(self)
|
dlg = DateTypePicker(self)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
output = dlg.parse_form()
|
output = dlg.parse_form()
|
||||||
df = BasicSubmission.archive_submissions(**output)
|
df = BasicRun.archive_submissions(**output)
|
||||||
filepath = select_save_file(self, f"Submissions {output['start_date']}-{output['end_date']}", "xlsx")
|
filepath = select_save_file(self, f"Submissions {output['start_date']}-{output['end_date']}", "xlsx")
|
||||||
writer = ExcelWriter(filepath, "openpyxl")
|
writer = ExcelWriter(filepath, "openpyxl")
|
||||||
df.to_excel(writer)
|
df.to_excel(writer)
|
||||||
@@ -239,7 +239,7 @@ class AddSubForm(QWidget):
|
|||||||
self.tabs.addTab(self.tab3, "PCR Controls")
|
self.tabs.addTab(self.tab3, "PCR Controls")
|
||||||
self.tabs.addTab(self.tab4, "Cost Report")
|
self.tabs.addTab(self.tab4, "Cost Report")
|
||||||
self.tabs.addTab(self.tab5, "Turnaround Times")
|
self.tabs.addTab(self.tab5, "Turnaround Times")
|
||||||
# NOTE: Create submission adder form
|
# NOTE: Create run adder form
|
||||||
self.formwidget = SubmissionFormContainer(self)
|
self.formwidget = SubmissionFormContainer(self)
|
||||||
self.formlayout = QVBoxLayout(self)
|
self.formlayout = QVBoxLayout(self)
|
||||||
self.formwidget.setLayout(self.formlayout)
|
self.formwidget.setLayout(self.formlayout)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from PyQt6.QtCore import Qt, QSignalBlocker
|
|||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QDialog, QComboBox, QCheckBox, QLabel, QWidget, QVBoxLayout, QDialogButtonBox, QGridLayout
|
QDialog, QComboBox, QCheckBox, QLabel, QWidget, QVBoxLayout, QDialogButtonBox, QGridLayout
|
||||||
)
|
)
|
||||||
from backend.db.models import Equipment, BasicSubmission, Process
|
from backend.db.models import Equipment, BasicRun, Process
|
||||||
from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips
|
from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips
|
||||||
import logging
|
import logging
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
@@ -16,7 +16,7 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
|
|
||||||
class EquipmentUsage(QDialog):
|
class EquipmentUsage(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, submission: BasicSubmission):
|
def __init__(self, parent, submission: BasicRun):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.submission = submission
|
self.submission = submission
|
||||||
self.setWindowTitle(f"Equipment Checklist - {submission.rsl_plate_num}")
|
self.setWindowTitle(f"Equipment Checklist - {submission.rsl_plate_num}")
|
||||||
@@ -137,7 +137,7 @@ class RoleComboBox(QWidget):
|
|||||||
if process.tip_roles:
|
if process.tip_roles:
|
||||||
for iii, tip_role in enumerate(process.tip_roles):
|
for iii, tip_role in enumerate(process.tip_roles):
|
||||||
widget = QComboBox()
|
widget = QComboBox()
|
||||||
tip_choices = [item.name for item in tip_role.instances]
|
tip_choices = [item.name for item in tip_role.controls]
|
||||||
widget.setEditable(False)
|
widget.setEditable(False)
|
||||||
widget.addItems(tip_choices)
|
widget.addItems(tip_choices)
|
||||||
widget.setObjectName(f"tips_{tip_role.name}")
|
widget.setObjectName(f"tips_{tip_role.name}")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import logging, numpy as np
|
|||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from backend.db.models import BasicSubmission
|
from backend.db.models import BasicRun
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
# Main window class
|
# Main window class
|
||||||
class GelBox(QDialog):
|
class GelBox(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, img_path: str | Path, submission: BasicSubmission):
|
def __init__(self, parent, img_path: str | Path, submission: BasicRun):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
# NOTE: setting title
|
# NOTE: setting title
|
||||||
self.setWindowTitle(f"Gel - {img_path}")
|
self.setWindowTitle(f"Gel - {img_path}")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Webview to show submission and sample details.
|
Webview to show run and sample details.
|
||||||
"""
|
"""
|
||||||
from PyQt6.QtWidgets import (QDialog, QPushButton, QVBoxLayout,
|
from PyQt6.QtWidgets import (QDialog, QPushButton, QVBoxLayout,
|
||||||
QDialogButtonBox, QTextEdit, QGridLayout)
|
QDialogButtonBox, QTextEdit, QGridLayout)
|
||||||
@@ -7,7 +7,7 @@ from PyQt6.QtWebEngineWidgets import QWebEngineView
|
|||||||
from PyQt6.QtWebChannel import QWebChannel
|
from PyQt6.QtWebChannel import QWebChannel
|
||||||
from PyQt6.QtCore import Qt, pyqtSlot
|
from PyQt6.QtCore import Qt, pyqtSlot
|
||||||
from jinja2 import TemplateNotFound
|
from jinja2 import TemplateNotFound
|
||||||
from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType, Equipment, Process, Tips
|
from backend.db.models import BasicRun, BasicSample, Reagent, KitType, Equipment, Process, Tips
|
||||||
from tools import is_power_user, jinja_template_loading, timezone, get_application_from_parent
|
from tools import is_power_user, jinja_template_loading, timezone, get_application_from_parent
|
||||||
from .functions import select_save_file, save_pdf
|
from .functions import select_save_file, save_pdf
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -23,10 +23,10 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
|
|
||||||
class SubmissionDetails(QDialog):
|
class SubmissionDetails(QDialog):
|
||||||
"""
|
"""
|
||||||
a window showing text details of submission
|
a window showing text details of run
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent, sub: BasicSubmission | BasicSample | Reagent) -> None:
|
def __init__(self, parent, sub: BasicRun | BasicSample | Reagent) -> None:
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.app = get_application_from_parent(parent)
|
self.app = get_application_from_parent(parent)
|
||||||
@@ -51,8 +51,8 @@ class SubmissionDetails(QDialog):
|
|||||||
self.channel = QWebChannel()
|
self.channel = QWebChannel()
|
||||||
self.channel.registerObject('backend', self)
|
self.channel.registerObject('backend', self)
|
||||||
match sub:
|
match sub:
|
||||||
case BasicSubmission():
|
case BasicRun():
|
||||||
self.submission_details(submission=sub)
|
self.run_details(run=sub)
|
||||||
self.rsl_plate_num = sub.rsl_plate_num
|
self.rsl_plate_num = sub.rsl_plate_num
|
||||||
case BasicSample():
|
case BasicSample():
|
||||||
self.sample_details(sample=sub)
|
self.sample_details(sample=sub)
|
||||||
@@ -203,52 +203,52 @@ class SubmissionDetails(QDialog):
|
|||||||
logger.error(f"Reagent with lot {old_lot} not found.")
|
logger.error(f"Reagent with lot {old_lot} not found.")
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def submission_details(self, submission: str | BasicSubmission):
|
def run_details(self, run: str | BasicRun):
|
||||||
"""
|
"""
|
||||||
Sets details view to summary of Submission.
|
Sets details view to summary of Submission.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission (str | BasicSubmission): Submission of interest.
|
run (str | BasicRun): Submission of interest.
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Submission details.")
|
logger.debug(f"Submission details.")
|
||||||
if isinstance(submission, str):
|
if isinstance(run, str):
|
||||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
run = BasicRun.query(rsl_plate_num=run)
|
||||||
self.rsl_plate_num = submission.rsl_plate_num
|
self.rsl_plate_num = run.rsl_plate_num
|
||||||
self.base_dict = submission.to_dict(full_data=True)
|
self.base_dict = run.to_dict(full_data=True)
|
||||||
# NOTE: don't want id
|
# NOTE: don't want id
|
||||||
self.base_dict['platemap'] = submission.make_plate_map(sample_list=submission.hitpicked)
|
self.base_dict['platemap'] = run.make_plate_map(sample_list=run.hitpicked)
|
||||||
self.base_dict['excluded'] = submission.get_default_info("details_ignore")
|
self.base_dict['excluded'] = run.get_default_info("details_ignore")
|
||||||
self.base_dict, self.template = submission.get_details_template(base_dict=self.base_dict)
|
self.base_dict, self.template = run.get_details_template(base_dict=self.base_dict)
|
||||||
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
|
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
|
||||||
with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
||||||
css = f.read()
|
css = f.read()
|
||||||
# logger.debug(f"Base dictionary of submission {self.rsl_plate_num}: {pformat(self.base_dict)}")
|
# logger.debug(f"Base dictionary of run {self.rsl_plate_num}: {pformat(self.base_dict)}")
|
||||||
self.html = self.template.render(sub=self.base_dict, permission=is_power_user(), css=css)
|
self.html = self.template.render(sub=self.base_dict, permission=is_power_user(), css=css)
|
||||||
self.webview.setHtml(self.html)
|
self.webview.setHtml(self.html)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def sign_off(self, submission: str | BasicSubmission) -> None:
|
def sign_off(self, run: str | BasicRun) -> None:
|
||||||
"""
|
"""
|
||||||
Allows power user to signify a submission is complete.
|
Allows power user to signify a run is complete.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
submission (str | BasicSubmission): Submission to be completed
|
run (str | BasicRun): Submission to be completed
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
"""
|
"""
|
||||||
logger.info(f"Signing off on {submission} - ({getuser()})")
|
logger.info(f"Signing off on {run} - ({getuser()})")
|
||||||
if isinstance(submission, str):
|
if isinstance(run, str):
|
||||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
run = BasicRun.query(rsl_plate_num=run)
|
||||||
submission.signed_by = getuser()
|
run.signed_by = getuser()
|
||||||
submission.completed_date = datetime.now()
|
run.completed_date = datetime.now()
|
||||||
submission.completed_date.replace(tzinfo=timezone)
|
run.completed_date.replace(tzinfo=timezone)
|
||||||
submission.save()
|
run.save()
|
||||||
self.submission_details(submission=self.rsl_plate_num)
|
self.run_details(run=self.rsl_plate_num)
|
||||||
|
|
||||||
def save_pdf(self):
|
def save_pdf(self):
|
||||||
"""
|
"""
|
||||||
Renders submission to html, then creates and saves .pdf file to user selected file.
|
Renders run to html, then creates and saves .pdf file to user selected file.
|
||||||
"""
|
"""
|
||||||
fname = select_save_file(obj=self, default_name=self.export_plate, extension="pdf")
|
fname = select_save_file(obj=self, default_name=self.export_plate, extension="pdf")
|
||||||
save_pdf(obj=self.webview, filename=fname)
|
save_pdf(obj=self.webview, filename=fname)
|
||||||
@@ -256,10 +256,10 @@ class SubmissionDetails(QDialog):
|
|||||||
|
|
||||||
class SubmissionComment(QDialog):
|
class SubmissionComment(QDialog):
|
||||||
"""
|
"""
|
||||||
a window for adding comment text to a submission
|
a window for adding comment text to a run
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent, submission: BasicSubmission) -> None:
|
def __init__(self, parent, submission: BasicRun) -> None:
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.app = get_application_from_parent(parent)
|
self.app = get_application_from_parent(parent)
|
||||||
@@ -282,7 +282,7 @@ class SubmissionComment(QDialog):
|
|||||||
|
|
||||||
def parse_form(self) -> List[dict]:
|
def parse_form(self) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
Adds comment to submission object.
|
Adds comment to run object.
|
||||||
"""
|
"""
|
||||||
commenter = getuser()
|
commenter = getuser()
|
||||||
comment = self.txt_editor.toPlainText()
|
comment = self.txt_editor.toPlainText()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Contains widgets specific to the submission summary and submission details.
|
Contains widgets specific to the run summary and run details.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
@@ -8,7 +8,7 @@ from PyQt6.QtWidgets import QTableView, QMenu, QTreeView, QStyledItemDelegate, Q
|
|||||||
QHeaderView, QAbstractItemView, QWidget, QTreeWidgetItemIterator
|
QHeaderView, QAbstractItemView, QWidget, QTreeWidgetItemIterator
|
||||||
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, pyqtSlot, QModelIndex
|
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, pyqtSlot, QModelIndex
|
||||||
from PyQt6.QtGui import QAction, QCursor, QStandardItemModel, QStandardItem, QIcon, QColor
|
from PyQt6.QtGui import QAction, QCursor, QStandardItemModel, QStandardItem, QIcon, QColor
|
||||||
from backend.db.models import BasicSubmission, ClientSubmission
|
from backend.db.models import BasicRun, ClientSubmission
|
||||||
from tools import Report, Result, report_result
|
from tools import Report, Result, report_result
|
||||||
from .functions import select_open_file
|
from .functions import select_open_file
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ class pandasModel(QAbstractTableModel):
|
|||||||
|
|
||||||
class SubmissionsSheet(QTableView):
|
class SubmissionsSheet(QTableView):
|
||||||
"""
|
"""
|
||||||
presents submission summary to user in tab1
|
presents run summary to user in tab1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent) -> None:
|
def __init__(self, parent) -> None:
|
||||||
@@ -74,20 +74,20 @@ class SubmissionsSheet(QTableView):
|
|||||||
page_size = self.app.page_size
|
page_size = self.app.page_size
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
page_size = 250
|
page_size = 250
|
||||||
self.setData(page=1, page_size=page_size)
|
self.set_data(page=1, page_size=page_size)
|
||||||
self.resizeColumnsToContents()
|
self.resizeColumnsToContents()
|
||||||
self.resizeRowsToContents()
|
self.resizeRowsToContents()
|
||||||
self.setSortingEnabled(True)
|
self.setSortingEnabled(True)
|
||||||
self.doubleClicked.connect(lambda x: BasicSubmission.query(id=x.sibling(x.row(), 0).data()).show_details(self))
|
self.doubleClicked.connect(lambda x: BasicRun.query(id=x.sibling(x.row(), 0).data()).show_details(self))
|
||||||
# NOTE: Have to run native query here because mine just returns results?
|
# NOTE: Have to run native query here because mine just returns results?
|
||||||
self.total_count = BasicSubmission.__database_session__.query(BasicSubmission).count()
|
self.total_count = BasicRun.__database_session__.query(BasicRun).count()
|
||||||
|
|
||||||
def setData(self, page: int = 1, page_size: int = 250) -> None:
|
def set_data(self, page: int = 1, page_size: int = 250) -> None:
|
||||||
"""
|
"""
|
||||||
sets data in model
|
sets data in model
|
||||||
"""
|
"""
|
||||||
# self.data = ClientSubmission.submissions_to_df(page=page, page_size=page_size)
|
# self.data = ClientSubmission.submissions_to_df(page=page, page_size=page_size)
|
||||||
self.data = BasicSubmission.submissions_to_df(page=page, page_size=page_size)
|
self.data = BasicRun.submissions_to_df(page=page, page_size=page_size)
|
||||||
try:
|
try:
|
||||||
self.data['Id'] = self.data['Id'].apply(str)
|
self.data['Id'] = self.data['Id'].apply(str)
|
||||||
self.data['Id'] = self.data['Id'].str.zfill(4)
|
self.data['Id'] = self.data['Id'].str.zfill(4)
|
||||||
@@ -108,7 +108,7 @@ class SubmissionsSheet(QTableView):
|
|||||||
id = self.selectionModel().currentIndex()
|
id = self.selectionModel().currentIndex()
|
||||||
# NOTE: Convert to data in id column (i.e. column 0)
|
# NOTE: Convert to data in id column (i.e. column 0)
|
||||||
id = id.sibling(id.row(), 0).data()
|
id = id.sibling(id.row(), 0).data()
|
||||||
submission = BasicSubmission.query(id=id)
|
submission = BasicRun.query(id=id)
|
||||||
self.menu = QMenu(self)
|
self.menu = QMenu(self)
|
||||||
self.con_actions = submission.custom_context_events()
|
self.con_actions = submission.custom_context_events()
|
||||||
for k in self.con_actions.keys():
|
for k in self.con_actions.keys():
|
||||||
@@ -167,8 +167,8 @@ class SubmissionsSheet(QTableView):
|
|||||||
for ii in range(6, len(run)):
|
for ii in range(6, len(run)):
|
||||||
new_run[f"column{str(ii - 5)}_vol"] = run[ii]
|
new_run[f"column{str(ii - 5)}_vol"] = run[ii]
|
||||||
# NOTE: Lookup imported submissions
|
# NOTE: Lookup imported submissions
|
||||||
sub = BasicSubmission.query(rsl_plate_num=new_run['rsl_plate_num'])
|
sub = BasicRun.query(rsl_plate_num=new_run['rsl_plate_num'])
|
||||||
# NOTE: If no such submission exists, move onto the next run
|
# NOTE: If no such run exists, move onto the next run
|
||||||
if sub is None:
|
if sub is None:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@@ -192,7 +192,7 @@ class SubmissionsSheet(QTableView):
|
|||||||
|
|
||||||
def link_pcr_function(self):
|
def link_pcr_function(self):
|
||||||
"""
|
"""
|
||||||
Link PCR data from run logs to an imported submission
|
Link PCR data from run logs to an imported run
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (QMainWindow): original app window
|
obj (QMainWindow): original app window
|
||||||
@@ -215,9 +215,9 @@ class SubmissionsSheet(QTableView):
|
|||||||
experiment_name=run[4].strip(),
|
experiment_name=run[4].strip(),
|
||||||
end_time=run[5].strip()
|
end_time=run[5].strip()
|
||||||
)
|
)
|
||||||
# NOTE: lookup imported submission
|
# NOTE: lookup imported run
|
||||||
sub = BasicSubmission.query(rsl_number=new_run['rsl_plate_num'])
|
sub = BasicRun.query(rsl_number=new_run['rsl_plate_num'])
|
||||||
# NOTE: if imported submission doesn't exist move on to next run
|
# NOTE: if imported run doesn't exist move on to next run
|
||||||
if sub is None:
|
if sub is None:
|
||||||
continue
|
continue
|
||||||
sub.set_attribute('pcr_info', new_run)
|
sub.set_attribute('pcr_info', new_run)
|
||||||
@@ -302,7 +302,7 @@ class SubmissionsTree(QTreeView):
|
|||||||
id = int(id.data())
|
id = int(id.data())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return
|
return
|
||||||
BasicSubmission.query(id=id).show_details(self)
|
BasicRun.query(id=id).show_details(self)
|
||||||
|
|
||||||
|
|
||||||
def link_extractions(self):
|
def link_extractions(self):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Contains all submission related frontend functions
|
Contains all run related frontend functions
|
||||||
"""
|
"""
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QPushButton, QVBoxLayout,
|
QWidget, QPushButton, QVBoxLayout,
|
||||||
@@ -10,11 +10,11 @@ from .functions import select_open_file, select_save_file
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tools import Report, Result, check_not_nan, main_form_style, report_result, get_application_from_parent
|
from tools import Report, Result, check_not_nan, main_form_style, report_result, get_application_from_parent
|
||||||
from backend.excel.parsers import SheetParser, InfoParserV2
|
from backend.excel import SheetParser, InfoParser
|
||||||
from backend.validators import PydSubmission, PydReagent
|
from backend.validators import PydSubmission, PydReagent
|
||||||
from backend.db import (
|
from backend.db import (
|
||||||
Organization, SubmissionType, Reagent,
|
Organization, SubmissionType, Reagent,
|
||||||
ReagentRole, KitTypeReagentRoleAssociation, BasicSubmission
|
ReagentRole, KitTypeReagentRoleAssociation, BasicRun
|
||||||
)
|
)
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from .pop_ups import QuestionAsker, AlertPop
|
from .pop_ups import QuestionAsker, AlertPop
|
||||||
@@ -93,7 +93,7 @@ class SubmissionFormContainer(QWidget):
|
|||||||
@report_result
|
@report_result
|
||||||
def import_submission_function(self, fname: Path | None = None) -> Report:
|
def import_submission_function(self, fname: Path | None = None) -> Report:
|
||||||
"""
|
"""
|
||||||
Import a new submission to the app window
|
Import a new run to the app window
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (QMainWindow): original app window
|
obj (QMainWindow): original app window
|
||||||
@@ -122,12 +122,12 @@ class SubmissionFormContainer(QWidget):
|
|||||||
# NOTE: create sheetparser using excel sheet and context from gui
|
# NOTE: create sheetparser using excel sheet and context from gui
|
||||||
try:
|
try:
|
||||||
# self.prsr = SheetParser(filepath=fname)
|
# self.prsr = SheetParser(filepath=fname)
|
||||||
self.parser = InfoParserV2(filepath=fname)
|
self.parser = InfoParser(filepath=fname)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
logger.error(f"Couldn't get permission to access file: {fname}")
|
logger.error(f"Couldn't get permission to access file: {fname}")
|
||||||
return
|
return
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.parser = InfoParserV2(filepath=fname)
|
self.parser = InfoParser(filepath=fname)
|
||||||
self.pyd = self.parser.to_pydantic()
|
self.pyd = self.parser.to_pydantic()
|
||||||
# logger.debug(f"Samples: {pformat(self.pyd.samples)}")
|
# logger.debug(f"Samples: {pformat(self.pyd.samples)}")
|
||||||
checker = SampleChecker(self, "Sample Checker", self.pyd)
|
checker = SampleChecker(self, "Sample Checker", self.pyd)
|
||||||
@@ -150,7 +150,7 @@ class SubmissionFormContainer(QWidget):
|
|||||||
instance (Reagent | None): Blank reagent instance to be edited and then added.
|
instance (Reagent | None): Blank reagent instance to be edited and then added.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
models.Reagent: the constructed reagent object to add to submission
|
models.Reagent: the constructed reagent object to add to run
|
||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
if not instance:
|
if not instance:
|
||||||
@@ -178,7 +178,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
self.missing_info = []
|
self.missing_info = []
|
||||||
self.submission_type = SubmissionType.query(name=self.pyd.submission_type['value'])
|
self.submission_type = SubmissionType.query(name=self.pyd.submission_type['value'])
|
||||||
basic_submission_class = self.submission_type.submission_class
|
basic_submission_class = self.submission_type.submission_class
|
||||||
logger.debug(f"Basic submission class: {basic_submission_class}")
|
logger.debug(f"Basic run class: {basic_submission_class}")
|
||||||
defaults = basic_submission_class.get_default_info("form_recover", "form_ignore", submission_type=self.pyd.submission_type['value'])
|
defaults = basic_submission_class.get_default_info("form_recover", "form_ignore", submission_type=self.pyd.submission_type['value'])
|
||||||
self.recover = defaults['form_recover']
|
self.recover = defaults['form_recover']
|
||||||
self.ignore = defaults['form_ignore']
|
self.ignore = defaults['form_ignore']
|
||||||
@@ -202,7 +202,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
value = dict(value=None, missing=True)
|
value = dict(value=None, missing=True)
|
||||||
logger.debug(f"Pydantic value: {value}")
|
logger.debug(f"Pydantic value: {value}")
|
||||||
add_widget = self.create_widget(key=k, value=value, submission_type=self.submission_type,
|
add_widget = self.create_widget(key=k, value=value, submission_type=self.submission_type,
|
||||||
sub_obj=basic_submission_class, disable=check)
|
run_object=basic_submission_class, disable=check)
|
||||||
if add_widget is not None:
|
if add_widget is not None:
|
||||||
self.layout.addWidget(add_widget)
|
self.layout.addWidget(add_widget)
|
||||||
if k in self.__class__.update_reagent_fields:
|
if k in self.__class__.update_reagent_fields:
|
||||||
@@ -223,7 +223,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
reagent.flip_check(self.disabler.checkbox.isChecked())
|
reagent.flip_check(self.disabler.checkbox.isChecked())
|
||||||
|
|
||||||
def create_widget(self, key: str, value: dict | PydReagent, submission_type: str | SubmissionType | None = None,
|
def create_widget(self, key: str, value: dict | PydReagent, submission_type: str | SubmissionType | None = None,
|
||||||
extraction_kit: str | None = None, sub_obj: BasicSubmission | None = None,
|
extraction_kit: str | None = None, run_object: BasicRun | None = None,
|
||||||
disable: bool = False) -> "self.InfoItem":
|
disable: bool = False) -> "self.InfoItem":
|
||||||
"""
|
"""
|
||||||
Make an InfoItem widget to hold a field
|
Make an InfoItem widget to hold a field
|
||||||
@@ -248,7 +248,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
widget = None
|
widget = None
|
||||||
case _:
|
case _:
|
||||||
widget = self.InfoItem(parent=self, key=key, value=value, submission_type=submission_type,
|
widget = self.InfoItem(parent=self, key=key, value=value, submission_type=submission_type,
|
||||||
sub_obj=sub_obj)
|
run_object=run_object)
|
||||||
if disable:
|
if disable:
|
||||||
widget.input.setEnabled(False)
|
widget.input.setEnabled(False)
|
||||||
widget.input.setToolTip("Widget disabled to protect database integrity.")
|
widget.input.setToolTip("Widget disabled to protect database integrity.")
|
||||||
@@ -373,14 +373,14 @@ class SubmissionFormWidget(QWidget):
|
|||||||
return report
|
return report
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
# NOTE: add reagents to submission object
|
# NOTE: add reagents to run object
|
||||||
if base_submission is None:
|
if base_submission is None:
|
||||||
return
|
return
|
||||||
for reagent in base_submission.reagents:
|
for reagent in base_submission.reagents:
|
||||||
reagent.update_last_used(kit=base_submission.extraction_kit)
|
reagent.update_last_used(kit=base_submission.extraction_kit)
|
||||||
save_output = base_submission.save()
|
save_output = base_submission.save()
|
||||||
# NOTE: update summary sheet
|
# NOTE: update summary sheet
|
||||||
self.app.table_widget.sub_wid.setData()
|
self.app.table_widget.sub_wid.set_data()
|
||||||
# NOTE: reset form
|
# NOTE: reset form
|
||||||
try:
|
try:
|
||||||
check = save_output.results == []
|
check = save_output.results == []
|
||||||
@@ -393,7 +393,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
|
|
||||||
def export_csv_function(self, fname: Path | None = None):
|
def export_csv_function(self, fname: Path | None = None):
|
||||||
"""
|
"""
|
||||||
Save the submission's csv file.
|
Save the run's csv file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
fname (Path | None, optional): Input filename. Defaults to None.
|
fname (Path | None, optional): Input filename. Defaults to None.
|
||||||
@@ -405,7 +405,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
except PermissionError:
|
except PermissionError:
|
||||||
logger.warning(f"Could not get permissions to {fname}. Possibly the request was cancelled.")
|
logger.warning(f"Could not get permissions to {fname}. Possibly the request was cancelled.")
|
||||||
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 run at this point.")
|
||||||
|
|
||||||
def parse_form(self) -> Report:
|
def parse_form(self) -> Report:
|
||||||
"""
|
"""
|
||||||
@@ -446,14 +446,14 @@ class SubmissionFormWidget(QWidget):
|
|||||||
class InfoItem(QWidget):
|
class InfoItem(QWidget):
|
||||||
|
|
||||||
def __init__(self, parent: QWidget, key: str, value: dict, submission_type: str | SubmissionType | None = None,
|
def __init__(self, parent: QWidget, key: str, value: dict, submission_type: str | SubmissionType | None = None,
|
||||||
sub_obj: BasicSubmission | None = None) -> None:
|
run_object: BasicRun | None = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
submission_type = SubmissionType.query(name=submission_type)
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
self.label = self.ParsedQLabel(key=key, value=value)
|
self.label = self.ParsedQLabel(key=key, value=value)
|
||||||
self.input: QWidget = self.set_widget(parent=parent, key=key, value=value, submission_type=submission_type,
|
self.input: QWidget = self.set_widget(parent=parent, key=key, value=value, submission_type=submission_type,
|
||||||
sub_obj=sub_obj)
|
sub_obj=run_object)
|
||||||
self.setObjectName(key)
|
self.setObjectName(key)
|
||||||
try:
|
try:
|
||||||
self.missing: bool = value['missing']
|
self.missing: bool = value['missing']
|
||||||
@@ -492,7 +492,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
|
|
||||||
def set_widget(self, parent: QWidget, key: str, value: dict,
|
def set_widget(self, parent: QWidget, key: str, value: dict,
|
||||||
submission_type: str | SubmissionType | None = None,
|
submission_type: str | SubmissionType | None = None,
|
||||||
sub_obj: BasicSubmission | None = None) -> QWidget:
|
sub_obj: BasicRun | None = None) -> QWidget:
|
||||||
"""
|
"""
|
||||||
Creates form widget
|
Creates form widget
|
||||||
|
|
||||||
@@ -568,7 +568,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
categories.insert(0, categories.pop(categories.index(submission_type)))
|
categories.insert(0, categories.pop(categories.index(submission_type)))
|
||||||
add_widget.addItems(categories)
|
add_widget.addItems(categories)
|
||||||
add_widget.setToolTip("Enter submission category or select from list.")
|
add_widget.setToolTip("Enter run category or select from list.")
|
||||||
case _:
|
case _:
|
||||||
if key in sub_obj.timestamps:
|
if key in sub_obj.timestamps:
|
||||||
add_widget = MyQDateEdit(calendarPopup=True, scrollWidget=parent)
|
add_widget = MyQDateEdit(calendarPopup=True, scrollWidget=parent)
|
||||||
@@ -692,7 +692,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
wanted_reagent = self.parent.parent().add_reagent(instance=wanted_reagent)
|
wanted_reagent = self.parent.parent().add_reagent(instance=wanted_reagent)
|
||||||
return wanted_reagent, report
|
return wanted_reagent, report
|
||||||
else:
|
else:
|
||||||
# NOTE: In this case we will have an empty reagent and the submission will fail kit integrity check
|
# NOTE: In this case we will have an empty reagent and the run will fail kit integrity check
|
||||||
return None, report
|
return None, report
|
||||||
else:
|
else:
|
||||||
# NOTE: Since this now gets passed in directly from the parser -> pyd -> form and the parser gets the name from the db, it should no longer be necessary to query the db with reagent/kit, but with rt name directly.
|
# NOTE: Since this now gets passed in directly from the parser -> pyd -> form and the parser gets the name from the db, it should no longer be necessary to query the db with reagent/kit, but with rt name directly.
|
||||||
@@ -801,7 +801,7 @@ class ClientSubmissionFormWidget(SubmissionFormWidget):
|
|||||||
Report: Report on status of parse.
|
Report: Report on status of parse.
|
||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
logger.info(f"Hello from client submission form parser!")
|
logger.info(f"Hello from client run form parser!")
|
||||||
info = {}
|
info = {}
|
||||||
reagents = []
|
reagents = []
|
||||||
for widget in self.findChildren(QWidget):
|
for widget in self.findChildren(QWidget):
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class Summary(InfoPane):
|
|||||||
orgs = self.org_select.get_checked()
|
orgs = self.org_select.get_checked()
|
||||||
self.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs)
|
self.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs)
|
||||||
self.webview.setHtml(self.report_obj.html)
|
self.webview.setHtml(self.report_obj.html)
|
||||||
if self.report_obj.subs:
|
if self.report_obj.runs:
|
||||||
self.save_pdf_button.setEnabled(True)
|
self.save_pdf_button.setEnabled(True)
|
||||||
self.save_excel_button.setEnabled(True)
|
self.save_excel_button.setEnabled(True)
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user