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
- 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

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] 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.

View File

@@ -14,7 +14,7 @@ from sqlalchemy.orm import relationship, validates, Query
from sqlalchemy.ext.associationproxy import association_proxy
from datetime import date
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 pandas import ExcelFile
from pathlib import Path
@@ -175,7 +175,7 @@ class KitType(BaseClass):
return [item.reagent_role for item in relevant_associations]
# 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
@@ -1139,6 +1139,23 @@ class KitTypeReagentRoleAssociation(BaseClass):
base_dict[k] = v
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):
"""

View File

@@ -124,17 +124,17 @@ class CustomFigure(Figure):
fig_len = len(self.data)
if len(modes) > 1:
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
# And break it into {len(modes)} chunks
# NOTE: And break it into {len(modes)} chunks
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):
if jj != ii:
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]
# Now, yield button to add to list
# NOTE: Now, yield button to add to list
yield dict(label=mode, method="update", args=[
{"visible": mode_vis},
{"yaxis.title.text": mode},

View File

@@ -152,9 +152,10 @@ class SubmissionDetails(QDialog):
with open(template_path.joinpath("css", "styles.css"), "r") as f:
css = f.read()
# 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.webview.setHtml(self.html)
# self.btn.setEnabled(True)
@pyqtSlot(str)
def sign_off(self, submission: str | BasicSubmission):

View File

@@ -1,18 +1,15 @@
'''
Contains all submission related frontend functions
'''
import re
import sys
from PyQt6.QtWidgets import (
QWidget, QPushButton, QVBoxLayout,
QComboBox, QDateEdit, QLineEdit, QLabel
)
from PyQt6.QtCore import pyqtSignal, Qt
from pathlib import Path
from . import select_open_file, select_save_file
import logging, difflib, inspect
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.validators import PydSubmission, PydReagent
from backend.db import (
@@ -27,6 +24,7 @@ from datetime import date
logger = logging.getLogger(f"submissions.{__name__}")
class MyQComboBox(QComboBox):
def __init__(self, scrollWidget=None, *args, **kwargs):
super(MyQComboBox, self).__init__(*args, **kwargs)
@@ -40,6 +38,7 @@ class MyQComboBox(QComboBox):
else:
return self.scrollWidget.wheelEvent(*args, **kwargs)
class MyQDateEdit(QDateEdit):
def __init__(self, scrollWidget=None, *args, **kwargs):
super(MyQDateEdit, self).__init__(*args, **kwargs)
@@ -243,7 +242,8 @@ class SubmissionFormWidget(QWidget):
else:
widget = None
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}")
if disable:
widget.input.setEnabled(False)
@@ -266,9 +266,6 @@ class SubmissionFormWidget(QWidget):
"""
extraction_kit = args[0]
# 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()
# logger.debug(f"Extraction kit: {extraction_kit}")
# NOTE: Remove previous reagent widgets
@@ -723,26 +720,13 @@ class SubmissionFormWidget(QWidget):
def __init__(self, scrollWidget, reagent, extraction_kit: str) -> None:
super().__init__(scrollWidget=scrollWidget)
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,
kit_type=extraction_kit)
try:
regex = re.compile(rf"{looked_up_rt.uses['exclude_regex']}")
except KeyError:
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
# relevant_reagents = [str(item.lot) for item in
# self.relevant_reagents(assoc=looked_up_rt)]
relevant_reagents = [str(item.lot) for item in looked_up_rt.get_all_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 check_not_nan(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.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>
<img height="600px" width="1300px" src="data:image/jpeg;base64,{{ sub['export_map'] | safe }}">
{% endif %}
{% endblock %}
{% if signing_permission %}
<button type="button" id="sign_btn">Sign Off</button>
{% endif %}
{% endblock %}
<br>
<br>
<br>
@@ -94,10 +95,8 @@
{% endfor %}
document.getElementById("sign_btn").addEventListener("click", function(){
backend.sign_off("{{ sub['plate_number'] }}");
})
document.addEventListener('DOMContentLoaded', function() {
backend.activate_export(true);
}, false);
});
{% endblock %}
</script>
</html>