Troubleshooting reports

This commit is contained in:
Landon Wark
2023-02-16 15:30:38 -06:00
parent 85dad791ec
commit 1c89c31d25
11 changed files with 146 additions and 40 deletions

View File

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

View File

@@ -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()

View File

@@ -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']}")

View File

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

View File

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

View File

@@ -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):

View File

@@ -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)
@@ -108,6 +118,34 @@ class SubmissionsSheet(QTableView):
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

View File

@@ -20,6 +20,7 @@
&nbsp;&nbsp;&nbsp;&nbsp;{{ 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 @@
&nbsp;&nbsp;&nbsp;&nbsp;{{ item['well'] }}: {{ item['name'] }}<br>
{% endif %}
{% endfor %}</p>
{% endif %}
{% if sub['controls'] %}
<h3><u>Attached Controls:</u></h3>
{% for item in sub['controls'] %}

View File

@@ -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'] %}

View File

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