Created WastewaterArticAssociation, added tip handling.

This commit is contained in:
lwark
2024-06-14 10:38:46 -05:00
parent 4a7d0b0bd4
commit 62b826c2d1
7 changed files with 163 additions and 59 deletions

View File

@@ -55,11 +55,11 @@ def upgrade() -> None:
op.create_table('_submissiontipsassociation',
sa.Column('tip_id', sa.INTEGER(), nullable=False),
sa.Column('submission_id', sa.INTEGER(), nullable=False),
sa.Column('role_name', sa.String(), nullable=True),
sa.ForeignKeyConstraint(['role_name'], ['_tiprole.name'], ),
sa.ForeignKeyConstraint(['submission_id'], ['_submissiontype.id'], ),
sa.Column('role_name', sa.String(), nullable=False),
# sa.ForeignKeyConstraint(['role_name'], ['_tiprole.name'], ),
sa.ForeignKeyConstraint(['submission_id'], ['_basicsubmission.id'], ),
sa.ForeignKeyConstraint(['tip_id'], ['_tips.id'], ),
sa.PrimaryKeyConstraint('tip_id', 'submission_id')
sa.PrimaryKeyConstraint('tip_id', 'submission_id', 'role_name')
)
op.create_table('_tiproles_tips',
sa.Column('tiprole_id', sa.INTEGER(), nullable=True),

View File

@@ -1601,7 +1601,7 @@ class SubmissionTipsAssociation(BaseClass):
back_populates="submission_tips_associations") #: associated submission
tips = relationship(Tips,
back_populates="tips_submission_associations") #: associated equipment
role_name = Column(String(32)) #, ForeignKey("_tiprole.name"))
role_name = Column(String(32), primary_key=True) #, ForeignKey("_tiprole.name"))
# role = relationship(TipRole)

View File

@@ -2095,7 +2095,7 @@ class BasicSample(BaseClass):
used_class = cls.find_polymorphic_subclass(attrs=sanitized_kwargs, polymorphic_identity=sample_type)
instance = used_class(**sanitized_kwargs)
instance.sample_type = sample_type
logger.debug(f"Creating instance: {instance}")
# logger.debug(f"Creating instance: {instance}")
return instance
@classmethod
@@ -2352,7 +2352,7 @@ class SubmissionSampleAssociation(BaseClass):
self.id = id
else:
self.id = self.__class__.autoincrement_id()
logger.debug(f"Looking at kwargs: {pformat(kwargs)}")
# logger.debug(f"Looking at kwargs: {pformat(kwargs)}")
for k,v in kwargs.items():
try:
self.__setattr__(k, v)
@@ -2538,7 +2538,7 @@ class SubmissionSampleAssociation(BaseClass):
Returns:
SubmissionSampleAssociation: Queried or new association.
"""
logger.debug(f"Attempting create or query with {kwargs}")
# logger.debug(f"Attempting create or query with {kwargs}")
match submission:
case BasicSubmission():
pass

View File

@@ -613,7 +613,7 @@ class EquipmentParser(object):
previous_asset = asset
asset = self.get_asset_number(input=asset)
logger.debug(f"asset: {asset}")
eq = Equipment.query(name=asset)
eq = Equipment.query(asset_number=asset)
process = ws.cell(row=v['process']['row'], column=v['process']['column']).value
try:
output.append(

View File

@@ -15,7 +15,7 @@ logger = logging.getLogger(f"submissions.{__name__}")
class SheetWriter(object):
"""
object to pull and contain data from excel file
object to manage data placement into excel file
"""
def __init__(self, submission: PydSubmission, missing_only: bool = False):
@@ -60,35 +60,52 @@ class SheetWriter(object):
self.write_tips()
def write_info(self):
"""
Calls info writer
"""
disallowed = ['filepath', 'reagents', 'samples', 'equipment', 'controls']
info_dict = {k: v for k, v in self.sub.items() if k not in disallowed}
writer = InfoWriter(xl=self.xl, submission_type=self.submission_type, info_dict=info_dict)
self.xl = writer.write_info()
def write_reagents(self):
"""
Calls reagent writer
"""
reagent_list = self.sub['reagents']
writer = ReagentWriter(xl=self.xl, submission_type=self.submission_type,
extraction_kit=self.sub['extraction_kit'], reagent_list=reagent_list)
self.xl = writer.write_reagents()
def write_samples(self):
"""
Calls sample writer
"""
sample_list = self.sub['samples']
writer = SampleWriter(xl=self.xl, submission_type=self.submission_type, sample_list=sample_list)
self.xl = writer.write_samples()
def write_equipment(self):
"""
Calls equipment writer
"""
equipment_list = self.sub['equipment']
writer = EquipmentWriter(xl=self.xl, submission_type=self.submission_type, equipment_list=equipment_list)
self.xl = writer.write_equipment()
def write_tips(self):
"""
Calls tip writer
"""
tips_list = self.sub['tips']
writer = TipWriter(xl=self.xl, submission_type=self.submission_type, tips_list=tips_list)
self.xl = writer.write_tips()
class InfoWriter(object):
"""
object to write general submission info into excel file
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, info_dict: dict, sub_object:BasicSubmission|None=None):
logger.debug(f"Info_dict coming into InfoWriter: {pformat(info_dict)}")
if isinstance(submission_type, str):
@@ -103,6 +120,16 @@ class InfoWriter(object):
# logger.debug(pformat(self.info))
def reconcile_map(self, info_dict: dict, info_map: dict) -> dict:
"""
Merge info with its locations
Args:
info_dict (dict): dictionary of info items
info_map (dict): dictionary of info locations
Returns:
dict: merged dictionary
"""
output = {}
for k, v in info_dict.items():
if v is None:
@@ -119,7 +146,13 @@ class InfoWriter(object):
# logger.debug(f"Reconciled info: {pformat(output)}")
return output
def write_info(self):
def write_info(self) -> Workbook:
"""
Performs write operations
Returns:
Workbook: workbook with info written.
"""
for k, v in self.info.items():
# NOTE: merge all comments to fit in single cell.
if k == "comment" and isinstance(v['value'], list):
@@ -138,7 +171,9 @@ class InfoWriter(object):
class ReagentWriter(object):
"""
object to write reagent data into excel file
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, extraction_kit: KitType | str,
reagent_list: list):
self.xl = xl
@@ -149,7 +184,17 @@ class ReagentWriter(object):
reagent_map = kit_type.construct_xl_map_for_use(submission_type)
self.reagents = self.reconcile_map(reagent_list=reagent_list, reagent_map=reagent_map)
def reconcile_map(self, reagent_list, reagent_map) -> List[dict]:
def reconcile_map(self, reagent_list:List[dict], reagent_map:dict) -> List[dict]:
"""
Merge reagents with their locations
Args:
reagent_list (List[dict]): List of reagent dictionaries
reagent_map (dict): Reagent locations
Returns:
List[dict]: merged dictionary
"""
output = []
for reagent in reagent_list:
try:
@@ -168,7 +213,13 @@ class ReagentWriter(object):
output.append(placeholder)
return output
def write_reagents(self):
def write_reagents(self) -> Workbook:
"""
Performs write operations
Returns:
Workbook: Workbook with reagents written
"""
for reagent in self.reagents:
sheet = self.xl[reagent['sheet']]
for k, v in reagent.items():
@@ -181,7 +232,9 @@ class ReagentWriter(object):
class SampleWriter(object):
"""
object to write sample data into excel file
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, sample_list: list):
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
@@ -190,7 +243,16 @@ class SampleWriter(object):
self.sample_map = submission_type.construct_sample_map()['lookup_table']
self.samples = self.reconcile_map(sample_list)
def reconcile_map(self, sample_list: list):
def reconcile_map(self, sample_list: list) -> List[dict]:
"""
Merge sample info with locations
Args:
sample_list (list): List of sample information
Returns:
List[dict]: List of merged dictionaries
"""
output = []
multiples = ['row', 'column', 'assoc_id', 'submission_rank']
for sample in sample_list:
@@ -204,10 +266,16 @@ class SampleWriter(object):
output.append(new)
return sorted(output, key=lambda k: k['submission_rank'])
def write_samples(self):
def write_samples(self) -> Workbook:
"""
Performs writing operations.
Returns:
Workbook: Workbook with samples written
"""
sheet = self.xl[self.sample_map['sheet']]
columns = self.sample_map['sample_columns']
for ii, sample in enumerate(self.samples):
for sample in self.samples:
row = self.sample_map['start_row'] + (sample['submission_rank'] - 1)
for k, v in sample.items():
try:
@@ -219,7 +287,9 @@ class SampleWriter(object):
class EquipmentWriter(object):
"""
object to write equipment data into excel file
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, equipment_list: list):
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
@@ -228,7 +298,17 @@ class EquipmentWriter(object):
equipment_map = self.submission_type.construct_equipment_map()
self.equipment = self.reconcile_map(equipment_list=equipment_list, equipment_map=equipment_map)
def reconcile_map(self, equipment_list: list, equipment_map: list):
def reconcile_map(self, equipment_list: list, equipment_map: dict) -> List[dict]:
"""
Merges equipment with location data
Args:
equipment_list (list): List of equipment
equipment_map (dict): Dictionary of equipment locations
Returns:
List[dict]: List of merged dictionaries
"""
output = []
if equipment_list is None:
return output
@@ -248,6 +328,8 @@ class EquipmentWriter(object):
# logger.error(f"Keyerror: {e}")
continue
placeholder[k] = dicto
if "asset_number" not in mp_info.keys():
placeholder['name']['value'] = f"{equipment['name']} - {equipment['asset_number']}"
try:
placeholder['sheet'] = mp_info['sheet']
except KeyError:
@@ -256,7 +338,13 @@ class EquipmentWriter(object):
output.append(placeholder)
return output
def write_equipment(self):
def write_equipment(self) -> Workbook:
"""
Performs write operations
Returns:
Workbook: Workbook with equipment written
"""
for equipment in self.equipment:
try:
sheet = self.xl[equipment['sheet']]
@@ -280,7 +368,9 @@ class EquipmentWriter(object):
class TipWriter(object):
"""
object to write tips data into excel file
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, tips_list: list):
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
@@ -289,7 +379,17 @@ class TipWriter(object):
tips_map = self.submission_type.construct_tips_map()
self.tips = self.reconcile_map(tips_list=tips_list, tips_map=tips_map)
def reconcile_map(self, tips_list: list, tips_map: list):
def reconcile_map(self, tips_list: List[dict], tips_map: dict) -> List[dict]:
"""
Merges tips with location data
Args:
tips_list (List[dict]): List of tips
tips_map (dict): Tips locations
Returns:
List[dict]: List of merged dictionaries
"""
output = []
if tips_list is None:
return output
@@ -317,7 +417,13 @@ class TipWriter(object):
output.append(placeholder)
return output
def write_tips(self):
def write_tips(self) -> Workbook:
"""
Performs write operations
Returns:
Workbook: Workbook with tips written
"""
for tips in self.tips:
try:
sheet = self.xl[tips['sheet']]

View File

@@ -257,7 +257,7 @@ class PydSample(BaseModel, extra='allow'):
sample=instance,
row=row, column=column, id=aid,
submission_rank=submission_rank, **self.model_extra)
logger.debug(f"Using submission_sample_association: {association}")
# logger.debug(f"Using submission_sample_association: {association}")
try:
# instance.sample_submission_associations.append(association)
out_associations.append(association)
@@ -734,6 +734,7 @@ class PydSubmission(BaseModel, extra='allow'):
for tips in self.tips:
if tips is None:
continue
logger.debug(f"Converting tips: {tips} to sql.")
association = tips.to_sql(submission=instance)
if association is not None:
# association.save()

View File

@@ -2,8 +2,7 @@ import sys
from pprint import pformat
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (QDialog, QComboBox, QCheckBox,
QLabel, QWidget, QHBoxLayout,
QVBoxLayout, QDialogButtonBox, QGridLayout)
QLabel, QWidget, QVBoxLayout, QDialogButtonBox, QGridLayout)
from backend.db.models import Equipment, BasicSubmission, Process
from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips
import logging
@@ -11,9 +10,10 @@ from typing import List
logger = logging.getLogger(f"submissions.{__name__}")
class EquipmentUsage(QDialog):
def __init__(self, parent, submission:BasicSubmission) -> QDialog:
def __init__(self, parent, submission: BasicSubmission) -> QDialog:
super().__init__(parent)
self.submission = submission
self.setWindowTitle("Equipment Checklist")
@@ -53,7 +53,7 @@ class EquipmentUsage(QDialog):
output = []
for widget in self.findChildren(QWidget):
match widget:
case RoleComboBox() :
case RoleComboBox():
if widget.check.isChecked():
output.append(widget.parse_form())
case _:
@@ -65,15 +65,15 @@ class EquipmentUsage(QDialog):
def __init__(self, parent) -> None:
super().__init__(parent)
self.layout = QHBoxLayout()
self.layout = QGridLayout()
self.check = QCheckBox()
self.layout.addWidget(self.check)
self.layout.addWidget(self.check, 0, 0)
self.check.stateChanged.connect(self.check_all)
for item in ["Role", "Equipment", "Process", "Tips"]:
for iii, item in enumerate(["Role", "Equipment", "Process", "Tips"], start=1):
l = QLabel(item)
l.setMaximumWidth(200)
l.setMinimumWidth(200)
self.layout.addWidget(l)
self.layout.addWidget(l, 0, iii, alignment=Qt.AlignmentFlag.AlignRight)
self.setLayout(self.layout)
def check_all(self):
@@ -83,9 +83,10 @@ class EquipmentUsage(QDialog):
for object in self.parent().findChildren(QCheckBox):
object.setChecked(self.check.isChecked())
class RoleComboBox(QWidget):
def __init__(self, parent, role:PydEquipmentRole, used:list) -> None:
def __init__(self, parent, role: PydEquipmentRole, used: list) -> None:
super().__init__(parent)
# self.layout = QHBoxLayout()
self.layout = QGridLayout()
@@ -105,18 +106,14 @@ class RoleComboBox(QWidget):
self.process.setMinimumWidth(200)
self.process.setEditable(False)
self.process.currentTextChanged.connect(self.update_tips)
# self.tips = QComboBox()
# self.tips.setMaximumWidth(200)
# self.tips.setMinimumWidth(200)
# self.tips.setEditable(True)
self.layout.addWidget(self.check,0,0)
self.layout.addWidget(self.check, 0, 0)
label = QLabel(f"{role.name}:")
label.setMinimumWidth(200)
label.setMaximumWidth(200)
label.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.layout.addWidget(label,0,1)
self.layout.addWidget(self.box,0,2)
self.layout.addWidget(self.process,0,3)
self.layout.addWidget(label, 0, 1)
self.layout.addWidget(self.box, 0, 2)
self.layout.addWidget(self.process, 0, 3)
self.setLayout(self.layout)
def update_processes(self):
@@ -125,7 +122,7 @@ class RoleComboBox(QWidget):
"""
equip = self.box.currentText()
# logger.debug(f"Updating equipment: {equip}")
equip2 = [item for item in self.role.equipment if item.name==equip][0]
equip2 = [item for item in self.role.equipment if item.name == equip][0]
# logger.debug(f"Using: {equip2}")
self.process.clear()
self.process.addItems([item for item in equip2.processes if item in self.role.processes])
@@ -143,16 +140,16 @@ class RoleComboBox(QWidget):
widget.addItems(tip_choices)
# logger.debug(f"Tiprole: {tip_role.__dict__}")
widget.setObjectName(f"tips_{tip_role.name}")
widget.setMinimumWidth(100)
widget.setMaximumWidth(100)
widget.setMinimumWidth(200)
widget.setMaximumWidth(200)
self.layout.addWidget(widget, iii, 4)
else:
widget = QLabel("")
widget.setMinimumWidth(100)
widget.setMaximumWidth(100)
self.layout.addWidget(widget,0,4)
widget.setMinimumWidth(200)
widget.setMaximumWidth(200)
self.layout.addWidget(widget, 0, 4)
def parse_form(self) -> PydEquipment|None:
def parse_form(self) -> PydEquipment | None:
"""
Creates PydEquipment for values in form
@@ -160,7 +157,8 @@ class RoleComboBox(QWidget):
PydEquipment|None: PydEquipment matching form
"""
eq = Equipment.query(name=self.box.currentText())
tips = [PydTips(name=item.currentText(), role=item.objectName().lstrip("tips").lstrip("_")) for item in self.findChildren(QComboBox) if item.objectName().startswith("tips")]
tips = [PydTips(name=item.currentText(), role=item.objectName().lstrip("tips").lstrip("_")) for item in
self.findChildren(QComboBox) if item.objectName().startswith("tips")]
logger.debug(tips)
try:
return PydEquipment(
@@ -173,4 +171,3 @@ class RoleComboBox(QWidget):
)
except Exception as e:
logger.error(f"Could create PydEquipment due to: {e}")