Mid-code cleanup

This commit is contained in:
lwark
2024-12-05 11:19:47 -06:00
parent 51cb5c41a4
commit 5fc02ffeec
8 changed files with 176 additions and 83 deletions

View File

@@ -24,8 +24,8 @@ def set_sqlite_pragma(dbapi_connection, connection_record):
if ctx.database_schema == "sqlite": if ctx.database_schema == "sqlite":
execution_phrase = "PRAGMA foreign_keys=ON" execution_phrase = "PRAGMA foreign_keys=ON"
# cursor.execute(execution_phrase) # cursor.execute(execution_phrase)
elif ctx.database_schema == "mssql+pyodbc": # elif ctx.database_schema == "mssql+pyodbc":
execution_phrase = "SET IDENTITY_INSERT dbo._wastewater ON;" # execution_phrase = "SET IDENTITY_INSERT dbo._wastewater ON;"
else: else:
print("Nothing to execute, returning") print("Nothing to execute, returning")
cursor.close() cursor.close()

View File

@@ -261,9 +261,9 @@ class KitType(BaseClass):
Returns: Returns:
dict: Dictionary containing relevant info for SubmissionType construction dict: Dictionary containing relevant info for SubmissionType construction
""" """
base_dict = dict(name=self.name) base_dict = dict(name=self.name, reagent_roles=[], equipment_roles=[])
base_dict['reagent roles'] = [] # base_dict['reagent roles'] = []
base_dict['equipment roles'] = [] # base_dict['equipment roles'] = []
for k, v in self.construct_xl_map_for_use(submission_type=submission_type): for k, v in self.construct_xl_map_for_use(submission_type=submission_type):
# logger.debug(f"Value: {v}") # logger.debug(f"Value: {v}")
try: try:
@@ -272,17 +272,17 @@ class KitType(BaseClass):
continue continue
for kk, vv in assoc.to_export_dict().items(): for kk, vv in assoc.to_export_dict().items():
v[kk] = vv v[kk] = vv
base_dict['reagent roles'].append(v) base_dict['reagent_roles'].append(v)
for k, v in submission_type.construct_field_map("equipment"): for k, v in submission_type.construct_field_map("equipment"):
try: try:
assoc = next(item for item in submission_type.submissiontype_equipmentrole_associations if assoc = next(item for item in submission_type.submissiontype_equipmentrole_associations if
item.equipment_role.name == k) item.equipment_role.name == k)
except StopIteration: except StopIteration:
continue continue
for kk, vv in assoc.to_export_dict(kit_type=self).items(): for kk, vv in assoc.to_export_dict(extraction_kit=self).items():
# logger.debug(f"{kk}:{vv}") # logger.debug(f"{kk}:{vv}")
v[kk] = vv v[kk] = vv
base_dict['equipment roles'].append(v) base_dict['equipment_roles'].append(v)
# logger.debug(f"KT returning {base_dict}") # logger.debug(f"KT returning {base_dict}")
return base_dict return base_dict
@@ -360,12 +360,12 @@ class ReagentRole(BaseClass):
assert reagent.role assert reagent.role
# logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}") # logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}")
# logger.debug(f"Kit reagent types: {kit_type.reagent_types}") # logger.debug(f"Kit reagent types: {kit_type.reagent_types}")
result = list(set(kit_type.reagent_roles).intersection(reagent.role)) result = set(kit_type.reagent_roles).intersection(reagent.role)
# logger.debug(f"Result: {result}") # logger.debug(f"Result: {result}")
try: # try:
return result[0] return next((item for item in result), None)
except IndexError: # except IndexError:
return None # return None
match name: match name:
case str(): case str():
# logger.debug(f"Looking up reagent type by name str: {name}") # logger.debug(f"Looking up reagent type by name str: {name}")
@@ -445,12 +445,8 @@ class Reagent(BaseClass, LogMixin):
if extraction_kit is not None: if extraction_kit is not None:
# NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType # NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType
reagent_role = next((item for item in set(self.role).intersection(extraction_kit.reagent_roles)), self.role[0]) reagent_role = next((item for item in set(self.role).intersection(extraction_kit.reagent_roles)),
# try: self.role[0])
# reagent_role = list(set(self.role).intersection(extraction_kit.reagent_roles))[0]
# # NOTE: Most will be able to fall back to first ReagentType in itself because most will only have 1.
# except:
# reagent_role = self.role[0]
else: else:
try: try:
reagent_role = self.role[0] reagent_role = self.role[0]
@@ -580,11 +576,14 @@ class Reagent(BaseClass, LogMixin):
for key, value in vars.items(): for key, value in vars.items():
match key: match key:
case "expiry": case "expiry":
if not isinstance(value, date): if isinstance(value, str):
field_value = datetime.strptime(value, "%Y-%m-%d").date field_value = datetime.strptime(value, "%Y-%m-%d")
field_value.replace(tzinfo=timezone) # field_value.replace(tzinfo=timezone)
elif isinstance(value, date):
field_value = datetime.combine(value, datetime.min.time())
else: else:
field_value = value field_value = value
field_value.replace(tzinfo=timezone)
case "role": case "role":
continue continue
case _: case _:
@@ -792,6 +791,15 @@ class SubmissionType(BaseClass):
return self.sample_map return self.sample_map
def construct_field_map(self, field: Literal['equipment', 'tip']) -> Generator[(str, dict), None, None]: def construct_field_map(self, field: Literal['equipment', 'tip']) -> Generator[(str, dict), None, None]:
"""
Make a map of all locations for tips or equipment.
Args:
field (Literal['equipment', 'tip']): the field to construct a map for
Returns:
Generator[(str, dict), None, None]: Generator composing key, locations for each item in the map
"""
for item in self.__getattribute__(f"submissiontype_{field}role_associations"): for item in self.__getattribute__(f"submissiontype_{field}role_associations"):
fmap = item.uses fmap = item.uses
if fmap is None: if fmap is None:
@@ -799,6 +807,12 @@ class SubmissionType(BaseClass):
yield getattr(item, f"{field}_role").name, fmap yield getattr(item, f"{field}_role").name, fmap
def get_default_kit(self) -> KitType | None: def get_default_kit(self) -> KitType | None:
"""
If only one kits exists for this Submission Type, return it.
Returns:
KitType | None:
"""
if len(self.kit_types) == 1: if len(self.kit_types) == 1:
return self.kit_types[0] return self.kit_types[0]
else: else:
@@ -1379,7 +1393,7 @@ class Equipment(BaseClass):
else: else:
return {k: v for k, v in self.__dict__.items()} return {k: v for k, v in self.__dict__.items()}
def get_processes(self, submission_type: SubmissionType, extraction_kit: str | KitType | None = None) -> List[str]: def get_processes(self, submission_type: str | SubmissionType | None = None, extraction_kit: str | KitType | None = None) -> List[str]:
""" """
Get all processes associated with this Equipment for a given SubmissionType Get all processes associated with this Equipment for a given SubmissionType
@@ -1390,23 +1404,35 @@ class Equipment(BaseClass):
Returns: Returns:
List[Process]: List of process names List[Process]: List of process names
""" """
processes = [process for process in self.processes if submission_type in process.submission_types] if isinstance(submission_type, str):
match extraction_kit: submission_type = SubmissionType.query(name=submission_type)
case str(): if isinstance(extraction_kit, str):
# logger.debug(f"Filtering processes by extraction_kit str {extraction_kit}") extraction_kit = KitType.query(name=extraction_kit)
processes = [process for process in processes if for process in self.processes:
extraction_kit in [kit.name for kit in process.kit_types]] if submission_type not in process.submission_types:
case KitType(): continue
# logger.debug(f"Filtering processes by extraction_kit KitType {extraction_kit}") if extraction_kit and extraction_kit not in process.kit_types:
processes = [process for process in processes if extraction_kit in process.kit_types] continue
case _: yield process
pass # processes = (process for process in self.processes if submission_type in process.submission_types)
# NOTE: Convert to strings # match extraction_kit:
processes = [process.name for process in processes] # case str():
assert all([isinstance(process, str) for process in processes]) # # logger.debug(f"Filtering processes by extraction_kit str {extraction_kit}")
if len(processes) == 0: # processes = (process for process in processes if
processes = [''] # extraction_kit in [kit.name for kit in process.kit_types])
return processes # case KitType():
# # logger.debug(f"Filtering processes by extraction_kit KitType {extraction_kit}")
# processes = (process for process in processes if extraction_kit in process.kit_types)
# case _:
# pass
# # NOTE: Convert to strings
# # processes = [process.name for process in processes]
# # assert all([isinstance(process, str) for process in processes])
# # if len(processes) == 0:
# # processes = ['']
# # return processes
# for process in processes:
# yield process.name
@classmethod @classmethod
@setup_lookup @setup_lookup
@@ -1452,8 +1478,7 @@ class Equipment(BaseClass):
pass pass
return cls.execute_query(query=query, limit=limit) return cls.execute_query(query=query, limit=limit)
def to_pydantic(self, submission_type: SubmissionType, def to_pydantic(self, submission_type: SubmissionType, extraction_kit: str | KitType | None = None,
extraction_kit: str | KitType | None = None,
role: str = None) -> "PydEquipment": role: str = None) -> "PydEquipment":
""" """
Creates PydEquipment of this Equipment Creates PydEquipment of this Equipment
@@ -1466,8 +1491,8 @@ class Equipment(BaseClass):
PydEquipment: pydantic equipment object PydEquipment: pydantic equipment object
""" """
from backend.validators.pydant import PydEquipment from backend.validators.pydant import PydEquipment
return PydEquipment( processes = self.get_processes(submission_type=submission_type, extraction_kit=extraction_kit)
processes=self.get_processes(submission_type=submission_type, extraction_kit=extraction_kit), role=role, return PydEquipment(processes=processes, role=role,
**self.to_dict(processes=False)) **self.to_dict(processes=False))
@classmethod @classmethod
@@ -1603,7 +1628,7 @@ class EquipmentRole(BaseClass):
return cls.execute_query(query=query, limit=limit) return cls.execute_query(query=query, limit=limit)
def get_processes(self, submission_type: str | SubmissionType | None, def get_processes(self, submission_type: str | SubmissionType | None,
extraction_kit: str | KitType | None = None) -> List[Process]: extraction_kit: str | KitType | None = None) -> Generator[Process, None, None]:
""" """
Get processes used by this EquipmentRole Get processes used by this EquipmentRole
@@ -1612,30 +1637,38 @@ class EquipmentRole(BaseClass):
extraction_kit (str | KitType | None, optional): KitType of interest. Defaults to None. extraction_kit (str | KitType | None, optional): KitType of interest. Defaults to None.
Returns: Returns:
List[Process]: _description_ List[Process]: List of processes
""" """
if isinstance(submission_type, str): if isinstance(submission_type, str):
# logger.debug(f"Checking if str {submission_type} exists") # logger.debug(f"Checking if str {submission_type} exists")
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
if submission_type is not None: if isinstance(extraction_kit, str):
# logger.debug("Getting all processes for this EquipmentRole") extraction_kit = KitType.query(name=extraction_kit)
processes = [process for process in self.processes if submission_type in process.submission_types] for process in self.processes:
else: if submission_type and submission_type not in process.submission_types:
processes = self.processes continue
match extraction_kit: if extraction_kit and extraction_kit not in process.kit_types:
case str(): continue
# logger.debug(f"Filtering processes by extraction_kit str {extraction_kit}") yield process.name
processes = [item for item in processes if extraction_kit in [kit.name for kit in item.kit_types]] # if submission_type is not None:
case KitType(): # # logger.debug("Getting all processes for this EquipmentRole")
# logger.debug(f"Filtering processes by extraction_kit KitType {extraction_kit}") # processes = [process for process in self.processes if submission_type in process.submission_types]
processes = [item for item in processes if extraction_kit in [kit for kit in item.kit_types]] # else:
case _: # processes = self.processes
pass # match extraction_kit:
output = [item.name for item in processes] # case str():
if len(output) == 0: # # logger.debug(f"Filtering processes by extraction_kit str {extraction_kit}")
return [''] # processes = [item for item in processes if extraction_kit in [kit.name for kit in item.kit_types]]
else: # case KitType():
return output # # logger.debug(f"Filtering processes by extraction_kit KitType {extraction_kit}")
# processes = [item for item in processes if extraction_kit in [kit for kit in item.kit_types]]
# case _:
# pass
# output = [item.name for item in processes]
# if len(output) == 0:
# return ['']
# else:
# return output
def to_export_dict(self, submission_type: SubmissionType, kit_type: KitType): def to_export_dict(self, submission_type: SubmissionType, kit_type: KitType):
""" """
@@ -1644,8 +1677,8 @@ class EquipmentRole(BaseClass):
Returns: Returns:
dict: dictionary of Association and related reagent role dict: dictionary of Association and related reagent role
""" """
return dict(role=self.name, processes = self.get_processes(submission_type=submission_type, extraction_kit=kit_type)
processes=self.get_processes(submission_type=submission_type, extraction_kit=kit_type)) return dict(role=self.name, processes=[item for item in processes])
class SubmissionEquipmentAssociation(BaseClass): class SubmissionEquipmentAssociation(BaseClass):

View File

@@ -1074,6 +1074,7 @@ class BasicSubmission(BaseClass, LogMixin):
@setup_lookup @setup_lookup
def query(cls, def query(cls,
submission_type: str | SubmissionType | None = None, submission_type: str | SubmissionType | None = None,
submission_type_name: str|None = None,
id: int | str | None = None, id: int | str | None = None,
rsl_plate_num: str | None = None, rsl_plate_num: str | None = None,
start_date: date | str | int | None = None, start_date: date | str | int | None = None,
@@ -1173,6 +1174,11 @@ class BasicSubmission(BaseClass, LogMixin):
limit = 1 limit = 1
case _: case _:
pass pass
match submission_type_name:
case str():
query = query.filter(model.submission_type_name == submission_type_name)
case _:
pass
# NOTE: by id (returns only a single value) # NOTE: by id (returns only a single value)
match id: match id:
case int(): case int():
@@ -1389,13 +1395,23 @@ class BasicSubmission(BaseClass, LogMixin):
@classmethod @classmethod
def calculate_turnaround(cls, start_date:date|None=None, end_date:date|None=None) -> Tuple[int|None, bool|None]: def calculate_turnaround(cls, start_date:date|None=None, end_date:date|None=None) -> Tuple[int|None, bool|None]:
if 'pytest' not in sys.modules:
from tools import ctx
else:
from test_settings import ctx
if not end_date: if not end_date:
return None, None return None, None
try: try:
delta = np.busday_count(start_date, end_date, holidays=create_holidays_for_year(start_date.year)) + 1 delta = np.busday_count(start_date, end_date, holidays=create_holidays_for_year(start_date.year)) + 1
except ValueError: except ValueError:
return None, None return None, None
return delta, delta <= ctx.TaT_threshold try:
tat = cls.get_default_info("turnaround_time")
except (AttributeError, KeyError):
tat = None
if not tat:
tat = ctx.TaT_threshold
return delta, delta <= tat
# Below are the custom submission types # Below are the custom submission types

View File

@@ -142,18 +142,18 @@ class ReportMaker(object):
class TurnaroundMaker(object): class TurnaroundMaker(object):
def __init__(self, start_date: date, end_date: date): def __init__(self, start_date: date, end_date: date, submission_type:str):
self.start_date = start_date self.start_date = start_date
self.end_date = end_date self.end_date = end_date
# NOTE: Set page size to zero to override limiting query size. # 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) self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date, submission_type_name=submission_type, page_size=0)
records = [self.build_record(sub) for sub in self.subs] records = [self.build_record(sub) for sub in self.subs]
self.df = DataFrame.from_records(records) self.df = DataFrame.from_records(records)
@classmethod @classmethod
def build_record(cls, sub): def build_record(cls, sub):
days, tat_ok = sub.get_turnaround_time() days, tat_ok = sub.get_turnaround_time()
return dict(name=sub.rsl_plate_num, days=days, submitted_date=sub.submitted_date, return dict(name=str(sub.rsl_plate_num), days=days, submitted_date=sub.submitted_date,
completed_date=sub.completed_date, acceptable=tat_ok) completed_date=sub.completed_date, acceptable=tat_ok)
def write_report(self, filename: Path | str, obj: QWidget | None = None): def write_report(self, filename: Path | str, obj: QWidget | None = None):

View File

@@ -9,6 +9,7 @@ from datetime import date, datetime, timedelta
from dateutil.parser import parse from dateutil.parser import parse
from dateutil.parser import ParserError from dateutil.parser import ParserError
from typing import List, Tuple, Literal from typing import List, Tuple, Literal
from types import GeneratorType
from . import RSLNamer from . import RSLNamer
from pathlib import Path from pathlib import Path
from tools import check_not_nan, convert_nans_to_nones, Report, Result, timezone from tools import check_not_nan, convert_nans_to_nones, Report, Result, timezone
@@ -343,6 +344,8 @@ class PydEquipment(BaseModel, extra='ignore'):
@classmethod @classmethod
def make_empty_list(cls, value): def make_empty_list(cls, value):
# logger.debug(f"Pydantic value: {value}") # logger.debug(f"Pydantic value: {value}")
if isinstance(value, GeneratorType):
value = [item.name for item in value]
value = convert_nans_to_nones(value) value = convert_nans_to_nones(value)
if not value: if not value:
value = [''] value = ['']
@@ -380,6 +383,7 @@ class PydEquipment(BaseModel, extra='ignore'):
assoc = None assoc = None
if assoc is None: if assoc is None:
assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment) assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment)
# TODO: This seems precarious. What if there is more than one process?
process = Process.query(name=self.processes[0]) process = Process.query(name=self.processes[0])
if process is None: if process is None:
logger.error(f"Found unknown process: {process}.") logger.error(f"Found unknown process: {process}.")
@@ -1122,6 +1126,13 @@ class PydEquipmentRole(BaseModel):
equipment: List[PydEquipment] equipment: List[PydEquipment]
processes: List[str] | None processes: List[str] | None
@field_validator("processes", mode="before")
@classmethod
def expand_processes(cls, value):
if isinstance(value, GeneratorType):
value = [item for item in value]
return value
def to_form(self, parent, used: list) -> "RoleComboBox": def to_form(self, parent, used: list) -> "RoleComboBox":
""" """
Creates a widget for user input into this class. Creates a widget for user input into this class.

View File

@@ -11,25 +11,33 @@ logger = logging.getLogger(f"submissions.{__name__}")
class TurnaroundChart(CustomFigure): class TurnaroundChart(CustomFigure):
def __init__(self, df: pd.DataFrame, modes: list, settings: dict, ytitle: str | None = None, def __init__(self, df: pd.DataFrame, modes: list, settings: dict, threshold: float | None = None,
ytitle: str | None = None,
parent: QWidget | None = None, parent: QWidget | None = None,
months: int = 6): months: int = 6):
super().__init__(df=df, modes=modes, settings=settings) super().__init__(df=df, modes=modes, settings=settings)
self.df = df
try: try:
months = int(settings['months']) months = int(settings['months'])
except KeyError: except KeyError:
months = 6 months = 6
# logger.debug(f"DF: {self.df}") # logger.debug(f"DF: {self.df}")
self.construct_chart(df=df) self.construct_chart()
self.add_hline(y=3.5) if threshold:
self.add_hline(y=threshold)
# self.update_xaxes() # self.update_xaxes()
self.update_layout(showlegend=False) self.update_layout(showlegend=False)
def construct_chart(self, df: pd.DataFrame): def construct_chart(self, df: pd.DataFrame | None = None):
if df:
self.df = df
# logger.debug(f"PCR df:\n {df}") # logger.debug(f"PCR df:\n {df}")
df = df.sort_values(by=['submitted_date', 'name']) self.df = self.df[self.df.days.notnull()]
self.df = self.df.sort_values(['submitted_date', 'name'], ascending=[True, True]).reset_index(drop=True)
self.df = self.df.reset_index().rename(columns={"index": "idx"})
# logger.debug(f"DF: {self.df}")
try: try:
scatter = px.scatter(data_frame=df, x='name', y="days", scatter = px.scatter(data_frame=self.df, x='idx', y="days",
hover_data=["name", "submitted_date", "completed_date", "days"], hover_data=["name", "submitted_date", "completed_date", "days"],
color="acceptable", color_discrete_map={True: "green", False: "red"} color="acceptable", color_discrete_map={True: "green", False: "red"}
) )
@@ -37,3 +45,12 @@ class TurnaroundChart(CustomFigure):
scatter = px.scatter() scatter = px.scatter()
self.add_traces(scatter.data) self.add_traces(scatter.data)
self.update_traces(marker={'size': 15}) self.update_traces(marker={'size': 15})
tickvals = self.df['idx'].tolist()
ticklabels = self.df['name'].tolist()
self.update_layout(
xaxis=dict(
tickmode='array',
tickvals=tickvals,
ticktext=ticklabels,
)
)

View File

@@ -8,7 +8,7 @@ from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtCore import Qt, pyqtSlot
from jinja2 import TemplateNotFound from jinja2 import TemplateNotFound
from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType
from tools import is_power_user, jinja_template_loading from tools import is_power_user, jinja_template_loading, timezone
from .functions import select_save_file from .functions import select_save_file
from .misc import save_pdf from .misc import save_pdf
from pathlib import Path from pathlib import Path
@@ -176,7 +176,8 @@ class SubmissionDetails(QDialog):
if isinstance(submission, str): if isinstance(submission, str):
submission = BasicSubmission.query(rsl_plate_num=submission) submission = BasicSubmission.query(rsl_plate_num=submission)
submission.signed_by = getuser() submission.signed_by = getuser()
submission.completed_date = datetime.now().date() submission.completed_date = datetime.now()
submission.completed_date.replace(tzinfo=timezone)
submission.save() submission.save()
self.submission_details(submission=self.rsl_plate_num) self.submission_details(submission=self.rsl_plate_num)

View File

@@ -1,10 +1,10 @@
from PyQt6.QtCore import QSignalBlocker from PyQt6.QtCore import QSignalBlocker
from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QLabel from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QLabel, QComboBox
from .info_tab import InfoPane from .info_tab import InfoPane
from backend.excel.reports import TurnaroundMaker from backend.excel.reports import TurnaroundMaker
from pandas import DataFrame from pandas import DataFrame
from backend.db import BasicSubmission from backend.db import BasicSubmission, SubmissionType
from frontend.visualizations.turnaround_chart import TurnaroundChart from frontend.visualizations.turnaround_chart import TurnaroundChart
import logging import logging
@@ -17,6 +17,11 @@ class TurnaroundTime(InfoPane):
super().__init__(parent) super().__init__(parent)
self.chart = None self.chart = None
self.report_object = None self.report_object = None
self.submission_typer = QComboBox(self)
subs = ["Any"] + [item.name for item in SubmissionType.query()]
self.submission_typer.addItems(subs)
self.layout.addWidget(self.submission_typer, 1, 1, 1, 3)
self.submission_typer.currentTextChanged.connect(self.date_changed)
self.date_changed() self.date_changed()
def date_changed(self): def date_changed(self):
@@ -31,6 +36,16 @@ class TurnaroundTime(InfoPane):
return return
super().date_changed() super().date_changed()
chart_settings = dict(start_date=self.start_date, end_date=self.end_date) chart_settings = dict(start_date=self.start_date, end_date=self.end_date)
self.report_obj = TurnaroundMaker(start_date=self.start_date, end_date=self.end_date) if self.submission_typer.currentText() == "Any":
self.chart = TurnaroundChart(df=self.report_obj.df, settings=chart_settings, modes=[]) submission_type = None
subtype_obj = None
else:
submission_type = self.submission_typer.currentText()
subtype_obj = SubmissionType.query(name = submission_type)
self.report_obj = TurnaroundMaker(start_date=self.start_date, end_date=self.end_date, submission_type=submission_type)
if subtype_obj:
threshold = subtype_obj.defaults['turnaround_time'] + 0.5
else:
threshold = None
self.chart = TurnaroundChart(df=self.report_obj.df, settings=chart_settings, modes=[], threshold=threshold)
self.webview.setHtml(self.chart.to_html()) self.webview.setHtml(self.chart.to_html())