Improved reporting, price tracking
This commit is contained in:
@@ -5,7 +5,7 @@ import sqlite3
|
||||
# from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
# from sqlite3 import IntegrityError, OperationalError
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
from datetime import date, datetime, timedelta
|
||||
from sqlalchemy import and_
|
||||
import uuid
|
||||
import base64
|
||||
@@ -132,6 +132,12 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
|
||||
except AttributeError:
|
||||
logger.debug(f"Could not set attribute: {item} to {info_dict[item]}")
|
||||
continue
|
||||
# calculate cost of the run: immutable cost + mutable times number of columns
|
||||
try:
|
||||
instance.run_cost = instance.extraction_kit.immutable_cost + (instance.extraction_kit.mutable_cost * ((instance.sample_count / 8)/12))
|
||||
except TypeError:
|
||||
logger.debug(f"Looks like that kit doesn't have cost breakdown yet, using full plate cost.")
|
||||
instance.run_cost = instance.extraction_kit.cost_per_run
|
||||
logger.debug(f"Constructed instance: {instance.to_string()}")
|
||||
logger.debug(msg)
|
||||
return instance, {'message':msg}
|
||||
@@ -395,12 +401,13 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> None:
|
||||
exp (dict): Experiment dictionary created from yaml file
|
||||
"""
|
||||
try:
|
||||
super_users = ctx['super_users']
|
||||
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."}
|
||||
if getuser not in super_users:
|
||||
logger.debug("This user does not have permission to add kits.")
|
||||
logger.debug(f"Adding kit for user: {getuser()}")
|
||||
if getuser() not in power_users:
|
||||
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:
|
||||
if type == "password":
|
||||
@@ -410,7 +417,7 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> None:
|
||||
for r in exp[type]['kits'][kt]['reagenttypes']:
|
||||
look_up = ctx['database_session'].query(models.ReagentType).filter(models.ReagentType.name==r).first()
|
||||
if look_up == None:
|
||||
rt = models.ReagentType(name=r.replace(" ", "_").lower(), eol_ext=datetime.timedelta(30*exp[type]['kits'][kt]['reagenttypes'][r]['eol_ext']), kits=[kit])
|
||||
rt = models.ReagentType(name=r.replace(" ", "_").lower(), eol_ext=timedelta(30*exp[type]['kits'][kt]['reagenttypes'][r]['eol_ext']), kits=[kit])
|
||||
else:
|
||||
rt = look_up
|
||||
rt.kits.append(kit)
|
||||
|
||||
@@ -18,6 +18,8 @@ class KitType(Base):
|
||||
submissions = relationship("BasicSubmission", back_populates="extraction_kit") #: submissions this kit was used for
|
||||
used_for = Column(JSON) #: list of names of sample types this kit can process
|
||||
cost_per_run = Column(FLOAT(2)) #: dollar amount for each full run of this kit
|
||||
mutable_cost = Column(FLOAT(2)) #: dollar amount that can change with number of columns (reagents, tips, etc)
|
||||
constant_cost = Column(FLOAT(2)) #: dollar amount that will remain constant (plates, man hours, etc)
|
||||
reagent_types = relationship("ReagentType", back_populates="kits", uselist=True, secondary=reagenttypes_kittypes) #: reagent types this kit contains
|
||||
reagent_types_id = Column(INTEGER, ForeignKey("_reagent_types.id", ondelete='SET NULL', use_alter=True, name="fk_KT_reagentstype_id")) #: joined reagent type id
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from . import Base
|
||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, Table
|
||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, Table, JSON, FLOAT
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime as dt
|
||||
|
||||
@@ -26,6 +26,8 @@ class BasicSubmission(Base):
|
||||
# Move this into custom types?
|
||||
reagents = relationship("Reagent", back_populates="submissions", secondary=reagents_submissions) #: relationship to reagents
|
||||
reagents_id = Column(String, ForeignKey("_reagents.id", ondelete="SET NULL", name="fk_BS_reagents_id")) #: id of used reagents
|
||||
extraction_info = Column(JSON) #: unstructured output from the extraction table logger.
|
||||
run_cost = Column(FLOAT(2))
|
||||
|
||||
__mapper_args__ = {
|
||||
"polymorphic_identity": "basic_submission",
|
||||
@@ -73,6 +75,7 @@ class BasicSubmission(Base):
|
||||
"Sample Count": self.sample_count,
|
||||
"Extraction Kit": ext_kit,
|
||||
"Technician": self.technician,
|
||||
"Cost": self.run_cost,
|
||||
}
|
||||
return output
|
||||
|
||||
@@ -99,10 +102,11 @@ class BasicSubmission(Base):
|
||||
except AttributeError:
|
||||
ext_kit = None
|
||||
# get extraction kit cost from nested kittype object
|
||||
try:
|
||||
cost = self.extraction_kit.cost_per_run
|
||||
except AttributeError:
|
||||
cost = None
|
||||
# depreciated as it will change kit cost overtime
|
||||
# try:
|
||||
# cost = self.extraction_kit.cost_per_run
|
||||
# except AttributeError:
|
||||
# cost = None
|
||||
output = {
|
||||
"id": self.id,
|
||||
"Plate Number": self.rsl_plate_num,
|
||||
@@ -112,7 +116,7 @@ class BasicSubmission(Base):
|
||||
"Submitting Lab": sub_lab,
|
||||
"Sample Count": self.sample_count,
|
||||
"Extraction Kit": ext_kit,
|
||||
"Cost": cost
|
||||
"Cost": self.run_cost
|
||||
}
|
||||
return output
|
||||
|
||||
|
||||
@@ -88,7 +88,31 @@ class SheetParser(object):
|
||||
def _parse_bacterial_culture(self) -> None:
|
||||
"""
|
||||
pulls info specific to bacterial culture sample type
|
||||
"""
|
||||
"""
|
||||
|
||||
def _parse_reagents(df:pd.DataFrame) -> None:
|
||||
for ii, row in df.iterrows():
|
||||
logger.debug(f"Running reagent parse for {row[1]} with type {type(row[1])} and value: {row[2]} with type {type(row[2])}")
|
||||
try:
|
||||
check = not np.isnan(row[1])
|
||||
except TypeError:
|
||||
check = True
|
||||
if not isinstance(row[2], float) and check:
|
||||
# must be prefixed with 'lot_' to be recognized by gui
|
||||
try:
|
||||
reagent_type = row[1].replace(' ', '_').lower().strip()
|
||||
except AttributeError:
|
||||
pass
|
||||
if reagent_type == "//":
|
||||
reagent_type = row[0].replace(' ', '_').lower().strip()
|
||||
try:
|
||||
output_var = row[2].upper()
|
||||
except AttributeError:
|
||||
logger.debug(f"Couldn't upperize {row[2]}, must be a number")
|
||||
output_var = row[2]
|
||||
logger.debug(f"Output variable is {output_var}")
|
||||
self.sub[f"lot_{reagent_type}"] = output_var
|
||||
|
||||
submission_info = self._parse_generic("Sample List")
|
||||
# iloc is [row][column] and the first row is set as header row so -2
|
||||
tech = str(submission_info.iloc[11][1])
|
||||
@@ -100,54 +124,91 @@ class SheetParser(object):
|
||||
self.sub['technician'] = tech
|
||||
# reagents
|
||||
# must be prefixed with 'lot_' to be recognized by gui
|
||||
self.sub['lot_wash_1'] = submission_info.iloc[1][6] #if pd.isnull(submission_info.iloc[1][6]) else string_formatter(submission_info.iloc[1][6])
|
||||
self.sub['lot_wash_2'] = submission_info.iloc[2][6] #if pd.isnull(submission_info.iloc[2][6]) else string_formatter(submission_info.iloc[2][6])
|
||||
self.sub['lot_binding_buffer'] = submission_info.iloc[3][6] #if pd.isnull(submission_info.iloc[3][6]) else string_formatter(submission_info.iloc[3][6])
|
||||
self.sub['lot_magnetic_beads'] = submission_info.iloc[4][6] #if pd.isnull(submission_info.iloc[4][6]) else string_formatter(submission_info.iloc[4][6])
|
||||
self.sub['lot_lysis_buffer'] = submission_info.iloc[5][6] #if np.nan(submission_info.iloc[5][6]) else string_formatter(submission_info.iloc[5][6])
|
||||
self.sub['lot_elution_buffer'] = submission_info.iloc[6][6] #if pd.isnull(submission_info.iloc[6][6]) else string_formatter(submission_info.iloc[6][6])
|
||||
self.sub['lot_isopropanol'] = submission_info.iloc[9][6] #if pd.isnull(submission_info.iloc[9][6]) else string_formatter(submission_info.iloc[9][6])
|
||||
self.sub['lot_ethanol'] = submission_info.iloc[10][6] #if pd.isnull(submission_info.iloc[10][6]) else string_formatter(submission_info.iloc[10][6])
|
||||
self.sub['lot_positive_control'] = submission_info.iloc[103][1] #if pd.isnull(submission_info.iloc[103][1]) else string_formatter(submission_info.iloc[103][1])
|
||||
self.sub['lot_plate'] = submission_info.iloc[12][6] #if pd.isnull(submission_info.iloc[12][6]) else string_formatter(submission_info.iloc[12][6])
|
||||
# Todo: find a more adaptable way to read reagents.
|
||||
|
||||
reagent_range = submission_info.iloc[1:13, 4:8]
|
||||
_parse_reagents(reagent_range)
|
||||
# self.sub['lot_wash_1'] = submission_info.iloc[1][6] #if pd.isnull(submission_info.iloc[1][6]) else string_formatter(submission_info.iloc[1][6])
|
||||
# self.sub['lot_wash_2'] = submission_info.iloc[2][6] #if pd.isnull(submission_info.iloc[2][6]) else string_formatter(submission_info.iloc[2][6])
|
||||
# self.sub['lot_binding_buffer'] = submission_info.iloc[3][6] #if pd.isnull(submission_info.iloc[3][6]) else string_formatter(submission_info.iloc[3][6])
|
||||
# self.sub['lot_magnetic_beads'] = submission_info.iloc[4][6] #if pd.isnull(submission_info.iloc[4][6]) else string_formatter(submission_info.iloc[4][6])
|
||||
# self.sub['lot_lysis_buffer'] = submission_info.iloc[5][6] #if np.nan(submission_info.iloc[5][6]) else string_formatter(submission_info.iloc[5][6])
|
||||
# self.sub['lot_elution_buffer'] = submission_info.iloc[6][6] #if pd.isnull(submission_info.iloc[6][6]) else string_formatter(submission_info.iloc[6][6])
|
||||
# self.sub['lot_isopropanol'] = submission_info.iloc[9][6] #if pd.isnull(submission_info.iloc[9][6]) else string_formatter(submission_info.iloc[9][6])
|
||||
# self.sub['lot_ethanol'] = submission_info.iloc[10][6] #if pd.isnull(submission_info.iloc[10][6]) else string_formatter(submission_info.iloc[10][6])
|
||||
# self.sub['lot_positive_control'] = submission_info.iloc[103][1] #if pd.isnull(submission_info.iloc[103][1]) else string_formatter(submission_info.iloc[103][1])
|
||||
# self.sub['lot_plate'] = submission_info.iloc[12][6] #if pd.isnull(submission_info.iloc[12][6]) else string_formatter(submission_info.iloc[12][6])
|
||||
# get individual sample info
|
||||
sample_parser = SampleParser(submission_info.iloc[15:111])
|
||||
sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples")
|
||||
logger.debug(f"Parser result: {self.sub}")
|
||||
self.sub['samples'] = sample_parse()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _parse_wastewater(self) -> None:
|
||||
"""
|
||||
pulls info specific to wastewater sample type
|
||||
"""
|
||||
|
||||
def _parse_reagents(df:pd.DataFrame) -> None:
|
||||
logger.debug(df)
|
||||
for ii, row in df.iterrows():
|
||||
try:
|
||||
check = not np.isnan(row[5])
|
||||
except TypeError:
|
||||
check = True
|
||||
if not isinstance(row[5], float) and check:
|
||||
# must be prefixed with 'lot_' to be recognized by gui
|
||||
output_key = re.sub(r"\d{1,3}%", "", row[0].replace(' ', '_').lower())
|
||||
try:
|
||||
output_var = row[5].upper()
|
||||
except AttributeError:
|
||||
logger.debug(f"Couldn't upperize {row[2]}, must be a number")
|
||||
output_var = row[5]
|
||||
self.sub[f"lot_{output_key}"] = output_var
|
||||
|
||||
# submission_info = self.xl.parse("WW Submissions (ENTER HERE)")
|
||||
submission_info = self._parse_generic("WW Submissions (ENTER HERE)")
|
||||
enrichment_info = self.xl.parse("Enrichment Worksheet", dtype=object)
|
||||
enr_reagent_range = enrichment_info.iloc[0:4, 9:20]
|
||||
extraction_info = self.xl.parse("Extraction Worksheet", dtype=object)
|
||||
ext_reagent_range = extraction_info.iloc[0:5, 9:20]
|
||||
qprc_info = self.xl.parse("qPCR Worksheet", dtype=object)
|
||||
pcr_reagent_range = qprc_info.iloc[0:5, 9:20]
|
||||
self.sub['technician'] = f"Enr: {enrichment_info.columns[2]}, Ext: {extraction_info.columns[2]}, PCR: {qprc_info.columns[2]}"
|
||||
_parse_reagents(enr_reagent_range)
|
||||
_parse_reagents(ext_reagent_range)
|
||||
_parse_reagents(pcr_reagent_range)
|
||||
# reagents
|
||||
logger.debug(qprc_info)
|
||||
self.sub['lot_lysis_buffer'] = enrichment_info.iloc[0][14] #if pd.isnull(enrichment_info.iloc[0][14]) else string_formatter(enrichment_info.iloc[0][14])
|
||||
self.sub['lot_proteinase_K'] = enrichment_info.iloc[1][14] #if pd.isnull(enrichment_info.iloc[1][14]) else string_formatter(enrichment_info.iloc[1][14])
|
||||
self.sub['lot_magnetic_virus_particles'] = enrichment_info.iloc[2][14] #if pd.isnull(enrichment_info.iloc[2][14]) else string_formatter(enrichment_info.iloc[2][14])
|
||||
self.sub['lot_enrichment_reagent_1'] = enrichment_info.iloc[3][14] #if pd.isnull(enrichment_info.iloc[3][14]) else string_formatter(enrichment_info.iloc[3][14])
|
||||
self.sub['lot_binding_buffer'] = extraction_info.iloc[0][14] #if pd.isnull(extraction_info.iloc[0][14]) else string_formatter(extraction_info.iloc[0][14])
|
||||
self.sub['lot_magnetic_beads'] = extraction_info.iloc[1][14] #if pd.isnull(extraction_info.iloc[1][14]) else string_formatter(extraction_info.iloc[1][14])
|
||||
self.sub['lot_wash'] = extraction_info.iloc[2][14] #if pd.isnull(extraction_info.iloc[2][14]) else string_formatter(extraction_info.iloc[2][14])
|
||||
self.sub['lot_ethanol'] = extraction_info.iloc[3][14] #if pd.isnull(extraction_info.iloc[3][14]) else string_formatter(extraction_info.iloc[3][14])
|
||||
self.sub['lot_elution_buffer'] = extraction_info.iloc[4][14] #if pd.isnull(extraction_info.iloc[4][14]) else string_formatter(extraction_info.iloc[4][14])
|
||||
self.sub['lot_master_mix'] = qprc_info.iloc[0][14] #if pd.isnull(qprc_info.iloc[0][14]) else string_formatter(qprc_info.iloc[0][14])
|
||||
self.sub['lot_pre_mix_1'] = qprc_info.iloc[1][14] #if pd.isnull(qprc_info.iloc[1][14]) else string_formatter(qprc_info.iloc[1][14])
|
||||
self.sub['lot_pre_mix_2'] = qprc_info.iloc[2][14] #if pd.isnull(qprc_info.iloc[2][14]) else string_formatter(qprc_info.iloc[2][14])
|
||||
self.sub['lot_positive_control'] = qprc_info.iloc[3][14] #if pd.isnull(qprc_info.iloc[3][14]) else string_formatter(qprc_info.iloc[3][14])
|
||||
self.sub['lot_ddh2o'] = qprc_info.iloc[4][14] #if pd.isnull(qprc_info.iloc[4][14]) else string_formatter(qprc_info.iloc[4][14])
|
||||
# logger.debug(qprc_info)
|
||||
# self.sub['lot_lysis_buffer'] = enrichment_info.iloc[0][14] #if pd.isnull(enrichment_info.iloc[0][14]) else string_formatter(enrichment_info.iloc[0][14])
|
||||
# self.sub['lot_proteinase_K'] = enrichment_info.iloc[1][14] #if pd.isnull(enrichment_info.iloc[1][14]) else string_formatter(enrichment_info.iloc[1][14])
|
||||
# self.sub['lot_magnetic_virus_particles'] = enrichment_info.iloc[2][14] #if pd.isnull(enrichment_info.iloc[2][14]) else string_formatter(enrichment_info.iloc[2][14])
|
||||
# self.sub['lot_enrichment_reagent_1'] = enrichment_info.iloc[3][14] #if pd.isnull(enrichment_info.iloc[3][14]) else string_formatter(enrichment_info.iloc[3][14])
|
||||
# self.sub['lot_binding_buffer'] = extraction_info.iloc[0][14] #if pd.isnull(extraction_info.iloc[0][14]) else string_formatter(extraction_info.iloc[0][14])
|
||||
# self.sub['lot_magnetic_beads'] = extraction_info.iloc[1][14] #if pd.isnull(extraction_info.iloc[1][14]) else string_formatter(extraction_info.iloc[1][14])
|
||||
# self.sub['lot_wash'] = extraction_info.iloc[2][14] #if pd.isnull(extraction_info.iloc[2][14]) else string_formatter(extraction_info.iloc[2][14])
|
||||
# self.sub['lot_ethanol'] = extraction_info.iloc[3][14] #if pd.isnull(extraction_info.iloc[3][14]) else string_formatter(extraction_info.iloc[3][14])
|
||||
# self.sub['lot_elution_buffer'] = extraction_info.iloc[4][14] #if pd.isnull(extraction_info.iloc[4][14]) else string_formatter(extraction_info.iloc[4][14])
|
||||
# self.sub['lot_master_mix'] = qprc_info.iloc[0][14] #if pd.isnull(qprc_info.iloc[0][14]) else string_formatter(qprc_info.iloc[0][14])
|
||||
# self.sub['lot_pre_mix_1'] = qprc_info.iloc[1][14] #if pd.isnull(qprc_info.iloc[1][14]) else string_formatter(qprc_info.iloc[1][14])
|
||||
# self.sub['lot_pre_mix_2'] = qprc_info.iloc[2][14] #if pd.isnull(qprc_info.iloc[2][14]) else string_formatter(qprc_info.iloc[2][14])
|
||||
# self.sub['lot_positive_control'] = qprc_info.iloc[3][14] #if pd.isnull(qprc_info.iloc[3][14]) else string_formatter(qprc_info.iloc[3][14])
|
||||
# self.sub['lot_ddh2o'] = qprc_info.iloc[4][14] #if pd.isnull(qprc_info.iloc[4][14]) else string_formatter(qprc_info.iloc[4][14])
|
||||
# gt individual sample info
|
||||
sample_parser = SampleParser(submission_info.iloc[16:40])
|
||||
sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples")
|
||||
self.sub['samples'] = sample_parse()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SampleParser(object):
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
import numpy as np
|
||||
|
||||
from pandas import DataFrame, concat
|
||||
from backend.db import models
|
||||
import json
|
||||
import logging
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from datetime import date
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
loader_path = Path(sys._MEIPASS).joinpath("files", "templates")
|
||||
else:
|
||||
loader_path = Path(__file__).parents[2].joinpath('templates').absolute().__str__()
|
||||
loader = FileSystemLoader(loader_path)
|
||||
env = Environment(loader=loader)
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -22,12 +34,50 @@ def make_report_xlsx(records:list[dict]) -> DataFrame:
|
||||
df = df.sort_values("Submitting Lab")
|
||||
# aggregate cost and sample count columns
|
||||
df2 = df.groupby(["Submitting Lab", "Extraction Kit"]).agg({'Cost': ['sum', 'count'], 'Sample Count':['sum']})
|
||||
logger.debug(df2.columns)
|
||||
# apply formating to cost column
|
||||
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)
|
||||
# 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 make_report_html(df:DataFrame, start_date:date, end_date:date) -> str:
|
||||
|
||||
"""
|
||||
generates html from the report dataframe
|
||||
|
||||
Args:
|
||||
df (DataFrame): input dataframe generated from 'make_report_xlsx' above
|
||||
start_date (date): starting date of the report period
|
||||
end_date (date): ending date of the report period
|
||||
|
||||
Returns:
|
||||
str: html string
|
||||
"""
|
||||
old_lab = ""
|
||||
output = []
|
||||
for ii, row in enumerate(df.iterrows()):
|
||||
row = [item for item in row]
|
||||
lab = row[0][0]
|
||||
logger.debug(f"Old lab: {old_lab}, Current lab: {lab}")
|
||||
kit = dict(name=row[0][1], cost=row[1][('Cost', 'sum')], plate_count=int(row[1][('Cost', 'count')]), sample_count=int(row[1][('Sample Count', 'sum')]))
|
||||
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']
|
||||
else:
|
||||
adder = dict(lab=lab, kits=[kit], total_cost=kit['cost'], total_samples=kit['sample_count'], total_plates=kit['plate_count'])
|
||||
output.append(adder)
|
||||
old_lab = lab
|
||||
logger.debug(output)
|
||||
dicto = {'start_date':start_date, 'end_date':end_date, 'labs':output}#, "table":table}
|
||||
temp = env.get_template('summary_report.html')
|
||||
html = temp.render(input=dicto)
|
||||
return html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# def split_controls_dictionary(ctx:dict, input_dict) -> list[dict]:
|
||||
# # this will be the date in string form
|
||||
# dict_name = list(input_dict.keys())[0]
|
||||
@@ -126,3 +176,4 @@ def convert_data_list_to_df(ctx:dict, input:list[dict], subtype:str|None=None) -
|
||||
del df[column]
|
||||
# logger.debug(df)
|
||||
return df
|
||||
|
||||
|
||||
@@ -226,6 +226,8 @@ def copy_settings(settings_path:Path, settings:dict) -> dict:
|
||||
# if the current user is not a superuser remove the superusers entry
|
||||
if not getpass.getuser() in settings['super_users']:
|
||||
del settings['super_users']
|
||||
if not getpass.getuser() in settings['power_users']:
|
||||
del settings['power_users']
|
||||
with open(settings_path, 'w') as f:
|
||||
yaml.dump(settings, f)
|
||||
return settings
|
||||
|
||||
@@ -14,6 +14,8 @@ from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
|
||||
from pathlib import Path
|
||||
import plotly
|
||||
import pandas as pd
|
||||
from xhtml2pdf import pisa
|
||||
# import plotly.express as px
|
||||
import yaml
|
||||
|
||||
@@ -25,7 +27,7 @@ from backend.db import (construct_submission_info, lookup_reagent,
|
||||
get_all_Control_Types_names, create_kit_from_yaml, get_all_available_modes, get_all_controls_by_type,
|
||||
get_control_subtypes
|
||||
)
|
||||
from backend.excel.reports import make_report_xlsx
|
||||
from backend.excel.reports import make_report_xlsx, make_report_html
|
||||
import numpy
|
||||
from frontend.custom_widgets import AddReagentQuestion, AddReagentForm, SubmissionsSheet, ReportDatePicker, KitAdder, ControlsDatePicker, OverwriteSubQuestion
|
||||
import logging
|
||||
@@ -362,17 +364,22 @@ class App(QMainWindow):
|
||||
subs = lookup_submissions_by_date_range(ctx=self.ctx, start_date=info['start_date'], end_date=info['end_date'])
|
||||
# convert each object to dict
|
||||
records = [item.report_dict() for item in subs]
|
||||
# make dataframe from record dictionaries
|
||||
df = make_report_xlsx(records=records)
|
||||
# setup filedialog to handle save location of report
|
||||
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submissions_Report_{info['start_date']}-{info['end_date']}.xlsx").resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".xlsx")[0])
|
||||
# save file
|
||||
try:
|
||||
df.to_excel(fname, engine="openpyxl")
|
||||
except PermissionError:
|
||||
pass
|
||||
html = make_report_html(df=df, start_date=info['start_date'], end_date=info['end_date'])
|
||||
# make dataframe from record dictionaries
|
||||
# df = make_report_xlsx(records=records)
|
||||
# # setup filedialog to handle save location of report
|
||||
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submissions_Report_{info['start_date']}-{info['end_date']}.pdf").resolve().__str__()
|
||||
# fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".xlsx")[0])
|
||||
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0])
|
||||
# logger.debug(f"report output name: {fname}")
|
||||
# df.to_excel(fname, engine='openpyxl')
|
||||
with open(fname, "w+b") as f:
|
||||
pisa.CreatePDF(html, dest=f)
|
||||
df.to_excel(fname.with_suffix(".xlsx"), engine='openpyxl')
|
||||
|
||||
|
||||
|
||||
|
||||
def add_kit(self):
|
||||
"""
|
||||
|
||||
@@ -302,11 +302,16 @@ class KitAdder(QWidget):
|
||||
used_for.setEditable(True)
|
||||
self.grid.addWidget(used_for,3,1)
|
||||
# set cost per run
|
||||
self.grid.addWidget(QLabel("Cost per run:"),4,0)
|
||||
self.grid.addWidget(QLabel("Constant cost per full plate (plates, work hours, etc.):"),4,0)
|
||||
cost = QSpinBox()
|
||||
cost.setMinimum(0)
|
||||
cost.setMaximum(9999)
|
||||
self.grid.addWidget(cost,4,1)
|
||||
self.grid.addWidget(QLabel("Mutable cost per full plate (tips, reagents, etc.):"),5,0)
|
||||
cost = QSpinBox()
|
||||
cost.setMinimum(0)
|
||||
cost.setMaximum(9999)
|
||||
self.grid.addWidget(cost,5,1)
|
||||
# button to add additional reagent types
|
||||
self.add_RT_btn = QPushButton("Add Reagent Type")
|
||||
self.grid.addWidget(self.add_RT_btn)
|
||||
@@ -338,7 +343,8 @@ class KitAdder(QWidget):
|
||||
yml_type[used] = {}
|
||||
yml_type[used]['kits'] = {}
|
||||
yml_type[used]['kits'][info['kit_name']] = {}
|
||||
yml_type[used]['kits'][info['kit_name']]['cost'] = info['cost_per_run']
|
||||
yml_type[used]['kits'][info['kit_name']]['constant_cost'] = info["Constant cost per full plate (plates, work hours, etc.)"]
|
||||
yml_type[used]['kits'][info['kit_name']]['mutable_cost'] = info["Mutable cost per full plate (tips, reagents, etc.)"]
|
||||
yml_type[used]['kits'][info['kit_name']]['reagenttypes'] = reagents
|
||||
logger.debug(yml_type)
|
||||
# send to kit constructor
|
||||
@@ -381,20 +387,19 @@ class KitAdder(QWidget):
|
||||
if not isinstance(prev_item, QDateEdit) and not isinstance(prev_item, QComboBox) and not isinstance(prev_item, QSpinBox) and not isinstance(prev_item, QScrollBar):
|
||||
logger.debug(f"Previous: {prev_item}")
|
||||
logger.debug(f"Item: {item}, {item.text()}")
|
||||
values.append(item.text())
|
||||
values.append(item.text().strip())
|
||||
case QComboBox():
|
||||
values.append(item.currentText())
|
||||
values.append(item.currentText().strip())
|
||||
case QDateEdit():
|
||||
values.append(item.date().toPyDate())
|
||||
case QSpinBox():
|
||||
values.append(item.value())
|
||||
case ReagentTypeForm():
|
||||
|
||||
re_labels, re_values, _ = self.extract_form_info(item)
|
||||
reagent = {item[0]:item[1] for item in zip(re_labels, re_values)}
|
||||
logger.debug(reagent)
|
||||
# reagent = {reagent['name:']:{'eol':reagent['extension_of_life_(months):']}}
|
||||
reagents[reagent['name']] = {'eol_ext':int(reagent['extension_of_life_(months)'])}
|
||||
reagents[reagent["name_(*exactly*_as_it_appears_in_the_excel_submission_form)"].strip()] = {'eol_ext':int(reagent['extension_of_life_(months)'])}
|
||||
prev_item = item
|
||||
return labels, values, reagents
|
||||
|
||||
@@ -408,7 +413,7 @@ class ReagentTypeForm(QWidget):
|
||||
super().__init__()
|
||||
grid = QGridLayout()
|
||||
self.setLayout(grid)
|
||||
grid.addWidget(QLabel("Name:"),0,0)
|
||||
grid.addWidget(QLabel("Name (*Exactly* as it appears in the excel submission form):"),0,0)
|
||||
reagent_getter = QComboBox()
|
||||
# lookup all reagent type names from db
|
||||
reagent_getter.addItems(get_all_reagenttype_names(ctx=parent_ctx))
|
||||
|
||||
22
src/submissions/templates/summary_report.html
Normal file
22
src/submissions/templates/summary_report.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Submissions Report for {{ input['start_date'] }} - {{ input['end_date'] }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Submissions Report {{ input['start_date'] }} - {{ input['end_date'] }}</h2>
|
||||
<br>
|
||||
{{ input['table'] }}
|
||||
<br>
|
||||
{% for lab in input['labs'] %}
|
||||
<h3><u>{{ lab['lab'] }}:</u></h3>
|
||||
{% for kit in lab['kits'] %}
|
||||
<p><b>{{ kit['name'] }}</b></p>
|
||||
<p> Plates: {{ kit['plate_count'] }}, Samples: {{ kit['sample_count'] }}, Cost: {{ "${:,.2f}".format(kit['cost']) }}</p>
|
||||
{% endfor %}
|
||||
<p><b>Lab total:</b></p>
|
||||
<p> Plates: {{ lab['total_plates'] }}, Samples: {{ lab['total_samples'] }}, Cost: {{ "${:,.2f}".format(lab['total_cost']) }}</p>
|
||||
<br>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user