update to First Strand constructor
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
## 202310.02
|
||||||
|
|
||||||
|
- Improvements to First strand constructor.
|
||||||
|
- Submission forms can now be dragged and dropped into the form widget.
|
||||||
|
|
||||||
## 202310.01
|
## 202310.01
|
||||||
|
|
||||||
- Controls linker is now depreciated.
|
- Controls linker is now depreciated.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
## Startup:
|
## Startup:
|
||||||
1. Open the app using the shortcut in the Submissions folder. For example: 'L:\Robotics Laboratory Support\Submissions\submissions_v122b.exe - Shortcut.lnk' (Version may have changed).
|
1. Open the app using the shortcut in the Submissions folder. For example: L:\\Robotics Laboratory Support\\Submissions\\submissions_v122b.exe - Shortcut.lnk (Version may have changed).
|
||||||
a. Ignore the large black window of fast scrolling text, it is there for debugging purposes.
|
a. Ignore the large black window of fast scrolling text, it is there for debugging purposes.
|
||||||
b. The 'Submissions' tab should be open by default.
|
b. The 'Submissions' tab should be open by default.
|
||||||
|
|
||||||
@@ -60,10 +60,6 @@ This is meant to import .xslx files created from the Design & Analysis Software
|
|||||||
c. Repeat 6b for the Lot and the Expiry row and columns.
|
c. Repeat 6b for the Lot and the Expiry row and columns.
|
||||||
7. Click the "Submit" button at the top.
|
7. Click the "Submit" button at the top.
|
||||||
|
|
||||||
## Linking Controls:
|
|
||||||
|
|
||||||
1. Click "Monthly" -> "Link Controls". Entire process should be handled automatically.
|
|
||||||
|
|
||||||
## Linking Extraction Logs:
|
## Linking Extraction Logs:
|
||||||
|
|
||||||
1. Click "Monthly" -> "Link Extraction Logs".
|
1. Click "Monthly" -> "Link Extraction Logs".
|
||||||
|
|||||||
3
TODO.md
3
TODO.md
@@ -1,6 +1,7 @@
|
|||||||
|
- [x] Drag and drop files into submission form area?
|
||||||
- [ ] Get info for controls into their sample hitpicks.
|
- [ ] Get info for controls into their sample hitpicks.
|
||||||
- [x] Move submission-type specific parser functions into class methods in their respective models.
|
- [x] Move submission-type specific parser functions into class methods in their respective models.
|
||||||
- [ ] Improve results reporting.
|
- [ ] Improve function results reporting.
|
||||||
- Maybe make it a list until it gets to the reporter?
|
- Maybe make it a list until it gets to the reporter?
|
||||||
- [x] Increase robustness of form parsers by adding custom procedures for each.
|
- [x] Increase robustness of form parsers by adding custom procedures for each.
|
||||||
- [x] Rerun Kit integrity if extraction kit changed in the form.
|
- [x] Rerun Kit integrity if extraction kit changed in the form.
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
|||||||
# output_encoding = utf-8
|
# output_encoding = utf-8
|
||||||
|
|
||||||
; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
|
; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
|
||||||
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-new.db
|
sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-new.db
|
||||||
sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions-test.db
|
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions-test.db
|
||||||
|
|
||||||
|
|
||||||
[post_write_hooks]
|
[post_write_hooks]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
# Version of the realpython-reader package
|
# Version of the realpython-reader package
|
||||||
__project__ = "submissions"
|
__project__ = "submissions"
|
||||||
__version__ = "202310.1b"
|
__version__ = "202310.2b"
|
||||||
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
|
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
|
||||||
__copyright__ = "2022-2023, Government of Canada"
|
__copyright__ = "2022-2023, Government of Canada"
|
||||||
|
|
||||||
|
|||||||
@@ -417,7 +417,8 @@ def lookup_reagenttype_kittype_association(ctx:Settings,
|
|||||||
def lookup_submission_sample_association(ctx:Settings,
|
def lookup_submission_sample_association(ctx:Settings,
|
||||||
submission:models.BasicSubmission|str|None=None,
|
submission:models.BasicSubmission|str|None=None,
|
||||||
sample:models.BasicSample|str|None=None,
|
sample:models.BasicSample|str|None=None,
|
||||||
limit:int=0
|
limit:int=0,
|
||||||
|
chronologic:bool=False
|
||||||
) -> models.SubmissionSampleAssociation|List[models.SubmissionSampleAssociation]:
|
) -> models.SubmissionSampleAssociation|List[models.SubmissionSampleAssociation]:
|
||||||
query = setup_lookup(ctx=ctx, locals=locals()).query(models.SubmissionSampleAssociation)
|
query = setup_lookup(ctx=ctx, locals=locals()).query(models.SubmissionSampleAssociation)
|
||||||
match submission:
|
match submission:
|
||||||
@@ -435,6 +436,8 @@ def lookup_submission_sample_association(ctx:Settings,
|
|||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
logger.debug(f"Query count: {query.count()}")
|
logger.debug(f"Query count: {query.count()}")
|
||||||
|
if chronologic:
|
||||||
|
query.join(models.BasicSubmission).order_by(models.BasicSubmission.submitted_date)
|
||||||
if query.count() == 1:
|
if query.count() == 1:
|
||||||
limit = 1
|
limit = 1
|
||||||
return query_return(query=query, limit=limit)
|
return query_return(query=query, limit=limit)
|
||||||
|
|||||||
@@ -247,6 +247,8 @@ def get_polymorphic_subclass(base:object, polymorphic_identity:str|None=None):
|
|||||||
Returns:
|
Returns:
|
||||||
_type_: Subclass, or parent class on
|
_type_: Subclass, or parent class on
|
||||||
"""
|
"""
|
||||||
|
if isinstance(polymorphic_identity, dict):
|
||||||
|
polymorphic_identity = polymorphic_identity['value']
|
||||||
if polymorphic_identity == None:
|
if polymorphic_identity == None:
|
||||||
return base
|
return base
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'''
|
'''
|
||||||
Models for the main submission types.
|
Models for the main submission types.
|
||||||
'''
|
'''
|
||||||
|
from getpass import getuser
|
||||||
import math
|
import math
|
||||||
from . import Base
|
from . import Base
|
||||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, Table, JSON, FLOAT, case
|
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, Table, JSON, FLOAT, case
|
||||||
@@ -15,7 +16,8 @@ from pandas import Timestamp
|
|||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
import re
|
import re
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from tools import row_map
|
from openpyxl import Workbook
|
||||||
|
from tools import check_not_nan, row_map
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -238,6 +240,20 @@ class BasicSubmission(Base):
|
|||||||
continue
|
continue
|
||||||
return output_list
|
return output_list
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def custom_platemap(cls, xl:pd.ExcelFile, plate_map:pd.DataFrame) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Stupid stopgap solution to there being an issue with the Bacterial Culture plate map
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xl (pd.ExcelFile): original xl workbook
|
||||||
|
plate_map (pd.DataFrame): original plate map
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
pd.DataFrame: updated plate map.
|
||||||
|
"""
|
||||||
|
return plate_map
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_info(cls, input_dict:dict, xl:pd.ExcelFile|None=None) -> dict:
|
def parse_info(cls, input_dict:dict, xl:pd.ExcelFile|None=None) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -266,6 +282,19 @@ class BasicSubmission(Base):
|
|||||||
logger.debug(f"Called {cls.__name__} sample parser")
|
logger.debug(f"Called {cls.__name__} sample parser")
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def custom_autofill(cls, input_excel:Workbook) -> Workbook:
|
||||||
|
"""
|
||||||
|
Adds custom autofill methods for submission
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_excel (Workbook): input workbook
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Workbook: updated workbook
|
||||||
|
"""
|
||||||
|
return input_excel
|
||||||
|
|
||||||
# Below are the custom submission types
|
# Below are the custom submission types
|
||||||
|
|
||||||
class BacterialCulture(BasicSubmission):
|
class BacterialCulture(BasicSubmission):
|
||||||
@@ -287,6 +316,49 @@ class BacterialCulture(BasicSubmission):
|
|||||||
output['controls'] = [item.to_sub_dict() for item in self.controls]
|
output['controls'] = [item.to_sub_dict() for item in self.controls]
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def custom_platemap(cls, xl: pd.ExcelFile, plate_map: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Stupid stopgap solution to there being an issue with the Bacterial Culture plate map. Extends parent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xl (pd.ExcelFile): original xl workbook
|
||||||
|
plate_map (pd.DataFrame): original plate map
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
pd.DataFrame: updated plate map.
|
||||||
|
"""
|
||||||
|
plate_map = super().custom_platemap(xl, plate_map)
|
||||||
|
num1 = xl.parse("Sample List").iloc[40,1]
|
||||||
|
num2 = xl.parse("Sample List").iloc[41,1]
|
||||||
|
logger.debug(f"Broken: {plate_map.iloc[5,0]}, {plate_map.iloc[6,0]}")
|
||||||
|
logger.debug(f"Replace: {num1}, {num2}")
|
||||||
|
if not check_not_nan(plate_map.iloc[5,0]):
|
||||||
|
plate_map.iloc[5,0] = num1
|
||||||
|
if not check_not_nan(plate_map.iloc[6,0]):
|
||||||
|
plate_map.iloc[6,0] = num2
|
||||||
|
return plate_map
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def custom_autofill(cls, input_excel: Workbook) -> Workbook:
|
||||||
|
"""
|
||||||
|
Stupid stopgap solution to there being an issue with the Bacterial Culture plate map. Extends parent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_excel (Workbook): Input openpyxl workbook
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Workbook: Updated openpyxl workbook
|
||||||
|
"""
|
||||||
|
input_excel = super().custom_autofill(input_excel)
|
||||||
|
sheet = input_excel['Plate Map']
|
||||||
|
if sheet.cell(12,2).value == None:
|
||||||
|
sheet.cell(row=12, column=2, value="=IF(ISBLANK('Sample List'!$B42),\"\",'Sample List'!$B42)")
|
||||||
|
if sheet.cell(13,2).value == None:
|
||||||
|
sheet.cell(row=13, column=2, value="=IF(ISBLANK('Sample List'!$B43),\"\",'Sample List'!$B43)")
|
||||||
|
input_excel["Sample List"].cell(row=15, column=2, value=getuser()[0:2].upper())
|
||||||
|
return input_excel
|
||||||
|
|
||||||
class Wastewater(BasicSubmission):
|
class Wastewater(BasicSubmission):
|
||||||
"""
|
"""
|
||||||
derivative submission type from BasicSubmission
|
derivative submission type from BasicSubmission
|
||||||
@@ -327,7 +399,6 @@ class Wastewater(BasicSubmission):
|
|||||||
input_dict['csv'] = xl.parse("Copy to import file")
|
input_dict['csv'] = xl.parse("Copy to import file")
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
|
|
||||||
class WastewaterArtic(BasicSubmission):
|
class WastewaterArtic(BasicSubmission):
|
||||||
"""
|
"""
|
||||||
derivative submission type for artic wastewater
|
derivative submission type for artic wastewater
|
||||||
@@ -371,7 +442,6 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
input_dict['submitter_id'] = re.sub(r"\s\(.+\)$", "", str(input_dict['submitter_id'])).strip()
|
input_dict['submitter_id'] = re.sub(r"\s\(.+\)$", "", str(input_dict['submitter_id'])).strip()
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
|
|
||||||
class BasicSample(Base):
|
class BasicSample(Base):
|
||||||
"""
|
"""
|
||||||
Base of basic sample which polymorphs into BCSample and WWSample
|
Base of basic sample which polymorphs into BCSample and WWSample
|
||||||
@@ -457,7 +527,7 @@ class BasicSample(Base):
|
|||||||
Sample name: {self.submitter_id}<br>
|
Sample name: {self.submitter_id}<br>
|
||||||
Well: {row_map[assoc.row]}{assoc.column}
|
Well: {row_map[assoc.row]}{assoc.column}
|
||||||
"""
|
"""
|
||||||
return dict(name=self.submitter_id, positive=False, tooltip=tooltip_text)
|
return dict(name=self.submitter_id[:10], positive=False, tooltip=tooltip_text)
|
||||||
|
|
||||||
class WastewaterSample(BasicSample):
|
class WastewaterSample(BasicSample):
|
||||||
"""
|
"""
|
||||||
@@ -539,6 +609,16 @@ class WastewaterSample(BasicSample):
|
|||||||
logger.error(f"Couldn't set tooltip for {self.rsl_number}. Looks like there isn't PCR data.")
|
logger.error(f"Couldn't set tooltip for {self.rsl_number}. Looks like there isn't PCR data.")
|
||||||
return sample
|
return sample
|
||||||
|
|
||||||
|
def get_recent_ww_submission(self):
|
||||||
|
results = [sub for sub in self.submissions if isinstance(sub, Wastewater)]
|
||||||
|
if len(results) > 1:
|
||||||
|
results = results.sort(key=lambda sub: sub.submitted_date)
|
||||||
|
try:
|
||||||
|
return results[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class BacterialCultureSample(BasicSample):
|
class BacterialCultureSample(BasicSample):
|
||||||
"""
|
"""
|
||||||
base of bacterial culture sample
|
base of bacterial culture sample
|
||||||
|
|||||||
@@ -376,6 +376,8 @@ class SampleParser(object):
|
|||||||
df = df.iloc[plate_map_location['start_row']-1:plate_map_location['end_row'], plate_map_location['start_column']-1:plate_map_location['end_column']]
|
df = df.iloc[plate_map_location['start_row']-1:plate_map_location['end_row'], plate_map_location['start_column']-1:plate_map_location['end_column']]
|
||||||
df = pd.DataFrame(df.values[1:], columns=df.iloc[0])
|
df = pd.DataFrame(df.values[1:], columns=df.iloc[0])
|
||||||
df = df.set_index(df.columns[0])
|
df = df.set_index(df.columns[0])
|
||||||
|
custom_mapper = get_polymorphic_subclass(models.BasicSubmission, self.submission_type)
|
||||||
|
df = custom_mapper.custom_platemap(self.xl, df)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def construct_lookup_table(self, lookup_table_location:dict) -> pd.DataFrame:
|
def construct_lookup_table(self, lookup_table_location:dict) -> pd.DataFrame:
|
||||||
|
|||||||
@@ -8,17 +8,15 @@ from PyQt6.QtWidgets import (
|
|||||||
QMainWindow, QToolBar,
|
QMainWindow, QToolBar,
|
||||||
QTabWidget, QWidget, QVBoxLayout,
|
QTabWidget, QWidget, QVBoxLayout,
|
||||||
QComboBox, QHBoxLayout,
|
QComboBox, QHBoxLayout,
|
||||||
QScrollArea, QLineEdit, QDateEdit,
|
QScrollArea, QLineEdit, QDateEdit
|
||||||
QSpinBox
|
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt, pyqtSignal
|
||||||
from PyQt6.QtGui import QAction
|
from PyQt6.QtGui import QAction
|
||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from backend.db import (
|
from backend.db import (
|
||||||
construct_reagent, store_object, lookup_control_types, lookup_modes
|
construct_reagent, store_object, lookup_control_types, lookup_modes
|
||||||
)
|
)
|
||||||
# from .all_window_functions import extract_form_info
|
|
||||||
from tools import check_if_app, Settings
|
from tools import check_if_app, Settings
|
||||||
from frontend.custom_widgets import SubmissionsSheet, AlertPop, AddReagentForm, KitAdder, ControlsDatePicker, ImportReagent
|
from frontend.custom_widgets import SubmissionsSheet, AlertPop, AddReagentForm, KitAdder, ControlsDatePicker, ImportReagent
|
||||||
import logging
|
import logging
|
||||||
@@ -55,7 +53,6 @@ class App(QMainWindow):
|
|||||||
self._createToolBar()
|
self._createToolBar()
|
||||||
self._connectActions()
|
self._connectActions()
|
||||||
self._controls_getter()
|
self._controls_getter()
|
||||||
# self.status_bar = self.statusBar()
|
|
||||||
self.show()
|
self.show()
|
||||||
self.statusBar().showMessage('Ready', 5000)
|
self.statusBar().showMessage('Ready', 5000)
|
||||||
|
|
||||||
@@ -68,7 +65,6 @@ class App(QMainWindow):
|
|||||||
menuBar = self.menuBar()
|
menuBar = self.menuBar()
|
||||||
fileMenu = menuBar.addMenu("&File")
|
fileMenu = menuBar.addMenu("&File")
|
||||||
# Creating menus using a title
|
# Creating menus using a title
|
||||||
# editMenu = menuBar.addMenu("&Edit")
|
|
||||||
methodsMenu = menuBar.addMenu("&Methods")
|
methodsMenu = menuBar.addMenu("&Methods")
|
||||||
reportMenu = menuBar.addMenu("&Reports")
|
reportMenu = menuBar.addMenu("&Reports")
|
||||||
maintenanceMenu = menuBar.addMenu("&Monthly")
|
maintenanceMenu = menuBar.addMenu("&Monthly")
|
||||||
@@ -79,7 +75,6 @@ class App(QMainWindow):
|
|||||||
fileMenu.addAction(self.importPCRAction)
|
fileMenu.addAction(self.importPCRAction)
|
||||||
methodsMenu.addAction(self.constructFS)
|
methodsMenu.addAction(self.constructFS)
|
||||||
reportMenu.addAction(self.generateReportAction)
|
reportMenu.addAction(self.generateReportAction)
|
||||||
# maintenanceMenu.addAction(self.joinControlsAction)
|
|
||||||
maintenanceMenu.addAction(self.joinExtractionAction)
|
maintenanceMenu.addAction(self.joinExtractionAction)
|
||||||
maintenanceMenu.addAction(self.joinPCRAction)
|
maintenanceMenu.addAction(self.joinPCRAction)
|
||||||
|
|
||||||
@@ -105,7 +100,6 @@ class App(QMainWindow):
|
|||||||
self.generateReportAction = QAction("Make Report", self)
|
self.generateReportAction = QAction("Make Report", self)
|
||||||
self.addKitAction = QAction("Import Kit", self)
|
self.addKitAction = QAction("Import Kit", self)
|
||||||
self.addOrgAction = QAction("Import Org", self)
|
self.addOrgAction = QAction("Import Org", self)
|
||||||
# self.joinControlsAction = QAction("Link Controls")
|
|
||||||
self.joinExtractionAction = QAction("Link Extraction Logs")
|
self.joinExtractionAction = QAction("Link Extraction Logs")
|
||||||
self.joinPCRAction = QAction("Link PCR Logs")
|
self.joinPCRAction = QAction("Link PCR Logs")
|
||||||
self.helpAction = QAction("&About", self)
|
self.helpAction = QAction("&About", self)
|
||||||
@@ -128,12 +122,12 @@ class App(QMainWindow):
|
|||||||
self.table_widget.mode_typer.currentIndexChanged.connect(self._controls_getter)
|
self.table_widget.mode_typer.currentIndexChanged.connect(self._controls_getter)
|
||||||
self.table_widget.datepicker.start_date.dateChanged.connect(self._controls_getter)
|
self.table_widget.datepicker.start_date.dateChanged.connect(self._controls_getter)
|
||||||
self.table_widget.datepicker.end_date.dateChanged.connect(self._controls_getter)
|
self.table_widget.datepicker.end_date.dateChanged.connect(self._controls_getter)
|
||||||
# self.joinControlsAction.triggered.connect(self.linkControls)
|
|
||||||
self.joinExtractionAction.triggered.connect(self.linkExtractions)
|
self.joinExtractionAction.triggered.connect(self.linkExtractions)
|
||||||
self.joinPCRAction.triggered.connect(self.linkPCR)
|
self.joinPCRAction.triggered.connect(self.linkPCR)
|
||||||
self.helpAction.triggered.connect(self.showAbout)
|
self.helpAction.triggered.connect(self.showAbout)
|
||||||
self.docsAction.triggered.connect(self.openDocs)
|
self.docsAction.triggered.connect(self.openDocs)
|
||||||
self.constructFS.triggered.connect(self.construct_first_strand)
|
self.constructFS.triggered.connect(self.construct_first_strand)
|
||||||
|
self.table_widget.formwidget.import_drag.connect(self.importSubmission)
|
||||||
|
|
||||||
def showAbout(self):
|
def showAbout(self):
|
||||||
"""
|
"""
|
||||||
@@ -168,12 +162,14 @@ class App(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
self.statusBar().showMessage("Action completed sucessfully.", 5000)
|
self.statusBar().showMessage("Action completed sucessfully.", 5000)
|
||||||
|
|
||||||
def importSubmission(self):
|
def importSubmission(self, fname:Path|None=None):
|
||||||
"""
|
"""
|
||||||
import submission from excel sheet into form
|
import submission from excel sheet into form
|
||||||
"""
|
"""
|
||||||
from .main_window_functions import import_submission_function
|
from .main_window_functions import import_submission_function
|
||||||
self, result = import_submission_function(self)
|
self.raise_()
|
||||||
|
self.activateWindow()
|
||||||
|
self, result = import_submission_function(self, fname)
|
||||||
logger.debug(f"Import result: {result}")
|
logger.debug(f"Import result: {result}")
|
||||||
self.result_reporter(result)
|
self.result_reporter(result)
|
||||||
|
|
||||||
@@ -395,12 +391,25 @@ class AddSubForm(QWidget):
|
|||||||
|
|
||||||
class SubmissionFormWidget(QWidget):
|
class SubmissionFormWidget(QWidget):
|
||||||
|
|
||||||
|
import_drag = pyqtSignal(Path)
|
||||||
|
|
||||||
def __init__(self, parent: QWidget) -> None:
|
def __init__(self, parent: QWidget) -> None:
|
||||||
logger.debug(f"Setting form widget...")
|
logger.debug(f"Setting form widget...")
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer",
|
self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer",
|
||||||
"qt_scrollarea_vcontainer", "submit_btn"
|
"qt_scrollarea_vcontainer", "submit_btn"
|
||||||
]
|
]
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
|
||||||
|
def dragEnterEvent(self, event):
|
||||||
|
if event.mimeData().hasUrls():
|
||||||
|
event.accept()
|
||||||
|
else:
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
def dropEvent(self, event):
|
||||||
|
fname = Path([u.toLocalFile() for u in event.mimeData().urls()][0])
|
||||||
|
self.import_drag.emit(fname)
|
||||||
|
|
||||||
def parse_form(self) -> Tuple[dict, list]:
|
def parse_form(self) -> Tuple[dict, list]:
|
||||||
logger.debug(f"Hello from parser!")
|
logger.debug(f"Hello from parser!")
|
||||||
|
|||||||
@@ -3,17 +3,19 @@ Contains miscellaneous widgets for frontend functions
|
|||||||
'''
|
'''
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
from PyQt6 import QtCore
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QLabel, QVBoxLayout,
|
QLabel, QVBoxLayout,
|
||||||
QLineEdit, QComboBox, QDialog,
|
QLineEdit, QComboBox, QDialog,
|
||||||
QDialogButtonBox, QDateEdit, QSizePolicy, QWidget,
|
QDialogButtonBox, QDateEdit, QSizePolicy, QWidget,
|
||||||
QGridLayout, QPushButton, QSpinBox, QDoubleSpinBox,
|
QGridLayout, QPushButton, QSpinBox, QDoubleSpinBox,
|
||||||
QHBoxLayout, QScrollArea
|
QHBoxLayout, QScrollArea, QFormLayout
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QDate, QSize
|
from PyQt6.QtCore import Qt, QDate, QSize
|
||||||
from tools import check_not_nan, jinja_template_loading, Settings
|
from tools import check_not_nan, jinja_template_loading, Settings
|
||||||
from backend.db.functions import construct_kit_from_yaml, \
|
from backend.db.functions import construct_kit_from_yaml, \
|
||||||
lookup_reagent_types, lookup_reagents, lookup_submission_type, lookup_reagenttype_kittype_association
|
lookup_reagent_types, lookup_reagents, lookup_submission_type, lookup_reagenttype_kittype_association, \
|
||||||
|
lookup_submissions
|
||||||
from backend.db.models import SubmissionTypeKitTypeAssociation
|
from backend.db.models import SubmissionTypeKitTypeAssociation
|
||||||
from sqlalchemy import FLOAT, INTEGER, String
|
from sqlalchemy import FLOAT, INTEGER, String
|
||||||
import logging
|
import logging
|
||||||
@@ -277,7 +279,6 @@ class KitAdder(QWidget):
|
|||||||
info[widget.objectName()] = widget.date().toPyDate()
|
info[widget.objectName()] = widget.date().toPyDate()
|
||||||
return info, reagents
|
return info, reagents
|
||||||
|
|
||||||
|
|
||||||
class ReagentTypeForm(QWidget):
|
class ReagentTypeForm(QWidget):
|
||||||
"""
|
"""
|
||||||
custom widget to add information about a new reagenttype
|
custom widget to add information about a new reagenttype
|
||||||
@@ -458,3 +459,67 @@ class ParsedQLabel(QLabel):
|
|||||||
else:
|
else:
|
||||||
self.setText(f"MISSING {output}")
|
self.setText(f"MISSING {output}")
|
||||||
|
|
||||||
|
class FirstStrandSalvage(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, ctx:Settings, submitter_id:str, rsl_plate_num:str|None=None) -> None:
|
||||||
|
super().__init__()
|
||||||
|
if rsl_plate_num == None:
|
||||||
|
rsl_plate_num = ""
|
||||||
|
self.setWindowTitle("Add Reagent")
|
||||||
|
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
|
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
self.submitter_id_input = QLineEdit()
|
||||||
|
self.submitter_id_input.setText(submitter_id)
|
||||||
|
self.rsl_plate_num = QLineEdit()
|
||||||
|
self.rsl_plate_num.setText(rsl_plate_num)
|
||||||
|
self.row_letter = QComboBox()
|
||||||
|
self.row_letter.addItems(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'])
|
||||||
|
self.row_letter.setEditable(False)
|
||||||
|
self.column_number = QComboBox()
|
||||||
|
self.column_number.addItems(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'])
|
||||||
|
self.column_number.setEditable(False)
|
||||||
|
self.layout = QFormLayout()
|
||||||
|
self.layout.addRow(self.tr("&Sample Number:"), self.submitter_id_input)
|
||||||
|
self.layout.addRow(self.tr("&Plate Number:"), self.rsl_plate_num)
|
||||||
|
self.layout.addRow(self.tr("&Source Row:"), self.row_letter)
|
||||||
|
self.layout.addRow(self.tr("&Source Column:"), self.column_number)
|
||||||
|
self.layout.addWidget(self.buttonBox)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
def parse_form(self):
|
||||||
|
return dict(plate=self.rsl_plate_num.text(), submitter_id=self.submitter_id_input.text(), well=f"{self.row_letter.currentText()}{self.column_number.currentText()}")
|
||||||
|
|
||||||
|
class FirstStrandPlateList(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, ctx:Settings) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("First Strand Plates")
|
||||||
|
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
|
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
ww = [item.rsl_plate_num for item in lookup_submissions(ctx=ctx, submission_type="Wastewater")]
|
||||||
|
self.plate1 = QComboBox()
|
||||||
|
self.plate2 = QComboBox()
|
||||||
|
self.plate3 = QComboBox()
|
||||||
|
self.layout = QFormLayout()
|
||||||
|
for ii, plate in enumerate([self.plate1, self.plate2, self.plate3]):
|
||||||
|
plate.addItems(ww)
|
||||||
|
self.layout.addRow(self.tr(f"&Plate {ii+1}:"), plate)
|
||||||
|
self.layout.addWidget(self.buttonBox)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
def parse_form(self):
|
||||||
|
output = []
|
||||||
|
for plate in [self.plate1, self.plate2, self.plate3]:
|
||||||
|
output.append(plate.currentText())
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -291,8 +291,6 @@ class SubmissionDetails(QDialog):
|
|||||||
btn.setParent(self)
|
btn.setParent(self)
|
||||||
btn.setFixedWidth(900)
|
btn.setFixedWidth(900)
|
||||||
btn.clicked.connect(self.export)
|
btn.clicked.connect(self.export)
|
||||||
with open("test.html", "w") as f:
|
|
||||||
f.write(self.html)
|
|
||||||
|
|
||||||
def export(self):
|
def export(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,10 +22,12 @@ from PyQt6.QtWidgets import (
|
|||||||
)
|
)
|
||||||
from .all_window_functions import select_open_file, select_save_file
|
from .all_window_functions import select_open_file, select_save_file
|
||||||
from PyQt6.QtCore import QSignalBlocker
|
from PyQt6.QtCore import QSignalBlocker
|
||||||
|
from backend.db.models import BasicSubmission
|
||||||
from backend.db.functions import (
|
from backend.db.functions import (
|
||||||
construct_submission_info, lookup_reagents, construct_kit_from_yaml, construct_org_from_yaml, get_control_subtypes,
|
construct_submission_info, lookup_reagents, construct_kit_from_yaml, construct_org_from_yaml, get_control_subtypes,
|
||||||
update_subsampassoc_with_pcr, check_kit_integrity, update_last_used, lookup_organizations, lookup_kit_types,
|
update_subsampassoc_with_pcr, check_kit_integrity, update_last_used, lookup_organizations, lookup_kit_types,
|
||||||
lookup_submissions, lookup_controls, lookup_samples, lookup_submission_sample_association, store_object, lookup_submission_type
|
lookup_submissions, lookup_controls, lookup_samples, lookup_submission_sample_association, store_object, lookup_submission_type,
|
||||||
|
get_polymorphic_subclass
|
||||||
)
|
)
|
||||||
from backend.excel.parser import SheetParser, PCRParser, SampleParser
|
from backend.excel.parser import SheetParser, PCRParser, SampleParser
|
||||||
from backend.excel.reports import make_report_html, make_report_xlsx, convert_data_list_to_df
|
from backend.excel.reports import make_report_html, make_report_xlsx, convert_data_list_to_df
|
||||||
@@ -34,10 +36,12 @@ from .custom_widgets.pop_ups import AlertPop, QuestionAsker
|
|||||||
from .custom_widgets import ReportDatePicker
|
from .custom_widgets import ReportDatePicker
|
||||||
from .custom_widgets.misc import ImportReagent, ParsedQLabel
|
from .custom_widgets.misc import ImportReagent, ParsedQLabel
|
||||||
from .visualizations.control_charts import create_charts, construct_html
|
from .visualizations.control_charts import create_charts, construct_html
|
||||||
|
from pathlib import Path
|
||||||
|
from frontend.custom_widgets.misc import FirstStrandSalvage, FirstStrandPlateList
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
def import_submission_function(obj:QMainWindow) -> Tuple[QMainWindow, dict|None]:
|
def import_submission_function(obj:QMainWindow, fname:Path|None=None) -> Tuple[QMainWindow, dict|None]:
|
||||||
"""
|
"""
|
||||||
Import a new submission to the app window
|
Import a new submission to the app window
|
||||||
|
|
||||||
@@ -56,6 +60,7 @@ def import_submission_function(obj:QMainWindow) -> Tuple[QMainWindow, dict|None]
|
|||||||
obj.missing_info = []
|
obj.missing_info = []
|
||||||
|
|
||||||
# set file dialog
|
# set file dialog
|
||||||
|
if isinstance(fname, bool) or fname == None:
|
||||||
fname = select_open_file(obj, file_extension="xlsx")
|
fname = select_open_file(obj, file_extension="xlsx")
|
||||||
logger.debug(f"Attempting to parse file: {fname}")
|
logger.debug(f"Attempting to parse file: {fname}")
|
||||||
if not fname.exists():
|
if not fname.exists():
|
||||||
@@ -113,7 +118,6 @@ def import_submission_function(obj:QMainWindow) -> Tuple[QMainWindow, dict|None]
|
|||||||
add_widget = QComboBox()
|
add_widget = QComboBox()
|
||||||
# lookup existing kits by 'submission_type' decided on by sheetparser
|
# lookup existing kits by 'submission_type' decided on by sheetparser
|
||||||
logger.debug(f"Looking up kits used for {pyd.submission_type['value']}")
|
logger.debug(f"Looking up kits used for {pyd.submission_type['value']}")
|
||||||
# uses = [item.__str__() for item in lookup_kittype_by_use(ctx=obj.ctx, used_for=pyd.submission_type['value'])]
|
|
||||||
uses = [item.__str__() for item in lookup_kit_types(ctx=obj.ctx, used_for=pyd.submission_type['value'])]
|
uses = [item.__str__() for item in lookup_kit_types(ctx=obj.ctx, used_for=pyd.submission_type['value'])]
|
||||||
logger.debug(f"Kits received for {pyd.submission_type['value']}: {uses}")
|
logger.debug(f"Kits received for {pyd.submission_type['value']}: {uses}")
|
||||||
if check_not_nan(value['value']):
|
if check_not_nan(value['value']):
|
||||||
@@ -125,7 +129,6 @@ def import_submission_function(obj:QMainWindow) -> Tuple[QMainWindow, dict|None]
|
|||||||
obj.ext_kit = uses[0]
|
obj.ext_kit = uses[0]
|
||||||
# Run reagent scraper whenever extraction kit is changed.
|
# Run reagent scraper whenever extraction kit is changed.
|
||||||
add_widget.currentTextChanged.connect(obj.scrape_reagents)
|
add_widget.currentTextChanged.connect(obj.scrape_reagents)
|
||||||
# add_widget.addItems(uses)
|
|
||||||
case 'submitted_date':
|
case 'submitted_date':
|
||||||
# uses base calendar
|
# uses base calendar
|
||||||
add_widget = QDateEdit(calendarPopup=True)
|
add_widget = QDateEdit(calendarPopup=True)
|
||||||
@@ -221,7 +224,7 @@ def kit_integrity_completion_function(obj:QMainWindow) -> Tuple[QMainWindow, dic
|
|||||||
# get current kit being used
|
# get current kit being used
|
||||||
obj.ext_kit = kit_widget.currentText()
|
obj.ext_kit = kit_widget.currentText()
|
||||||
for item in obj.reagents:
|
for item in obj.reagents:
|
||||||
obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':True}, item.type, title=False))
|
obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':True}, item.type, title=False, label_name=f"lot_{item.type}"))
|
||||||
reagent = dict(type=item.type, lot=item.lot, exp=item.exp, name=item.name)
|
reagent = dict(type=item.type, lot=item.lot, exp=item.exp, name=item.name)
|
||||||
add_widget = ImportReagent(ctx=obj.ctx, reagent=reagent, extraction_kit=obj.ext_kit)
|
add_widget = ImportReagent(ctx=obj.ctx, reagent=reagent, extraction_kit=obj.ext_kit)
|
||||||
obj.table_widget.formlayout.addWidget(add_widget)
|
obj.table_widget.formlayout.addWidget(add_widget)
|
||||||
@@ -231,7 +234,7 @@ def kit_integrity_completion_function(obj:QMainWindow) -> Tuple[QMainWindow, dic
|
|||||||
result = dict(message=f"The submission you are importing is missing some reagents expected by the kit.\n\nIt looks like you are missing: {[item.type.upper() for item in obj.missing_reagents]}\n\nAlternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.\n\nPlease make sure you check the lots carefully!", status="Warning")
|
result = dict(message=f"The submission you are importing is missing some reagents expected by the kit.\n\nIt looks like you are missing: {[item.type.upper() for item in obj.missing_reagents]}\n\nAlternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.\n\nPlease make sure you check the lots carefully!", status="Warning")
|
||||||
for item in obj.missing_reagents:
|
for item in obj.missing_reagents:
|
||||||
# Add label that has parsed as False to show "MISSING" label.
|
# Add label that has parsed as False to show "MISSING" label.
|
||||||
obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':False}, item.type, title=False))
|
obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':False}, item.type, title=False, label_name=f"missing_{item.type}"))
|
||||||
# Set default parameters for the empty reagent.
|
# Set default parameters for the empty reagent.
|
||||||
reagent = dict(type=item.type, lot=None, exp=date.today(), name=None)
|
reagent = dict(type=item.type, lot=None, exp=date.today(), name=None)
|
||||||
# create and add widget
|
# create and add widget
|
||||||
@@ -896,8 +899,8 @@ def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_re
|
|||||||
logger.debug(f"Attempting: {item['type']}")
|
logger.debug(f"Attempting: {item['type']}")
|
||||||
worksheet.cell(row=item['location']['row'], column=item['location']['column'], value=item['value'])
|
worksheet.cell(row=item['location']['row'], column=item['location']['column'], value=item['value'])
|
||||||
# Hacky way to pop in 'signed by'
|
# Hacky way to pop in 'signed by'
|
||||||
if info['submission_type'] == "Bacterial Culture":
|
custom_parser = get_polymorphic_subclass(BasicSubmission, info['submission_type'])
|
||||||
workbook["Sample List"].cell(row=14, column=2, value=getuser()[0:2].upper())
|
workbook = custom_parser.custom_autofill(workbook)
|
||||||
fname = select_save_file(obj=obj, default_name=info['rsl_plate_num'], extension="xlsx")
|
fname = select_save_file(obj=obj, default_name=info['rsl_plate_num'], extension="xlsx")
|
||||||
workbook.save(filename=fname.__str__())
|
workbook.save(filename=fname.__str__())
|
||||||
|
|
||||||
@@ -934,46 +937,63 @@ def construct_first_strand_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]
|
|||||||
logger.debug(f"Samples: {pformat(samples)}")
|
logger.debug(f"Samples: {pformat(samples)}")
|
||||||
logger.debug("Called first strand sample parser")
|
logger.debug("Called first strand sample parser")
|
||||||
plates = sprsr.grab_plates()
|
plates = sprsr.grab_plates()
|
||||||
|
# Fix no plates found in form.
|
||||||
|
if plates == []:
|
||||||
|
dlg = FirstStrandPlateList(ctx=obj.ctx)
|
||||||
|
if dlg.exec():
|
||||||
|
plates = dlg.parse_form()
|
||||||
|
plates = list(set(plates))
|
||||||
logger.debug(f"Plates: {pformat(plates)}")
|
logger.debug(f"Plates: {pformat(plates)}")
|
||||||
output_samples = []
|
output_samples = []
|
||||||
logger.debug(f"Samples: {pformat(samples)}")
|
logger.debug(f"Samples: {pformat(samples)}")
|
||||||
old_plate_number = 1
|
old_plate_number = 1
|
||||||
|
old_plate = ''
|
||||||
for item in samples:
|
for item in samples:
|
||||||
try:
|
try:
|
||||||
item['well'] = re.search(r"\s\((.*)\)$", item['submitter_id']).groups()[0]
|
item['well'] = re.search(r"\s\((.*)\)$", item['submitter_id']).groups()[0]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
item['well'] = item
|
pass
|
||||||
item['submitter_id'] = re.sub(r"\s\(.*\)$", "", str(item['submitter_id'])).strip()
|
item['submitter_id'] = re.sub(r"\s\(.*\)$", "", str(item['submitter_id'])).strip()
|
||||||
new_dict = {}
|
new_dict = {}
|
||||||
new_dict['sample'] = item['submitter_id']
|
new_dict['sample'] = item['submitter_id']
|
||||||
if item['submitter_id'] == "NTC1":
|
|
||||||
new_dict['destination_row'] = 8
|
|
||||||
new_dict['destination_column'] = 2
|
|
||||||
new_dict['plate_number'] = 'control'
|
|
||||||
elif item['submitter_id'] == "NTC2":
|
|
||||||
new_dict['destination_row'] = 8
|
|
||||||
new_dict['destination_column'] = 5
|
|
||||||
new_dict['plate_number'] = 'control'
|
|
||||||
else:
|
|
||||||
new_dict['destination_row'] = item['row']
|
|
||||||
new_dict['destination_column'] = item['column']
|
|
||||||
plate_num, plate = get_plates(input_sample_number=new_dict['sample'], plates=plates)
|
plate_num, plate = get_plates(input_sample_number=new_dict['sample'], plates=plates)
|
||||||
if plate_num == None:
|
if plate_num == None:
|
||||||
plate_num = str(old_plate_number) + "*"
|
plate_num = str(old_plate_number) + "*"
|
||||||
else:
|
else:
|
||||||
old_plate_number = plate_num
|
old_plate_number = plate_num
|
||||||
logger.debug(f"Got plate number: {plate_num}, plate: {plate}")
|
logger.debug(f"Got plate number: {plate_num}, plate: {plate}")
|
||||||
|
if item['submitter_id'] == "NTC1":
|
||||||
|
new_dict['destination_row'] = 8
|
||||||
|
new_dict['destination_column'] = 2
|
||||||
|
new_dict['plate_number'] = 'control'
|
||||||
|
new_dict['plate'] = None
|
||||||
|
output_samples.append(new_dict)
|
||||||
|
continue
|
||||||
|
elif item['submitter_id'] == "NTC2":
|
||||||
|
new_dict['destination_row'] = 8
|
||||||
|
new_dict['destination_column'] = 5
|
||||||
|
new_dict['plate_number'] = 'control'
|
||||||
|
new_dict['plate'] = None
|
||||||
|
output_samples.append(new_dict)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
new_dict['destination_row'] = item['row']
|
||||||
|
new_dict['destination_column'] = item['column']
|
||||||
|
new_dict['plate_number'] = plate_num
|
||||||
|
# Fix plate association not found
|
||||||
if plate == None:
|
if plate == None:
|
||||||
|
dlg = FirstStrandSalvage(ctx=obj.ctx, submitter_id=item['submitter_id'], rsl_plate_num=old_plate)
|
||||||
|
if dlg.exec():
|
||||||
|
item.update(dlg.parse_form())
|
||||||
try:
|
try:
|
||||||
new_dict['source_row'], new_dict['source_column'] = convert_well_to_row_column(item['well'])
|
new_dict['source_row'], new_dict['source_column'] = convert_well_to_row_column(item['well'])
|
||||||
new_dict['plate_number'] = plate_num
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
new_dict['plate_number'] = plate_num
|
|
||||||
new_dict['plate'] = plate.submission.rsl_plate_num
|
new_dict['plate'] = plate.submission.rsl_plate_num
|
||||||
new_dict['source_row'] = plate.row
|
new_dict['source_row'] = plate.row
|
||||||
new_dict['source_column'] = plate.column
|
new_dict['source_column'] = plate.column
|
||||||
|
old_plate = plate.submission.rsl_plate_num
|
||||||
output_samples.append(new_dict)
|
output_samples.append(new_dict)
|
||||||
df = pd.DataFrame.from_records(output_samples)
|
df = pd.DataFrame.from_records(output_samples)
|
||||||
df.sort_values(by=['destination_column', 'destination_row'], ascending=True, inplace=True)
|
df.sort_values(by=['destination_column', 'destination_row'], ascending=True, inplace=True)
|
||||||
@@ -1001,6 +1021,7 @@ def scrape_reagents(obj:QMainWindow, extraction_kit:str) -> Tuple[QMainWindow, d
|
|||||||
obj.missing_reagents = []
|
obj.missing_reagents = []
|
||||||
# Remove previous reagent widgets
|
# Remove previous reagent widgets
|
||||||
[item.setParent(None) for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget) if item.objectName().startswith("lot_") or item.objectName().startswith("missing_")]
|
[item.setParent(None) for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget) if item.objectName().startswith("lot_") or item.objectName().startswith("missing_")]
|
||||||
|
[item.setParent(None) for item in obj.table_widget.formlayout.parentWidget().findChildren(QPushButton)]
|
||||||
reagents = obj.prsr.parse_reagents(extraction_kit=extraction_kit)
|
reagents = obj.prsr.parse_reagents(extraction_kit=extraction_kit)
|
||||||
logger.debug(f"Got reagents: {reagents}")
|
logger.debug(f"Got reagents: {reagents}")
|
||||||
for reagent in obj.prsr.sub['reagents']:
|
for reagent in obj.prsr.sub['reagents']:
|
||||||
@@ -1013,5 +1034,3 @@ def scrape_reagents(obj:QMainWindow, extraction_kit:str) -> Tuple[QMainWindow, d
|
|||||||
logger.debug(f"Missing reagents: {obj.missing_reagents}")
|
logger.debug(f"Missing reagents: {obj.missing_reagents}")
|
||||||
return obj, None
|
return obj, None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user