From e5894208152c19cb58c387cdd151b38cdd90e0ca Mon Sep 17 00:00:00 2001 From: lwark Date: Fri, 5 Jul 2024 14:00:25 -0500 Subject: [PATCH] post documentation and code clean-up. --- CHANGELOG.md | 4 ++ README.md | 61 +++++++++++++++---- src/submissions/backend/db/__init__.py | 8 ++- src/submissions/backend/db/models/__init__.py | 9 ++- src/submissions/backend/db/models/kits.py | 49 ++++++++------- .../backend/db/models/submissions.py | 8 +-- src/submissions/frontend/widgets/app.py | 11 ++++ .../frontend/widgets/equipment_usage.py | 2 +- 8 files changed, 106 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b064ee..b519884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 202407.01 + +- Better documentation. + ## 202406.04 - Exported submission details will now be in docx format. diff --git a/README.md b/README.md index fb26a18..c3a96f2 100644 --- a/README.md +++ b/README.md @@ -9,20 +9,57 @@ 1. Ensure a properly formatted Submission Excel form has been filled out. a. The program can fill in reagent fields and some other information automatically, but should be checked for accuracy afterwards. 2. Click on 'File' in the menu bar, followed by 'Import Submission' and use the file dialog to locate the form. + 1. The excel file may also be dragged into the grey area on the left hand side of the screen from Windows File Explorer. If so, skip step 3. 3. Click 'Ok'. 4. Most of the fields in the form should be automatically filled in from the form area to the left of the screen. 5. You may need to maximize the app to ensure you can see all the info. 6. Any fields that are not automatically filled in can be filled in manually from the drop down menus. - a. Any reagent lots not found in the drop downs can be typed in manually. + 1. Any reagent lots not found in the drop downs can be typed in manually. 7. Once you are certain all the information is correct, click 'Submit' at the bottom of the form. 8. Add in any new reagents the app doesn't have in the database. 9. Once the new run shows up at the bottom of the Submissions, everything is fine. 10. In case of any mistakes, the run can be overwritten by a reimport. +## Adding Equipment to a Run: + +1. Right click on the run in the Submissions Table to access the context menu. +2. Click on “Add Equipment”. +3. Select equipment used for each equipment role from the drop down menu. + 1. Any tips associated with a liquid handler will also be available in a separate drop down menu. +5. Select (or input) the process used on with the equipment. + 1. Equipment that is not necessary may disabled using the check boxes to the left of each entry. + +## Importing PCR results (Wastewater only): + +This is meant to import .xslx files created from the Design & Analysis Software +1. Click on 'File' -> 'Import PCR Results'. +2. Use the file dialog to locate the .xlsx file you want to import. +3. Click 'Okay'. + +## Using the Gel Box (Wastewater Artic only): + +1. Right click on the run in the Submissions Table to access the context menu. +2. Click on “Gel Box”. +3. Choose the .jpg file exported from the Egel reader. +4. Click “Okay”. +5. If none exists, eEnter the DNA Core Submission Number and gel barcode at the top of the window. +6. Use the histogram slide on the right side of the window to adjust the image contrast. +7. Use the mouse scroll to zoom in on relevant areas of the image. +8. Enter the control status in the grid at the bottom of the window. +9. Add any relevant comments. +10. Click “Okay”. + ## Check existing Run: 1. Details of existing runs can be checked by double clicking on the row of interest in the summary sheet on the right of the 'Submissions' tab. -2. All information available on the run should be available in the resulting text window. This information can be exported by clicking 'Export PDF' at the top. +2. All information available on the run should be available in the resulting text window. + 1. This information can be exported by clicking 'Export DOCX' at the top. + +## Signing Off on a run: + +1. Open the “Submission Details” window (see 7.6 above). +2. Scroll down to bottom of the details window. +3. If the current user is authorized a button marked “Sign Off” will appear at the bottom of the page. Click it. ## Generating a report: @@ -31,20 +68,22 @@ 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. -## Importing PCR results: +## Exporting a run as an Excel file: + +1. Right click on the run in the Submissions Table to access the context menu. +2. Select “Export” from the context menu. +3. Select the folder and input the filename in the “Save File” dialog. +4. Click “Okay”. +5. Ensure the resulting Excel workbook contains all the relevant information. -This is meant to import .xslx files created from the Design & Analysis Software -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: 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) 2. Click on the "Controls" tab. 3. Range of dates for controls can be selected from the date pickers at the top. - a. If start date is set after end date, the start date will default back to 3 months before end date. - b. Recommendation is to use less than 6 month date range keeping in mind that higher data density will affect performance (with kraken being the worst so far) + 1. If start date is set after end date, the start date will default back to 3 months before end date. + 2. Recommendation is to use less than 6 month date range keeping in mind that higher data density will affect performance (with kraken being the worst so far) 4. Analysis type and subtype can be set using the drop down menus. (Only kraken has a subtype so far). ## Adding new Kit: @@ -69,7 +108,3 @@ This is meant to import .xslx files created from the Design & Analysis Software 1. Click "Monthly" -> "Link PCR Logs". 2. Chose the .csv file taken from the PCR table runlogs folder. -## Hitpicking: -1. Select all submissions you wish to hitpick using "Ctrl + click". All must have PCR results. -2. Right click on the last sample and select "Hitpick" from the contex menu. -3. Select location to save csv file. diff --git a/src/submissions/backend/db/__init__.py b/src/submissions/backend/db/__init__.py index 5abf531..280f15f 100644 --- a/src/submissions/backend/db/__init__.py +++ b/src/submissions/backend/db/__init__.py @@ -1,9 +1,10 @@ -''' +""" All database related operations. -''' +""" from sqlalchemy import event from sqlalchemy.engine import Engine + @event.listens_for(Engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): """ @@ -19,4 +20,5 @@ def set_sqlite_pragma(dbapi_connection, connection_record): cursor.execute("PRAGMA foreign_keys=ON") cursor.close() -from .models import * \ No newline at end of file + +from .models import * diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index b0627b9..f054ff1 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -1,6 +1,6 @@ -''' +""" Contains all models for sqlalchemy -''' +""" from __future__ import annotations import sys, logging from sqlalchemy import Column, INTEGER, String, JSON @@ -27,6 +27,7 @@ class BaseClass(Base): __table_args__ = {'extend_existing': True} #: Will only add new columns + @classmethod @declared_attr def __tablename__(cls) -> str: """ @@ -37,6 +38,7 @@ class BaseClass(Base): """ return f"_{cls.__name__.lower()}" + @classmethod @declared_attr def __database_session__(cls) -> Session: """ @@ -51,6 +53,7 @@ class BaseClass(Base): from test_settings import ctx return ctx.database_session + @classmethod @declared_attr def __directory_path__(cls) -> Path: """ @@ -65,6 +68,7 @@ class BaseClass(Base): from test_settings import ctx return ctx.directory_path + @classmethod @declared_attr def __backup_path__(cls) -> Path: """ @@ -192,3 +196,4 @@ from .controls import * from .organizations import * from .kits import * from .submissions import * +BasicSubmission.reagents.creator = lambda reg: SubmissionReagentAssociation(reagent=reg) diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index 813fb9a..97b8b94 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -8,7 +8,7 @@ from sqlalchemy.ext.associationproxy import association_proxy from datetime import date import logging, re from tools import check_authorization, setup_lookup, Report, Result -from typing import List, Literal, Any +from typing import List, Literal from pandas import ExcelFile from pathlib import Path from . import Base, BaseClass, Organization @@ -126,8 +126,9 @@ class KitType(BaseClass): cascade="all, delete-orphan", ) #: Relation to SubmissionType - used_for = association_proxy("kit_submissiontype_associations", - "submission_type") #: Association proxy to SubmissionTypeKitTypeAssociation + used_for = association_proxy("kit_submissiontype_associations", "submission_type", + creator=lambda ST: SubmissionTypeKitTypeAssociation( + submission_type=ST)) #: Association proxy to SubmissionTypeKitTypeAssociation def __repr__(self) -> str: """ @@ -375,8 +376,9 @@ class Reagent(BaseClass): cascade="all, delete-orphan", ) #: Relation to SubmissionSampleAssociation - submissions = association_proxy("reagent_submission_associations", - "submission") #: Association proxy to SubmissionSampleAssociation.samples + submissions = association_proxy("reagent_submission_associations", "submission", + creator=lambda sub: SubmissionReagentAssociation( + submission=sub)) #: Association proxy to SubmissionSampleAssociation.samples def __repr__(self): if self.name is not None: @@ -597,7 +599,9 @@ class SubmissionType(BaseClass): cascade="all, delete-orphan", ) #: Association of kittypes - kit_types = association_proxy("submissiontype_kit_associations", "kit_type") #: Proxy of kittype association + kit_types = association_proxy("submissiontype_kit_associations", "kit_type", + creator=lambda kit: SubmissionTypeKitTypeAssociation( + kit_type=kit)) #: Proxy of kittype association submissiontype_equipmentrole_associations = relationship( "SubmissionTypeEquipmentRoleAssociation", @@ -605,8 +609,8 @@ class SubmissionType(BaseClass): cascade="all, delete-orphan" ) #: Association of equipmentroles - equipment = association_proxy("submissiontype_equipmentrole_associations", - "equipment_role") #: Proxy of equipmentrole associations + equipment = association_proxy("submissiontype_equipmentrole_associations", "equipment_role", + creator=lambda eq: SubmissionTypeEquipmentRoleAssociation(equipment_role=eq)) #: Proxy of equipmentrole associations submissiontype_kit_rt_associations = relationship( "KitTypeReagentRoleAssociation", @@ -710,7 +714,7 @@ class SubmissionType(BaseClass): Returns: dict: Tip locations in the excel sheet. - """ + """ output = {} for item in self.submissiontype_tiprole_associations: tmap = item.uses @@ -827,12 +831,13 @@ class SubmissionTypeKitTypeAssociation(BaseClass): submission_type = relationship(SubmissionType, back_populates="submissiontype_kit_associations") #: joined submission type - def __init__(self, kit_type=None, submission_type=None): + def __init__(self, kit_type=None, submission_type=None, + mutable_cost_column: int = 0.00, mutable_cost_sample: int = 0.00, constant_cost: int = 0.00): self.kit_type = kit_type self.submission_type = submission_type - self.mutable_cost_column = 0.00 - self.mutable_cost_sample = 0.00 - self.constant_cost = 0.00 + self.mutable_cost_column = mutable_cost_column + self.mutable_cost_sample = mutable_cost_sample + self.constant_cost = constant_cost def __repr__(self) -> str: """ @@ -1539,15 +1544,15 @@ class Process(BaseClass): pass return cls.execute_query(query=query, limit=limit) - @check_authorization def save(self): super().save() + class TipRole(BaseClass): """ An abstract role that a tip fills during a process - """ + """ id = Column(INTEGER, primary_key=True) #: primary key name = Column(String(64)) #: name of reagent type instances = relationship("Tips", back_populates="role", @@ -1564,7 +1569,7 @@ class TipRole(BaseClass): def __repr__(self): return f"" - + @check_authorization def save(self): super().save() @@ -1573,7 +1578,7 @@ class TipRole(BaseClass): class Tips(BaseClass): """ A concrete instance of tips. - """ + """ id = Column(INTEGER, primary_key=True) #: primary key role = relationship("TipRole", back_populates="instances", secondary=tiproles_tips) #: joined parent reagent type @@ -1606,7 +1611,7 @@ class Tips(BaseClass): Returns: Tips | List[Tips]: Tips matching criteria - """ + """ query = cls.__database_session__.query(cls) match name: case str(): @@ -1622,7 +1627,7 @@ class Tips(BaseClass): case _: pass return cls.execute_query(query=query, limit=limit) - + @check_authorization def save(self): super().save() @@ -1642,7 +1647,7 @@ class SubmissionTypeTipRoleAssociation(BaseClass): back_populates="submissiontype_tiprole_associations") #: associated submission tip_role = relationship(TipRole, back_populates="tiprole_submissiontype_associations") #: associated equipment - + @check_authorization def save(self): super().save() @@ -1651,7 +1656,7 @@ class SubmissionTypeTipRoleAssociation(BaseClass): class SubmissionTipsAssociation(BaseClass): """ Association between a concrete submission instance and concrete tips - """ + """ tip_id = Column(INTEGER, ForeignKey("_tips.id"), primary_key=True) #: id of associated equipment submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission submission = relationship("BasicSubmission", @@ -1668,5 +1673,5 @@ class SubmissionTipsAssociation(BaseClass): Returns: dict: Values of this object - """ + """ return dict(role=self.role_name, name=self.tips.name, lot=self.tips.lot) diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index b8141d3..6243cd9 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -79,7 +79,7 @@ class BasicSubmission(BaseClass): ) #: Relation to SubmissionSampleAssociation samples = association_proxy("submission_sample_associations", - "sample") #: Association proxy to SubmissionSampleAssociation.samples + "sample", creator=lambda sample: SubmissionSampleAssociation(sample=sample)) #: Association proxy to SubmissionSampleAssociation.samples submission_reagent_associations = relationship( "SubmissionReagentAssociation", @@ -853,14 +853,14 @@ class BasicSubmission(BaseClass): @classmethod def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int: """ - _summary_ + Updates row information Args: sample (_type_): _description_ worksheet (Workbook): _description_ Returns: - int: _description_ + int: New row number """ return None @@ -1307,7 +1307,6 @@ class BacterialCulture(BasicSubmission): row = idx.index.to_list()[0] return row + 1 - class Wastewater(BasicSubmission): """ derivative submission type from BasicSubmission @@ -2053,7 +2052,6 @@ class BasicSample(BaseClass): dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above """ # logger.debug(f"Converting {self} to dict.") - # start = time() sample = {} sample['submitter_id'] = self.submitter_id sample['sample_type'] = self.sample_type diff --git a/src/submissions/frontend/widgets/app.py b/src/submissions/frontend/widgets/app.py index 410d122..0bc9323 100644 --- a/src/submissions/frontend/widgets/app.py +++ b/src/submissions/frontend/widgets/app.py @@ -69,6 +69,7 @@ class App(QMainWindow): helpMenu = menuBar.addMenu("&Help") helpMenu.addAction(self.helpAction) helpMenu.addAction(self.docsAction) + helpMenu.addAction(self.githubAction) fileMenu.addAction(self.importAction) methodsMenu.addAction(self.searchLog) methodsMenu.addAction(self.searchSample) @@ -103,6 +104,7 @@ class App(QMainWindow): self.docsAction = QAction("&Docs", self) self.searchLog = QAction("Search Log", self) self.searchSample = QAction("Search Sample", self) + self.githubAction = QAction("Github", self) def _connectActions(self): """ @@ -118,6 +120,7 @@ class App(QMainWindow): self.docsAction.triggered.connect(self.openDocs) self.searchLog.triggered.connect(self.runSearch) self.searchSample.triggered.connect(self.runSampleSearch) + self.githubAction.triggered.connect(self.openGithub) def showAbout(self): """ @@ -138,6 +141,14 @@ class App(QMainWindow): # logger.debug(f"Attempting to open {url}") webbrowser.get('windows-default').open(f"file://{url.__str__()}") + def openGithub(self): + """ + Opens the instructions html page + """ + url = "https://github.com/landowark/submissions" + webbrowser.get('windows-default').open(url) + + def result_reporter(self): """ Report any anomolous results - if any - to the user diff --git a/src/submissions/frontend/widgets/equipment_usage.py b/src/submissions/frontend/widgets/equipment_usage.py index a6f3233..df75d27 100644 --- a/src/submissions/frontend/widgets/equipment_usage.py +++ b/src/submissions/frontend/widgets/equipment_usage.py @@ -18,7 +18,7 @@ class EquipmentUsage(QDialog): def __init__(self, parent, submission: BasicSubmission) -> QDialog: super().__init__(parent) self.submission = submission - self.setWindowTitle("Equipment Checklist") + self.setWindowTitle(f"Equipment Checklist - {submission.rsl_plate_num}") self.used_equipment = self.submission.get_used_equipment() self.kit = self.submission.extraction_kit # logger.debug(f"Existing equipment: {self.used_equipment}")