Added importing of PCR results.
This commit is contained in:
@@ -1,9 +1,14 @@
|
|||||||
**202303.04**
|
## 202303.05
|
||||||
|
|
||||||
|
- Added in ability to scrape and include PCR results for wastewater.
|
||||||
|
|
||||||
|
## 202303.04
|
||||||
|
|
||||||
|
- Added in scraping of logs from the PCR table to add to wastewater submissions.
|
||||||
- Completed partial imports that will add in missing reagents found in the kit indicated by the user.
|
- Completed partial imports that will add in missing reagents found in the kit indicated by the user.
|
||||||
- Added web documentation to the help menu.
|
- Added web documentation to the help menu.
|
||||||
|
|
||||||
**202303.03**
|
## 202303.03
|
||||||
|
|
||||||
- Increased robustness by utilizing PyQT6 widget names to pull data from forms instead of previously used label/input zip.
|
- Increased robustness by utilizing PyQT6 widget names to pull data from forms instead of previously used label/input zip.
|
||||||
- Above allowed for creation of more helpful prompts.
|
- Above allowed for creation of more helpful prompts.
|
||||||
|
|||||||
44
README.md
44
README.md
@@ -1,20 +1,22 @@
|
|||||||
|
## 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).
|
||||||
|
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.
|
||||||
|
|
||||||
## Logging in New Run:
|
## Logging in New Run:
|
||||||
*should fit 90% of usage cases*
|
*should fit 90% of usage cases*
|
||||||
|
|
||||||
1. Ensure a properly formatted Submission Excel form has been filled out. (It will save you a few headaches)
|
1. Ensure a properly formatted Submission Excel form has been filled out. (It will save you a few headaches)
|
||||||
a. All fields should be filled in to ensure proper lookups of reagents.
|
a. All fields should be filled in to ensure proper lookups of reagents.
|
||||||
2. 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).
|
2. Click on 'File' in the menu bar, followed by 'Import Submission' and use the file dialog to locate the form you definitely made sure was properly filled out in step 1.
|
||||||
a. Ignore the large black window of fast scrolling text, it is there for debugging purposes.
|
3. Click 'Ok'.
|
||||||
b. The 'Submissions' tab should be open by default.
|
4. Most of the fields in the form should be automatically filled in from the form area to the left of the screen.
|
||||||
3. Click on 'File' in the menu bar, followed by 'Import' and use the locate the form you definitely made sure was properly filled out in step 1.
|
5. You may need to maximize the app to ensure you can see all the info.
|
||||||
4. Click "Ok".
|
6. Any fields that are not automatically filled in can be filled in manually from the drop down menus.
|
||||||
5. Most of the fields in the form should be automatically filled in from the form area to the left of the screen.
|
7. Once you are certain all the information is correct, click 'Submit' at the bottom of the form.
|
||||||
6. You may need to maximize the app to ensure you can see all the info.
|
8. Add in any reagents the app doesn't recognize.
|
||||||
7. Any fields that are not automatically filled in can be filled in manually from the drop down menus.
|
9. Once the new run shows up at the bottom of the Submissions, everything is fine.
|
||||||
8. Once you are certain all the information is correct, click 'Submit' at the bottom of the form.
|
10. In case of any mistakes, the run can be overwritten by a reimport.
|
||||||
9. Add in any reagents the app doesn't recognize.
|
|
||||||
10. Once the new run shows up at the bottom of the Submissions, everything is fine.
|
|
||||||
11. In case of any mistakes, the run can be overwritten by a reimport.
|
|
||||||
|
|
||||||
## Check existing Run:
|
## Check existing Run:
|
||||||
|
|
||||||
@@ -28,6 +30,11 @@
|
|||||||
3. Use the file dialog to select a location to save the report.
|
3. Use the file dialog to select a location to save the report.
|
||||||
a. Both an excel sheet and a pdf should be generated containing summary information for submissions made by each client lab.
|
a. Both an excel sheet and a pdf should be generated containing summary information for submissions made by each client lab.
|
||||||
|
|
||||||
|
## Importing PCR results:
|
||||||
|
1. Click on 'File' -> 'Import PCR Results'.
|
||||||
|
2. Use the file dialog to locate the .xlsx file you want to import.
|
||||||
|
3. Click 'Okay'.
|
||||||
|
|
||||||
## Checking Controls:
|
## Checking Controls:
|
||||||
|
|
||||||
1. Controls for bacterial runs are now incorporated directly into the submissions database using webview. (Admittedly this performance is not as good as with a browser, so you will have to triage your data)
|
1. Controls for bacterial runs are now incorporated directly into the submissions database using webview. (Admittedly this performance is not as good as with a browser, so you will have to triage your data)
|
||||||
@@ -40,3 +47,16 @@
|
|||||||
## Adding new Kit:
|
## Adding new Kit:
|
||||||
|
|
||||||
1. Instructions to come.
|
1. Instructions to come.
|
||||||
|
|
||||||
|
## Linking Controls:
|
||||||
|
|
||||||
|
1. Click "Monthly" -> "Link Controls". Entire process should be handled automatically.
|
||||||
|
|
||||||
|
## Linking Extraction Logs:
|
||||||
|
|
||||||
|
1. Click "Monthly" -> "Link Extraction Logs".
|
||||||
|
2. Chose the .csv file taken from the extraction table runlogs folder.
|
||||||
|
|
||||||
|
## Linking PCR Logs:
|
||||||
|
1. Click "Monthly" -> "Link PCR Logs".
|
||||||
|
2. Chose the .csv file taken from the PCR table runlogs folder.
|
||||||
|
|||||||
@@ -55,8 +55,9 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
|||||||
# are written from script.py.mako
|
# are written from script.py.mako
|
||||||
# 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\DB_backups\submissions-20230213.db
|
sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\DB_backups\submissions-20230302.db
|
||||||
|
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions_test.db
|
||||||
|
|
||||||
|
|
||||||
[post_write_hooks]
|
[post_write_hooks]
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""added pcr info to wastewater subs
|
||||||
|
|
||||||
|
Revision ID: 0ee7ffa026b2
|
||||||
|
Revises: 3d80e4a17a26
|
||||||
|
Create Date: 2023-03-22 14:51:37.871062
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0ee7ffa026b2'
|
||||||
|
down_revision = '3d80e4a17a26'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('_submissions', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('pcr_info', sa.JSON(), nullable=True))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('_submissions', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('pcr_info')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"""added pcr info to wastewater samples
|
||||||
|
|
||||||
|
Revision ID: 8adc85dd9b92
|
||||||
|
Revises: 0ee7ffa026b2
|
||||||
|
Create Date: 2023-03-27 13:46:06.173379
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '8adc85dd9b92'
|
||||||
|
down_revision = '0ee7ffa026b2'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('_ww_samples', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('pcr_results', sa.JSON(), nullable=True))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('_ww_samples', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('pcr_results')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -1,6 +1,22 @@
|
|||||||
# __init__.py
|
# __init__.py
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# Version of the realpython-reader package
|
# Version of the realpython-reader package
|
||||||
__version__ = "202303.3b"
|
__project__ = "submissions"
|
||||||
|
__version__ = "202303.4b"
|
||||||
__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"
|
||||||
|
|
||||||
|
project_path = Path(__file__).parents[2].absolute()
|
||||||
|
|
||||||
|
class bcolors:
|
||||||
|
HEADER = '\033[95m'
|
||||||
|
OKBLUE = '\033[94m'
|
||||||
|
OKCYAN = '\033[96m'
|
||||||
|
OKGREEN = '\033[92m'
|
||||||
|
WARNING = '\033[93m'
|
||||||
|
FAIL = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
UNDERLINE = '\033[4m'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Convenience functions for interacting with the database.
|
|||||||
from . import models
|
from . import models
|
||||||
from .models.kits import reagenttypes_kittypes
|
from .models.kits import reagenttypes_kittypes
|
||||||
from .models.submissions import reagents_submissions
|
from .models.submissions import reagents_submissions
|
||||||
|
from .models.samples import WWSample
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import sqlalchemy.exc
|
import sqlalchemy.exc
|
||||||
import sqlite3
|
import sqlite3
|
||||||
@@ -392,7 +393,11 @@ def submissions_to_df(ctx:dict, sub_type:str|None=None) -> pd.DataFrame:
|
|||||||
try:
|
try:
|
||||||
df = df.drop("ext_info", axis=1)
|
df = df.drop("ext_info", axis=1)
|
||||||
except:
|
except:
|
||||||
logger.warning(f"Couldn't drop 'controls' column from submissionsheet df.")
|
logger.warning(f"Couldn't drop 'ext_info' column from submissionsheet df.")
|
||||||
|
try:
|
||||||
|
df = df.drop("pcr_info", axis=1)
|
||||||
|
except:
|
||||||
|
logger.warning(f"Couldn't drop 'pcr_info' column from submissionsheet df.")
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
@@ -677,3 +682,38 @@ def delete_submission_by_id(ctx:dict, id:int) -> None:
|
|||||||
ctx['database_session'].delete(sample)
|
ctx['database_session'].delete(sample)
|
||||||
ctx["database_session"].delete(sub)
|
ctx["database_session"].delete(sub)
|
||||||
ctx["database_session"].commit()
|
ctx["database_session"].commit()
|
||||||
|
|
||||||
|
|
||||||
|
def lookup_ww_sample_by_rsl_sample_number(ctx:dict, rsl_number:str) -> models.WWSample:
|
||||||
|
"""
|
||||||
|
Retrieves wastewater sampel from database by rsl sample number
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ctx (dict): settings passed dwon from gui
|
||||||
|
rsl_number (str): sample number assigned by robotics lab
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
models.WWSample: instance of wastewater sample
|
||||||
|
"""
|
||||||
|
return ctx['database_session'].query(models.WWSample).filter(models.WWSample.rsl_number==rsl_number).first()
|
||||||
|
|
||||||
|
|
||||||
|
def update_ww_sample(ctx:dict, sample_obj:dict):
|
||||||
|
"""
|
||||||
|
Retrieves wastewater sample by rsl number (sample_obj['sample']) and updates values from constructed dictionary
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ctx (dict): settings passed down from gui
|
||||||
|
sample_obj (dict): dictionary representing new values for database object
|
||||||
|
"""
|
||||||
|
ww_samp = lookup_ww_sample_by_rsl_sample_number(ctx=ctx, rsl_number=sample_obj['sample'])
|
||||||
|
if ww_samp != None:
|
||||||
|
for key, value in sample_obj.items():
|
||||||
|
logger.debug(f"Setting {key} to {value}")
|
||||||
|
# set attribute 'key' to 'value'
|
||||||
|
setattr(ww_samp, key, value)
|
||||||
|
else:
|
||||||
|
logger.error(f"Unable to find sample {sample_obj['sample']}")
|
||||||
|
return
|
||||||
|
ctx['database_session'].add(ww_samp)
|
||||||
|
ctx["database_session"].commit()
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
All models for individual samples.
|
All models for individual samples.
|
||||||
'''
|
'''
|
||||||
from . import Base
|
from . import Base
|
||||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, FLOAT, BOOLEAN
|
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, FLOAT, BOOLEAN, JSON
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
|
||||||
@@ -25,11 +25,12 @@ class WWSample(Base):
|
|||||||
testing_type = Column(String(64))
|
testing_type = Column(String(64))
|
||||||
site_status = Column(String(64))
|
site_status = Column(String(64))
|
||||||
notes = Column(String(2000))
|
notes = Column(String(2000))
|
||||||
ct_n1 = Column(FLOAT(2))
|
ct_n1 = Column(FLOAT(2)) #: AKA ct for N1
|
||||||
ct_n2 = Column(FLOAT(2))
|
ct_n2 = Column(FLOAT(2)) #: AKA ct for N2
|
||||||
seq_submitted = Column(BOOLEAN())
|
seq_submitted = Column(BOOLEAN())
|
||||||
ww_seq_run_id = Column(String(64))
|
ww_seq_run_id = Column(String(64))
|
||||||
sample_type = Column(String(8))
|
sample_type = Column(String(8))
|
||||||
|
pcr_results = Column(JSON)
|
||||||
|
|
||||||
|
|
||||||
def to_string(self) -> str:
|
def to_string(self) -> str:
|
||||||
@@ -48,9 +49,13 @@ class WWSample(Base):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: well location and id NOTE: keys must sync with BCSample to_sub_dict below
|
dict: well location and id NOTE: keys must sync with BCSample to_sub_dict below
|
||||||
"""
|
"""
|
||||||
|
if self.ct_n1 != None and self.ct_n2 != None:
|
||||||
|
name = f"{self.ww_sample_full_id}\n\t- ct N1: {'{:.2f}'.format(self.ct_n1)}, ct N2: {'{:.2f}'.format(self.ct_n1)}"
|
||||||
|
else:
|
||||||
|
name = self.ww_sample_full_id
|
||||||
return {
|
return {
|
||||||
"well": self.well_number,
|
"well": self.well_number,
|
||||||
"name": self.ww_sample_full_id,
|
"name": name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -179,5 +179,20 @@ class Wastewater(BasicSubmission):
|
|||||||
derivative submission type from BasicSubmission
|
derivative submission type from BasicSubmission
|
||||||
"""
|
"""
|
||||||
samples = relationship("WWSample", back_populates="rsl_plate", uselist=True)
|
samples = relationship("WWSample", back_populates="rsl_plate", uselist=True)
|
||||||
|
pcr_info = Column(JSON)
|
||||||
# ww_sample_id = Column(String, ForeignKey("_ww_samples.id", ondelete="SET NULL", name="fk_WW_sample_id"))
|
# ww_sample_id = Column(String, ForeignKey("_ww_samples.id", ondelete="SET NULL", name="fk_WW_sample_id"))
|
||||||
__mapper_args__ = {"polymorphic_identity": "wastewater", "polymorphic_load": "inline"}
|
__mapper_args__ = {"polymorphic_identity": "wastewater", "polymorphic_load": "inline"}
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""
|
||||||
|
Extends parent class method to add controls to dict
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: dictionary used in submissions summary
|
||||||
|
"""
|
||||||
|
output = super().to_dict()
|
||||||
|
try:
|
||||||
|
output['pcr_info'] = json.loads(self.pcr_info)
|
||||||
|
except TypeError as e:
|
||||||
|
pass
|
||||||
|
return output
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
'''
|
'''
|
||||||
contains parser object for pulling values from client generated submission sheets.
|
contains parser object for pulling values from client generated submission sheets.
|
||||||
'''
|
'''
|
||||||
|
from getpass import getuser
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from backend.db.models import WWSample, BCSample
|
from backend.db.models import WWSample, BCSample
|
||||||
|
from backend.db import lookup_ww_sample_by_rsl_sample_number
|
||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import re
|
import re
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import uuid
|
import uuid
|
||||||
from tools import check_not_nan
|
from tools import check_not_nan, retrieve_rsl_number
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -203,6 +205,7 @@ class SheetParser(object):
|
|||||||
sample_parser = SampleParser(submission_info.iloc[16:40])
|
sample_parser = SampleParser(submission_info.iloc[16:40])
|
||||||
sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples")
|
sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples")
|
||||||
self.sub['samples'] = sample_parse()
|
self.sub['samples'] = sample_parse()
|
||||||
|
self.sub['csv'] = self.xl.parse("Copy to import file", dtype=object)
|
||||||
|
|
||||||
|
|
||||||
class SampleParser(object):
|
class SampleParser(object):
|
||||||
@@ -284,3 +287,112 @@ class SampleParser(object):
|
|||||||
new.well_number = sample['Unnamed: 1']
|
new.well_number = sample['Unnamed: 1']
|
||||||
new_list.append(new)
|
new_list.append(new)
|
||||||
return new_list
|
return new_list
|
||||||
|
|
||||||
|
|
||||||
|
class PCRParser(object):
|
||||||
|
"""
|
||||||
|
Object to pull data from Design and Analysis PCR export file.
|
||||||
|
"""
|
||||||
|
def __init__(self, ctx:dict, filepath:Path|None = None) -> None:
|
||||||
|
"""
|
||||||
|
Initializes object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ctx (dict): settings passed down from gui.
|
||||||
|
filepath (Path | None, optional): file to parse. Defaults to None.
|
||||||
|
"""
|
||||||
|
self.ctx = ctx
|
||||||
|
logger.debug(f"Parsing {filepath.__str__()}")
|
||||||
|
if filepath == None:
|
||||||
|
logger.error(f"No filepath given.")
|
||||||
|
self.xl = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.xl = pd.ExcelFile(filepath.__str__())
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(f"Incorrect value: {e}")
|
||||||
|
self.xl = None
|
||||||
|
except PermissionError:
|
||||||
|
logger.error(f"Couldn't get permissions for {filepath.__str__()}. Operation might have been cancelled.")
|
||||||
|
return
|
||||||
|
# self.pcr = OrderedDict()
|
||||||
|
self.pcr = {}
|
||||||
|
self.plate_num, self.submission_type = retrieve_rsl_number(filepath.__str__())
|
||||||
|
logger.debug(f"Set plate number to {self.plate_num} and type to {self.submission_type}")
|
||||||
|
self.samples = []
|
||||||
|
parser = getattr(self, f"parse_{self.submission_type}")
|
||||||
|
parser()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_general(self, sheet_name:str):
|
||||||
|
"""
|
||||||
|
Parse general info rows for all types of PCR results
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sheet_name (str): Name of sheet in excel workbook that holds info.
|
||||||
|
"""
|
||||||
|
df = self.xl.parse(sheet_name=sheet_name, dtype=object).fillna("")
|
||||||
|
# self.pcr['file'] = df.iloc[1][1]
|
||||||
|
self.pcr['comment'] = df.iloc[0][1]
|
||||||
|
self.pcr['operator'] = df.iloc[1][1]
|
||||||
|
self.pcr['barcode'] = df.iloc[2][1]
|
||||||
|
self.pcr['instrument'] = df.iloc[3][1]
|
||||||
|
self.pcr['block_type'] = df.iloc[4][1]
|
||||||
|
self.pcr['instrument_name'] = df.iloc[5][1]
|
||||||
|
self.pcr['instrument_serial'] = df.iloc[6][1]
|
||||||
|
self.pcr['heated_cover_serial'] = df.iloc[7][1]
|
||||||
|
self.pcr['block_serial'] = df.iloc[8][1]
|
||||||
|
self.pcr['run-start'] = df.iloc[9][1]
|
||||||
|
self.pcr['run_end'] = df.iloc[10][1]
|
||||||
|
self.pcr['run_duration'] = df.iloc[11][1]
|
||||||
|
self.pcr['sample_volume'] = df.iloc[12][1]
|
||||||
|
self.pcr['cover_temp'] = df.iloc[13][1]
|
||||||
|
self.pcr['passive_ref'] = df.iloc[14][1]
|
||||||
|
self.pcr['pcr_step'] = df.iloc[15][1]
|
||||||
|
self.pcr['quant_cycle_method'] = df.iloc[16][1]
|
||||||
|
self.pcr['analysis_time'] = df.iloc[17][1]
|
||||||
|
self.pcr['software'] = df.iloc[18][1]
|
||||||
|
self.pcr['plugin'] = df.iloc[19][1]
|
||||||
|
self.pcr['exported_on'] = df.iloc[20][1]
|
||||||
|
self.pcr['imported_by'] = getuser()
|
||||||
|
return df
|
||||||
|
|
||||||
|
def parse_wastewater(self):
|
||||||
|
"""
|
||||||
|
Parse specific to wastewater samples.
|
||||||
|
"""
|
||||||
|
df = self.parse_general(sheet_name="Results")
|
||||||
|
self.samples_df = df.iloc[23:][0:]
|
||||||
|
# iloc is [row][column]
|
||||||
|
for ii, row in self.samples_df.iterrows():
|
||||||
|
try:
|
||||||
|
sample_obj = [sample for sample in self.samples if sample['sample'] == row[3]][0]
|
||||||
|
except IndexError:
|
||||||
|
sample_obj = dict(
|
||||||
|
sample = row[3],
|
||||||
|
)
|
||||||
|
logger.debug(f"Got sample obj: {sample_obj}")
|
||||||
|
# logger.debug(f"row: {row}")
|
||||||
|
# rsl_num = row[3]
|
||||||
|
# # logger.debug(f"Looking up: {rsl_num}")
|
||||||
|
# ww_samp = lookup_ww_sample_by_rsl_sample_number(ctx=self.ctx, rsl_number=rsl_num)
|
||||||
|
# logger.debug(f"Got: {ww_samp}")
|
||||||
|
match row[4]:
|
||||||
|
case "N1":
|
||||||
|
if isinstance(row[12], float):
|
||||||
|
sample_obj['ct_n1'] = row[12]
|
||||||
|
else:
|
||||||
|
sample_obj['ct_n1'] = 0.0
|
||||||
|
case "N2":
|
||||||
|
if isinstance(row[12], float):
|
||||||
|
sample_obj['ct_n2'] = row[12]
|
||||||
|
else:
|
||||||
|
sample_obj['ct_n2'] = 0.0
|
||||||
|
case _:
|
||||||
|
logger.warning(f"Unexpected input for row[4]: {row[4]}")
|
||||||
|
self.samples.append(sample_obj)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'''
|
'''
|
||||||
Operations for all user interactions.
|
Operations for all user interactions.
|
||||||
'''
|
'''
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -23,15 +24,15 @@ from xhtml2pdf import pisa
|
|||||||
# import plotly.express as px
|
# import plotly.express as px
|
||||||
import yaml
|
import yaml
|
||||||
import pprint
|
import pprint
|
||||||
from backend.excel import convert_data_list_to_df, make_report_xlsx, make_report_html, SheetParser
|
from backend.excel import convert_data_list_to_df, make_report_xlsx, make_report_html, SheetParser, PCRParser
|
||||||
from backend.db import (construct_submission_info, lookup_reagent,
|
from backend.db import (construct_submission_info, lookup_reagent,
|
||||||
construct_reagent, store_submission, lookup_kittype_by_use,
|
construct_reagent, store_submission, lookup_kittype_by_use,
|
||||||
lookup_regent_by_type_name, lookup_all_orgs, lookup_submissions_by_date_range,
|
lookup_regent_by_type_name, lookup_all_orgs, lookup_submissions_by_date_range,
|
||||||
get_all_Control_Types_names, create_kit_from_yaml, get_all_available_modes, get_all_controls_by_type,
|
get_all_Control_Types_names, create_kit_from_yaml, get_all_available_modes, get_all_controls_by_type,
|
||||||
get_control_subtypes, lookup_all_submissions_by_type, get_all_controls, lookup_submission_by_rsl_num,
|
get_control_subtypes, lookup_all_submissions_by_type, get_all_controls, lookup_submission_by_rsl_num,
|
||||||
create_org_from_yaml, store_reagent
|
create_org_from_yaml, store_reagent, lookup_ww_sample_by_rsl_sample_number, lookup_kittype_by_name,
|
||||||
|
update_ww_sample
|
||||||
)
|
)
|
||||||
from backend.db import lookup_kittype_by_name
|
|
||||||
from .functions import extract_form_info
|
from .functions import extract_form_info
|
||||||
from tools import check_not_nan, check_kit_integrity, check_if_app
|
from tools import check_not_nan, check_kit_integrity, check_if_app
|
||||||
# from backend.excel.reports import
|
# from backend.excel.reports import
|
||||||
@@ -89,9 +90,11 @@ class App(QMainWindow):
|
|||||||
helpMenu.addAction(self.helpAction)
|
helpMenu.addAction(self.helpAction)
|
||||||
helpMenu.addAction(self.docsAction)
|
helpMenu.addAction(self.docsAction)
|
||||||
fileMenu.addAction(self.importAction)
|
fileMenu.addAction(self.importAction)
|
||||||
|
fileMenu.addAction(self.importPCRAction)
|
||||||
reportMenu.addAction(self.generateReportAction)
|
reportMenu.addAction(self.generateReportAction)
|
||||||
maintenanceMenu.addAction(self.joinControlsAction)
|
maintenanceMenu.addAction(self.joinControlsAction)
|
||||||
maintenanceMenu.addAction(self.joinExtractionAction)
|
maintenanceMenu.addAction(self.joinExtractionAction)
|
||||||
|
maintenanceMenu.addAction(self.joinPCRAction)
|
||||||
|
|
||||||
def _createToolBar(self):
|
def _createToolBar(self):
|
||||||
"""
|
"""
|
||||||
@@ -107,13 +110,15 @@ class App(QMainWindow):
|
|||||||
"""
|
"""
|
||||||
creates actions
|
creates actions
|
||||||
"""
|
"""
|
||||||
self.importAction = QAction("&Import", self)
|
self.importAction = QAction("&Import Submission", self)
|
||||||
|
self.importPCRAction = QAction("&Import PCR Results", self)
|
||||||
self.addReagentAction = QAction("Add Reagent", self)
|
self.addReagentAction = QAction("Add Reagent", self)
|
||||||
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.joinControlsAction = QAction("Link Controls")
|
||||||
self.joinExtractionAction = QAction("Link Ext Logs")
|
self.joinExtractionAction = QAction("Link Extraction Logs")
|
||||||
|
self.joinPCRAction = QAction("Link PCR Logs")
|
||||||
self.helpAction = QAction("&About", self)
|
self.helpAction = QAction("&About", self)
|
||||||
self.docsAction = QAction("&Docs", self)
|
self.docsAction = QAction("&Docs", self)
|
||||||
|
|
||||||
@@ -123,6 +128,7 @@ class App(QMainWindow):
|
|||||||
connect menu and tool bar item to functions
|
connect menu and tool bar item to functions
|
||||||
"""
|
"""
|
||||||
self.importAction.triggered.connect(self.importSubmission)
|
self.importAction.triggered.connect(self.importSubmission)
|
||||||
|
self.importPCRAction.triggered.connect(self.importPCRResults)
|
||||||
self.addReagentAction.triggered.connect(self.add_reagent)
|
self.addReagentAction.triggered.connect(self.add_reagent)
|
||||||
self.generateReportAction.triggered.connect(self.generateReport)
|
self.generateReportAction.triggered.connect(self.generateReport)
|
||||||
self.addKitAction.triggered.connect(self.add_kit)
|
self.addKitAction.triggered.connect(self.add_kit)
|
||||||
@@ -133,6 +139,7 @@ class App(QMainWindow):
|
|||||||
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.joinControlsAction.triggered.connect(self.linkControls)
|
||||||
self.joinExtractionAction.triggered.connect(self.linkExtractions)
|
self.joinExtractionAction.triggered.connect(self.linkExtractions)
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -180,7 +187,8 @@ class App(QMainWindow):
|
|||||||
(?P<submitted_date>^submitted_date$) |
|
(?P<submitted_date>^submitted_date$) |
|
||||||
(?P<submitting_lab>)^submitting_lab$ |
|
(?P<submitting_lab>)^submitting_lab$ |
|
||||||
(?P<samples>)^samples$ |
|
(?P<samples>)^samples$ |
|
||||||
(?P<reagent>^lot_.*$)
|
(?P<reagent>^lot_.*$) |
|
||||||
|
(?P<csv>^csv$)
|
||||||
""", re.VERBOSE)
|
""", re.VERBOSE)
|
||||||
for item in prsr.sub:
|
for item in prsr.sub:
|
||||||
logger.debug(f"Item: {item}")
|
logger.debug(f"Item: {item}")
|
||||||
@@ -213,14 +221,19 @@ class App(QMainWindow):
|
|||||||
msg = AlertPop(message="Make sure to check your extraction kit in the excel sheet!", status="warning")
|
msg = AlertPop(message="Make sure to check your extraction kit in the excel sheet!", status="warning")
|
||||||
msg.exec()
|
msg.exec()
|
||||||
# create combobox to hold looked up kits
|
# create combobox to hold looked up kits
|
||||||
|
# add_widget = KitSelector(ctx=self.ctx, submission_type=prsr.sub['submission_type'], parent=self)
|
||||||
add_widget = QComboBox()
|
add_widget = QComboBox()
|
||||||
|
# add_widget.currentTextChanged.connect(self.kit_reload)
|
||||||
# lookup existing kits by 'submission_type' decided on by sheetparser
|
# lookup existing kits by 'submission_type' decided on by sheetparser
|
||||||
uses = [item.__str__() for item in lookup_kittype_by_use(ctx=self.ctx, used_by=prsr.sub['submission_type'])]
|
uses = [item.__str__() for item in lookup_kittype_by_use(ctx=self.ctx, used_by=prsr.sub['submission_type'])]
|
||||||
# if len(uses) > 0:
|
# if len(uses) > 0:
|
||||||
add_widget.addItems(uses)
|
add_widget.addItems(uses)
|
||||||
# else:
|
# else:
|
||||||
# add_widget.addItems(['bacterial_culture'])
|
# add_widget.addItems(['bacterial_culture'])
|
||||||
|
if check_not_nan(prsr.sub[item]):
|
||||||
self.ext_kit = prsr.sub[item]
|
self.ext_kit = prsr.sub[item]
|
||||||
|
else:
|
||||||
|
self.ext_kit = add_widget.currentText()
|
||||||
case 'submitted_date':
|
case 'submitted_date':
|
||||||
# create label
|
# create label
|
||||||
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
||||||
@@ -234,7 +247,9 @@ class App(QMainWindow):
|
|||||||
add_widget.setDate(date.today())
|
add_widget.setDate(date.today())
|
||||||
case 'reagent':
|
case 'reagent':
|
||||||
# create label
|
# create label
|
||||||
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
reg_label = QLabel(item.replace("_", " ").title())
|
||||||
|
reg_label.setObjectName(f"lot_{item}_label")
|
||||||
|
self.table_widget.formlayout.addWidget(reg_label)
|
||||||
# create reagent choice widget
|
# create reagent choice widget
|
||||||
add_widget = ImportReagent(ctx=self.ctx, item=item, prsr=prsr)
|
add_widget = ImportReagent(ctx=self.ctx, item=item, prsr=prsr)
|
||||||
self.reagents[item] = prsr.sub[item]
|
self.reagents[item] = prsr.sub[item]
|
||||||
@@ -243,10 +258,13 @@ class App(QMainWindow):
|
|||||||
logger.debug(f"{item}: {prsr.sub[item]}")
|
logger.debug(f"{item}: {prsr.sub[item]}")
|
||||||
self.samples = prsr.sub[item]
|
self.samples = prsr.sub[item]
|
||||||
add_widget = None
|
add_widget = None
|
||||||
|
case 'csv':
|
||||||
|
self.csv = prsr.sub[item]
|
||||||
case _:
|
case _:
|
||||||
# anything else gets added in as a line edit
|
# anything else gets added in as a line edit
|
||||||
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
||||||
add_widget = QLineEdit()
|
add_widget = QLineEdit()
|
||||||
|
logger.debug(f"Setting widget text to {str(prsr.sub[item]).replace('_', ' ')}")
|
||||||
add_widget.setText(str(prsr.sub[item]).replace("_", " "))
|
add_widget.setText(str(prsr.sub[item]).replace("_", " "))
|
||||||
try:
|
try:
|
||||||
add_widget.setObjectName(item)
|
add_widget.setObjectName(item)
|
||||||
@@ -256,8 +274,46 @@ class App(QMainWindow):
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
# compare self.reagents with expected reagents in kit
|
# compare self.reagents with expected reagents in kit
|
||||||
if hasattr(self, 'ext_kit'):
|
if hasattr(self, 'ext_kit'):
|
||||||
|
self.kit_integrity_completion()
|
||||||
|
# create submission button
|
||||||
|
# submit_btn = QPushButton("Submit")
|
||||||
|
# submit_btn.setObjectName("submit_btn")
|
||||||
|
# self.table_widget.formlayout.addWidget(submit_btn)
|
||||||
|
# submit_btn.clicked.connect(self.submit_new_sample)
|
||||||
|
logger.debug(f"Imported reagents: {self.reagents}")
|
||||||
|
|
||||||
|
|
||||||
|
def kit_reload(self):
|
||||||
|
"""
|
||||||
|
Removes all reagents from form before running kit integrity completion.
|
||||||
|
"""
|
||||||
|
for item in self.table_widget.formlayout.parentWidget().findChildren(QWidget):
|
||||||
|
# item.setParent(None)
|
||||||
|
if isinstance(item, QLabel):
|
||||||
|
if item.text().startswith("Lot"):
|
||||||
|
item.setParent(None)
|
||||||
|
else:
|
||||||
|
logger.debug(f"Type of {item.objectName()} is {type(item)}")
|
||||||
|
if item.objectName().startswith("lot_"):
|
||||||
|
item.setParent(None)
|
||||||
|
self.kit_integrity_completion()
|
||||||
|
|
||||||
|
|
||||||
|
def kit_integrity_completion(self):
|
||||||
|
"""
|
||||||
|
Performs check of imported reagents
|
||||||
|
NOTE: this will not change self.reagents which should be fine
|
||||||
|
since it's only used when looking up
|
||||||
|
"""
|
||||||
|
logger.debug(inspect.currentframe().f_back.f_code.co_name)
|
||||||
|
kit_widget = self.table_widget.formlayout.parentWidget().findChild(QComboBox, 'extraction_kit')
|
||||||
|
logger.debug(f"Kit selector: {kit_widget}")
|
||||||
|
self.ext_kit = kit_widget.currentText()
|
||||||
|
logger.debug(f"Checking integrity of {self.ext_kit}")
|
||||||
kit = lookup_kittype_by_name(ctx=self.ctx, name=self.ext_kit)
|
kit = lookup_kittype_by_name(ctx=self.ctx, name=self.ext_kit)
|
||||||
kit_integrity = check_kit_integrity(kit, [item.replace("lot_", "") for item in self.reagents])
|
reagents_to_lookup = [item.replace("lot_", "") for item in self.reagents]
|
||||||
|
logger.debug(f"Reagents for lookup for {kit.name}: {reagents_to_lookup}")
|
||||||
|
kit_integrity = check_kit_integrity(kit, reagents_to_lookup)
|
||||||
if kit_integrity != None:
|
if kit_integrity != None:
|
||||||
msg = AlertPop(message=kit_integrity['message'], status="critical")
|
msg = AlertPop(message=kit_integrity['message'], status="critical")
|
||||||
msg.exec()
|
msg.exec()
|
||||||
@@ -265,11 +321,11 @@ class App(QMainWindow):
|
|||||||
self.table_widget.formlayout.addWidget(QLabel(f"Lot {item.replace('_', ' ').title()}"))
|
self.table_widget.formlayout.addWidget(QLabel(f"Lot {item.replace('_', ' ').title()}"))
|
||||||
add_widget = ImportReagent(ctx=self.ctx, item=item)
|
add_widget = ImportReagent(ctx=self.ctx, item=item)
|
||||||
self.table_widget.formlayout.addWidget(add_widget)
|
self.table_widget.formlayout.addWidget(add_widget)
|
||||||
# create submission button
|
|
||||||
submit_btn = QPushButton("Submit")
|
submit_btn = QPushButton("Submit")
|
||||||
|
submit_btn.setObjectName("lot_submit_btn")
|
||||||
self.table_widget.formlayout.addWidget(submit_btn)
|
self.table_widget.formlayout.addWidget(submit_btn)
|
||||||
submit_btn.clicked.connect(self.submit_new_sample)
|
submit_btn.clicked.connect(self.submit_new_sample)
|
||||||
logger.debug(f"Imported reagents: {self.reagents}")
|
|
||||||
|
|
||||||
def submit_new_sample(self):
|
def submit_new_sample(self):
|
||||||
"""
|
"""
|
||||||
@@ -341,6 +397,15 @@ class App(QMainWindow):
|
|||||||
# reset form
|
# reset form
|
||||||
for item in self.table_widget.formlayout.parentWidget().findChildren(QWidget):
|
for item in self.table_widget.formlayout.parentWidget().findChildren(QWidget):
|
||||||
item.setParent(None)
|
item.setParent(None)
|
||||||
|
if hasattr(self, 'csv'):
|
||||||
|
dlg = QuestionAsker("Export CSV?", "Would you like to export the csv file?")
|
||||||
|
if dlg.exec():
|
||||||
|
home_dir = Path(self.ctx["directory_path"]).joinpath(f"{base_submission.rsl_plate_num}.csv").resolve().__str__()
|
||||||
|
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".csv")[0])
|
||||||
|
try:
|
||||||
|
self.csv.to_csv(fname.__str__(), index=False)
|
||||||
|
except PermissionError:
|
||||||
|
logger.debug(f"Could not get permissions to {fname}. Possibly the request was cancelled.")
|
||||||
|
|
||||||
|
|
||||||
def add_reagent(self, reagent_lot:str|None=None, reagent_type:str|None=None, expiry:date|None=None):
|
def add_reagent(self, reagent_lot:str|None=None, reagent_type:str|None=None, expiry:date|None=None):
|
||||||
@@ -468,8 +533,6 @@ class App(QMainWindow):
|
|||||||
msg.exec()
|
msg.exec()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _controls_getter(self):
|
def _controls_getter(self):
|
||||||
"""
|
"""
|
||||||
Lookup controls from database and send to chartmaker
|
Lookup controls from database and send to chartmaker
|
||||||
@@ -550,6 +613,9 @@ class App(QMainWindow):
|
|||||||
|
|
||||||
|
|
||||||
def linkControls(self):
|
def linkControls(self):
|
||||||
|
"""
|
||||||
|
Adds controls pulled from irida to relevant submissions
|
||||||
|
"""
|
||||||
all_bcs = lookup_all_submissions_by_type(self.ctx, "Bacterial Culture")
|
all_bcs = lookup_all_submissions_by_type(self.ctx, "Bacterial Culture")
|
||||||
logger.debug(all_bcs)
|
logger.debug(all_bcs)
|
||||||
all_controls = get_all_controls(self.ctx)
|
all_controls = get_all_controls(self.ctx)
|
||||||
@@ -598,6 +664,9 @@ class App(QMainWindow):
|
|||||||
|
|
||||||
|
|
||||||
def linkExtractions(self):
|
def linkExtractions(self):
|
||||||
|
"""
|
||||||
|
Links extraction logs from .csv files to relevant submissions.
|
||||||
|
"""
|
||||||
home_dir = str(Path(self.ctx["directory_path"]))
|
home_dir = str(Path(self.ctx["directory_path"]))
|
||||||
fname = Path(QFileDialog.getOpenFileName(self, 'Open file', home_dir, filter = "csv(*.csv)")[0])
|
fname = Path(QFileDialog.getOpenFileName(self, 'Open file', home_dir, filter = "csv(*.csv)")[0])
|
||||||
with open(fname.__str__(), 'r') as f:
|
with open(fname.__str__(), 'r') as f:
|
||||||
@@ -648,6 +717,107 @@ class App(QMainWindow):
|
|||||||
dlg.exec()
|
dlg.exec()
|
||||||
|
|
||||||
|
|
||||||
|
def linkPCR(self):
|
||||||
|
"""
|
||||||
|
Links PCR logs from .csv files to relevant submissions.
|
||||||
|
"""
|
||||||
|
home_dir = str(Path(self.ctx["directory_path"]))
|
||||||
|
fname = Path(QFileDialog.getOpenFileName(self, 'Open file', home_dir, filter = "csv(*.csv)")[0])
|
||||||
|
with open(fname.__str__(), 'r') as f:
|
||||||
|
runs = [col.strip().split(",") for col in f.readlines()]
|
||||||
|
count = 0
|
||||||
|
for run in runs:
|
||||||
|
obj = dict(
|
||||||
|
start_time=run[0].strip(),
|
||||||
|
rsl_plate_num=run[1].strip(),
|
||||||
|
biomek_status=run[2].strip(),
|
||||||
|
quant_status=run[3].strip(),
|
||||||
|
experiment_name=run[4].strip(),
|
||||||
|
end_time=run[5].strip()
|
||||||
|
)
|
||||||
|
# for ii in range(6, len(run)):
|
||||||
|
# obj[f"column{str(ii-5)}_vol"] = run[ii]
|
||||||
|
sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=obj['rsl_plate_num'])
|
||||||
|
try:
|
||||||
|
logger.debug(f"Found submission: {sub.rsl_plate_num}")
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
if hasattr(sub, 'pcr_info') and sub.pcr_info != None:
|
||||||
|
existing = json.loads(sub.pcr_info)
|
||||||
|
else:
|
||||||
|
existing = None
|
||||||
|
try:
|
||||||
|
if json.dumps(obj) in sub.pcr_info:
|
||||||
|
logger.debug(f"Looks like we already have that info.")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
count += 1
|
||||||
|
except TypeError:
|
||||||
|
logger.error(f"No json to dump")
|
||||||
|
if existing != None:
|
||||||
|
try:
|
||||||
|
logger.debug(f"Updating {type(existing)}: {existing} with {type(obj)}: {obj}")
|
||||||
|
existing.append(obj)
|
||||||
|
logger.debug(f"Setting: {existing}")
|
||||||
|
sub.pcr_info = json.dumps(existing)
|
||||||
|
except TypeError:
|
||||||
|
logger.error(f"Error updating!")
|
||||||
|
sub.pcr_info = json.dumps([obj])
|
||||||
|
logger.debug(f"Final ext info for {sub.rsl_plate_num}: {sub.pcr_info}")
|
||||||
|
else:
|
||||||
|
sub.pcr_info = json.dumps([obj])
|
||||||
|
self.ctx['database_session'].add(sub)
|
||||||
|
self.ctx["database_session"].commit()
|
||||||
|
dlg = AlertPop(message=f"We added {count} logs to the database.", status='information')
|
||||||
|
dlg.exec()
|
||||||
|
|
||||||
|
|
||||||
|
def importPCRResults(self):
|
||||||
|
"""
|
||||||
|
Imports results exported from Design and Analysis .eds files
|
||||||
|
"""
|
||||||
|
home_dir = str(Path(self.ctx["directory_path"]))
|
||||||
|
fname = Path(QFileDialog.getOpenFileName(self, 'Open file', home_dir, filter = "xlsx(*.xlsx)")[0])
|
||||||
|
parser = PCRParser(ctx=self.ctx, filepath=fname)
|
||||||
|
logger.debug(f"Attempting lookup for {parser.plate_num}")
|
||||||
|
sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=parser.plate_num)
|
||||||
|
try:
|
||||||
|
logger.debug(f"Found submission: {sub.rsl_plate_num}")
|
||||||
|
except AttributeError:
|
||||||
|
logger.error(f"Submission of number {parser.plate_num} not found.")
|
||||||
|
return
|
||||||
|
# jout = json.dumps(parser.pcr)
|
||||||
|
count = 0
|
||||||
|
if hasattr(sub, 'pcr_info') and sub.pcr_info != None:
|
||||||
|
existing = json.loads(sub.pcr_info)
|
||||||
|
else:
|
||||||
|
# jout = None
|
||||||
|
existing = None
|
||||||
|
if existing != None:
|
||||||
|
try:
|
||||||
|
logger.debug(f"Updating {type(existing)}: {existing} with {type(parser.pcr)}: {parser.pcr}")
|
||||||
|
if json.dumps(parser.pcr) not in sub.pcr_info:
|
||||||
|
existing.append(parser.pcr)
|
||||||
|
logger.debug(f"Setting: {existing}")
|
||||||
|
sub.pcr_info = json.dumps(existing)
|
||||||
|
except TypeError:
|
||||||
|
logger.error(f"Error updating!")
|
||||||
|
sub.pcr_info = json.dumps([parser.pcr])
|
||||||
|
logger.debug(f"Final pcr info for {sub.rsl_plate_num}: {sub.pcr_info}")
|
||||||
|
else:
|
||||||
|
sub.pcr_info = json.dumps([parser.pcr])
|
||||||
|
self.ctx['database_session'].add(sub)
|
||||||
|
logger.debug(f"Existing {type(sub.pcr_info)}: {sub.pcr_info}")
|
||||||
|
logger.debug(f"Inserting {type(json.dumps(parser.pcr))}: {json.dumps(parser.pcr)}")
|
||||||
|
self.ctx["database_session"].commit()
|
||||||
|
logger.debug(f"Got {len(parser.samples)} to update!")
|
||||||
|
for sample in parser.samples:
|
||||||
|
logger.debug(f"Running update on: {sample['sample']}")
|
||||||
|
update_ww_sample(ctx=self.ctx, sample_obj=sample)
|
||||||
|
dlg = AlertPop(message=f"We added PCR info to {sub.rsl_plate_num}.", status='information')
|
||||||
|
dlg.exec()
|
||||||
|
|
||||||
|
|
||||||
class AddSubForm(QWidget):
|
class AddSubForm(QWidget):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Contains miscellaneous widgets for frontend functions
|
Contains miscellaneous widgets for frontend functions
|
||||||
'''
|
'''
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
import typing
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QLabel, QVBoxLayout,
|
QLabel, QVBoxLayout,
|
||||||
QLineEdit, QComboBox, QDialog,
|
QLineEdit, QComboBox, QDialog,
|
||||||
@@ -10,10 +11,11 @@ from PyQt6.QtWidgets import (
|
|||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QDate, QSize
|
from PyQt6.QtCore import Qt, QDate, QSize
|
||||||
|
# from submissions.backend.db.functions import lookup_kittype_by_use
|
||||||
# from submissions.backend.db import lookup_regent_by_type_name_and_kit_name
|
# from submissions.backend.db import lookup_regent_by_type_name_and_kit_name
|
||||||
from tools import check_not_nan
|
from tools import check_not_nan
|
||||||
from ..functions import extract_form_info
|
from ..functions import extract_form_info
|
||||||
from backend.db import get_all_reagenttype_names, lookup_all_sample_types, create_kit_from_yaml, lookup_regent_by_type_name#, lookup_regent_by_type_name_and_kit_name
|
from backend.db import get_all_reagenttype_names, lookup_all_sample_types, create_kit_from_yaml, lookup_regent_by_type_name, lookup_kittype_by_use#, lookup_regent_by_type_name_and_kit_name
|
||||||
from backend.excel.parser import SheetParser
|
from backend.excel.parser import SheetParser
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
import sys
|
import sys
|
||||||
@@ -329,9 +331,13 @@ class ImportReagent(QComboBox):
|
|||||||
else:
|
else:
|
||||||
if len(relevant_reagents) > 1:
|
if len(relevant_reagents) > 1:
|
||||||
logger.debug(f"Found {prsr.sub[item]['lot']} in relevant reagents: {relevant_reagents}. Moving to front of list.")
|
logger.debug(f"Found {prsr.sub[item]['lot']} in relevant reagents: {relevant_reagents}. Moving to front of list.")
|
||||||
relevant_reagents.insert(0, relevant_reagents.pop(relevant_reagents.index(prsr.sub[item]['lot'])))
|
idx = relevant_reagents.index(str(prsr.sub[item]['lot']))
|
||||||
|
logger.debug(f"The index we got for {prsr.sub[item]['lot']} in {relevant_reagents} was {idx}")
|
||||||
|
moved_reag = relevant_reagents.pop(idx)
|
||||||
|
relevant_reagents.insert(0, moved_reag)
|
||||||
else:
|
else:
|
||||||
logger.debug(f"Found {prsr.sub[item]['lot']} in relevant reagents: {relevant_reagents}. But no need to move due to short list.")
|
logger.debug(f"Found {prsr.sub[item]['lot']} in relevant reagents: {relevant_reagents}. But no need to move due to short list.")
|
||||||
logger.debug(f"New relevant reagents: {relevant_reagents}")
|
logger.debug(f"New relevant reagents: {relevant_reagents}")
|
||||||
|
self.setObjectName(f"lot_{item}")
|
||||||
self.addItems(relevant_reagents)
|
self.addItems(relevant_reagents)
|
||||||
|
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Submission Details for {{ sub['Plate Number'] }}</title>
|
<title>Submission Details for {{ sub['Plate Number'] }}</title>
|
||||||
</head>
|
</head>
|
||||||
|
{% set excluded = ['reagents', 'samples', 'controls', 'ext_info', 'pcr_info'] %}
|
||||||
<body>
|
<body>
|
||||||
<h2><u>Submission Details for {{ sub['Plate Number'] }}</u></h2>
|
<h2><u>Submission Details for {{ sub['Plate Number'] }}</u></h2>
|
||||||
<p>{% for key, value in sub.items() if key != 'reagents' and key != 'samples' and key != 'controls' and key != 'ext_info' %}
|
<p>{% for key, value in sub.items() if key not in excluded %}
|
||||||
{% if loop.index == 1 %}
|
{% if loop.index == 1 %}
|
||||||
{% if key=='Cost' %}{{ key }}: {{ "${:,.2f}".format(value) }}{% else %}{{ key }}: {{ value }}{% endif %}<br>
|
{% if key=='Cost' %}{{ key }}: {{ "${:,.2f}".format(value) }}{% else %}{{ key }}: {{ value }}{% endif %}<br>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -62,5 +63,25 @@
|
|||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if sub['pcr_info'] %}
|
||||||
|
{% for entry in sub['pcr_info'] %}
|
||||||
|
{% if 'comment' not in entry.keys() %}
|
||||||
|
<h3><u>qPCR Momentum Status:</u></h3>
|
||||||
|
{% else %}
|
||||||
|
<h3><u>qPCR Status:</u></h3>
|
||||||
|
{% endif %}
|
||||||
|
<p>{% for key, value in entry.items() if key != 'imported_by'%}
|
||||||
|
{% if loop.index == 1%}
|
||||||
|
{{ key|replace('_', ' ')|title() }}: {{ value }}<br>
|
||||||
|
{% else %}
|
||||||
|
{% if "column" in key %}
|
||||||
|
{{ key|replace('_', ' ')|title() }}: {{ value }}uL<br>
|
||||||
|
{% else %}
|
||||||
|
{{ key|replace('_', ' ')|title() }}: {{ value }}<br>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{# template for constructing submission details #}
|
{# template for constructing submission details #}
|
||||||
|
{% set excluded = ['reagents', 'samples', 'controls', 'ext_info', 'pcr_info'] %}
|
||||||
{% for key, value in sub.items() if key != 'reagents' and key != 'samples' and key != 'controls' and key != 'ext_info' %}
|
{# for key, value in sub.items() if key != 'reagents' and key != 'samples' and key != 'controls' and key != 'ext_info' #}
|
||||||
|
{% for key, value in sub.items() if key not in excluded %}
|
||||||
{% if key=='Cost' %} {{ key }}: {{ "${:,.2f}".format(value) }} {% else %} {{ key }}: {{ value }} {% endif %}
|
{% if key=='Cost' %} {{ key }}: {{ "${:,.2f}".format(value) }} {% else %} {{ key }}: {{ value }} {% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
Reagents:
|
Reagents:
|
||||||
@@ -22,6 +23,10 @@ Attached Controls:
|
|||||||
{% if sub['ext_info'] %}{% for entry in sub['ext_info'] %}
|
{% if sub['ext_info'] %}{% for entry in sub['ext_info'] %}
|
||||||
Extraction Status:
|
Extraction Status:
|
||||||
{% for key, value in entry.items() %}
|
{% for key, value in entry.items() %}
|
||||||
{{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %}
|
{{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %}{% endfor %}{% endif %}
|
||||||
{% endfor %}
|
{% if sub['pcr_info'] %}{% for entry in sub['pcr_info'] %}
|
||||||
|
{% if 'comment' not in entry.keys() %}qPCR Momentum Status:{% else %}
|
||||||
|
qPCR Status{% endif %}
|
||||||
|
{% for key, value in entry.items() if key != 'imported_by' %}
|
||||||
|
{{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %}{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
'''
|
'''
|
||||||
Contains miscellaenous functions used by both frontend and backend.
|
Contains miscellaenous functions used by both frontend and backend.
|
||||||
'''
|
'''
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import logging
|
import logging
|
||||||
import getpass
|
import getpass
|
||||||
from backend.db.models import BasicSubmission, KitType
|
from backend.db.models import BasicSubmission, KitType
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -116,3 +118,14 @@ def check_if_app(ctx:dict=None) -> bool:
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve_rsl_number(in_str:str) -> Tuple[str, str]:
|
||||||
|
in_str = in_str.split("\\")[-1]
|
||||||
|
logger.debug(f"Attempting match of {in_str}")
|
||||||
|
regex = re.compile(r"""
|
||||||
|
(?P<wastewater>RSL-WW-20\d{6})|(?P<bacterial_culture>RSL-\d{2}-\d{4})
|
||||||
|
""", re.VERBOSE)
|
||||||
|
m = regex.search(in_str)
|
||||||
|
return (m.group(), m.lastgroup)
|
||||||
|
|
||||||
Reference in New Issue
Block a user