Compare commits

...

2 Commits

Author SHA1 Message Date
lwark
dba4ca0130 Fixed bug in adding new reagent during procedure creation. 2025-09-26 15:09:52 -05:00
lwark
7f40e091fa Qubit sample results now written to export. 2025-09-23 15:06:18 -05:00
13 changed files with 128 additions and 24 deletions

View File

@@ -1,3 +1,5 @@
- [ ] Do results writing.
- [ ] Allow use of multiple tips per process.
- [x] Add in database objects for rsl_run (submission -> run), procedure (run -> procedure), many more things will likely be associated with procedure. - [x] Add in database objects for rsl_run (submission -> run), procedure (run -> procedure), many more things will likely be associated with procedure.
- [x] Add in database object for client submission. - [x] Add in database object for client submission.
- [ ] Add arbitrary pipette addition to equipment UI. - [ ] Add arbitrary pipette addition to equipment UI.

View File

@@ -2,6 +2,8 @@
All kittype and reagent related models All kittype and reagent related models
""" """
from __future__ import annotations from __future__ import annotations
import sys
import zipfile, logging, re, numpy as np import zipfile, logging, re, numpy as np
from operator import itemgetter from operator import itemgetter
from pathlib import Path from pathlib import Path
@@ -593,7 +595,7 @@ class SubmissionType(BaseClass):
query: Query = cls.__database_session__.query(cls) query: Query = cls.__database_session__.query(cls)
match name: match name:
case str(): case str():
logger.debug(f"querying with {name}") # logger.debug(f"querying with {name}")
query = query.filter(cls.name == name) query = query.filter(cls.name == name)
limit = 1 limit = 1
case _: case _:
@@ -927,7 +929,7 @@ class Procedure(BaseClass):
logger.info(f"Add Results! {resultstype_name}") logger.info(f"Add Results! {resultstype_name}")
from backend.managers import results from backend.managers import results
results_manager = getattr(results, f"{resultstype_name}Manager") results_manager = getattr(results, f"{resultstype_name}Manager")
rs = results_manager(procedure=self, parent=obj, fname=Path("C:\\Users\lwark\Documents\Submission_Forms\QubitData_18-09-2025_13-43-53.csv")) rs = results_manager(procedure=self, parent=obj)#, fname=Path("C:\\Users\lwark\Documents\Submission_Forms\QubitData_18-09-2025_13-43-53.csv"))
procedure = rs.procedure_to_pydantic() procedure = rs.procedure_to_pydantic()
samples = rs.samples_to_pydantic() samples = rs.samples_to_pydantic()
if procedure: if procedure:
@@ -982,7 +984,6 @@ class Procedure(BaseClass):
output['sample'] = active_samples + inactive_samples output['sample'] = active_samples + inactive_samples
output['reagent'] = [reagent.details_dict() for reagent in output['procedurereagentlotassociation']] output['reagent'] = [reagent.details_dict() for reagent in output['procedurereagentlotassociation']]
output['equipment'] = [equipment.details_dict() for equipment in output['procedureequipmentassociation']] output['equipment'] = [equipment.details_dict() for equipment in output['procedureequipmentassociation']]
# logger.debug(f"equipment: {pformat([item for item in output['equipment']])}")
output['repeat'] = self.repeat output['repeat'] = self.repeat
output['run'] = self.run.name output['run'] = self.run.name
output['excluded'] += self.get_default_info("details_ignore") output['excluded'] += self.get_default_info("details_ignore")
@@ -995,7 +996,6 @@ class Procedure(BaseClass):
def to_pydantic(self, **kwargs): def to_pydantic(self, **kwargs):
from backend.validators.pydant import PydReagent from backend.validators.pydant import PydReagent
output = super().to_pydantic() output = super().to_pydantic()
print(f"Super to_pydantic: {output.equipment}")
output.sample = [item.to_pydantic() for item in output.proceduresampleassociation] output.sample = [item.to_pydantic() for item in output.proceduresampleassociation]
reagents = [] reagents = []
for reagent in output.reagent: for reagent in output.reagent:

View File

@@ -646,7 +646,6 @@ class Run(BaseClass, LogMixin):
'permission', "clientsubmission"] 'permission', "clientsubmission"]
output['sample_count'] = self.sample_count output['sample_count'] = self.sample_count
output['clientsubmission'] = self.clientsubmission.name output['clientsubmission'] = self.clientsubmission.name
# output['clientlab'] = self.clientsubmission.clientlab
output['started_date'] = self.started_date output['started_date'] = self.started_date
output['completed_date'] = self.completed_date output['completed_date'] = self.completed_date
return output return output
@@ -1852,6 +1851,9 @@ class RunSampleAssociation(BaseClass):
class ProcedureSampleAssociation(BaseClass): class ProcedureSampleAssociation(BaseClass):
pyd_model_name = "PydSample"
id = Column(INTEGER, unique=True, nullable=False) id = Column(INTEGER, unique=True, nullable=False)
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure 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 sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated equipment
@@ -1924,12 +1926,13 @@ class ProcedureSampleAssociation(BaseClass):
# NOTE: Figure out how to merge the misc_info if doing .update instead. # NOTE: Figure out how to merge the misc_info if doing .update instead.
relevant = {k: v for k, v in output.items() if k not in ['sample']} relevant = {k: v for k, v in output.items() if k not in ['sample']}
output = output['sample'].details_dict() output = output['sample'].details_dict()
# logger.debug(output)
misc = output['misc_info'] misc = output['misc_info']
output.update(relevant) output.update(relevant)
output['misc_info'] = misc output['misc_info'] = misc
output['row'] = self.row output['row'] = self.row
output['column'] = self.column output['column'] = self.column
output['results'] = [result.details_dict() for result in output['results']] output['results'] = [item.details_dict() for item in self.results]
return output return output
def to_pydantic(self, **kwargs): def to_pydantic(self, **kwargs):

View File

@@ -62,7 +62,7 @@ class DefaultParser(object):
self.sheet = sheet self.sheet = sheet
if not start_row: if not start_row:
start_row = self.__class__.start_row start_row = self.__class__.start_row
if self.filepath.suffix == ".xslx": if self.filepath.suffix == ".xlsx":
self.workbook = load_workbook(self.filepath, data_only=True) self.workbook = load_workbook(self.filepath, data_only=True)
self.worksheet = self.workbook[self.sheet] self.worksheet = self.workbook[self.sheet]
elif self.filepath.suffix == ".csv": elif self.filepath.suffix == ".csv":

View File

@@ -40,7 +40,6 @@ class DefaultWriter(object):
case x if issubclass(value.__class__, BaseClass): case x if issubclass(value.__class__, BaseClass):
value = value.name value = value.name
case x if issubclass(value.__class__, PydBaseClass): case x if issubclass(value.__class__, PydBaseClass):
logger.warning(f"PydBaseClass: {value}")
value = value.name value = value.name
case bytes() | list(): case bytes() | list():
value = None value = None
@@ -241,6 +240,7 @@ class DefaultTABLEWriter(DefaultWriter):
from .procedure_writers import ProcedureInfoWriter, ProcedureSampleWriter, ProcedureReagentWriter, ProcedureEquipmentWriter from .procedure_writers import ProcedureInfoWriter, ProcedureSampleWriter, ProcedureReagentWriter, ProcedureEquipmentWriter
from .results_writers import ( from .results_writers import (
PCRInfoWriter, PCRSampleWriter PCRInfoWriter, PCRSampleWriter,
QubitInfoWriter, QubitSampleWriter
) )
from .clientsubmission_writer import ClientSubmissionInfoWriter, ClientSubmissionSampleWriter from .clientsubmission_writer import ClientSubmissionInfoWriter, ClientSubmissionSampleWriter

View File

@@ -1 +1,32 @@
"""
"""
from openpyxl import Workbook
from backend.excel.writers import DefaultKEYVALUEWriter, DefaultTABLEWriter
from backend.db.models import ProcedureType
from tools import flatten_list
class DefaultResultsInfoWriter(DefaultKEYVALUEWriter):
pass
class DefaultResultsSampleWriter(DefaultTABLEWriter):
def __init__(self, pydant_obj, proceduretype: ProcedureType | None = None, *args, **kwargs):
super().__init__(pydant_obj=pydant_obj, proceduretype=proceduretype, *args, **kwargs)
self.pydant_obj = flatten_list([sample.results for sample in pydant_obj.sample])
def write_to_workbook(self, workbook: Workbook, sheet: str | None = None,
start_row: int | None = None, *args, **kwargs) -> Workbook:
try:
self.worksheet = workbook[f"{self.proceduretype.name[:15]} Results"]
except KeyError:
self.worksheet = workbook.create_sheet(f"{self.proceduretype.name[:15]} Results")
# worksheet = workbook[f"{self.proceduretype.name[:15]} Results"]
return workbook
from .qubit_results_writer import QubitInfoWriter, QubitSampleWriter
from .pcr_results_writer import PCRInfoWriter, PCRSampleWriter from .pcr_results_writer import PCRInfoWriter, PCRSampleWriter

View File

@@ -7,14 +7,15 @@ from pprint import pformat
from typing import Generator, TYPE_CHECKING from typing import Generator, TYPE_CHECKING
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl.styles import Alignment from openpyxl.styles import Alignment
from backend.excel.writers import DefaultKEYVALUEWriter, DefaultTABLEWriter # from backend.excel.writers import DefaultKEYVALUEWriter, DefaultTABLEWriter
from . import DefaultResultsInfoWriter, DefaultResultsSampleWriter
from tools import flatten_list from tools import flatten_list
if TYPE_CHECKING: if TYPE_CHECKING:
from backend.db.models import ProcedureType from backend.db.models import ProcedureType
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
class PCRInfoWriter(DefaultKEYVALUEWriter): class PCRInfoWriter(DefaultResultsInfoWriter):
start_row = 1 start_row = 1
@@ -28,7 +29,7 @@ class PCRInfoWriter(DefaultKEYVALUEWriter):
return workbook return workbook
class PCRSampleWriter(DefaultTABLEWriter): class PCRSampleWriter(DefaultResultsSampleWriter):
def write_to_workbook(self, workbook: Workbook) -> Workbook: def write_to_workbook(self, workbook: Workbook) -> Workbook:
worksheet = workbook[f"{self.proceduretype.name} Results"] worksheet = workbook[f"{self.proceduretype.name} Results"]

View File

@@ -0,0 +1,51 @@
"""
Writers for PCR results from Qubit device
"""
from __future__ import annotations
import logging
from pprint import pformat
from openpyxl import Workbook
from openpyxl.styles import Alignment
from . import DefaultResultsInfoWriter, DefaultResultsSampleWriter
logger = logging.getLogger(f"submissions.{__name__}")
class QubitInfoWriter(DefaultResultsInfoWriter):
def write_to_workbook(self, workbook: Workbook, sheet: str | None = None,
start_row: int = 1, *args, **kwargs) -> Workbook:
return workbook
class QubitSampleWriter(DefaultResultsSampleWriter):
def write_to_workbook(self, workbook: Workbook, *args, **kwargs) -> Workbook:
workbook = super().write_to_workbook(workbook=workbook, *args, **kwargs)
header_row = self.proceduretype.allowed_result_methods['Qubit']['sample']['start_row']
for iii, header in enumerate(self.column_headers, start=1):
# logger.debug(f"Row: {header_row}, column: {iii}")
self.worksheet.cell(row=header_row, column=iii, value=header.replace("_", " ").title())
# logger.debug(f"Column headers: {self.column_headers}")
for iii, result in enumerate(self.pydant_obj, start = 1):
row = header_row + iii
for k, v in result.result.items():
try:
column = next((col[0].column for col in self.worksheet.iter_cols() if col[0].value == k.replace("_", " ").title()))
except StopIteration:
print(f"fail for {k.replace('_', ' ').title()}")
continue
# logger.debug(f"Writing to row: {row}, column {column}")
cell = self.worksheet.cell(row=row, column=column)
cell.value = v
cell.alignment = Alignment(horizontal='left')
self.worksheet = self.postwrite(self.worksheet)
return workbook
@property
def column_headers(self):
output = []
for result in self.pydant_obj:
for k, value in result.result.items():
output.append(k)
return sorted(list(set(output)))

View File

@@ -2,6 +2,7 @@
Module for manager defaults. Module for manager defaults.
""" """
import logging import logging
from pprint import pformat
from pathlib import Path from pathlib import Path
from frontend.widgets.functions import select_open_file from frontend.widgets.functions import select_open_file
from tools import get_application_from_parent from tools import get_application_from_parent
@@ -14,6 +15,7 @@ class DefaultManager(object):
def __init__(self, parent, input_object: Path | str | None = None): def __init__(self, parent, input_object: Path | str | None = None):
self.parent = parent self.parent = parent
match input_object: match input_object:
case str(): case str():
self.input_object = Path(input_object) self.input_object = Path(input_object)

View File

@@ -22,6 +22,7 @@ class DefaultProcedureManager(DefaultManager):
if isinstance(proceduretype, str): if isinstance(proceduretype, str):
proceduretype = ProcedureType.query(name=proceduretype) proceduretype = ProcedureType.query(name=proceduretype)
self.proceduretype = proceduretype self.proceduretype = proceduretype
self.procedure = input_object
super().__init__(parent=parent, input_object=input_object) super().__init__(parent=parent, input_object=input_object)
@@ -84,4 +85,8 @@ class DefaultProcedureManager(DefaultManager):
Writer = getattr(results_writers, f"{result.result_type}InfoWriter") Writer = getattr(results_writers, f"{result.result_type}InfoWriter")
res_info_writer = Writer(pydant_obj=result, proceduretype=self.proceduretype) res_info_writer = Writer(pydant_obj=result, proceduretype=self.proceduretype)
workbook = res_info_writer.write_to_workbook(workbook=workbook) workbook = res_info_writer.write_to_workbook(workbook=workbook)
for result in self.pyd.sample_results:
Writer = getattr(results_writers, f"{result.result_type}SampleWriter")
res_sample_writer = Writer(pydant_obj=self.procedure, proceduretype=self.proceduretype)
workbook = res_sample_writer.write_to_workbook(workbook=workbook)
return workbook return workbook

View File

@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
from openpyxl.reader.excel import load_workbook from openpyxl.reader.excel import load_workbook
from backend.db.models import Procedure from backend.db.models import Procedure
from backend.excel.parsers.results_parsers.qubit_results_parser import QubitSampleParser, QubitInfoParser from backend.excel.parsers.results_parsers.qubit_results_parser import QubitSampleParser, QubitInfoParser
# from backend.excel.writers.results_writers.pcr_results_writer import QubitInfoWriter, QubitSampleWriter from backend.excel.writers.results_writers.qubit_results_writer import QubitInfoWriter, QubitSampleWriter
from . import DefaultResultsManager from . import DefaultResultsManager
if TYPE_CHECKING: if TYPE_CHECKING:
from backend.validators.pydant import PydResults from backend.validators.pydant import PydResults
@@ -28,6 +28,6 @@ class QubitManager(DefaultResultsManager):
def write(self): def write(self):
workbook = load_workbook(BytesIO(self.procedure.proceduretype.template_file)) workbook = load_workbook(BytesIO(self.procedure.proceduretype.template_file))
self.info_writer = PCRInfoWriter(pydant_obj=self.procedure.to_pydantic(), proceduretype=self.procedure.proceduretype) self.sample_writer = QubitSampleWriter(pydant_obj=self.procedure.to_pydantic(), proceduretype=self.procedure.proceduretype)
workbook = self.info_writer.write_to_workbook(workbook) workbook = self.sample_writer.write_to_workbook(workbook)
self.sample_writer = PCRSampleWriter(pydant_obj=self.procedure.to_pydantic(), proceduretype=self.procedure.proceduretype) return workbook

View File

@@ -254,12 +254,12 @@ class PydReagent(PydBaseClass):
report = Report() report = Report()
if self.model_extra is not None: if self.model_extra is not None:
self.__dict__.update(self.model_extra) self.__dict__.update(self.model_extra)
reagent, new = ReagentLot.query_or_create(lot=self.lot, name=self.name) reagentlot, new = ReagentLot.query_or_create(lot=self.lot, name=self.name)
if new: if new:
reagentrole = ReagentRole.query(name=self.reagentrole) reagent = Reagent.query(name=self.name)
reagent.reagentrole = reagentrole reagentlot.reagent = reagent
reagent.expiry = self.expiry reagentlot.expiry = self.expiry
return reagent, report return reagentlot, report
class PydSample(PydBaseClass): class PydSample(PydBaseClass):
@@ -268,6 +268,7 @@ class PydSample(PydBaseClass):
enabled: bool = Field(default=True) enabled: bool = Field(default=True)
row: int = Field(default=0) row: int = Field(default=0)
column: int = Field(default=0) column: int = Field(default=0)
results: List[PydResults] | PydResults = Field(default=[])
@field_validator("sample_id", mode="before") @field_validator("sample_id", mode="before")
@classmethod @classmethod
@@ -389,7 +390,6 @@ class PydEquipment(PydBaseClass):
@field_validator('tips', mode='before') @field_validator('tips', mode='before')
@classmethod @classmethod
def tips_to_pydantic(cls, value, values): def tips_to_pydantic(cls, value, values):
print(f"Value coming into tips: {value}")
if isinstance(value, GeneratorType): if isinstance(value, GeneratorType):
value = [item for item in value] value = [item for item in value]
value = convert_nans_to_nones(value) value = convert_nans_to_nones(value)

View File

@@ -56,6 +56,14 @@ class ProcedureCreation(QDialog):
proceduretype_dict = self.proceduretype.details_dict() proceduretype_dict = self.proceduretype.details_dict()
# NOTE: Add --New-- as an option for reagents. # NOTE: Add --New-- as an option for reagents.
for key, value in self.procedure.reagentrole.items(): for key, value in self.procedure.reagentrole.items():
try:
check = "--New--" in [v['name'] for v in value]
except TypeError:
try:
check = "--New--" in [v.name for v in value]
except (TypeError, AttributeError):
check = True
if not check:
value.append(dict(name="--New--")) value.append(dict(name="--New--"))
if self.procedure.equipment: if self.procedure.equipment:
for equipmentrole in proceduretype_dict['equipment']: for equipmentrole in proceduretype_dict['equipment']:
@@ -150,6 +158,7 @@ class ProcedureCreation(QDialog):
def add_new_reagent(self, reagentrole: str, name: str, lot: str, expiry: str): def add_new_reagent(self, reagentrole: str, name: str, lot: str, expiry: str):
from backend.validators.pydant import PydReagent from backend.validators.pydant import PydReagent
expiry = datetime.datetime.strptime(expiry, "%Y-%m-%d") expiry = datetime.datetime.strptime(expiry, "%Y-%m-%d")
logger.debug(f"{reagentrole}, {name}, {lot}, {expiry}")
pyd = PydReagent(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry) pyd = PydReagent(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry)
self.procedure.reagentrole[reagentrole].insert(0, pyd) self.procedure.reagentrole[reagentrole].insert(0, pyd)
self.set_html() self.set_html()