From 514781fd293a23021a0582a5f3e3b60bc02f8dcf Mon Sep 17 00:00:00 2001 From: lwark Date: Wed, 13 Nov 2024 13:36:03 -0600 Subject: [PATCH] Allow for grabbing of single kit if only one exists for submission type. --- src/submissions/backend/db/models/kits.py | 11 +++++++ .../backend/db/models/organizations.py | 5 +-- .../backend/db/models/submissions.py | 5 +-- src/submissions/backend/excel/parser.py | 33 ++++++++++++------- src/submissions/backend/excel/reports.py | 13 ++++---- src/submissions/backend/excel/writer.py | 3 ++ src/submissions/frontend/widgets/app.py | 3 +- .../frontend/widgets/submission_widget.py | 13 +++++--- src/submissions/frontend/widgets/summary.py | 1 + 9 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index d8b3e20..1257cac 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -683,6 +683,11 @@ class SubmissionType(BaseClass): """ return f"" + @classmethod + def retrieve_template_file(cls): + submission_type = cls.query(name="Bacterial Culture") + return submission_type.template_file + def get_template_file_sheets(self) -> List[str]: logger.debug(f"Submission type to get sheets for: {self.name}") """ @@ -779,6 +784,12 @@ class SubmissionType(BaseClass): tmap = {} yield item.tip_role.name, tmap + def get_default_kit(self) -> KitType | None: + if len(self.kit_types) == 1: + return self.kit_types[0] + else: + return None + def get_equipment(self, extraction_kit: str | KitType | None = None) -> Generator['PydEquipmentRole', None, None]: """ Returns PydEquipmentRole of all equipment associated with this SubmissionType diff --git a/src/submissions/backend/db/models/organizations.py b/src/submissions/backend/db/models/organizations.py index 373b031..0de7736 100644 --- a/src/submissions/backend/db/models/organizations.py +++ b/src/submissions/backend/db/models/organizations.py @@ -8,7 +8,7 @@ from pprint import pformat from sqlalchemy import Column, String, INTEGER, ForeignKey, Table from sqlalchemy.orm import relationship, Query from . import Base, BaseClass -from tools import check_authorization, setup_lookup +from tools import check_authorization, setup_lookup, yaml_regex_creator from typing import List logger = logging.getLogger(f"submissions.{__name__}") @@ -88,6 +88,7 @@ class Organization(BaseClass): Returns: """ + yaml.add_constructor("!regex", yaml_regex_creator) if isinstance(filepath, str): filepath = Path(filepath) if not filepath.exists(): @@ -97,7 +98,7 @@ class Organization(BaseClass): if filepath.suffix == ".json": import_dict = json.load(fp=f) elif filepath.suffix == ".yml": - import_dict = yaml.safe_load(stream=f) + import_dict = yaml.load(stream=f, Loader=yaml.Loader) else: raise Exception(f"Filetype {filepath.suffix} not supported.") data = import_dict['orgs'] diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index 570dfe1..adde834 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -1038,7 +1038,7 @@ class BasicSubmission(BaseClass): chronologic: bool = False, limit: int = 0, page: int = 1, - page_size: int = 250, + page_size: None | int = 250, **kwargs ) -> BasicSubmission | List[BasicSubmission]: """ @@ -1059,6 +1059,7 @@ class BasicSubmission(BaseClass): """ # logger.debug(f"Incoming kwargs: {kwargs}") # NOTE: if you go back to using 'model' change the appropriate cls to model in the query filters + # logger.debug(f"Page size: {page_size}") if submission_type is not None: model = cls.find_polymorphic_subclass(polymorphic_identity=submission_type) elif len(kwargs) > 0: @@ -1139,7 +1140,7 @@ class BasicSubmission(BaseClass): # if chronologic: # logger.debug("Attempting sort by date descending") query = query.order_by(cls.submitted_date.desc()) - if page_size is not None: + if page_size > 0: query = query.limit(page_size) page = page - 1 if page is not None: diff --git a/src/submissions/backend/excel/parser.py b/src/submissions/backend/excel/parser.py index efa5824..90d91dd 100644 --- a/src/submissions/backend/excel/parser.py +++ b/src/submissions/backend/excel/parser.py @@ -96,7 +96,7 @@ class SheetParser(object): parser = ReagentParser(xl=self.xl, submission_type=self.submission_type, extraction_kit=extraction_kit) self.sub['reagents'] = parser.parse_reagents() - logger.debug(f"Reagents out of parser: {pformat(self.sub['reagents'])}") + # logger.debug(f"Reagents out of parser: {pformat(self.sub['reagents'])}") def parse_samples(self): """ @@ -273,11 +273,11 @@ class ReagentParser(object): self.kit_object = KitType.query(name=extraction_kit) logger.debug(f"Got extraction kit object: {self.kit_object}") self.map = self.fetch_kit_info_map(submission_type=submission_type) - # logger.debug(f"Reagent Parser map: {self.map}") + logger.debug(f"Reagent Parser map: {self.map}") self.xl = xl @report_result - def fetch_kit_info_map(self, submission_type: str|SubmissionType) -> Tuple[Report, dict]: + def fetch_kit_info_map(self, submission_type: str | SubmissionType) -> Tuple[Report, dict]: """ Gets location of kit reagents from database @@ -296,15 +296,24 @@ class ReagentParser(object): except KeyError: pass # logger.debug(f"Reagent map: {pformat(reagent_map)}") + # NOTE: If reagent map is empty, maybe the wrong kit was given, check if there's only one kit for that submission type and use it if so. if not reagent_map.keys(): - try: - ext_kit_loc = self.submission_type_obj.info_map['extraction_kit']['read'][0] - location_string = f"Sheet: {ext_kit_loc['sheet']}, Row: {ext_kit_loc['row']}, Column: {ext_kit_loc['column']}?" - except: - location_string = "" - report.add_result(Result(owner=__name__, code=0, msg=f"No kit map found for {self.kit_object.name}.\n\n" - f"Are you sure you put the right kit in:\n\n{location_string}?", - status="Critical")) + temp_kit_object = self.submission_type_obj.get_default_kit() + if temp_kit_object: + self.kit_object = temp_kit_object + reagent_map = {k: v for k, v in self.kit_object.construct_xl_map_for_use(submission_type)} + logger.warning(f"Attempting to salvage {self.kit_object} with default kit map: {reagent_map}") + if not reagent_map.keys(): + logger.error(f"Still no reagent map, displaying error.") + try: + ext_kit_loc = self.submission_type_obj.info_map['extraction_kit']['read'][0] + location_string = f"Sheet: {ext_kit_loc['sheet']}, Row: {ext_kit_loc['row']}, Column: {ext_kit_loc['column']}?" + except (IndexError, KeyError): + location_string = "" + report.add_result(Result(owner=__name__, code=0, + msg=f"No kit map found for {self.kit_object.name}.\n\n" + f"Are you sure you put the right kit in:\n\n{location_string}?", + status="Critical")) return report, reagent_map def parse_reagents(self) -> Generator[dict, None, None]: @@ -317,7 +326,7 @@ class ReagentParser(object): for sheet in self.xl.sheetnames: ws = self.xl[sheet] relevant = {k.strip(): v for k, v in self.map.items() if sheet in self.map[k]['sheet']} - # logger.debug(f"relevant map for {sheet}: {pformat(relevant)}") + logger.debug(f"relevant map for {sheet}: {pformat(relevant)}") if relevant == {}: continue for item in relevant: diff --git a/src/submissions/backend/excel/reports.py b/src/submissions/backend/excel/reports.py index 1b8d17b..756f924 100644 --- a/src/submissions/backend/excel/reports.py +++ b/src/submissions/backend/excel/reports.py @@ -18,10 +18,12 @@ env = jinja_template_loading() class ReportMaker(object): - def __init__(self, start_date: date, end_date: date, organizations:list|None=None): + def __init__(self, start_date: date, end_date: date, organizations: list | None = None): self.start_date = start_date self.end_date = end_date - self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date) + # NOTE: Set page size to zero to override limiting query size. + self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date, page_size=0) + # logger.debug(f"Number of subs returned: {len(self.subs)}") if organizations is not None: self.subs = [sub for sub in self.subs if sub.submitting_lab.name in organizations] self.detailed_df, self.summary_df = self.make_report_xlsx() @@ -46,6 +48,7 @@ class ReportMaker(object): # logger.debug(f"Output daftaframe for xlsx: {df2.columns}") df = df.drop('id', axis=1) df = df.sort_values(['submitting_lab', "submitted_date"]) + logger.debug(f"Details dataframe:\n{df2}") return df, df2 def make_report_html(self, df: DataFrame) -> str: @@ -97,7 +100,7 @@ class ReportMaker(object): Args: filename (Path | str): Basename of output file obj (QWidget | None, optional): Parent object. Defaults to None. - """ + """ if isinstance(filename, str): filename = Path(filename) filename = filename.absolute() @@ -111,7 +114,7 @@ class ReportMaker(object): def fix_up_xl(self): """ Handles formatting of xl file, mediocrely. - """ + """ # logger.debug(f"Updating worksheet") worksheet: Worksheet = self.writer.sheets['Report'] for idx, col in enumerate(self.summary_df, start=1): # NOTE: loop through all columns @@ -134,5 +137,3 @@ class ReportMaker(object): for cell in worksheet['D']: if cell.row > 1: cell.style = 'Currency' - - diff --git a/src/submissions/backend/excel/writer.py b/src/submissions/backend/excel/writer.py index 52d5505..71e8057 100644 --- a/src/submissions/backend/excel/writer.py +++ b/src/submissions/backend/excel/writer.py @@ -51,6 +51,9 @@ class SheetWriter(object): # except Exception as e: # logger.error(f"Couldn't open workbook due to {e}") template = self.submission_type.template_file + if not template: + logger.error(f"No template file found, falling back to Bacterial Culture") + template = SubmissionType.retrieve_template_file() workbook = load_workbook(BytesIO(template)) # self.workbook = workbook self.xl = workbook diff --git a/src/submissions/frontend/widgets/app.py b/src/submissions/frontend/widgets/app.py index e6cefa0..a847314 100644 --- a/src/submissions/frontend/widgets/app.py +++ b/src/submissions/frontend/widgets/app.py @@ -1,6 +1,7 @@ """ Constructs main application. """ +import os from pprint import pformat from PyQt6.QtCore import qInstallMessageHandler from PyQt6.QtWidgets import ( @@ -213,7 +214,7 @@ class App(QMainWindow): None """ if check_if_app(): - yaml_path = Path(sys._MEIPASS).joinpath("resources", "viral_culture.yml") + yaml_path = Path(sys._MEIPASS).joinpath("files", "resources", "viral_culture.yml") else: yaml_path = project_path.joinpath("src", "submissions", "resources", "viral_culture.yml") fname = select_save_file(obj=self, default_name="Submission Type Template.yml", extension="yml") diff --git a/src/submissions/frontend/widgets/submission_widget.py b/src/submissions/frontend/widgets/submission_widget.py index 4b324ed..0fdc3f4 100644 --- a/src/submissions/frontend/widgets/submission_widget.py +++ b/src/submissions/frontend/widgets/submission_widget.py @@ -225,7 +225,8 @@ class SubmissionFormWidget(QWidget): if k == "extraction_kit": add_widget.input.currentTextChanged.connect(self.scrape_reagents) self.setStyleSheet(main_form_style) - self.scrape_reagents(self.pyd.extraction_kit) + # self.scrape_reagents(self.pyd.extraction_kit) + self.scrape_reagents(self.extraction_kit) def create_widget(self, key: str, value: dict | PydReagent, submission_type: str | SubmissionType| None = None, extraction_kit: str | None = None, sub_obj: BasicSubmission | None = None, @@ -275,9 +276,9 @@ class SubmissionFormWidget(QWidget): Returns: Tuple[QMainWindow, dict]: Updated application and result """ - extraction_kit = args[0] + self.extraction_kit = args[0] report = Report() - logger.debug(f"Extraction kit: {extraction_kit}") + logger.debug(f"Extraction kit: {self.extraction_kit}") # NOTE: Remove previous reagent widgets try: old_reagents = self.find_widgets() @@ -288,10 +289,11 @@ class SubmissionFormWidget(QWidget): for reagent in old_reagents: if isinstance(reagent, self.ReagentFormWidget) or isinstance(reagent, QPushButton): reagent.setParent(None) - reagents, integrity_report = self.pyd.check_kit_integrity(extraction_kit=extraction_kit) + reagents, integrity_report = self.pyd.check_kit_integrity(extraction_kit=self.extraction_kit) logger.debug(f"Got reagents: {pformat(reagents)}") for reagent in reagents: - add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.pyd.extraction_kit) + # add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.pyd.extraction_kit) + add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.extraction_kit) self.layout.addWidget(add_widget) report.add_result(integrity_report) # logger.debug(f"Outgoing report: {report.results}") @@ -569,6 +571,7 @@ class SubmissionFormWidget(QWidget): obj.ext_kit = uses[0] add_widget.addItems(uses) add_widget.setToolTip("Select extraction kit.") + parent.extraction_kit = add_widget.currentText() case 'submission_category': add_widget = MyQComboBox(scrollWidget=parent) cats = ['Diagnostic', "Surveillance", "Research"] diff --git a/src/submissions/frontend/widgets/summary.py b/src/submissions/frontend/widgets/summary.py index 0bd360f..1f5c586 100644 --- a/src/submissions/frontend/widgets/summary.py +++ b/src/submissions/frontend/widgets/summary.py @@ -56,6 +56,7 @@ class Summary(QWidget): # NOTE: convert to python useable date objects self.start_date = self.datepicker.start_date.date().toPyDate() self.end_date = self.datepicker.end_date.date().toPyDate() + logger.debug(f"Getting report from {self.start_date} to {self.end_date} using {orgs}") self.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs) self.webview.setHtml(self.report_obj.html) if self.report_obj.subs: