Moments before disaster.

This commit is contained in:
lwark
2025-07-25 08:05:18 -05:00
parent 2c6a8c4cc7
commit 6f134d3870
9 changed files with 112 additions and 57 deletions

View File

@@ -1558,7 +1558,7 @@ class Procedure(BaseClass):
def to_pydantic(self, **kwargs): def to_pydantic(self, **kwargs):
from backend.validators.pydant import PydResults, PydReagent from backend.validators.pydant import PydResults, PydReagent
output = super().to_pydantic() output = super().to_pydantic()
print(f"Pydantic output: \n\n{pformat(output.__dict__)}\n\n") logger.debug(f"Pydantic output: \n\n{pformat(output.__dict__)}\n\n")
try: try:
output.kittype = dict(value=output.kittype['name'], missing=False) output.kittype = dict(value=output.kittype['name'], missing=False)
except KeyError: except KeyError:

View File

@@ -7,14 +7,16 @@ from pathlib import Path
from typing import Generator, Tuple, TYPE_CHECKING from typing import Generator, Tuple, TYPE_CHECKING
from openpyxl.reader.excel import load_workbook from openpyxl.reader.excel import load_workbook
from openpyxl.worksheet.worksheet import Worksheet
from pandas import DataFrame from pandas import DataFrame
from backend.validators import pydant from backend.validators import pydant
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 DefaultParser(object): class DefaultParser(object):
def __repr__(self): def __repr__(self):
@@ -32,8 +34,8 @@ class DefaultParser(object):
instance.filepath = filepath instance.filepath = filepath
return instance return instance
def __init__(self, filepath: Path | str, proceduretype: ProcedureType | None = None, range_dict: dict | None = None,
def __init__(self, filepath: Path | str, proceduretype: ProcedureType|None=None, range_dict: dict | None = None, *args, **kwargs): *args, **kwargs):
""" """
Args: Args:
@@ -43,23 +45,30 @@ class DefaultParser(object):
*args (): *args ():
**kwargs (): **kwargs ():
""" """
logger.debug(f"\n\nHello from {self.__class__.__name__}\n\n")
self.proceduretype = proceduretype self.proceduretype = proceduretype
try: try:
self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '').replace('Info', '')}") self._pyd_object = getattr(pydant,
f"Pyd{self.__class__.__name__.replace('Parser', '').replace('Info', '')}")
except AttributeError as e: except AttributeError as e:
logger.error(f"Couldn't get pyd object: Pyd{self.__class__.__name__.replace('Parser', '').replace('Info', '')}") logger.error(
f"Couldn't get pyd object: Pyd{self.__class__.__name__.replace('Parser', '').replace('Info', '')}, using {self.__class__.pyd_name}")
self._pyd_object = getattr(pydant, self.__class__.pyd_name) self._pyd_object = getattr(pydant, self.__class__.pyd_name)
self.workbook = load_workbook(self.filepath, data_only=True) self.workbook = load_workbook(self.filepath, data_only=True)
if not range_dict: if not range_dict:
self.range_dict = self.__class__.default_range_dict self.range_dict = self.__class__.default_range_dict
else: else:
self.range_dict = range_dict self.range_dict = range_dict
logger.debug(f"Default parser range dict: {self.range_dict}")
for item in self.range_dict: for item in self.range_dict:
item['worksheet'] = self.workbook[item['sheet']] item['worksheet'] = self.workbook[item['sheet']]
def to_pydantic(self): def to_pydantic(self):
data = {key: value for key, value in self.parsed_info} # data = {key: value['value'] for key, value in self.parsed_info.items()}
data = self.parsed_info
data['filepath'] = self.filepath data['filepath'] = self.filepath
return self._pyd_object(**data) return self._pyd_object(**data)
@classmethod @classmethod
@@ -69,48 +78,61 @@ class DefaultParser(object):
proceduretype = ProcedureType.query(name=proceduretype) proceduretype = ProcedureType.query(name=proceduretype)
return proceduretype return proceduretype
@classmethod
def delineate_end_row(cls, worksheet: Worksheet, start_row: int = 1):
for iii, row in enumerate(worksheet.iter_rows(min_row=start_row), start=1):
if all([item.value is None for item in row]):
return iii
class DefaultKEYVALUEParser(DefaultParser): class DefaultKEYVALUEParser(DefaultParser):
# default_range_dict = [dict(
# start_row=2,
# end_row=18,
# key_column=1,
# value_column=2,
# sheet="Sample List"
# )]
default_range_dict = [dict( # default_range_dict = [dict(sheet="Sample List", start_row=2)]
start_row=2,
end_row=18,
key_column=1,
value_column=2,
sheet="Sample List"
)]
@property
def parsed_info(self) -> Generator[Tuple, None, None]:
for item in self.range_dict:
rows = range(item['start_row'], item['end_row'] + 1)
for row in rows:
key = item['worksheet'].cell(row, item['key_column']).value
if key:
# Note: Remove anything in brackets.
key = re.sub(r"\(.*\)", "", key)
key = key.lower().replace(":", "").strip().replace(" ", "_")
value = item['worksheet'].cell(row, item['value_column']).value
missing = False if value else True
location_map = dict(row=row, key_column=item['key_column'], value_column=item['value_column'], sheet=item['sheet'])
value = dict(value=value, location=location_map, missing=missing)
logger.debug(f"Yieldings {value} for {key}")
yield key, value
class DefaultTABLEParser(DefaultParser):
default_range_dict = [dict(
header_row=20,
sheet="Sample List"
)]
@property @property
def parsed_info(self): def parsed_info(self):
for item in self.range_dict: for item in self.range_dict:
list_worksheet = self.workbook[item['sheet']] item['end_row'] = self.delineate_end_row(item['worksheet'], start_row=item['start_row'])
rows = range(item['start_row'], item['end_row'])
# item['start_row'] = item['end_row']
# del item['end_row']
for row in rows:
key = item['worksheet'].cell(row, 1).value
if key:
# Note: Remove anything in brackets.
key = re.sub(r"\(.*\)", "", key)
key = key.lower().replace(":", "").strip().replace(" ", "_")
value = item['worksheet'].cell(row, 2).value
missing = False if value else True
location_map = dict(row=row, key_column=1, value_column=2,
sheet=item['sheet'])
value = dict(value=value, location=location_map, missing=missing)
logger.debug(f"Yielding {value} for {key}")
yield key, value
class DefaultTABLEParser(DefaultParser):
default_range_dict = [dict(
header_row=18,
sheet="Sample List"
)]
@property
def parsed_info(self) -> Generator[dict, None, None]:
for item in self.range_dict:
# list_worksheet = self.workbook[item['sheet']]
list_worksheet = item['worksheet']
if "end_row" in item.keys(): if "end_row" in item.keys():
list_df = DataFrame([item for item in list_worksheet.values][item['header_row'] - 1:item['end_row']-1]) list_df = DataFrame(
[item for item in list_worksheet.values][item['header_row'] - 1:item['end_row'] - 1])
else: else:
list_df = DataFrame([item for item in list_worksheet.values][item['header_row'] - 1:]) list_df = DataFrame([item for item in list_worksheet.values][item['header_row'] - 1:])
list_df.columns = list_df.iloc[0] list_df.columns = list_df.iloc[0]
@@ -129,5 +151,6 @@ class DefaultTABLEParser(DefaultParser):
def to_pydantic(self, **kwargs): def to_pydantic(self, **kwargs):
return [self._pyd_object(**output) for output in self.parsed_info] return [self._pyd_object(**output) for output in self.parsed_info]
from .clientsubmission_parser import ClientSubmissionSampleParser, ClientSubmissionInfoParser from .clientsubmission_parser import ClientSubmissionSampleParser, ClientSubmissionInfoParser
from backend.excel.parsers.results_parsers.pcr_results_parser import PCRInfoParser, PCRSampleParser from backend.excel.parsers.results_parsers.pcr_results_parser import PCRInfoParser, PCRSampleParser

View File

@@ -50,7 +50,7 @@ class SubmissionTyperMixin(object):
def get_subtype_from_preparse(cls, filepath: Path): def get_subtype_from_preparse(cls, filepath: Path):
from backend.db.models import SubmissionType from backend.db.models import SubmissionType
parser = ClientSubmissionInfoParser(filepath) parser = ClientSubmissionInfoParser(filepath)
sub_type = next((value for k, value in parser.parsed_info if k == "submissiontype"), None) sub_type = next((value for k, value in parser.parsed_info.items() if k == "submissiontype"), None)
sub_type = SubmissionType.query(name=sub_type) sub_type = SubmissionType.query(name=sub_type)
if isinstance(sub_type, list): if isinstance(sub_type, list):
sub_type = None sub_type = None
@@ -91,9 +91,9 @@ class ClientSubmissionInfoParser(DefaultKEYVALUEParser, SubmissionTyperMixin):
self.submissiontype = self.retrieve_submissiontype(filepath=filepath) self.submissiontype = self.retrieve_submissiontype(filepath=filepath)
else: else:
self.submissiontype = submissiontype self.submissiontype = submissiontype
if "range_dict" not in kwargs: # if "range_dict" not in kwargs:
kwargs['range_dict'] = self.submissiontype.info_map # kwargs['range_dict'] = self.submissiontype.info_map
super().__init__(filepath=filepath, **kwargs) super().__init__(filepath=filepath, range_dict=[dict(sheet="Client Info")], **kwargs)
allowed_procedure_types = [item.name for item in self.submissiontype.proceduretype] allowed_procedure_types = [item.name for item in self.submissiontype.proceduretype]
for name in allowed_procedure_types: for name in allowed_procedure_types:
if name in self.workbook.sheetnames: if name in self.workbook.sheetnames:
@@ -108,6 +108,18 @@ class ClientSubmissionInfoParser(DefaultKEYVALUEParser, SubmissionTyperMixin):
self.manager = manager(proceduretype=name) self.manager = manager(proceduretype=name)
pass pass
@property
def parsed_info(self):
output = {k:v for k, v in super().parsed_info}
try:
output['clientlab'] = output['client_lab']
except KeyError:
pass
logger.debug(f"Data: {output}")
output['submissiontype'] = self.submissiontype.name
return output
class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin): class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin):
""" """
@@ -135,7 +147,7 @@ class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin):
def parsed_info(self) -> Generator[dict, None, None]: def parsed_info(self) -> Generator[dict, None, None]:
output = super().parsed_info output = super().parsed_info
for ii, sample in enumerate(output): for ii, sample in enumerate(output):
# logger.debug(f"Parsed info sample: {sample}") logger.debug(f"Parsed info sample: {sample}")
if isinstance(sample["row"], str) and sample["row"].lower() in ascii_lowercase[0:8]: if isinstance(sample["row"], str) and sample["row"].lower() in ascii_lowercase[0:8]:
try: try:
sample["row"] = row_keys[sample["row"]] sample["row"] = row_keys[sample["row"]]
@@ -145,4 +157,5 @@ class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin):
yield sample yield sample
def to_pydantic(self): def to_pydantic(self):
logger.debug(f"Attempting to pydantify: {self._pyd_object}")
return [self._pyd_object(**sample) for sample in self.parsed_info if sample['sample_id']] return [self._pyd_object(**sample) for sample in self.parsed_info if sample['sample_id']]

View File

@@ -29,7 +29,7 @@ class PCRInfoParser(DefaultKEYVALUEParser):
def to_pydantic(self): def to_pydantic(self):
# from backend.db.models import Procedure # from backend.db.models import Procedure
data = dict(results={key: value for key, value in self.parsed_info}, filepath=self.filepath, data = dict(results={k:v for k, v in self.parsed_info}, filepath=self.filepath,
result_type="PCR") result_type="PCR")
return self._pyd_object(**data, parent=self.procedure) return self._pyd_object(**data, parent=self.procedure)

View File

@@ -19,10 +19,10 @@ class DefaultManager(object):
match input_object: match input_object:
case str(): case str():
self.input_object = Path(input_object) self.input_object = Path(input_object)
self.pyd = self.parse() self.pyd = self.to_pydantic()
case Path(): case Path():
self.input_object = input_object self.input_object = input_object
self.pyd = self.parse() self.pyd = self.to_pydantic()
case x if issubclass(input_object.__class__, PydBaseClass): case x if issubclass(input_object.__class__, PydBaseClass):
# logger.debug("Subclass of PydBaseClass") # logger.debug("Subclass of PydBaseClass")
self.pyd = input_object self.pyd = input_object
@@ -31,7 +31,7 @@ class DefaultManager(object):
self.pyd = input_object.to_pydantic() self.pyd = input_object.to_pydantic()
case _: case _:
self.input_object = select_open_file(file_extension="xlsx", obj=get_application_from_parent(parent)) self.input_object = select_open_file(file_extension="xlsx", obj=get_application_from_parent(parent))
self.pyd = self.parse() self.pyd = self.to_pydantic()
# logger.debug(f"FName after correction: {input_object}") # logger.debug(f"FName after correction: {input_object}")

View File

@@ -39,16 +39,19 @@ class DefaultClientSubmissionManager(DefaultManager):
self.submissiontype = submissiontype self.submissiontype = submissiontype
super().__init__(parent=parent, input_object=input_object) super().__init__(parent=parent, input_object=input_object)
def parse(self): def to_pydantic(self):
self.info_parser = ClientSubmissionInfoParser(filepath=self.input_object, submissiontype=self.submissiontype) self.info_parser = ClientSubmissionInfoParser(filepath=self.input_object, submissiontype=self.submissiontype)
self.sample_parser = ClientSubmissionSampleParser(filepath=self.input_object, self.sample_parser = ClientSubmissionSampleParser(filepath=self.input_object,
submissiontype=self.submissiontype) submissiontype=self.submissiontype)
self.to_pydantic() logger.debug(f"Info Parser range dict: {self.info_parser.range_dict}")
self.clientsubmission = self.info_parser.to_pydantic()
self.clientsubmission.sample = self.sample_parser.to_pydantic()
return self.clientsubmission return self.clientsubmission
def to_pydantic(self): # def to_pydantic(self):
self.clientsubmission = self.info_parser.to_pydantic() # self.clientsubmission = self.info_parser.to_pydantic()
self.clientsubmission.sample = self.sample_parser.to_pydantic() # self.clientsubmission.sample = self.sample_parser.to_pydantic()
def write(self): def write(self):
workbook: Workbook = load_workbook(BytesIO(self.submissiontype.template_file)) workbook: Workbook = load_workbook(BytesIO(self.submissiontype.template_file))

View File

@@ -61,7 +61,7 @@ class ClientSubmissionNamer(DefaultNamer):
def get_subtype_from_preparse(self): def get_subtype_from_preparse(self):
from backend.excel.parsers.clientsubmission_parser import ClientSubmissionInfoParser from backend.excel.parsers.clientsubmission_parser import ClientSubmissionInfoParser
parser = ClientSubmissionInfoParser(self.filepath) parser = ClientSubmissionInfoParser(self.filepath)
sub_type = next((value for k, value in parser.parsed_info if k == "submissiontype"), None) sub_type = next((value for k, value in parser.parsed_info.items() if k == "submissiontype"), None)
sub_type = SubmissionType.query(name=sub_type) sub_type = SubmissionType.query(name=sub_type)
if isinstance(sub_type, list): if isinstance(sub_type, list):
sub_type = None sub_type = None

View File

@@ -1604,6 +1604,20 @@ class PydClientSubmission(PydBaseClass):
submitter_plate_id: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) submitter_plate_id: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
sample: List[PydSample] | None = Field(default=[]) sample: List[PydSample] | None = Field(default=[])
# @field_validator("submissiontype", mode="before")
# @classmethod
# def enforce_submissiontype(cls, value):
# if isinstance(value, str):
# value = dict(value=value, missing=False)
# return value
@field_validator("submissiontype", "clientlab", "contact", mode="before")
@classmethod
def enforce_value(cls, value):
if isinstance(value, str):
value = dict(value=value, missing=False)
return value
@field_validator("submitted_date", mode="before") @field_validator("submitted_date", mode="before")
@classmethod @classmethod
def enforce_submitted_date(cls, value): def enforce_submitted_date(cls, value):
@@ -1659,6 +1673,8 @@ class PydClientSubmission(PydBaseClass):
@field_validator("submitted_date") @field_validator("submitted_date")
@classmethod @classmethod
def rescue_date(cls, value): def rescue_date(cls, value):
if not value:
value = dict(value=None)
try: try:
check = value['value'] is None check = value['value'] is None
except TypeError: except TypeError:

View File

@@ -145,7 +145,7 @@ class SubmissionFormContainer(QWidget):
# self.pydsamples = self.sampleparser.to_pydantic() # self.pydsamples = self.sampleparser.to_pydantic()
# logger.debug(f"Samples: {pformat(self.pydclientsubmission.sample)}") # logger.debug(f"Samples: {pformat(self.pydclientsubmission.sample)}")
self.clientsubmission_manager = DefaultClientSubmissionManager(parent=self, input_object=fname) self.clientsubmission_manager = DefaultClientSubmissionManager(parent=self, input_object=fname)
self.pydclientsubmission = self.clientsubmission_manager.parse() self.pydclientsubmission = self.clientsubmission_manager.to_pydantic()
checker = SampleChecker(self, "Sample Checker", self.pydclientsubmission.sample) checker = SampleChecker(self, "Sample Checker", self.pydclientsubmission.sample)
if checker.exec(): if checker.exec():
# logger.debug(pformat(self.pydclientsubmission.sample)) # logger.debug(pformat(self.pydclientsubmission.sample))