Allow for grabbing of single kit if only one exists for submission type.

This commit is contained in:
lwark
2024-11-13 13:36:03 -06:00
parent 816a0a45f8
commit 514781fd29
9 changed files with 59 additions and 28 deletions

View File

@@ -683,6 +683,11 @@ class SubmissionType(BaseClass):
""" """
return f"<SubmissionType({self.name})>" return f"<SubmissionType({self.name})>"
@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]: def get_template_file_sheets(self) -> List[str]:
logger.debug(f"Submission type to get sheets for: {self.name}") logger.debug(f"Submission type to get sheets for: {self.name}")
""" """
@@ -779,6 +784,12 @@ class SubmissionType(BaseClass):
tmap = {} tmap = {}
yield item.tip_role.name, 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]: def get_equipment(self, extraction_kit: str | KitType | None = None) -> Generator['PydEquipmentRole', None, None]:
""" """
Returns PydEquipmentRole of all equipment associated with this SubmissionType Returns PydEquipmentRole of all equipment associated with this SubmissionType

View File

@@ -8,7 +8,7 @@ from pprint import pformat
from sqlalchemy import Column, String, INTEGER, ForeignKey, Table from sqlalchemy import Column, String, INTEGER, ForeignKey, Table
from sqlalchemy.orm import relationship, Query from sqlalchemy.orm import relationship, Query
from . import Base, BaseClass 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 from typing import List
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -88,6 +88,7 @@ class Organization(BaseClass):
Returns: Returns:
""" """
yaml.add_constructor("!regex", yaml_regex_creator)
if isinstance(filepath, str): if isinstance(filepath, str):
filepath = Path(filepath) filepath = Path(filepath)
if not filepath.exists(): if not filepath.exists():
@@ -97,7 +98,7 @@ class Organization(BaseClass):
if filepath.suffix == ".json": if filepath.suffix == ".json":
import_dict = json.load(fp=f) import_dict = json.load(fp=f)
elif filepath.suffix == ".yml": elif filepath.suffix == ".yml":
import_dict = yaml.safe_load(stream=f) import_dict = yaml.load(stream=f, Loader=yaml.Loader)
else: else:
raise Exception(f"Filetype {filepath.suffix} not supported.") raise Exception(f"Filetype {filepath.suffix} not supported.")
data = import_dict['orgs'] data = import_dict['orgs']

View File

@@ -1038,7 +1038,7 @@ class BasicSubmission(BaseClass):
chronologic: bool = False, chronologic: bool = False,
limit: int = 0, limit: int = 0,
page: int = 1, page: int = 1,
page_size: int = 250, page_size: None | int = 250,
**kwargs **kwargs
) -> BasicSubmission | List[BasicSubmission]: ) -> BasicSubmission | List[BasicSubmission]:
""" """
@@ -1059,6 +1059,7 @@ class BasicSubmission(BaseClass):
""" """
# logger.debug(f"Incoming kwargs: {kwargs}") # logger.debug(f"Incoming kwargs: {kwargs}")
# NOTE: if you go back to using 'model' change the appropriate cls to model in the query filters # 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: if submission_type is not None:
model = cls.find_polymorphic_subclass(polymorphic_identity=submission_type) model = cls.find_polymorphic_subclass(polymorphic_identity=submission_type)
elif len(kwargs) > 0: elif len(kwargs) > 0:
@@ -1139,7 +1140,7 @@ class BasicSubmission(BaseClass):
# if chronologic: # if chronologic:
# logger.debug("Attempting sort by date descending") # logger.debug("Attempting sort by date descending")
query = query.order_by(cls.submitted_date.desc()) query = query.order_by(cls.submitted_date.desc())
if page_size is not None: if page_size > 0:
query = query.limit(page_size) query = query.limit(page_size)
page = page - 1 page = page - 1
if page is not None: if page is not None:

View File

@@ -96,7 +96,7 @@ class SheetParser(object):
parser = ReagentParser(xl=self.xl, submission_type=self.submission_type, parser = ReagentParser(xl=self.xl, submission_type=self.submission_type,
extraction_kit=extraction_kit) extraction_kit=extraction_kit)
self.sub['reagents'] = parser.parse_reagents() 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): def parse_samples(self):
""" """
@@ -273,11 +273,11 @@ class ReagentParser(object):
self.kit_object = KitType.query(name=extraction_kit) self.kit_object = KitType.query(name=extraction_kit)
logger.debug(f"Got extraction kit object: {self.kit_object}") logger.debug(f"Got extraction kit object: {self.kit_object}")
self.map = self.fetch_kit_info_map(submission_type=submission_type) 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 self.xl = xl
@report_result @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 Gets location of kit reagents from database
@@ -296,15 +296,24 @@ class ReagentParser(object):
except KeyError: except KeyError:
pass pass
# logger.debug(f"Reagent map: {pformat(reagent_map)}") # 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(): if not reagent_map.keys():
try: temp_kit_object = self.submission_type_obj.get_default_kit()
ext_kit_loc = self.submission_type_obj.info_map['extraction_kit']['read'][0] if temp_kit_object:
location_string = f"Sheet: {ext_kit_loc['sheet']}, Row: {ext_kit_loc['row']}, Column: {ext_kit_loc['column']}?" self.kit_object = temp_kit_object
except: reagent_map = {k: v for k, v in self.kit_object.construct_xl_map_for_use(submission_type)}
location_string = "" logger.warning(f"Attempting to salvage {self.kit_object} with default kit map: {reagent_map}")
report.add_result(Result(owner=__name__, code=0, msg=f"No kit map found for {self.kit_object.name}.\n\n" if not reagent_map.keys():
f"Are you sure you put the right kit in:\n\n{location_string}?", logger.error(f"Still no reagent map, displaying error.")
status="Critical")) 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 return report, reagent_map
def parse_reagents(self) -> Generator[dict, None, None]: def parse_reagents(self) -> Generator[dict, None, None]:
@@ -317,7 +326,7 @@ class ReagentParser(object):
for sheet in self.xl.sheetnames: for sheet in self.xl.sheetnames:
ws = self.xl[sheet] ws = self.xl[sheet]
relevant = {k.strip(): v for k, v in self.map.items() if sheet in self.map[k]['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 == {}: if relevant == {}:
continue continue
for item in relevant: for item in relevant:

View File

@@ -18,10 +18,12 @@ env = jinja_template_loading()
class ReportMaker(object): 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.start_date = start_date
self.end_date = end_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: if organizations is not None:
self.subs = [sub for sub in self.subs if sub.submitting_lab.name in organizations] 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() 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}") # logger.debug(f"Output daftaframe for xlsx: {df2.columns}")
df = df.drop('id', axis=1) df = df.drop('id', axis=1)
df = df.sort_values(['submitting_lab', "submitted_date"]) df = df.sort_values(['submitting_lab', "submitted_date"])
logger.debug(f"Details dataframe:\n{df2}")
return df, df2 return df, df2
def make_report_html(self, df: DataFrame) -> str: def make_report_html(self, df: DataFrame) -> str:
@@ -97,7 +100,7 @@ class ReportMaker(object):
Args: Args:
filename (Path | str): Basename of output file filename (Path | str): Basename of output file
obj (QWidget | None, optional): Parent object. Defaults to None. obj (QWidget | None, optional): Parent object. Defaults to None.
""" """
if isinstance(filename, str): if isinstance(filename, str):
filename = Path(filename) filename = Path(filename)
filename = filename.absolute() filename = filename.absolute()
@@ -111,7 +114,7 @@ class ReportMaker(object):
def fix_up_xl(self): def fix_up_xl(self):
""" """
Handles formatting of xl file, mediocrely. Handles formatting of xl file, mediocrely.
""" """
# logger.debug(f"Updating worksheet") # logger.debug(f"Updating worksheet")
worksheet: Worksheet = self.writer.sheets['Report'] worksheet: Worksheet = self.writer.sheets['Report']
for idx, col in enumerate(self.summary_df, start=1): # NOTE: loop through all columns 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']: for cell in worksheet['D']:
if cell.row > 1: if cell.row > 1:
cell.style = 'Currency' cell.style = 'Currency'

View File

@@ -51,6 +51,9 @@ class SheetWriter(object):
# except Exception as e: # except Exception as e:
# logger.error(f"Couldn't open workbook due to {e}") # logger.error(f"Couldn't open workbook due to {e}")
template = self.submission_type.template_file 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)) workbook = load_workbook(BytesIO(template))
# self.workbook = workbook # self.workbook = workbook
self.xl = workbook self.xl = workbook

View File

@@ -1,6 +1,7 @@
""" """
Constructs main application. Constructs main application.
""" """
import os
from pprint import pformat from pprint import pformat
from PyQt6.QtCore import qInstallMessageHandler from PyQt6.QtCore import qInstallMessageHandler
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
@@ -213,7 +214,7 @@ class App(QMainWindow):
None None
""" """
if check_if_app(): 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: else:
yaml_path = project_path.joinpath("src", "submissions", "resources", "viral_culture.yml") 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") fname = select_save_file(obj=self, default_name="Submission Type Template.yml", extension="yml")

View File

@@ -225,7 +225,8 @@ class SubmissionFormWidget(QWidget):
if k == "extraction_kit": if k == "extraction_kit":
add_widget.input.currentTextChanged.connect(self.scrape_reagents) add_widget.input.currentTextChanged.connect(self.scrape_reagents)
self.setStyleSheet(main_form_style) 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, 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, extraction_kit: str | None = None, sub_obj: BasicSubmission | None = None,
@@ -275,9 +276,9 @@ class SubmissionFormWidget(QWidget):
Returns: Returns:
Tuple[QMainWindow, dict]: Updated application and result Tuple[QMainWindow, dict]: Updated application and result
""" """
extraction_kit = args[0] self.extraction_kit = args[0]
report = Report() report = Report()
logger.debug(f"Extraction kit: {extraction_kit}") logger.debug(f"Extraction kit: {self.extraction_kit}")
# NOTE: Remove previous reagent widgets # NOTE: Remove previous reagent widgets
try: try:
old_reagents = self.find_widgets() old_reagents = self.find_widgets()
@@ -288,10 +289,11 @@ class SubmissionFormWidget(QWidget):
for reagent in old_reagents: for reagent in old_reagents:
if isinstance(reagent, self.ReagentFormWidget) or isinstance(reagent, QPushButton): if isinstance(reagent, self.ReagentFormWidget) or isinstance(reagent, QPushButton):
reagent.setParent(None) 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)}") logger.debug(f"Got reagents: {pformat(reagents)}")
for reagent in 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) self.layout.addWidget(add_widget)
report.add_result(integrity_report) report.add_result(integrity_report)
# logger.debug(f"Outgoing report: {report.results}") # logger.debug(f"Outgoing report: {report.results}")
@@ -569,6 +571,7 @@ class SubmissionFormWidget(QWidget):
obj.ext_kit = uses[0] obj.ext_kit = uses[0]
add_widget.addItems(uses) add_widget.addItems(uses)
add_widget.setToolTip("Select extraction kit.") add_widget.setToolTip("Select extraction kit.")
parent.extraction_kit = add_widget.currentText()
case 'submission_category': case 'submission_category':
add_widget = MyQComboBox(scrollWidget=parent) add_widget = MyQComboBox(scrollWidget=parent)
cats = ['Diagnostic', "Surveillance", "Research"] cats = ['Diagnostic', "Surveillance", "Research"]

View File

@@ -56,6 +56,7 @@ class Summary(QWidget):
# NOTE: convert to python useable date objects # NOTE: convert to python useable date objects
self.start_date = self.datepicker.start_date.date().toPyDate() self.start_date = self.datepicker.start_date.date().toPyDate()
self.end_date = self.datepicker.end_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.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs)
self.webview.setHtml(self.report_obj.html) self.webview.setHtml(self.report_obj.html)
if self.report_obj.subs: if self.report_obj.subs: