diff --git a/CHANGELOG.md b/CHANGELOG.md index 72082e4..c750528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 202503.05 + +- Added concentrations chart tab. + # 202503.04 - Kit editor debugging. diff --git a/src/submissions/backend/excel/reports.py b/src/submissions/backend/excel/reports.py index 30c44ba..17e7647 100644 --- a/src/submissions/backend/excel/reports.py +++ b/src/submissions/backend/excel/reports.py @@ -1,6 +1,6 @@ -''' +""" Contains functions for generating summary reports -''' +""" import itertools import sys from pprint import pformat @@ -10,7 +10,7 @@ from pathlib import Path from datetime import date from typing import Tuple from backend.db.models import BasicSubmission, IridaControl -from tools import jinja_template_loading, get_first_blank_df_row, row_map, ctx +from tools import jinja_template_loading, get_first_blank_df_row, row_map from PyQt6.QtWidgets import QWidget from openpyxl.worksheet.worksheet import Worksheet @@ -36,6 +36,7 @@ class ReportArchetype(object): filename = Path(filename) filename = filename.absolute() self.writer = ExcelWriter(filename.with_suffix(".xlsx"), engine='openpyxl') + self.df.index += 1 self.df.to_excel(self.writer, sheet_name=self.sheet_name) self.writer.close() diff --git a/src/submissions/backend/validators/omni_gui_objects.py b/src/submissions/backend/validators/omni_gui_objects.py index 285faba..d1aeeed 100644 --- a/src/submissions/backend/validators/omni_gui_objects.py +++ b/src/submissions/backend/validators/omni_gui_objects.py @@ -22,86 +22,6 @@ class BaseOmni(BaseModel): def aliases(cls): return cls.class_object.aliases - # NOTE: Okay, this will not work for editing, since by definition not all attributes will line up. - # def check_all_attributes(self, attributes: dict) -> bool: - # """ - # Checks this instance against a dictionary of attributes to determine if they are a match. - # - # Args: - # attributes (dict): A dictionary of attributes to be check for equivalence - # - # Returns: - # bool: If a single unequivocal value is found will be false, else true. - # """ - # logger.debug(f"Incoming attributes: {attributes}") - # for key, value in attributes.items(): - # logger.debug(f"Comparing value class: {value.__class__} to omni class") - # if isinstance(value, str): - # try: - # check = value.lower() == "none" - # except AttributeError: - # continue - # if check: - # value = None - # logger.debug(f"Attempting to grab attribute: {key}") - # try: - # self_value = getattr(self, key) - # class_attr = getattr(self.class_object, key) - # except AttributeError: - # continue - # try: - # logger.debug(f"Check if {self_value.__class__} is subclass of {BaseOmni}") - # check = issubclass(self_value.__class__, BaseOmni) - # except TypeError as e: - # logger.error(f"Couldn't check if {self_value.__class__} is subclass of {BaseOmni} due to {e}") - # check = False - # if check: - # logger.debug(f"Checking for subclass name.") - # self_value = self_value.name - # try: - # logger.debug(f"Check if {value.__class__} is subclass of {BaseOmni}") - # check = issubclass(value.__class__, BaseOmni) - # except TypeError as e: - # logger.error(f"Couldn't check if {value.__class__} is subclass of {BaseOmni} due to {e}") - # check = False - # if check: - # logger.debug(f"Checking for subclass name.") - # value = value.name - # logger.debug(f"Self value: {self_value}, class attr: {class_attr} of type: {type(class_attr)}") - # if isinstance(class_attr, property): - # filter = "property" - # else: - # filter = class_attr.property - # match filter: - # case ColumnProperty(): - # match class_attr.type: - # case INTEGER(): - # if value.lower() == "true": - # value = 1 - # elif value.lower() == "false": - # value = 0 - # else: - # value = int(value) - # case FLOAT(): - # value = float(value) - # case "property": - # pass - # case _RelationshipDeclared(): - # logger.debug(f"Checking relationship value: {self_value}") - # try: - # self_value = self_value.name - # except AttributeError: - # pass - # if class_attr.property.uselist: - # self_value = self_value.__str__() - # logger.debug( - # f"Checking self_value {self_value} of type {type(self_value)} against attribute {value} of type {type(value)}") - # if self_value != value: - # output = False - # logger.debug(f"Value {key} is False, returning.") - # return output - # return True - def check_all_attributes(self, attributes: dict) -> bool: logger.debug(f"Incoming attributes: {attributes}") attributes = {k : v for k, v in attributes.items() if k in self.list_searchables.keys()} @@ -122,7 +42,6 @@ class BaseOmni(BaseModel): logger.debug("Everything checks out, these are the same object.") return True - def __setattr__(self, key, value): try: class_value = getattr(self.class_object, key) @@ -395,6 +314,13 @@ class OmniKitTypeReagentRoleAssociation(BaseOmni): return {} return value + @field_validator("required", mode="before") + @classmethod + def rescue_required_none(cls, value): + if not value: + value = 1 + return value + def __init__(self, instance_object: Any, **data): super().__init__(**data) self.instance_object = instance_object diff --git a/src/submissions/frontend/visualizations/concentrations_chart.py b/src/submissions/frontend/visualizations/concentrations_chart.py index ede8e3a..75e85e5 100644 --- a/src/submissions/frontend/visualizations/concentrations_chart.py +++ b/src/submissions/frontend/visualizations/concentrations_chart.py @@ -1,5 +1,5 @@ """ -Construct turnaround time charts +Construct BC control concentration charts """ from pprint import pformat from . import CustomFigure @@ -21,17 +21,15 @@ class ConcentrationsChart(CustomFigure): super().__init__(df=df, modes=modes, settings=settings) self.df = df self.construct_chart() - # if threshold: - # self.add_hline(y=threshold) self.update_layout(showlegend=False) def construct_chart(self, df: pd.DataFrame | None = None): if df: self.df = df - # logger.debug(f"Constructing concentration chart with df:\n{self.df}") try: self.df = self.df[self.df.concentration.notnull()] - self.df = self.df.sort_values(['submitted_date', 'submission'], ascending=[True, True]).reset_index(drop=True) + self.df = self.df.sort_values(['submitted_date', 'submission'], ascending=[True, True]).reset_index( + drop=True) self.df = self.df.reset_index().rename(columns={"index": "idx"}) # logger.debug(f"DF after changes:\n{self.df}") scatter = px.scatter(data_frame=self.df, x='submission', y="concentration", @@ -41,8 +39,6 @@ class ConcentrationsChart(CustomFigure): except (ValueError, AttributeError) as e: logger.error(f"Error constructing chart: {e}") scatter = px.scatter() - # logger.debug(f"Scatter data: {scatter.data}") - # self.add_traces(scatter.data) # NOTE: For some reason if data is allowed to sort itself it leads to wrong ordering of x axis. traces = sorted(scatter.data, key=itemgetter("name")) for trace in traces: @@ -60,6 +56,9 @@ class ConcentrationsChart(CustomFigure): tickmode='array', tickvals=tickvals, ticktext=ticklabels, + ), + yaxis=dict( + rangemode="nonnegative" ) ) self.update_traces(marker={'size': 15}) diff --git a/src/submissions/frontend/widgets/concentrations.py b/src/submissions/frontend/widgets/concentrations.py index e4ace71..f7facb0 100644 --- a/src/submissions/frontend/widgets/concentrations.py +++ b/src/submissions/frontend/widgets/concentrations.py @@ -1,7 +1,7 @@ """ -Pane showing turnaround time summary. +Pane showing BC control concentrations summary. """ -from PyQt6.QtWidgets import QWidget, QPushButton, QComboBox, QLabel +from PyQt6.QtWidgets import QWidget, QPushButton from .info_tab import InfoPane from backend.excel.reports import ConcentrationMaker from frontend.visualizations.concentrations_chart import ConcentrationsChart @@ -22,12 +22,6 @@ class Concentrations(InfoPane): self.layout.addWidget(self.export_button, 0, 3, 1, 1) self.fig = None self.report_object = None - # self.submission_typer = QComboBox(self) - # subs = ["All"] + [item.name for item in SubmissionType.query()] - # self.submission_typer.addItems(subs) - # self.layout.addWidget(QLabel("Submission Type"), 1, 0, 1, 1) - # self.layout.addWidget(self.submission_typer, 1, 1, 1, 3) - # self.submission_typer.currentTextChanged.connect(self.update_data) self.update_data() def update_data(self) -> None: @@ -40,17 +34,6 @@ class Concentrations(InfoPane): super().update_data() months = self.diff_month(self.start_date, self.end_date) chart_settings = dict(start_date=self.start_date, end_date=self.end_date) - # if self.submission_typer.currentText() == "All": - # submission_type = None - # subtype_obj = None - # else: - # submission_type = self.submission_typer.currentText() - # subtype_obj = SubmissionType.query(name = submission_type) - # self.report_obj = ConcentrationMaker(start_date=self.start_date, end_date=self.end_date)#, submission_type=submission_type) self.report_obj = ConcentrationMaker(**chart_settings) - # if subtype_obj: - # threshold = subtype_obj.defaults['turnaround_time'] + 0.5 - # else: - # threshold = None self.fig = ConcentrationsChart(df=self.report_obj.df, settings=chart_settings, modes=[], months=months) self.webview.setHtml(self.fig.html)