Moved relevant reagent lot aggregator to KitTypeReagentRole object.
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
2
TODO.md
2
TODO.md
@@ -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.
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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},
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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,6 +24,7 @@ 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)
|
||||||
@@ -40,6 +38,7 @@ 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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user