Attempt to update treeview.

This commit is contained in:
lwark
2025-06-04 07:18:21 -05:00
parent 26292e275c
commit db0b65b07b
11 changed files with 302 additions and 275 deletions

View File

@@ -22,7 +22,7 @@ from . import Base, BaseClass, ClientLab, LogMixin
from io import BytesIO
if TYPE_CHECKING:
from backend.db.models.submissions import Run
from backend.db.models.submissions import Run, ProcedureSampleAssociation
logger = logging.getLogger(f'procedure.{__name__}')
@@ -1175,8 +1175,9 @@ class ProcedureType(BaseClass):
proceduretype=self,
#name=dict(value=self.name, missing=True),
#possible_kits=[kittype.name for kittype in self.kittype],
repeat=False
repeat=False,
# plate_map=plate_map
run=run
)
return PydProcedure(**output)
@@ -1222,7 +1223,7 @@ class ProcedureType(BaseClass):
class Procedure(BaseClass):
id = Column(INTEGER, primary_key=True)
name = Column(String, unique=True)
_name = Column(String, unique=True)
repeat = Column(INTEGER, nullable=False)
technician = Column(JSON) #: name of processing tech(s)
proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id", ondelete="SET NULL",
@@ -1236,13 +1237,23 @@ class Procedure(BaseClass):
kittype = relationship("KitType", back_populates="procedure")
control = relationship("Control", back_populates="procedure", uselist=True) #: A control sample added to procedure
proceduresampleassociation = relationship(
"ProcedureSampleAssociation",
back_populates="procedure",
cascade="all, delete-orphan",
)
sample = association_proxy("proceduresampleassociation",
"sample", creator=lambda sample: ProcedureSampleAssociation(sample=sample)
)
procedurereagentassociation = relationship(
"ProcedureReagentAssociation",
back_populates="procedure",
cascade="all, delete-orphan",
) #: Relation to ProcedureReagentAssociation
reagents = association_proxy("procedurereagentassociation",
reagent = association_proxy("procedurereagentassociation",
"reagent", creator=lambda reg: ProcedureReagentAssociation(
reagent=reg)) #: Association proxy to RunReagentAssociation.reagent
@@ -1263,6 +1274,14 @@ class Procedure(BaseClass):
tips = association_proxy("proceduretipsassociation",
"tips")
@hybrid_property
def name(self):
return f"{self.proceduretype.name}-{self.run.rsl_plate_num}"
@name.setter
def name(self, value):
self._name = value
@validates('repeat')
def validate_repeat(self, key, value):
if value > 1:
@@ -1290,6 +1309,12 @@ class Procedure(BaseClass):
pass
return cls.execute_query(query=query, limit=limit)
def to_dict(self, full_data: bool=False):
output = dict()
output['name'] = self.name
return output
class ProcedureTypeKitTypeAssociation(BaseClass):
"""
@@ -2608,171 +2633,4 @@ class ProcedureTipsAssociation(BaseClass):
from backend.validators import PydTips
return PydTips(name=self.tips.name, lot=self.tips.lot, role=self.role_name)
#
# class ProcedureType(BaseClass):
# id = Column(INTEGER, primary_key=True)
# name = Column(String(64))
# reagent_map = Column(JSON)
#
# procedure = relationship("Procedure",
# back_populates="proceduretype") #: Concrete control of this type.
#
# process = relationship("Process", back_populates="proceduretype",
# secondary=proceduretype_process) #: Relation to equipment process used for this type.
#
# proceduretypekittypeassociation = relationship(
# "ProcedureTypeKitTypeAssociation",
# back_populates="proceduretype",
# cascade="all, delete-orphan",
# ) #: Association of kittypes
#
# kittype = association_proxy("proceduretypekittypeassociation", "kittype",
# creator=lambda kit: ProcedureTypeKitTypeAssociation(
# kittype=kit)) #: Proxy of kittype association
#
# proceduretypeequipmentroleassociation = relationship(
# "ProcedureTypeEquipmentRoleAssociation",
# back_populates="proceduretype",
# cascade="all, delete-orphan"
# ) #: Association of equipmentroles
#
# equipment = association_proxy("proceduretypeequipmentroleassociation", "equipmentrole",
# creator=lambda eq: ProcedureTypeEquipmentRoleAssociation(
# equipment_role=eq)) #: Proxy of equipmentrole associations
#
# kittypereagentroleassociation = relationship(
# "KitTypeReagentRoleAssociation",
# back_populates="proceduretype",
# cascade="all, delete-orphan"
# ) #: triple association of KitTypes, ReagentTypes, SubmissionTypes
#
# proceduretypetiproleassociation = relationship(
# "ProcedureTypeTipRoleAssociation",
# back_populates="proceduretype",
# cascade="all, delete-orphan"
# ) #: Association of tiproles
#
# def construct_field_map(self, field: Literal['equipment', 'tip']) -> Generator[(str, dict), None, None]:
# """
# Make a map of all locations for tips or equipment.
#
# Args:
# field (Literal['equipment', 'tip']): the field to construct a map for
#
# Returns:
# Generator[(str, dict), None, None]: Generator composing key, locations for each item in the map
# """
# for item in self.__getattribute__(f"proceduretype{field}role_associations"):
# fmap = item.uses
# if fmap is None:
# fmap = {}
# yield getattr(item, f"{field}_role").name, fmap
#
# @property
# def default_kit(self) -> KitType | None:
# """
# If only one kits exists for this Submission Type, return it.
#
# Returns:
# KitType | None:
# """
# if len(self.kittype) == 1:
# return self.kittype[0]
# else:
# return None
#
# def get_equipment(self, kittype: str | KitType | None = None) -> Generator['PydEquipmentRole', None, None]:
# """
# Returns PydEquipmentRole of all equipment associated with this SubmissionType
#
# Returns:
# Generator['PydEquipmentRole', None, None]: List of equipment roles
# """
# return (item.to_pydantic(proceduretype=self, kittype=kittype) for item in self.equipment)
#
# def get_processes_for_role(self, equipmentrole: str | EquipmentRole, kittype: str | KitType | None = None) -> list:
# """
# Get process associated with this SubmissionType for an EquipmentRole
#
# Args:
# equipmentrole (str | EquipmentRole): EquipmentRole of interest
# kittype (str | KitType | None, optional): Kit of interest. Defaults to None.
#
# Raises:
# TypeError: Raised if wrong type given for equipmentrole
#
# Returns:
# list: list of associated process
# """
# match equipmentrole:
# case str():
# relevant = [item.get_all_processes(kittype) for item in self.proceduretypeequipmentroleassociation if
# item.equipmentrole.name == equipmentrole]
# case EquipmentRole():
# relevant = [item.get_all_processes(kittype) for item in self.proceduretypeequipmentroleassociation if
# item.equipmentrole == equipmentrole]
# case _:
# raise TypeError(f"Type {type(equipmentrole)} is not allowed")
# return list(set([item for items in relevant for item in items if item is not None]))
#
#
# class Procedure(BaseClass):
# id = Column(INTEGER, primary_key=True)
# name = Column(String, unique=True)
# technician = Column(JSON) #: name of processing tech(s)
# proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id", ondelete="SET NULL",
# name="fk_PRO_proceduretype_id")) #: client lab id from _organizations))
# proceduretype = relationship("ProcedureType", back_populates="procedure")
# run_id = Column(INTEGER, ForeignKey("_run.id", ondelete="SET NULL",
# name="fk_PRO_basicrun_id")) #: client lab id from _organizations))
# run = relationship("Run", back_populates="procedure")
# kittype_id = Column(INTEGER, ForeignKey("_kittype.id", ondelete="SET NULL",
# name="fk_PRO_kittype_id")) #: client lab id from _organizations))
# kittype = relationship("KitType", back_populates="procedure")
#
# control = relationship("Control", back_populates="procedure",
# uselist=True) #: A control sample added to procedure
#
# procedurereagentassociations = relationship(
# "ProcedureReagentAssociation",
# back_populates="procedure",
# cascade="all, delete-orphan",
# ) #: Relation to ProcedureReagentAssociation
#
# reagents = association_proxy("procedurereagentassociations",
# "reagent") #: Association proxy to RunReagentAssociation.reagent
#
# procedureequipmentassociations = relationship(
# "ProcedureEquipmentAssociation",
# back_populates="procedure",
# cascade="all, delete-orphan"
# ) #: Relation to Equipment
#
# equipment = association_proxy("procedureequipmentassociations",
# "equipment") #: Association proxy to RunEquipmentAssociation.equipment
#
# proceduretipsassociations = relationship(
# "ProcedureTipsAssociation",
# back_populates="procedure",
# cascade="all, delete-orphan")
#
# tips = association_proxy("proceduretipsassociations",
# "tips")
#
# @classmethod
# @setup_lookup
# def query(cls, id: int|None = None, name: str | None = None, limit: int = 0, **kwargs) -> Procedure | List[Procedure]:
# query: Query = cls.__database_session__.query(cls)
# match id:
# case int():
# query = query.filter(cls.id == id)
# limit = 1
# case _:
# pass
# match name:
# case str():
# query = query.filter(cls.name == name)
# limit = 1
# case _:
# pass
# return cls.execute_query(query=query, limit=limit)

View File

@@ -15,7 +15,7 @@ from operator import itemgetter
from pprint import pformat
from pandas import DataFrame
from sqlalchemy.ext.hybrid import hybrid_property
from . import Base, BaseClass, Reagent, SubmissionType, KitType, ClientLab, Contact, LogMixin
from . import Base, BaseClass, Reagent, SubmissionType, KitType, ClientLab, Contact, LogMixin, Procedure, kittype_procedure
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func, Table
from sqlalchemy.orm import relationship, validates, Query
from sqlalchemy.orm.attributes import flag_modified
@@ -34,9 +34,8 @@ from jinja2.exceptions import TemplateNotFound
from jinja2 import Template
from PIL import Image
if TYPE_CHECKING:
from backend.db.models.kits import ProcedureType
from backend.db.models.kits import ProcedureType, Procedure
from . import kittype_procedure
logger = logging.getLogger(f"procedure.{__name__}")
@@ -233,7 +232,7 @@ class ClientSubmission(BaseClass, LogMixin):
# dicto, _ = self.kittype.construct_xl_map_for_use(self.proceduretype)
# sample = self.generate_associations(name="clientsubmissionsampleassociation")
samples = None
runs = [item.to_dict() for item in self.run]
runs = [item.to_dict(full_data=True) for item in self.run]
# custom = self.custom
else:
samples = None
@@ -263,6 +262,7 @@ class ClientSubmission(BaseClass, LogMixin):
output["contact_phone"] = contact_phone
# output["custom"] = custom
output["run"] = runs
output['name'] = self.name
return output
def add_sample(self, sample: Sample):
@@ -539,12 +539,14 @@ class Run(BaseClass, LogMixin):
samples = self.generate_associations(name="clientsubmissionsampleassociation")
equipment = self.generate_associations(name="submission_equipment_associations")
tips = self.generate_associations(name="submission_tips_associations")
procedures = [item.to_dict(full_data=True) for item in self.procedure]
custom = self.custom
else:
samples = None
equipment = None
tips = None
custom = None
procedures = None
try:
comments = self.comment
except Exception as e:
@@ -570,6 +572,8 @@ class Run(BaseClass, LogMixin):
output["contact"] = contact
output["contact_phone"] = contact_phone
output["custom"] = custom
output['procedures'] = procedures
output['name'] = self.name
try:
output["completed_date"] = self.completed_date.strftime("%Y-%m-%d")
except AttributeError:
@@ -1131,7 +1135,10 @@ class Run(BaseClass, LogMixin):
logger.debug(f"Got ProcedureType: {procedure_type}")
dlg = ProcedureCreation(parent=obj, run=self, proceduretype=procedure_type)
if dlg.exec():
pass
sql, _ = dlg.return_sql()
logger.debug(f"Output run samples:\n{pformat(sql.run.sample)}")
sql.save()
def delete(self, obj=None):
@@ -1314,8 +1321,9 @@ class Run(BaseClass, LogMixin):
background_color="#6ffe1d"))
padded_list = []
for iii in range(1, proceduretype.total_wells+1):
row, column = proceduretype.ranked_plate[iii]
sample = next((item for item in ranked_samples if item['submission_rank']==iii),
dict(well_id=f"blank_{iii}", sample_id="", row=0, column=0, submission_rank=iii, background_color="#ffffff")
dict(well_id=f"blank_{iii}", sample_id="", row=row, column=column, submission_rank=iii, background_color="#ffffff")
)
padded_list.append(sample)
# logger.debug(f"Final padded list:\n{pformat(list(sorted(padded_list, key=itemgetter('submission_rank'))))}")
@@ -1361,6 +1369,14 @@ class Sample(BaseClass, LogMixin):
run = association_proxy("samplerunassociation", "run") #: proxy of associated procedure
sampleprocedureassociation = relationship(
"ProcedureSampleAssociation",
back_populates="sample",
cascade="all, delete-orphan",
)
procedure = association_proxy("sampleprocedureassociation", "procedure")
@hybrid_property
def name(self):
return self.sample_id
@@ -1806,10 +1822,10 @@ class RunSampleAssociation(BaseClass):
"""
# id = Column(INTEGER, unique=True, nullable=False) #: id to be used for inheriting purposes
sample_id = Column(INTEGER, ForeignKey("_sample.id"), nullable=False) #: id of associated sample
sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated sample
run_id = Column(INTEGER, ForeignKey("_run.id"), primary_key=True) #: id of associated procedure
row = Column(INTEGER, primary_key=True) #: row on the 96 well plate
column = Column(INTEGER, primary_key=True) #: column on the 96 well plate
row = Column(INTEGER) #: row on the 96 well plate
column = Column(INTEGER) #: column on the 96 well plate
# misc_info = Column(JSON)
# NOTE: reference to the Submission object
@@ -2009,3 +2025,16 @@ class RunSampleAssociation(BaseClass):
def delete(self):
raise AttributeError(f"Delete not implemented for {self.__class__}")
class ProcedureSampleAssociation(BaseClass):
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated equipment
row = Column(INTEGER)
column = Column(INTEGER)
procedure = relationship(Procedure,
back_populates="proceduresampleassociation") #: associated procedure
sample = relationship(Sample, back_populates="sampleprocedureassociation") #: associated equipment