Moved relevant reagent lot aggregator to KitTypeReagentRole object.

This commit is contained in:
lwark
2024-09-17 14:50:04 -05:00
parent 7c3dfd53a0
commit 0cb1d3a104
7 changed files with 60 additions and 44 deletions

View File

@@ -1,7 +1,7 @@
## 202409.04 ## 202409.04
- Fixed wastewater sample writing bug. - Fixed wastewater sample writing bug.
- Added regex exclusion for KitTypeReagentRole to trim down Bacteria Positive Control list. - Added regex exclusion for KitTypeReagentRole.uses to trim down Bacteria Positive Control lot list.
## 202409.03 ## 202409.03

View File

@@ -1,4 +1,4 @@
- [ ] Upgrade to generators when returning lists. - [x] Upgrade to generators when returning lists.
- [x] Revamp frontend.widgets.controls_chart to include visualizations? - [x] Revamp frontend.widgets.controls_chart to include visualizations?
- [x] Convert Parsers to using openpyxl. - [x] Convert Parsers to using openpyxl.
- The hardest part of this is going to be the sample parsing. I'm onto using the cell formulas in the plate map to suss out the location in the lookup table, but it could get a little recursive up in here. - The hardest part of this is going to be the sample parsing. I'm onto using the cell formulas in the plate map to suss out the location in the lookup table, but it could get a little recursive up in here.

View File

@@ -14,7 +14,7 @@ from sqlalchemy.orm import relationship, validates, Query
from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.associationproxy import association_proxy
from datetime import date from datetime import date
import logging, re import logging, re
from tools import check_authorization, setup_lookup, Report, Result, jinja_template_loading from tools import check_authorization, setup_lookup, Report, Result, jinja_template_loading, check_regex_match
from typing import List, Literal, Generator, Any from typing import List, Literal, Generator, Any
from pandas import ExcelFile from pandas import ExcelFile
from pathlib import Path from pathlib import Path
@@ -175,7 +175,7 @@ class KitType(BaseClass):
return [item.reagent_role for item in relevant_associations] return [item.reagent_role for item in relevant_associations]
# TODO: Move to BasicSubmission? # TODO: Move to BasicSubmission?
def construct_xl_map_for_use(self, submission_type: str | SubmissionType) -> Generator[str, str]: def construct_xl_map_for_use(self, submission_type: str | SubmissionType) -> Generator[(str, str)]:
""" """
Creates map of locations in Excel workbook for a SubmissionType Creates map of locations in Excel workbook for a SubmissionType
@@ -1139,6 +1139,23 @@ class KitTypeReagentRoleAssociation(BaseClass):
base_dict[k] = v base_dict[k] = v
return base_dict return base_dict
def get_all_relevant_reagents(self) -> Generator[Reagent, None, None]:
"""
Creates a generator that will resolve in to a list filling the role associated with this object.
Returns:
Generator: Generates of reagents.
"""
# logger.debug(f"Attempting lookup of reagents by type: {reagent.type}")
reagents = self.reagent_role.instances
try:
regex = self.uses['exclude_regex']
except KeyError:
regex = "^$"
relevant_reagents = [reagent for reagent in reagents if
not check_regex_match(pattern=regex, check=str(reagent.lot))]
for rel_reagent in relevant_reagents:
yield rel_reagent
class SubmissionReagentAssociation(BaseClass): class SubmissionReagentAssociation(BaseClass):
""" """

View File

@@ -124,17 +124,17 @@ class CustomFigure(Figure):
fig_len = len(self.data) fig_len = len(self.data)
if len(modes) > 1: if len(modes) > 1:
for ii, mode in enumerate(modes): for ii, mode in enumerate(modes):
# What I need to do is create a list of bools with the same length as the fig.data # NOTE: What I need to do is create a list of bools with the same length as the fig.data
mode_vis = [True] * fig_len mode_vis = [True] * fig_len
# And break it into {len(modes)} chunks # NOTE: And break it into {len(modes)} chunks
mode_vis = list(divide_chunks(mode_vis, len(modes))) mode_vis = list(divide_chunks(mode_vis, len(modes)))
# Then, for each chunk, if the chunk index isn't equal to the index of the current mode, set to false # NOTE: Then, for each chunk, if the chunk index isn't equal to the index of the current mode, set to false
for jj, sublist in enumerate(mode_vis): for jj, sublist in enumerate(mode_vis):
if jj != ii: if jj != ii:
mode_vis[jj] = [not elem for elem in mode_vis[jj]] mode_vis[jj] = [not elem for elem in mode_vis[jj]]
# Finally, flatten list. # NOTE: Finally, flatten list.
mode_vis = [item for sublist in mode_vis for item in sublist] mode_vis = [item for sublist in mode_vis for item in sublist]
# Now, yield button to add to list # NOTE: Now, yield button to add to list
yield dict(label=mode, method="update", args=[ yield dict(label=mode, method="update", args=[
{"visible": mode_vis}, {"visible": mode_vis},
{"yaxis.title.text": mode}, {"yaxis.title.text": mode},

View File

@@ -152,9 +152,10 @@ class SubmissionDetails(QDialog):
with open(template_path.joinpath("css", "styles.css"), "r") as f: with open(template_path.joinpath("css", "styles.css"), "r") as f:
css = f.read() css = f.read()
# logger.debug(f"Submission_details: {pformat(self.base_dict)}") # logger.debug(f"Submission_details: {pformat(self.base_dict)}")
# logger.debug(f"User is power user: {is_power_user()}")
self.html = self.template.render(sub=self.base_dict, signing_permission=is_power_user(), css=css) self.html = self.template.render(sub=self.base_dict, signing_permission=is_power_user(), css=css)
self.webview.setHtml(self.html) self.webview.setHtml(self.html)
# self.btn.setEnabled(True)
@pyqtSlot(str) @pyqtSlot(str)
def sign_off(self, submission: str | BasicSubmission): def sign_off(self, submission: str | BasicSubmission):

View File

@@ -1,18 +1,15 @@
''' '''
Contains all submission related frontend functions Contains all submission related frontend functions
''' '''
import re
import sys
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QWidget, QPushButton, QVBoxLayout, QWidget, QPushButton, QVBoxLayout,
QComboBox, QDateEdit, QLineEdit, QLabel QComboBox, QDateEdit, QLineEdit, QLabel
) )
from PyQt6.QtCore import pyqtSignal, Qt from PyQt6.QtCore import pyqtSignal, Qt
from pathlib import Path
from . import select_open_file, select_save_file from . import select_open_file, select_save_file
import logging, difflib, inspect import logging, difflib, inspect
from pathlib import Path from pathlib import Path
from tools import Report, Result, check_not_nan, main_form_style, report_result from tools import Report, Result, check_not_nan, main_form_style, report_result, check_regex_match
from backend.excel.parser import SheetParser from backend.excel.parser import SheetParser
from backend.validators import PydSubmission, PydReagent from backend.validators import PydSubmission, PydReagent
from backend.db import ( from backend.db import (
@@ -27,10 +24,11 @@ from datetime import date
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
class MyQComboBox(QComboBox): class MyQComboBox(QComboBox):
def __init__(self, scrollWidget=None, *args, **kwargs): def __init__(self, scrollWidget=None, *args, **kwargs):
super(MyQComboBox, self).__init__(*args, **kwargs) super(MyQComboBox, self).__init__(*args, **kwargs)
self.scrollWidget=scrollWidget self.scrollWidget = scrollWidget
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
logger.debug(f"Scrollwidget: {scrollWidget}") logger.debug(f"Scrollwidget: {scrollWidget}")
@@ -40,10 +38,11 @@ class MyQComboBox(QComboBox):
else: else:
return self.scrollWidget.wheelEvent(*args, **kwargs) return self.scrollWidget.wheelEvent(*args, **kwargs)
class MyQDateEdit(QDateEdit): class MyQDateEdit(QDateEdit):
def __init__(self, scrollWidget=None, *args, **kwargs): def __init__(self, scrollWidget=None, *args, **kwargs):
super(MyQDateEdit, self).__init__(*args, **kwargs) super(MyQDateEdit, self).__init__(*args, **kwargs)
self.scrollWidget=scrollWidget self.scrollWidget = scrollWidget
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
def wheelEvent(self, *args, **kwargs): def wheelEvent(self, *args, **kwargs):
@@ -243,7 +242,8 @@ class SubmissionFormWidget(QWidget):
else: else:
widget = None widget = None
case _: case _:
widget = self.InfoItem(parent=self, key=key, value=value, submission_type=submission_type, sub_obj=sub_obj) widget = self.InfoItem(parent=self, key=key, value=value, submission_type=submission_type,
sub_obj=sub_obj)
# logger.debug(f"Setting widget enabled to: {not disable}") # logger.debug(f"Setting widget enabled to: {not disable}")
if disable: if disable:
widget.input.setEnabled(False) widget.input.setEnabled(False)
@@ -266,9 +266,6 @@ class SubmissionFormWidget(QWidget):
""" """
extraction_kit = args[0] extraction_kit = args[0]
# caller = inspect.stack()[1].function.__repr__().replace("'", "") # caller = inspect.stack()[1].function.__repr__().replace("'", "")
# logger.debug(f"Self.reagents: {self.reagents}")
# logger.debug(f"\n\n{pformat(caller)}\n\n")
# logger.debug(f"SubmissionType: {self.submission_type}")
report = Report() report = Report()
# logger.debug(f"Extraction kit: {extraction_kit}") # logger.debug(f"Extraction kit: {extraction_kit}")
# NOTE: Remove previous reagent widgets # NOTE: Remove previous reagent widgets
@@ -723,26 +720,13 @@ class SubmissionFormWidget(QWidget):
def __init__(self, scrollWidget, reagent, extraction_kit: str) -> None: def __init__(self, scrollWidget, reagent, extraction_kit: str) -> None:
super().__init__(scrollWidget=scrollWidget) super().__init__(scrollWidget=scrollWidget)
self.setEditable(True) self.setEditable(True)
# logger.debug(f"Attempting lookup of reagents by type: {reagent.type}")
lookup = Reagent.query(reagent_role=reagent.role)
looked_up_rt = KitTypeReagentRoleAssociation.query(reagent_role=reagent.role, looked_up_rt = KitTypeReagentRoleAssociation.query(reagent_role=reagent.role,
kit_type=extraction_kit) kit_type=extraction_kit)
try: # relevant_reagents = [str(item.lot) for item in
regex = re.compile(rf"{looked_up_rt.uses['exclude_regex']}") # self.relevant_reagents(assoc=looked_up_rt)]
except KeyError: relevant_reagents = [str(item.lot) for item in looked_up_rt.get_all_relevant_reagents()]
regex = re.compile(r"^$")
relevant_reagents = [str(item.lot) for item in lookup if not regex.match(str(item.lot))]
output_reg = []
for rel_reagent in relevant_reagents:
# NOTE: extract strings from any sets.
if isinstance(rel_reagent, set):
for thing in rel_reagent:
output_reg.append(thing)
elif isinstance(rel_reagent, str):
output_reg.append(rel_reagent)
relevant_reagents = output_reg
# NOTE: if reagent in sheet is not found insert it into the front of relevant reagents so it shows
# logger.debug(f"Relevant reagents for {reagent.lot}: {relevant_reagents}") # logger.debug(f"Relevant reagents for {reagent.lot}: {relevant_reagents}")
# NOTE: if reagent in sheet is not found insert it into the front of relevant reagents so it shows
if str(reagent.lot) not in relevant_reagents: if str(reagent.lot) not in relevant_reagents:
if check_not_nan(reagent.lot): if check_not_nan(reagent.lot):
relevant_reagents.insert(0, str(reagent.lot)) relevant_reagents.insert(0, str(reagent.lot))
@@ -778,5 +762,20 @@ class SubmissionFormWidget(QWidget):
self.setToolTip(f"Enter lot number for the reagent used for {reagent.role}") self.setToolTip(f"Enter lot number for the reagent used for {reagent.role}")
# self.setStyleSheet(main_form_style) # self.setStyleSheet(main_form_style)
def relevant_reagents(self, assoc: KitTypeReagentRoleAssociation):
# logger.debug(f"Attempting lookup of reagents by type: {reagent.type}")
lookup = Reagent.query(reagent_role=assoc.reagent_role)
try:
regex = assoc.uses['exclude_regex']
except KeyError:
regex = "^$"
relevant_reagents = [item for item in lookup if
not check_regex_match(pattern=regex, check=str(item.lot))]
for rel_reagent in relevant_reagents:
# # NOTE: extract strings from any sets.
# if isinstance(rel_reagent, set):
# for thing in rel_reagent:
# yield thing
# elif isinstance(rel_reagent, str):
# yield rel_reagent
yield rel_reagent

View File

@@ -66,10 +66,11 @@
<h3><u>Plate map:</u></h3> <h3><u>Plate map:</u></h3>
<img height="600px" width="1300px" src="data:image/jpeg;base64,{{ sub['export_map'] | safe }}"> <img height="600px" width="1300px" src="data:image/jpeg;base64,{{ sub['export_map'] | safe }}">
{% endif %} {% endif %}
{% endblock %}
{% if signing_permission %} {% if signing_permission %}
<button type="button" id="sign_btn">Sign Off</button> <button type="button" id="sign_btn">Sign Off</button>
{% endif %} {% endif %}
{% endblock %}
<br> <br>
<br> <br>
<br> <br>
@@ -94,10 +95,8 @@
{% endfor %} {% endfor %}
document.getElementById("sign_btn").addEventListener("click", function(){ document.getElementById("sign_btn").addEventListener("click", function(){
backend.sign_off("{{ sub['plate_number'] }}"); backend.sign_off("{{ sub['plate_number'] }}");
}) });
document.addEventListener('DOMContentLoaded', function() {
backend.activate_export(true);
}, false);
{% endblock %} {% endblock %}
</script> </script>
</html> </html>