diff --git a/TODO.md b/TODO.md index 460c882..4595830 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,4 @@ +- [ ] Create Tips ... *sigh*. - [x] Create platemap image from html for export to pdf. - [x] Move plate map maker to submission. - [x] Finish Equipment Parser (add in regex to id asset_number) diff --git a/src/submissions/__init__.py b/src/submissions/__init__.py index 34b1874..581f3b2 100644 --- a/src/submissions/__init__.py +++ b/src/submissions/__init__.py @@ -4,7 +4,7 @@ from pathlib import Path # Version of the realpython-reader package __project__ = "submissions" -__version__ = "202402.2b" +__version__ = "202402.3b" __author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"} __copyright__ = "2022-2024, Government of Canada" diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index 08e04e0..110a009 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -4,10 +4,10 @@ All kit and reagent related models from __future__ import annotations from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT, BLOB from sqlalchemy.orm import relationship, validates, Query -from sqlalchemy.ext.associationproxy import association_proxy, AssociationProxy +from sqlalchemy.ext.associationproxy import association_proxy from datetime import date import logging, re -from tools import check_authorization, setup_lookup, Report, Result, Settings +from tools import check_authorization, setup_lookup, Report, Result from typing import List from pandas import ExcelFile from pathlib import Path @@ -921,23 +921,32 @@ class SubmissionReagentAssociation(BaseClass): from . import BasicSubmission query: Query = cls.__database_session__.query(cls) match reagent: - case Reagent(): + case Reagent() | str(): # logger.debug(f"Lookup SubmissionReagentAssociation by reagent Reagent {reagent}") + if isinstance(reagent, str): + reagent = Reagent.query(lot_number=reagent) query = query.filter(cls.reagent==reagent) - case str(): - # logger.debug(f"Lookup SubmissionReagentAssociation by reagent str {reagent}") - query = query.join(Reagent).filter(Reagent.lot==reagent) + # case str(): + # logger.debug(f"Lookup SubmissionReagentAssociation by reagent str {reagent}") + + # query = query.filter(cls.reagent==reagent) + # logger.debug(f"Result: {query.all()}") case _: pass match submission: - case BasicSubmission(): + case BasicSubmission() | str(): + if isinstance(submission, str): + submission = BasicSubmission.query(rsl_number=submission) # logger.debug(f"Lookup SubmissionReagentAssociation by submission BasicSubmission {submission}") query = query.filter(cls.submission==submission) - case str(): - # logger.debug(f"Lookup SubmissionReagentAssociation by submission str {submission}") - query = query.join(BasicSubmission).filter(BasicSubmission.rsl_plate_num==submission) + # case str(): + # logger.debug(f"Lookup SubmissionReagentAssociation by submission str {submission}") + # submission = BasicSubmission.query(rsl_number=submission) + # query = query.filter(cls.submission==submission) + # logger.debug(f"Result: {query.all()}") case int(): # logger.debug(f"Lookup SubmissionReagentAssociation by submission id {submission}") + submission = BasicSubmission.query(id=submission) query = query.join(BasicSubmission).filter(BasicSubmission.id==submission) case _: pass diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index f7047fc..c483d4e 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -338,7 +338,7 @@ class BasicSubmission(BaseClass): logger.debug(f"Got {len(subs)} submissions.") df = pd.DataFrame.from_records(subs) # Exclude sub information - for item in ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents', 'equipment']: + for item in ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents', 'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number']: try: df = df.drop(item, axis=1) except: @@ -1068,7 +1068,21 @@ class BacterialCulture(BasicSubmission): outstr = re.sub(r"BC(\d{6})", r"BC-\1", outstr, flags=re.IGNORECASE) except (AttributeError, TypeError) as e: outstr = RSLNamer.construct_new_plate_name(data=data) - return outstr + try: + plate_number = re.search(r"(?:(-|_)\d)(?!\d)", outstr).group().strip("_").strip("-") + # logger.debug(f"Plate number is: {plate_number}") + except AttributeError as e: + plate_number = "1" + outstr = re.sub(r"(\d{8})(-|_)?\d?(R\d?)?", rf"\1-{plate_number}\3", outstr) + # logger.debug(f"After addition of plate number the plate name is: {outstr}") + try: + repeat = re.search(r"-\dR(?P\d)?", outstr).groupdict()['repeat'] + if repeat == None: + repeat = "1" + except AttributeError as e: + repeat = "" + return re.sub(r"(-\dR)\d?", rf"\1 {repeat}", outstr).replace(" ", "") + # return outstr @classmethod def get_regex(cls) -> str: @@ -1078,7 +1092,7 @@ class BacterialCulture(BasicSubmission): Returns: str: string for regex construction """ - return "(?PRSL(?:-|_)?BC(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\s]|$)?R?\d?)?)" + return "(?PRSL(?:-|_)?BC(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\sA-QS-Z]|$)?R?\d?)?)" @classmethod def filename_template(cls): @@ -1244,7 +1258,7 @@ class Wastewater(BasicSubmission): Returns: str: String for regex construction """ - return "(?PRSL(?:-|_)?WW(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\s]|$)?R?\d?)?)" + return "(?PRSL(?:-|_)?WW(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\sA-QS-Z]|$)?R?\d?)?)" @classmethod def adjust_autofill_samples(cls, samples: List[Any]) -> List[Any]: diff --git a/src/submissions/backend/excel/parser.py b/src/submissions/backend/excel/parser.py index 7b38b41..de9ea1b 100644 --- a/src/submissions/backend/excel/parser.py +++ b/src/submissions/backend/excel/parser.py @@ -601,5 +601,3 @@ class PCRParser(object): self.pcr['plugin'] = df.iloc[19][1] self.pcr['exported_on'] = df.iloc[20][1] self.pcr['imported_by'] = getuser() - - \ No newline at end of file diff --git a/src/submissions/backend/excel/reports.py b/src/submissions/backend/excel/reports.py index fc1cf13..22ddba0 100644 --- a/src/submissions/backend/excel/reports.py +++ b/src/submissions/backend/excel/reports.py @@ -32,7 +32,6 @@ def make_report_xlsx(records:list[dict]) -> Tuple[DataFrame, DataFrame]: df = df.sort_values(['Submitting Lab', "Submitted Date"]) return df, df2 - def make_report_html(df:DataFrame, start_date:date, end_date:date) -> str: """ @@ -74,7 +73,6 @@ def make_report_html(df:DataFrame, start_date:date, end_date:date) -> str: html = temp.render(input=dicto) return html - def convert_data_list_to_df(input:list[dict], subtype:str|None=None) -> DataFrame: """ Convert list of control records to dataframe @@ -104,7 +102,6 @@ def convert_data_list_to_df(input:list[dict], subtype:str|None=None) -> DataFram df = df_column_renamer(df=df) return df - def df_column_renamer(df:DataFrame) -> DataFrame: """ Ad hoc function I created to clarify some fields @@ -123,7 +120,6 @@ def df_column_renamer(df:DataFrame) -> DataFrame: "kraken_percent":"kraken2_read_percent_(top_50)" }) - def displace_date(df:DataFrame) -> DataFrame: """ This function serves to split samples that were submitted on the same date by incrementing dates. @@ -182,7 +178,6 @@ def check_date(df:DataFrame, item:dict, previous_dates:list) -> Tuple[DataFrame, df, previous_dates = check_date(df, item, previous_dates) return df, previous_dates - def get_unique_values_in_df_column(df: DataFrame, column_name: str) -> list: """ get all unique values in a dataframe column by name @@ -196,7 +191,6 @@ def get_unique_values_in_df_column(df: DataFrame, column_name: str) -> list: """ return sorted(df[column_name].unique()) - def drop_reruns_from_df(ctx:Settings, df: DataFrame) -> DataFrame: """ Removes semi-duplicates from dataframe after finding sequencing repeats. @@ -227,4 +221,4 @@ def make_hitpicks(input:List[dict]) -> DataFrame: Returns: DataFrame: constructed dataframe. """ - return DataFrame.from_records(input) \ No newline at end of file + return DataFrame.from_records(input) diff --git a/src/submissions/backend/validators/__init__.py b/src/submissions/backend/validators/__init__.py index e6eb23c..8521858 100644 --- a/src/submissions/backend/validators/__init__.py +++ b/src/submissions/backend/validators/__init__.py @@ -133,10 +133,10 @@ class RSLNamer(object): else: today = data['submitted_date'] else: - today = re.search(r"\d{4}(_|-)?\d{2}(_|-)?\d{2}", data['rsl_plate_num']) try: + today = re.search(r"\d{4}(_|-)?\d{2}(_|-)?\d{2}", data['rsl_plate_num']) today = parse(today.group()) - except AttributeError: + except (AttributeError, KeyError): today = datetime.now() if "rsl_plate_num" in data.keys(): plate_number = data['rsl_plate_num'].split("-")[-1][0] diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index df1c4c4..d66a0eb 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -642,7 +642,14 @@ class PydSubmission(BaseModel, extra='allow'): new_item = {} new_item['type'] = k new_item['location'] = excel_map['info'][k] - new_item['value'] = v['value'] + match k: + case "comment": + if v['value'] is not None: + new_item['value'] = "--".join([comment['text'] for comment in v['value']]) + else: + new_item['value'] = None + case _: + new_item['value'] = v['value'] new_info.append(new_item) except KeyError: logger.error(f"Unable to fill in {k}, not found in relevant info.") diff --git a/src/submissions/frontend/widgets/__init__.py b/src/submissions/frontend/widgets/__init__.py index 757816e..a3e2ecd 100644 --- a/src/submissions/frontend/widgets/__init__.py +++ b/src/submissions/frontend/widgets/__init__.py @@ -10,4 +10,8 @@ from .submission_table import * from .submission_widget import * from .controls_chart import * from .kit_creator import * -from .app import App \ No newline at end of file +from .submission_details import * +from .equipment_usage import * +from .gel_checker import * +from .submission_type_creator import * +from .app import App diff --git a/src/submissions/frontend/widgets/equipment_usage.py b/src/submissions/frontend/widgets/equipment_usage.py index facc685..55e1061 100644 --- a/src/submissions/frontend/widgets/equipment_usage.py +++ b/src/submissions/frontend/widgets/equipment_usage.py @@ -80,7 +80,6 @@ class EquipmentUsage(QDialog): for object in self.parent().findChildren(QCheckBox): object.setChecked(self.check.isChecked()) -# TODO: Figure out how this is working again class RoleComboBox(QWidget): def __init__(self, parent, role:PydEquipmentRole, used:list) -> None: diff --git a/src/submissions/frontend/widgets/functions.py b/src/submissions/frontend/widgets/functions.py index e2b16b0..62bc412 100644 --- a/src/submissions/frontend/widgets/functions.py +++ b/src/submissions/frontend/widgets/functions.py @@ -49,4 +49,4 @@ def select_save_file(obj:QMainWindow, default_name:str, extension:str) -> Path: home_dir = obj.app.last_dir.joinpath(default_name).resolve().__str__() fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter = f"{extension}(*.{extension})")[0]) obj.last_dir = fname.parent - return fname \ No newline at end of file + return fname diff --git a/src/submissions/frontend/widgets/gel_checker.py b/src/submissions/frontend/widgets/gel_checker.py index 29d77a1..a3b06df 100644 --- a/src/submissions/frontend/widgets/gel_checker.py +++ b/src/submissions/frontend/widgets/gel_checker.py @@ -1,12 +1,12 @@ """ Gel box for artic quality control """ -from PyQt6.QtWidgets import * -from PyQt6.QtWidgets import QWidget +from PyQt6.QtWidgets import (QWidget, QDialog, QGridLayout, + QLabel, QLineEdit, QDialogButtonBox + ) import numpy as np import pyqtgraph as pg -from PyQt6.QtGui import * -from PyQt6.QtCore import * +from PyQt6.QtGui import QIcon from PIL import Image import numpy as np import logging diff --git a/src/submissions/frontend/widgets/submission_details.py b/src/submissions/frontend/widgets/submission_details.py index 56095f0..934aa3a 100644 --- a/src/submissions/frontend/widgets/submission_details.py +++ b/src/submissions/frontend/widgets/submission_details.py @@ -60,7 +60,6 @@ class SubmissionDetails(QDialog): btn.setFixedWidth(900) btn.clicked.connect(self.export) - def export(self): """ Renders submission to html, then creates and saves .pdf file to user selected file. diff --git a/src/submissions/tools.py b/src/submissions/tools.py index 5a483f3..913746d 100644 --- a/src/submissions/tools.py +++ b/src/submissions/tools.py @@ -9,7 +9,7 @@ import pandas as pd from jinja2 import Environment, FileSystemLoader from logging import handlers from pathlib import Path -from sqlalchemy.orm import Query, Session +from sqlalchemy.orm import Session from sqlalchemy import create_engine from pydantic import field_validator, BaseModel, Field from pydantic_settings import BaseSettings, SettingsConfigDict @@ -506,4 +506,4 @@ def check_authorization(func): else: logger.error(f"User {getpass.getuser()} is not authorized for this function.") return dict(code=1, message="This user does not have permission for this function.", status="warning") - return wrapper \ No newline at end of file + return wrapper