Updated controls to both Irida and PCR.

This commit is contained in:
lwark
2024-10-16 15:07:43 -05:00
parent 066d1af0f2
commit c3a4aac68b
11 changed files with 750 additions and 314 deletions

View File

@@ -4,6 +4,7 @@ Handles display of control charts
import re
import sys
from datetime import timedelta, date
from pprint import pformat
from typing import Tuple
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import (
@@ -11,20 +12,26 @@ from PyQt6.QtWidgets import (
QDateEdit, QLabel, QSizePolicy, QPushButton, QGridLayout
)
from PyQt6.QtCore import QSignalBlocker
from backend.db import ControlType, Control
from backend.db import ControlType, IridaControl
from PyQt6.QtCore import QDate, QSize
import logging
from pandas import DataFrame
from tools import Report, Result, get_unique_values_in_df_column, Settings, report_result
from frontend.visualizations.control_charts import CustomFigure
from frontend.visualizations import IridaFigure, PCRFigure
from .misc import StartEndDatePicker
logger = logging.getLogger(f"submissions.{__name__}")
class ControlsViewer(QWidget):
def __init__(self, parent: QWidget) -> None:
def __init__(self, parent: QWidget, archetype: str) -> None:
super().__init__(parent)
logger.debug(f"Incoming Archetype: {archetype}")
self.archetype = ControlType.query(name=archetype)
if not self.archetype:
return
logger.debug(f"Archetype set as: {self.archetype}")
self.app = self.parent().parent()
# logger.debug(f"\n\n{self.app}\n\n")
self.report = Report()
@@ -32,51 +39,53 @@ class ControlsViewer(QWidget):
self.webengineview = QWebEngineView()
# NOTE: set tab2 layout
self.layout = QGridLayout(self)
self.control_typer = QComboBox()
self.control_sub_typer = QComboBox()
# NOTE: fetch types of controls
con_types = [item.name for item in ControlType.query()]
self.control_typer.addItems(con_types)
con_sub_types = [item for item in self.archetype.targets.keys()]
self.control_sub_typer.addItems(con_sub_types)
# NOTE: create custom widget to get types of analysis
self.mode_typer = QComboBox()
mode_types = Control.get_modes()
mode_types = IridaControl.get_modes()
self.mode_typer.addItems(mode_types)
# NOTE: create custom widget to get subtypes of analysis
self.sub_typer = QComboBox()
self.sub_typer.setEnabled(False)
self.mode_sub_typer = QComboBox()
self.mode_sub_typer.setEnabled(False)
# NOTE: add widgets to tab2 layout
self.layout.addWidget(self.datepicker, 0,0,1,2)
self.layout.addWidget(self.datepicker, 0, 0, 1, 2)
self.save_button = QPushButton("Save Chart", parent=self)
self.layout.addWidget(self.save_button, 0,2,1,1)
self.layout.addWidget(self.control_typer, 1,0,1,3)
self.layout.addWidget(self.mode_typer, 2,0,1,3)
self.layout.addWidget(self.sub_typer, 3,0,1,3)
self.layout.addWidget(self.webengineview, 4,0,1,3)
self.layout.addWidget(self.save_button, 0, 2, 1, 1)
self.layout.addWidget(self.control_sub_typer, 1, 0, 1, 3)
self.layout.addWidget(self.mode_typer, 2, 0, 1, 3)
self.layout.addWidget(self.mode_sub_typer, 3, 0, 1, 3)
self.archetype.get_instance_class().make_parent_buttons(parent=self)
self.layout.addWidget(self.webengineview, self.layout.rowCount(), 0, 1, 3)
self.setLayout(self.layout)
self.controls_getter()
self.control_typer.currentIndexChanged.connect(self.controls_getter)
self.mode_typer.currentIndexChanged.connect(self.controls_getter)
self.datepicker.start_date.dateChanged.connect(self.controls_getter)
self.datepicker.end_date.dateChanged.connect(self.controls_getter)
self.controls_getter_function()
self.control_sub_typer.currentIndexChanged.connect(self.controls_getter_function)
self.mode_typer.currentIndexChanged.connect(self.controls_getter_function)
self.datepicker.start_date.dateChanged.connect(self.controls_getter_function)
self.datepicker.end_date.dateChanged.connect(self.controls_getter_function)
self.save_button.pressed.connect(self.save_chart_function)
def save_chart_function(self):
self.fig.save_figure(parent=self)
def controls_getter(self):
"""
Lookup controls from database and send to chartmaker
"""
self.controls_getter_function()
# def controls_getter(self):
# """
# Lookup controls from database and send to chartmaker
# """
# self.controls_getter_function()
@report_result
def controls_getter_function(self):
def controls_getter_function(self, *args, **kwargs):
"""
Get controls based on start/end dates
"""
report = Report()
# NOTE: subtype defaults to disabled
# NOTE: mode_sub_type defaults to disabled
try:
self.sub_typer.disconnect()
self.mode_sub_typer.disconnect()
except TypeError:
pass
# NOTE: correct start date being more recent than end date and rerun
@@ -93,37 +102,32 @@ class ControlsViewer(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()
self.con_type = self.control_typer.currentText()
self.con_sub_type = self.control_sub_typer.currentText()
self.mode = self.mode_typer.currentText()
self.sub_typer.clear()
self.mode_sub_typer.clear()
# NOTE: lookup subtypes
try:
sub_types = ControlType.query(name=self.con_type).get_subtypes(mode=self.mode)
sub_types = self.archetype.get_modes(mode=self.mode)
except AttributeError:
sub_types = []
if sub_types != []:
# NOTE: block signal that will rerun controls getter and update sub_typer
with QSignalBlocker(self.sub_typer) as blocker:
self.sub_typer.addItems(sub_types)
self.sub_typer.setEnabled(True)
self.sub_typer.currentTextChanged.connect(self.chart_maker)
if sub_types:
# NOTE: block signal that will rerun controls getter and update mode_sub_typer
with QSignalBlocker(self.mode_sub_typer) as blocker:
self.mode_sub_typer.addItems(sub_types)
self.mode_sub_typer.setEnabled(True)
self.mode_sub_typer.currentTextChanged.connect(self.chart_maker_function)
else:
self.sub_typer.clear()
self.sub_typer.setEnabled(False)
self.chart_maker()
self.mode_sub_typer.clear()
self.mode_sub_typer.setEnabled(False)
self.chart_maker_function()
return report
def diff_month(self, d1:date, d2:date):
def diff_month(self, d1: date, d2: date):
return abs((d1.year - d2.year) * 12 + d1.month - d2.month)
def chart_maker(self):
"""
Creates plotly charts for webview
"""
self.chart_maker_function()
@report_result
def chart_maker_function(self):
def chart_maker_function(self, *args, **kwargs):
# TODO: Generalize this by moving as much code as possible to IridaControl
"""
Create html chart for controls reporting
@@ -134,44 +138,26 @@ class ControlsViewer(QWidget):
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
"""
report = Report()
# logger.debug(f"Control getter context: \n\tControl type: {self.con_type}\n\tMode: {self.mode}\n\tStart
# Date: {self.start_date}\n\tEnd Date: {self.end_date}")
# NOTE: set the subtype for kraken
if self.sub_typer.currentText() == "":
self.subtype = None
# logger.debug(f"Control getter context: \n\tControl type: {self.con_sub_type}\n\tMode: {self.mode}\n\tStart \
# Date: {self.start_date}\n\tEnd Date: {self.end_date}")
# NOTE: set the mode_sub_type for kraken
if self.mode_sub_typer.currentText() == "":
self.mode_sub_type = None
else:
self.subtype = self.sub_typer.currentText()
# logger.debug(f"Subtype: {self.subtype}")
self.mode_sub_type = self.mode_sub_typer.currentText()
logger.debug(f"Subtype: {self.mode_sub_type}")
months = self.diff_month(self.start_date, self.end_date)
# NOTE: query all controls using the type/start and end dates from the gui
controls = Control.query(control_type=self.con_type, start_date=self.start_date, end_date=self.end_date)
# NOTE: if no data found from query set fig to none for reporting in webview
if controls is None:
fig = None
self.save_button.setEnabled(False)
else:
# NOTE: change each control to list of dictionaries
data = [control.convert_by_mode(mode=self.mode) for control in controls]
# NOTE: flatten data to one dimensional list
data = [item for sublist in data for item in sublist]
# logger.debug(f"Control objects going into df conversion: {type(data)}")
if not data:
report.add_result(Result(status="Critical", msg="No data found for controls in given date range."))
return
# NOTE send to dataframe creator
df = self.convert_data_list_to_df(input_df=data)
if self.subtype is None:
title = self.mode
else:
title = f"{self.mode} - {self.subtype}"
# NOTE: send dataframe to chart maker
df, modes = self.prep_df(ctx=self.app.ctx, df=df)
months = self.diff_month(self.start_date, self.end_date)
fig = CustomFigure(df=df, ytitle=title, modes=modes, parent=self, months=months)
self.save_button.setEnabled(True)
chart_settings = dict(sub_type=self.con_sub_type, start_date=self.start_date, end_date=self.end_date,
mode=self.mode,
sub_mode=self.mode_sub_type, parent=self, months=months)
_, self.fig = self.archetype.get_instance_class().make_chart(chart_settings=chart_settings, parent=self, ctx=self.app.ctx)
# if isinstance(self.fig, IridaFigure):
# self.save_button.setEnabled(True)
# logger.debug(f"Updating figure...")
self.fig = fig
# self.fig = fig
# NOTE: construct html for webview
html = fig.to_html()
html = self.fig.to_html()
# logger.debug(f"The length of html code is: {len(html)}")
self.webengineview.setHtml(html)
self.webengineview.update()
@@ -185,7 +171,7 @@ class ControlsViewer(QWidget):
Args:
ctx (dict): settings passed from gui
input_df (list[dict]): list of dictionaries containing records
subtype (str | None, optional): sub_type of submission type. Defaults to None.
mode_sub_type (str | None, optional): sub_type of submission type. Defaults to None.
Returns:
DataFrame: dataframe of controls
@@ -195,7 +181,7 @@ class ControlsViewer(QWidget):
safe = ['name', 'submitted_date', 'genus', 'target']
for column in df.columns:
if column not in safe:
if self.subtype is not None and column != self.subtype:
if self.mode_sub_type is not None and column != self.mode_sub_type:
continue
else:
safe.append(column)
@@ -338,34 +324,4 @@ class ControlsViewer(QWidget):
rerun_regex = re.compile(fr"{ctx.rerun_regex}")
exclude = [re.sub(rerun_regex, "", sample) for sample in sample_names if rerun_regex.search(sample)]
df = df[df.name not in exclude]
# for sample in sample_names:
# if rerun_regex.search(sample):
# first_run = re.sub(rerun_regex, "", sample)
# df = df.drop(df[df.name == first_run].index)
return df
# class ControlsDatePicker(QWidget):
# """
# custom widget to pick start and end dates for controls graphs
# """
#
# def __init__(self) -> None:
# super().__init__()
# self.start_date = QDateEdit(calendarPopup=True)
# # NOTE: start date is two months prior to end date by default
# sixmonthsago = QDate.currentDate().addDays(-180)
# self.start_date.setDate(sixmonthsago)
# self.end_date = QDateEdit(calendarPopup=True)
# self.end_date.setDate(QDate.currentDate())
# self.layout = QHBoxLayout()
# self.layout.addWidget(QLabel("Start Date"))
# self.layout.addWidget(self.start_date)
# self.layout.addWidget(QLabel("End Date"))
# self.layout.addWidget(self.end_date)
# self.setLayout(self.layout)
# self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
#
#
# def sizeHint(self) -> QSize:
# return QSize(80, 20)