Sample results writer improvements.

This commit is contained in:
lwark
2025-07-23 15:23:12 -05:00
parent b9ca9586ec
commit 1463cf9d2d
12 changed files with 160 additions and 46 deletions

View File

@@ -1558,7 +1558,7 @@ class Procedure(BaseClass):
def to_pydantic(self, **kwargs):
from backend.validators.pydant import PydResults, PydReagent
output = super().to_pydantic()
logger.debug(f"Pydantic output: \n\n{pformat(output.__dict__)}\n\n")
print(f"Pydantic output: \n\n{pformat(output.__dict__)}\n\n")
try:
output.kittype = dict(value=output.kittype['name'], missing=False)
except KeyError:
@@ -1580,17 +1580,18 @@ class Procedure(BaseClass):
pass
# output.reagent = [PydReagent(**item) for item in output.reagent]
output.reagent = reagents
results = []
for result in output.results:
match result:
case dict():
results.append(PydResults(**result))
case PydResults():
results.append(result)
case _:
pass
output.results = results
# results = []
# for result in output.results:
# match result:
# case dict():
# results.append(PydResults(**result))
# case PydResults():
# results.append(result)
# case _:
# pass
# output.results = results
output.result = [item.to_pydantic() for item in self.results]
output.sample_results = flatten_list([[result.to_pydantic() for result in item.results] for item in self.proceduresampleassociation])
# for sample in output.sample:
# sample.enabled = True
return output
@@ -3116,6 +3117,13 @@ class Results(BaseClass):
sampleprocedureassociation = relationship("ProcedureSampleAssociation", back_populates="results")
_img = Column(String(128))
@property
def sample_id(self):
if self.assoc_id:
return self.sampleprocedureassociation.sample.sample_id
else:
return None
@property
def image(self) -> bytes|None:
dir = self.__directory_path__.joinpath("submission_imgs.zip")
@@ -3131,3 +3139,10 @@ class Results(BaseClass):
@image.setter
def image(self, value):
self._img = value
def to_pydantic(self, pyd_model_name:str|None=None, **kwargs):
output = super().to_pydantic(pyd_model_name=pyd_model_name, **kwargs)
if self.sample_id:
output.sample_id = self.sample_id
return output

View File

@@ -75,6 +75,7 @@ class PCRSampleParser(DefaultTABLEParser):
if assoc and not isinstance(assoc, list):
output = self._pyd_object(results=list(item.values())[0], parent=assoc)
output.result_type = "PCR"
del output.result['result_type']
yield output
else:
continue

View File

@@ -10,7 +10,7 @@ from openpyxl.workbook.workbook import Workbook
from openpyxl.worksheet.worksheet import Worksheet
from pandas import DataFrame
from backend.db.models import BaseClass
from backend.db.models import BaseClass, ProcedureType
from backend.validators.pydant import PydBaseClass
logger = logging.getLogger(f"submissions.{__name__}")
@@ -21,10 +21,10 @@ class DefaultWriter(object):
def __repr__(self):
return f"{self.__class__.__name__}<{self.filepath.stem}>"
def __init__(self, pydant_obj, range_dict: dict | None = None, *args, **kwargs):
def __init__(self, pydant_obj, proceduretype: ProcedureType|None=None, range_dict: dict | None = None, *args, **kwargs):
# self.filepath = output_filepath
self.pydant_obj = pydant_obj
self.fill_dictionary = pydant_obj.improved_dict()
self.proceduretype = proceduretype
if range_dict:
self.range_dict = range_dict
else:
@@ -71,6 +71,10 @@ class DefaultKEYVALUEWriter(DefaultWriter):
sheet="Sample List"
)]
def __init__(self, pydant_obj, proceduretype: ProcedureType|None=None, range_dict: dict | None = None, *args, **kwargs):
super().__init__(pydant_obj=pydant_obj, proceduretype=proceduretype, range_dict=range_dict, *args, **kwargs)
self.fill_dictionary = self.pydant_obj.improved_dict()
@classmethod
def check_location(cls, locations: list, sheet: str):
return any([item['sheet'] == sheet for item in locations])
@@ -82,21 +86,6 @@ class DefaultKEYVALUEWriter(DefaultWriter):
worksheet = workbook[rng['sheet']]
try:
for ii, (k, v) in enumerate(self.fill_dictionary.items(), start=rng['start_row']):
# match v:
# case x if issubclass(v.__class__, BaseClass):
# v = v.name
# case x if issubclass(v.__class__, PydBaseClass):
# v = v.name
# case dict():
# try:
# v = v['value']
# except ValueError:
# try:
# v = v['name']
# except ValueError:
# v = v.__str__()
# case _:
# pass
try:
worksheet.cell(column=rng['key_column'], row=rows[ii], value=self.prettify_key(k))
worksheet.cell(column=rng['value_column'], row=rows[ii], value=self.stringify_value(v))
@@ -127,7 +116,7 @@ class DefaultTABLEWriter(DefaultWriter):
from backend import PydSample
output_samples = []
for iii in range(1, row_count + 1):
logger.debug(f"Submission rank: {iii}")
# logger.debug(f"Submission rank: {iii}")
if isinstance(self.pydant_obj, list):
iterator = self.pydant_obj
else:
@@ -139,8 +128,8 @@ class DefaultTABLEWriter(DefaultWriter):
for column in column_names:
setattr(sample, column[0], "")
sample.submission_rank = iii
logger.debug(f"Appending {sample.sample_id}")
logger.debug(f"Iterator now: {[item.submission_rank for item in iterator]}")
# logger.debug(f"Appending {sample.sample_id}")
# logger.debug(f"Iterator now: {[item.submission_rank for item in iterator]}")
output_samples.append(sample)
return sorted(output_samples, key=lambda x: x.submission_rank)

View File

@@ -80,9 +80,9 @@ class ProcedureSampleWriter(DefaultTABLEWriter):
list_worksheet[rng['header_row']] if item.value]
samples = self.pad_samples_to_length(row_count=row_count, column_names=column_names)
# samples = self.pydant_obj
logger.debug(f"Samples: {[item.submission_rank for item in samples]}")
# logger.debug(f"Samples: {[item.submission_rank for item in samples]}")
for sample in samples:
logger.debug(f"Writing sample: {sample}")
# logger.debug(f"Writing sample: {sample}")
write_row = rng['header_row'] + sample.submission_rank
for column in column_names:
if column[0].lower() in ["well"]:#, "row", "column"]:
@@ -92,6 +92,6 @@ class ProcedureSampleWriter(DefaultTABLEWriter):
value = getattr(sample, column[0])
except KeyError:
value = ""
logger.debug(f"{column} Writing {value} to row {write_row}, column {write_column}")
# logger.debug(f"{column} Writing {value} to row {write_row}, column {write_column}")
list_worksheet.cell(row=write_row, column=write_column, value=value)
return workbook

View File

@@ -0,0 +1 @@
from .pcr_results_writer import PCRInfoWriter, PCRSampleWriter

View File

@@ -0,0 +1,83 @@
import logging
from pathlib import Path
from typing import Generator
from openpyxl import Workbook
from openpyxl.styles import Alignment
from backend.excel.writers import DefaultKEYVALUEWriter, DefaultTABLEWriter
from tools import flatten_list
logger = logging.getLogger(f"submissions.{__name__}")
class PCRInfoWriter(DefaultKEYVALUEWriter):
default_range_dict = [dict(
start_row=1,
end_row=24,
key_column=1,
value_column=2,
sheet="Results"
)]
def write_to_workbook(self, workbook: Workbook) -> Workbook:
worksheet = workbook[f"{self.proceduretype.name} Results"]
for key, value in self.fill_dictionary['result'].items():
logger.debug(f"Filling in {key} with {value}")
worksheet.cell(value['location']['row'], value['location']['key_column'], value=key.replace("_", " ").title())
worksheet.cell(value['location']['row'], value['location']['value_column'], value=value['value'])
return workbook
class PCRSampleWriter(DefaultTABLEWriter):
def write_to_workbook(self, workbook: Workbook) -> Workbook:
worksheet = workbook[f"{self.proceduretype.name} Results"]
header_row = self.proceduretype.allowed_result_methods['PCR']['sample']['header_row']
proto_columns = [(1, "sample"), (2, "target")]
columns = []
for iii, header in enumerate(self.column_headers, start=3):
worksheet.cell(row=header_row, column=iii, value=header.replace("_", " ").title())
columns.append((iii, header))
columns = sorted(columns, key=lambda x: x[0])
columns = proto_columns + columns
logger.debug(columns)
all_results = flatten_list([[item for item in self.rearrange_results(result)] for result in self.pydant_obj])
if len(all_results) > 0 :
worksheet.cell(row=header_row, column=1, value="Sample")
worksheet.cell(row=header_row, column=2, value="Target")
for iii, item in enumerate(all_results, start=1):
row = header_row + iii
for k, v in item.items():
column = next((col[0] for col in columns if col[1]==k), None)
cell = worksheet.cell(row=row, column=column)
cell.value = v
cell.alignment = Alignment(horizontal='left')
return workbook
@classmethod
def rearrange_results(cls, result) -> Generator[dict, None, None]:
for target, values in result.result.items():
values['target'] = target
values['sample'] = result.sample_id
yield values
@property
def column_headers(self):
output = []
for item in self.pydant_obj:
logger.debug(item)
dicto: dict = item.result
for value in dicto.values():
for key in value.keys():
output.append(key)
return sorted(list(set(output)))

View File

@@ -1,15 +1,16 @@
from __future__ import annotations
import logging
from io import BytesIO
from pprint import pformat
from openpyxl.reader.excel import load_workbook
from openpyxl.workbook import Workbook
from backend.managers import DefaultManager
from backend.managers import DefaultManager, results
from typing import TYPE_CHECKING
from pathlib import Path
from backend.excel.parsers import procedure_parsers
from backend.excel.writers import procedure_writers
from backend.excel.writers import procedure_writers, results_writers
if TYPE_CHECKING:
from backend.db.models import ProcedureType
@@ -81,5 +82,15 @@ class DefaultProcedureManager(DefaultManager):
sample_writer = procedure_writers.ProcedureSampleWriter
self.sample_writer = sample_writer(pydant_obj=self.pyd, range_dict=self.proceduretype.sample_map)
workbook = self.sample_writer.write_to_workbook(workbook)
logger.debug(self.pyd.result)
# TODO: Find way to group results by result_type.
for result in self.pyd.result:
Writer = getattr(results_writers, f"{result.result_type}InfoWriter")
res_info_writer = Writer(pydant_obj=result, proceduretype=self.proceduretype)
workbook = res_info_writer.write_to_workbook(workbook=workbook)
# sample_results = [sample.result for sample in self.pyd.sample]
logger.debug(pformat(self.pyd.sample_results))
Writer = getattr(results_writers, "PCRSampleWriter")
res_sample_writer = Writer(pydant_obj=self.pyd.sample_results, proceduretype=self.proceduretype)
workbook = res_sample_writer.write_to_workbook(workbook=workbook)
return workbook

View File

@@ -3,10 +3,15 @@
"""
from __future__ import annotations
import logging
from io import BytesIO
from pathlib import Path
from typing import Tuple, List, TYPE_CHECKING
from openpyxl.reader.excel import load_workbook
from backend.db.models import Procedure
from backend.excel.parsers.results_parsers.pcr_results_parser import PCRSampleParser, PCRInfoParser
from backend.excel.writers.results_writers.pcr_results_writer import PCRInfoWriter, PCRSampleWriter
from . import DefaultResultsManager
if TYPE_CHECKING:
from backend.validators.pydant import PydResults
@@ -23,6 +28,13 @@ class PCRManager(DefaultResultsManager):
self.info_parser = PCRInfoParser(filepath=self.fname, procedure=self.procedure)
self.sample_parser = PCRSampleParser(filepath=self.fname, procedure=self.procedure)
def write(self):
workbook = load_workbook(BytesIO(self.procedure.proceduretype.template_file))
self.info_writer = PCRInfoWriter(pydant_obj=self.procedure.to_pydantic(), proceduretype=self.procedure.proceduretype)
workbook = self.info_writer.write_to_workbook(workbook)
self.sample_writer = PCRSampleWriter(pydant_obj=self.procedure.to_pydantic(), proceduretype=self.procedure.proceduretype)

View File

@@ -1,6 +1,8 @@
from __future__ import annotations
import logging
from pathlib import Path
from pprint import pformat
from openpyxl import load_workbook
from openpyxl.workbook.workbook import Workbook
from tools import copy_xl_sheet
@@ -18,8 +20,8 @@ class DefaultRunManager(DefaultManager):
clientsubmission = DefaultClientSubmissionManager(parent=self.parent, input_object=self.pyd.clientsubmission, submissiontype=self.pyd.clientsubmission.submissiontype)
workbook = clientsubmission.write()
for procedure in self.pyd.procedure:
logger.debug(f"Running procedure: {procedure}")
procedure = DefaultProcedureManager(proceduretype=procedure.proceduretype.name, parent=self.parent, input_object=procedure)
logger.debug(f"Running procedure: {pformat(procedure.__dict__)}")
procedure = DefaultProcedureManager(proceduretype=procedure.proceduretype, parent=self.parent, input_object=procedure)
wb: Workbook = procedure.write()
for sheetname in wb.sheetnames:
source_sheet = wb[sheetname]

View File

@@ -1309,7 +1309,7 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
reagentrole: dict | None = Field(default={}, validate_default=True)
sample: List[PydSample] = Field(default=[])
equipment: List[PydEquipment] = Field(default=[])
results: List[PydResults] | List[dict] = Field(default=[])
result: List[PydResults] | List[dict] = Field(default=[])
@field_validator("name", "technician", "kittype", mode="before")
@classmethod
@@ -1749,7 +1749,7 @@ class PydClientSubmission(PydBaseClass):
class PydResults(PydBaseClass, arbitrary_types_allowed=True):
results: dict = Field(default={})
result: dict = Field(default={})
result_type: str = Field(default="NA")
img: None | bytes = Field(default=None)
parent: Procedure | ProcedureSampleAssociation | None = Field(default=None)

View File

@@ -26,9 +26,9 @@ class SubmissionDetails(QDialog):
a window showing text details of procedure
"""
def __init__(self, parent, sub: Run | Sample | Reagent) -> None:
def __init__(self, parent, sub: Run | Sample | Reagent, **kwargs) -> None:
super().__init__(parent)
super().__init__(parent, **kwargs)
self.app = get_application_from_parent(parent)
self.webview = QWebEngineView(parent=self)
self.webview.setMinimumSize(900, 500)

View File

@@ -424,7 +424,7 @@ class SubmissionsTree(QTreeView):
# Run.query(id=id).show_details(self)
obj = dicto['item_type'].query(name=dicto['query_str'], limit=1)
logger.debug(obj)
obj.show_details(obj)
obj.show_details(self)
def link_extractions(self):
pass