Troubleshooting reports
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
# __init__.py
|
||||
|
||||
# Version of the realpython-reader package
|
||||
__version__ = "1.3.0"
|
||||
__version__ = "202302.3b"
|
||||
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
|
||||
__copyright__ = "2022-2023, Government of Canada"
|
||||
|
||||
@@ -15,7 +15,9 @@ import json
|
||||
# from dateutil.relativedelta import relativedelta
|
||||
from getpass import getuser
|
||||
import numpy as np
|
||||
from tools import check_not_nan
|
||||
from tools import check_not_nan, check_is_power_user
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -367,7 +369,8 @@ def lookup_org_by_name(ctx:dict, name:str|None) -> models.Organization:
|
||||
models.Organization: retrieved organization
|
||||
"""
|
||||
logger.debug(f"Querying organization: {name}")
|
||||
return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first()
|
||||
# return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first()
|
||||
return ctx['database_session'].query(models.Organization).filter(models.Organization.name.startswith(name)).first()
|
||||
|
||||
def submissions_to_df(ctx:dict, sub_type:str|None=None) -> pd.DataFrame:
|
||||
"""
|
||||
@@ -457,13 +460,10 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> dict:
|
||||
Returns:
|
||||
dict: a dictionary containing results of db addition
|
||||
"""
|
||||
try:
|
||||
power_users = ctx['power_users']
|
||||
except KeyError:
|
||||
logger.debug("This user does not have permission to add kits.")
|
||||
return {'code':1,'message':"This user does not have permission to add kits."}
|
||||
logger.debug(f"Adding kit for user: {getuser()}")
|
||||
if getuser() not in power_users:
|
||||
# try:
|
||||
# power_users = ctx['power_users']
|
||||
# except KeyError:
|
||||
if not check_is_power_user(ctx=ctx):
|
||||
logger.debug(f"{getuser()} does not have permission to add kits.")
|
||||
return {'code':1, 'message':"This user does not have permission to add kits."}
|
||||
for type in exp:
|
||||
@@ -499,13 +499,14 @@ def create_org_from_yaml(ctx:dict, org:dict) -> dict:
|
||||
Returns:
|
||||
dict: dictionary containing results of db addition
|
||||
"""
|
||||
try:
|
||||
power_users = ctx['power_users']
|
||||
except KeyError:
|
||||
logger.debug("This user does not have permission to add kits.")
|
||||
return {'code':1,'message':"This user does not have permission to add organizations."}
|
||||
logger.debug(f"Adding organization for user: {getuser()}")
|
||||
if getuser() not in power_users:
|
||||
# try:
|
||||
# power_users = ctx['power_users']
|
||||
# except KeyError:
|
||||
# logger.debug("This user does not have permission to add kits.")
|
||||
# return {'code':1,'message':"This user does not have permission to add organizations."}
|
||||
# logger.debug(f"Adding organization for user: {getuser()}")
|
||||
# if getuser() not in power_users:
|
||||
if not check_is_power_user(ctx=ctx):
|
||||
logger.debug(f"{getuser()} does not have permission to add kits.")
|
||||
return {'code':1, 'message':"This user does not have permission to add organizations."}
|
||||
for client in org:
|
||||
@@ -624,3 +625,23 @@ def lookup_submission_by_rsl_num(ctx:dict, rsl_num:str):
|
||||
|
||||
def lookup_submissions_using_reagent(ctx:dict, reagent:models.Reagent) -> list[models.BasicSubmission]:
|
||||
return ctx['database_session'].query(models.BasicSubmission).join(reagents_submissions).filter(reagents_submissions.c.reagent_id==reagent.id).all()
|
||||
|
||||
|
||||
def delete_submission_by_id(ctx:dict, id:int) -> None:
|
||||
"""
|
||||
Deletes a submission and its associated samples from the database.
|
||||
|
||||
Args:
|
||||
ctx (dict): settings passed down from gui
|
||||
id (int): id of submission to be deleted.
|
||||
"""
|
||||
# In order to properly do this Im' going to have to delete all of the secondary table stuff as well.
|
||||
sub = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.id==id).first()
|
||||
backup = sub.to_dict()
|
||||
with open(Path(ctx['backup_path']).joinpath(f"{sub.rsl_plate_num}-backup({date.today().strftime('%Y%m%d')}).yml"), "w") as f:
|
||||
yaml.dump(backup, f)
|
||||
sub.reagents = []
|
||||
for sample in sub.samples:
|
||||
ctx['database_session'].delete(sample)
|
||||
ctx["database_session"].delete(sub)
|
||||
ctx["database_session"].commit()
|
||||
@@ -56,7 +56,7 @@ class Control(Base):
|
||||
output = {
|
||||
"name" : self.name,
|
||||
"type" : self.controltype.name,
|
||||
"targets" : " ,".join(targets),
|
||||
"targets" : ", ".join(targets),
|
||||
"kraken" : new_kraken[0:5]
|
||||
}
|
||||
return output
|
||||
|
||||
@@ -74,6 +74,14 @@ class BasicSubmission(Base):
|
||||
ext_info = json.loads(self.extraction_info)
|
||||
except TypeError:
|
||||
ext_info = None
|
||||
try:
|
||||
reagents = [item.to_sub_dict() for item in self.reagents]
|
||||
except:
|
||||
reagents = None
|
||||
try:
|
||||
samples = [item.to_sub_dict() for item in self.samples]
|
||||
except:
|
||||
samples = None
|
||||
output = {
|
||||
"id": self.id,
|
||||
"Plate Number": self.rsl_plate_num,
|
||||
@@ -85,6 +93,8 @@ class BasicSubmission(Base):
|
||||
"Extraction Kit": ext_kit,
|
||||
"Technician": self.technician,
|
||||
"Cost": self.run_cost,
|
||||
"reagents": reagents,
|
||||
"samples": samples,
|
||||
"ext_info": ext_info
|
||||
}
|
||||
# logger.debug(f"{self.rsl_plate_num} extraction: {output['Extraction Status']}")
|
||||
|
||||
@@ -187,14 +187,18 @@ class SheetParser(object):
|
||||
if not isinstance(row[5], float) and check_not_nan(row[5]):
|
||||
# must be prefixed with 'lot_' to be recognized by gui
|
||||
# regex below will remove 80% from 80% ethanol in the Wastewater kit.
|
||||
output_key = re.sub(r"\d{1,3}%", "", row[0].lower().strip().replace(' ', '_'))
|
||||
output_key = re.sub(r"^\d{1,3}%\s?", "", row[0].lower().strip().replace(' ', '_'))
|
||||
output_key = output_key.strip("_")
|
||||
try:
|
||||
output_var = row[5].upper()
|
||||
except AttributeError:
|
||||
logger.debug(f"Couldn't upperize {row[5]}, must be a number")
|
||||
output_var = row[5]
|
||||
if check_not_nan(row[7]):
|
||||
try:
|
||||
expiry = row[7].date()
|
||||
except AttributeError:
|
||||
expiry = date.today()
|
||||
else:
|
||||
expiry = date.today()
|
||||
self.sub[f"lot_{output_key}"] = {'lot':output_var, 'exp':expiry}
|
||||
|
||||
@@ -41,6 +41,9 @@ def make_report_xlsx(records:list[dict]) -> DataFrame:
|
||||
# df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')] = df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')].applymap('${:,.2f}'.format)
|
||||
return df2
|
||||
|
||||
# def split_row_item(item:str) -> float:
|
||||
# return item.split(" ")[-1]
|
||||
|
||||
|
||||
def make_report_html(df:DataFrame, start_date:date, end_date:date) -> str:
|
||||
|
||||
@@ -59,17 +62,23 @@ def make_report_html(df:DataFrame, start_date:date, end_date:date) -> str:
|
||||
output = []
|
||||
logger.debug(f"Report DataFrame: {df}")
|
||||
for ii, row in enumerate(df.iterrows()):
|
||||
row = [item for item in row]
|
||||
logger.debug(f"Row: {row}")
|
||||
|
||||
# row = [item for item in row]
|
||||
logger.debug(f"Row {ii}: {row}")
|
||||
lab = row[0][0]
|
||||
logger.debug(type(row))
|
||||
logger.debug(f"Old lab: {old_lab}, Current lab: {lab}")
|
||||
kit = dict(name=row[0][1], cost=row[1]['Cost'], plate_count=int(row[1]['Kit Count']), sample_count=int(row[1]['Sample Count']))
|
||||
logger.debug(f"Name: {row[0][1]}")
|
||||
data = [item for item in row[1]]
|
||||
# logger.debug(data)
|
||||
# logger.debug(f"Cost: {split_row_item(data[1])}")
|
||||
# logger.debug(f"Kit count: {split_row_item(data[0])}")
|
||||
# logger.debug(f"Sample Count: {split_row_item(data[2])}")
|
||||
kit = dict(name=row[0][1], cost=data[1], plate_count=int(data[0]), sample_count=int(data[2]))
|
||||
if lab == old_lab:
|
||||
output[ii-1]['kits'].append(kit)
|
||||
output[ii-1]['total_cost'] += kit['cost']
|
||||
output[ii-1]['total_samples'] += kit['sample_count']
|
||||
output[ii-1]['total_plates'] += kit['plate_count']
|
||||
output[-1]['kits'].append(kit)
|
||||
output[-1]['total_cost'] += kit['cost']
|
||||
output[-1]['total_samples'] += kit['sample_count']
|
||||
output[-1]['total_plates'] += kit['plate_count']
|
||||
else:
|
||||
adder = dict(lab=lab, kits=[kit], total_cost=kit['cost'], total_samples=kit['sample_count'], total_plates=kit['plate_count'])
|
||||
output.append(adder)
|
||||
|
||||
@@ -52,7 +52,7 @@ class App(QMainWindow):
|
||||
super().__init__()
|
||||
self.ctx = ctx
|
||||
try:
|
||||
self.title = f"Submissions App (v{ctx['package'].__version__})"
|
||||
self.title = f"Submissions App (v{ctx['package'].__version__}) - {ctx['database']}"
|
||||
except AttributeError:
|
||||
self.title = f"Submissions App"
|
||||
# set initial app position and size
|
||||
@@ -85,6 +85,7 @@ class App(QMainWindow):
|
||||
reportMenu = menuBar.addMenu("&Reports")
|
||||
maintenanceMenu = menuBar.addMenu("&Monthly")
|
||||
helpMenu = menuBar.addMenu("&Help")
|
||||
helpMenu.addAction(self.helpAction)
|
||||
fileMenu.addAction(self.importAction)
|
||||
reportMenu.addAction(self.generateReportAction)
|
||||
maintenanceMenu.addAction(self.joinControlsAction)
|
||||
@@ -111,6 +112,7 @@ class App(QMainWindow):
|
||||
self.addOrgAction = QAction("Add Org", self)
|
||||
self.joinControlsAction = QAction("Link Controls")
|
||||
self.joinExtractionAction = QAction("Link Ext Logs")
|
||||
self.helpAction = QAction("&About", self)
|
||||
|
||||
|
||||
def _connectActions(self):
|
||||
@@ -128,6 +130,12 @@ class App(QMainWindow):
|
||||
self.table_widget.datepicker.end_date.dateChanged.connect(self._controls_getter)
|
||||
self.joinControlsAction.triggered.connect(self.linkControls)
|
||||
self.joinExtractionAction.triggered.connect(self.linkExtractions)
|
||||
self.helpAction.triggered.connect(self.showAbout)
|
||||
|
||||
def showAbout(self):
|
||||
output = f"Version: {self.ctx['package'].__version__}\n\nAuthor: {self.ctx['package'].__author__['name']} - {self.ctx['package'].__author__['email']}\n\nCopyright: {self.ctx['package'].__copyright__}"
|
||||
about = AlertPop(message=output, status="information")
|
||||
about.exec()
|
||||
|
||||
|
||||
def importSubmission(self):
|
||||
|
||||
@@ -2,17 +2,19 @@ from datetime import date
|
||||
from PyQt6.QtWidgets import (
|
||||
QVBoxLayout, QDialog, QTableView,
|
||||
QTextEdit, QPushButton, QScrollArea,
|
||||
QMessageBox, QFileDialog
|
||||
QMessageBox, QFileDialog, QMenu
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QAbstractTableModel
|
||||
from PyQt6.QtGui import QFontMetrics
|
||||
from PyQt6.QtGui import QFontMetrics, QAction, QCursor
|
||||
|
||||
from backend.db import submissions_to_df, lookup_submission_by_id, lookup_all_sample_types, create_kit_from_yaml
|
||||
from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from xhtml2pdf import pisa
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from .pop_ups import AlertPop, QuestionAsker
|
||||
from tools import check_is_power_user
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -91,6 +93,14 @@ class SubmissionsSheet(QTableView):
|
||||
sets data in model
|
||||
"""
|
||||
self.data = submissions_to_df(ctx=self.ctx)
|
||||
try:
|
||||
del self.data['samples']
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
del self.data['reagents']
|
||||
except KeyError:
|
||||
pass
|
||||
self.model = pandasModel(self.data)
|
||||
self.setModel(self.model)
|
||||
# self.resize(800,600)
|
||||
@@ -99,15 +109,43 @@ class SubmissionsSheet(QTableView):
|
||||
"""
|
||||
creates detailed data to show in seperate window
|
||||
"""
|
||||
index=(self.selectionModel().currentIndex())
|
||||
index = (self.selectionModel().currentIndex())
|
||||
# logger.debug(index)
|
||||
value=index.sibling(index.row(),0).data()
|
||||
value = index.sibling(index.row(),0).data()
|
||||
dlg = SubmissionDetails(ctx=self.ctx, id=value)
|
||||
# dlg.show()
|
||||
if dlg.exec():
|
||||
pass
|
||||
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
self.menu = QMenu(self)
|
||||
renameAction = QAction('Delete', self)
|
||||
detailsAction = QAction('Details', self)
|
||||
# Originally I intended to limit deletions to power users.
|
||||
# renameAction.setEnabled(False)
|
||||
# if check_is_power_user(ctx=self.ctx):
|
||||
# renameAction.setEnabled(True)
|
||||
renameAction.triggered.connect(lambda: self.delete_item(event))
|
||||
detailsAction.triggered.connect(lambda: self.show_details())
|
||||
self.menu.addAction(detailsAction)
|
||||
self.menu.addAction(renameAction)
|
||||
# add other required actions
|
||||
self.menu.popup(QCursor.pos())
|
||||
|
||||
|
||||
def delete_item(self, event):
|
||||
index = (self.selectionModel().currentIndex())
|
||||
value = index.sibling(index.row(),0).data()
|
||||
logger.debug(index)
|
||||
msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {index.sibling(index.row(),1).data()}?\n")
|
||||
if msg.exec():
|
||||
delete_submission_by_id(ctx=self.ctx, id=value)
|
||||
else:
|
||||
return
|
||||
self.setData()
|
||||
|
||||
|
||||
|
||||
|
||||
class SubmissionDetails(QDialog):
|
||||
@@ -130,8 +168,8 @@ class SubmissionDetails(QDialog):
|
||||
# don't want id
|
||||
del self.base_dict['id']
|
||||
# convert sub objects to dicts
|
||||
self.base_dict['reagents'] = [item.to_sub_dict() for item in data.reagents]
|
||||
self.base_dict['samples'] = [item.to_sub_dict() for item in data.samples]
|
||||
# self.base_dict['reagents'] = [item.to_sub_dict() for item in data.reagents]
|
||||
# self.base_dict['samples'] = [item.to_sub_dict() for item in data.samples]
|
||||
# retrieve jinja template
|
||||
template = env.get_template("submission_details.txt")
|
||||
# render using object dict
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
{{ item['type'] }}: {{ item['lot'] }} (EXP: {{ item['expiry'] }})<br>
|
||||
{% endif %}
|
||||
{% endfor %}</p>
|
||||
{% if sub['samples'] %}
|
||||
<h3><u>Samples:</u></h3>
|
||||
<p>{% for item in sub['samples'] %}
|
||||
{% if loop.index == 1 %}
|
||||
@@ -28,6 +29,7 @@
|
||||
{{ item['well'] }}: {{ item['name'] }}<br>
|
||||
{% endif %}
|
||||
{% endfor %}</p>
|
||||
{% endif %}
|
||||
{% if sub['controls'] %}
|
||||
<h3><u>Attached Controls:</u></h3>
|
||||
{% for item in sub['controls'] %}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
Reagents:
|
||||
{% for item in sub['reagents'] %}
|
||||
{{ item['type'] }}: {{ item['lot'] }} (EXP: {{ item['expiry'] }}){% endfor %}
|
||||
|
||||
{% if sub['samples']%}
|
||||
Samples:
|
||||
{% for item in sub['samples'] %}
|
||||
{{ item['well'] }}: {{ item['name'] }}{% endfor %}
|
||||
{{ item['well'] }}: {{ item['name'] }}{% endfor %}{% endif %}
|
||||
{% if sub['controls'] %}
|
||||
Attached Controls:
|
||||
{% for item in sub['controls'] %}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import numpy as np
|
||||
import logging
|
||||
import getpass
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -11,3 +12,14 @@ def check_not_nan(cell_contents) -> bool:
|
||||
except Exception as e:
|
||||
logger.debug(f"Check encounteded unknown error: {type(e).__name__} - {e}")
|
||||
return False
|
||||
|
||||
|
||||
def check_is_power_user(ctx:dict) -> bool:
|
||||
try:
|
||||
check = getpass.getuser() in ctx['power_users']
|
||||
except KeyError as e:
|
||||
check = False
|
||||
except Exception as e:
|
||||
logger.debug(f"Check encounteded unknown error: {type(e).__name__} - {e}")
|
||||
check = False
|
||||
return check
|
||||
Reference in New Issue
Block a user