Plate map in procedure details.
This commit is contained in:
@@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
import sys, logging, json, inspect
|
||||
from datetime import datetime, date
|
||||
from pprint import pformat
|
||||
|
||||
from dateutil.parser import parse
|
||||
from jinja2 import TemplateNotFound, Template
|
||||
from pandas import DataFrame
|
||||
@@ -19,7 +18,7 @@ from sqlalchemy.exc import ArgumentError
|
||||
from typing import Any, List, ClassVar
|
||||
from pathlib import Path
|
||||
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||
from tools import report_result, list_sort_dict, jinja_template_loading, Report, Result
|
||||
from tools import report_result, list_sort_dict, jinja_template_loading, Report, Result, ctx
|
||||
|
||||
# NOTE: Load testing environment
|
||||
if 'pytest' in sys.modules:
|
||||
@@ -92,10 +91,6 @@ class BaseClass(Base):
|
||||
Returns:
|
||||
Session: DB session from ctx settings.
|
||||
"""
|
||||
if 'pytest' not in sys.modules:
|
||||
from tools import ctx
|
||||
else:
|
||||
from test_settings import ctx
|
||||
return ctx.database_session
|
||||
|
||||
@classmethod
|
||||
@@ -107,10 +102,6 @@ class BaseClass(Base):
|
||||
Returns:
|
||||
Path: Location of the Submissions directory in Settings object
|
||||
"""
|
||||
if 'pytest' not in sys.modules:
|
||||
from tools import ctx
|
||||
else:
|
||||
from test_settings import ctx
|
||||
return ctx.directory_path
|
||||
|
||||
@classmethod
|
||||
@@ -122,10 +113,6 @@ class BaseClass(Base):
|
||||
Returns:
|
||||
Path: Location of the Submissions backup directory in Settings object
|
||||
"""
|
||||
if 'pytest' not in sys.modules:
|
||||
from tools import ctx
|
||||
else:
|
||||
from test_settings import ctx
|
||||
return ctx.backup_path
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -284,7 +271,7 @@ class BaseClass(Base):
|
||||
if issubclass(v.__class__, PydBaseClass):
|
||||
setattr(instance, k, v.to_sql())
|
||||
instance._misc_info.update(outside_kwargs)
|
||||
logger.info(f"Instance from query or create: {instance}, new: {new}")
|
||||
# logger.info(f"Instance from query or create: {instance}, new: {new}")
|
||||
return instance, new
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -87,7 +87,7 @@ class ReagentRole(BaseClass):
|
||||
new = True
|
||||
for k, v in sanitized_kwargs.items():
|
||||
setattr(instance, k, v)
|
||||
logger.info(f"Instance from query or create: {instance}")
|
||||
# logger.info(f"Instance from query or create: {instance}")
|
||||
return instance, new
|
||||
|
||||
@classmethod
|
||||
@@ -785,7 +785,7 @@ class ProcedureType(BaseClass):
|
||||
)
|
||||
return PydProcedure(**output)
|
||||
|
||||
def construct_plate_map(self, sample_dicts: List["PydSample"]) -> str:
|
||||
def construct_plate_map(self, sample_dicts: List["PydSample"], creation:bool=True, vw_modifier:float=1.0) -> str:
|
||||
"""
|
||||
Constructs an html based plate map for procedure details.
|
||||
|
||||
@@ -800,13 +800,13 @@ class ProcedureType(BaseClass):
|
||||
if self.plate_rows == 0 or self.plate_columns == 0:
|
||||
return "<br/>"
|
||||
sample_dicts = self.pad_sample_dicts(sample_dicts=sample_dicts)
|
||||
vw = round((-0.07 * len(sample_dicts)) + 12.2, 1)
|
||||
vw = round((-0.07 * len(sample_dicts)) + (12.2 * vw_modifier), 1)
|
||||
# NOTE: An overly complicated list comprehension create a list of sample locations
|
||||
# NOTE: next will return a blank cell if no value found for row/column
|
||||
env = jinja_template_loading()
|
||||
template = env.get_template("support/plate_map.html")
|
||||
html = template.render(plate_rows=self.plate_rows, plate_columns=self.plate_columns, samples=sample_dicts,
|
||||
vw=vw)
|
||||
vw=vw, creation=creation)
|
||||
return html + "<br/>"
|
||||
|
||||
def pad_sample_dicts(self, sample_dicts: List["PydSample"]):
|
||||
@@ -985,6 +985,7 @@ class Procedure(BaseClass):
|
||||
output['sample_count'] = len(active_samples)
|
||||
output['clientlab'] = self.run.clientsubmission.clientlab.name
|
||||
output['cost'] = 0.00
|
||||
output['platemap'] = self.make_procedure_platemap()
|
||||
return output
|
||||
|
||||
def to_pydantic(self, **kwargs):
|
||||
@@ -1038,6 +1039,12 @@ class Procedure(BaseClass):
|
||||
output = {k: v for k, v in dicto.items()}
|
||||
return output
|
||||
|
||||
def make_procedure_platemap(self):
|
||||
dicto = [sample.to_pydantic() for sample in self.proceduresampleassociation]
|
||||
html = self.proceduretype.construct_plate_map(sample_dicts=dicto, creation=False, vw_modifier=1.15)
|
||||
return html
|
||||
|
||||
|
||||
|
||||
class ProcedureTypeReagentRoleAssociation(BaseClass):
|
||||
"""
|
||||
@@ -1143,7 +1150,7 @@ class ProcedureTypeReagentRoleAssociation(BaseClass):
|
||||
case _:
|
||||
pass
|
||||
setattr(instance, k, v)
|
||||
logger.info(f"Instance from query or create: {instance.__dict__}\nis new: {new}")
|
||||
# logger.info(f"Instance from query or create: {instance.__dict__}\nis new: {new}")
|
||||
return instance, new
|
||||
|
||||
@classmethod
|
||||
@@ -1423,7 +1430,7 @@ class EquipmentRole(BaseClass):
|
||||
new = True
|
||||
for k, v in sanitized_kwargs.items():
|
||||
setattr(instance, k, v)
|
||||
logger.info(f"Instance from query or create: {instance}")
|
||||
# logger.info(f"Instance from query or create: {instance}")
|
||||
return instance, new
|
||||
|
||||
@classmethod
|
||||
@@ -1816,6 +1823,9 @@ class Process(BaseClass):
|
||||
|
||||
|
||||
class ProcessVersion(BaseClass):
|
||||
|
||||
pyd_model_name = "Process"
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: Process id, primary key
|
||||
version = Column(FLOAT(2), default=1.00) #: Version number
|
||||
date_verified = Column(TIMESTAMP) #: Date this version was deemed worthy
|
||||
@@ -1867,7 +1877,7 @@ class ProcessVersion(BaseClass):
|
||||
version: str | float | None = None,
|
||||
name: str | None = None,
|
||||
limit: int = 0,
|
||||
**kwargs) -> ReagentLot | List[ReagentLot]:
|
||||
**kwargs) -> ProcessVersion | List[ProcessVersion]:
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match name:
|
||||
case str():
|
||||
@@ -1881,6 +1891,9 @@ class ProcessVersion(BaseClass):
|
||||
pass
|
||||
return cls.execute_query(query=query, limit=limit)
|
||||
|
||||
# def to_pydantic(self, pyd_model_name: str | None = None, **kwargs):
|
||||
# output = super().to_pydantic(pyd_model_name=pyd_model_name, **kwargs)
|
||||
|
||||
|
||||
class Tips(BaseClass):
|
||||
"""
|
||||
|
||||
@@ -879,7 +879,7 @@ class Run(BaseClass, LogMixin):
|
||||
Returns:
|
||||
PydSubmission: converted object.
|
||||
"""
|
||||
from backend.validators import PydRun
|
||||
from backend.validators import PydClientSubmission, PydRun
|
||||
dicto = self.details_dict(full_data=True, backup=backup)
|
||||
new_dict = {}
|
||||
for key, value in dicto.items():
|
||||
@@ -1916,6 +1916,8 @@ class ProcedureSampleAssociation(BaseClass):
|
||||
misc = output['misc_info']
|
||||
output.update(relevant)
|
||||
output['misc_info'] = misc
|
||||
output['row'] = self.row
|
||||
output['column'] = self.column
|
||||
output['results'] = [result.details_dict() for result in output['results']]
|
||||
return output
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Contains pandas and openpyxl convenience functions for interacting with excel workbooks
|
||||
"""
|
||||
|
||||
# from backend.excel.parsers.clientsubmission_parser import ClientSubmissionInfoParser, ClientSubmissionSampleParser
|
||||
from .parsers import (
|
||||
DefaultParser, DefaultKEYVALUEParser, DefaultTABLEParser,
|
||||
ProcedureInfoParser, ProcedureSampleParser, ProcedureReagentParser, ProcedureEquipmentParser,
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from datetime import date
|
||||
from typing import Tuple, List
|
||||
from backend.db.models import Procedure, Run
|
||||
from tools import jinja_template_loading, get_first_blank_df_row, row_map, flatten_list
|
||||
from tools import jinja_template_loading, get_first_blank_df_row, row_map, flatten_list, ctx
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
|
||||
@@ -173,10 +173,6 @@ class TurnaroundMaker(ReportArchetype):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if 'pytest' not in sys.modules:
|
||||
from tools import ctx
|
||||
else:
|
||||
from test_settings import ctx
|
||||
days = sub.turnaround_time
|
||||
try:
|
||||
tat = sub.get_default_info("turnaround_time")
|
||||
|
||||
@@ -36,7 +36,6 @@ class DefaultWriter(object):
|
||||
value = value['name']
|
||||
except (KeyError, ValueError):
|
||||
return
|
||||
# logger.debug(f"Value type: {type(value)}")
|
||||
match value:
|
||||
case x if issubclass(value.__class__, BaseClass):
|
||||
value = value.name
|
||||
@@ -81,7 +80,6 @@ class DefaultWriter(object):
|
||||
self.worksheet = self.prewrite(self.worksheet, start_row=start_row)
|
||||
self.start_row = self.delineate_start_row(start_row=start_row)
|
||||
self.end_row = self.delineate_end_row(start_row=start_row)
|
||||
logger.debug(f"Rows for {self.__class__.__name__}:\tstart: {self.start_row}, end: {self.end_row}")
|
||||
return workbook
|
||||
|
||||
def delineate_start_row(self, start_row: int = 1) -> int:
|
||||
@@ -93,7 +91,6 @@ class DefaultWriter(object):
|
||||
Returns:
|
||||
int
|
||||
"""
|
||||
logger.debug(f"{self.__class__.__name__} will start looking for blank rows at {start_row}")
|
||||
for iii, row in enumerate(self.worksheet.iter_rows(min_row=start_row), start=start_row):
|
||||
if all([item.value is None for item in row]):
|
||||
return iii
|
||||
@@ -146,7 +143,6 @@ class DefaultKEYVALUEWriter(DefaultWriter):
|
||||
dictionary = sort_dict_by_list(dictionary=dictionary, order_list=self.key_order)
|
||||
for ii, (k, v) in enumerate(dictionary.items(), start=self.start_row):
|
||||
value = self.stringify_value(value=v)
|
||||
logger.debug(f"{self.__class__.__name__} attempting to write {value}")
|
||||
if value is None:
|
||||
continue
|
||||
self.worksheet.cell(column=1, row=ii, value=self.prettify_key(k))
|
||||
@@ -172,7 +168,6 @@ class DefaultTABLEWriter(DefaultWriter):
|
||||
|
||||
def delineate_end_row(self, start_row: int = 1) -> int:
|
||||
end_row = start_row + len(self.pydant_obj) + 1
|
||||
logger.debug(f"End row has been delineated as {start_row} + {len(self.pydant_obj)} + 1 = {end_row}")
|
||||
return end_row
|
||||
|
||||
def pad_samples_to_length(self, row_count,
|
||||
@@ -220,7 +215,6 @@ class DefaultTABLEWriter(DefaultWriter):
|
||||
value = object.improved_dict()[header.lower().replace(" ", "_")]
|
||||
except (AttributeError, KeyError):
|
||||
value = ""
|
||||
# logger.debug(f"{self.__class__.__name__} attempting to write {value}")
|
||||
self.worksheet.cell(row=write_row, column=column, value=self.stringify_value(value))
|
||||
self.worksheet = self.postwrite(self.worksheet)
|
||||
return workbook
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Default
|
||||
Default writers for procedures.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import logging, sys
|
||||
@@ -18,9 +18,7 @@ class ProcedureInfoWriter(DefaultKEYVALUEWriter):
|
||||
'reagentrole', 'results', 'sample', 'tips', 'reagentlot']
|
||||
|
||||
def __init__(self, pydant_obj, *args, **kwargs):
|
||||
|
||||
super().__init__(pydant_obj=pydant_obj, *args, **kwargs)
|
||||
|
||||
self.fill_dictionary = {k: v for k, v in self.fill_dictionary.items() if k not in self.__class__.exclude}
|
||||
|
||||
def write_to_workbook(self, workbook: Workbook, sheet: str | None = None,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
|
||||
Writers for PCR results from Design and Analysis Software
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -69,13 +69,10 @@ class ProcedureCreation(QDialog):
|
||||
equipment['name'] == relevant_procedure_item.name))
|
||||
equipmentrole['equipment'].insert(0, equipmentrole['equipment'].pop(
|
||||
equipmentrole['equipment'].index(item_in_er_list)))
|
||||
# proceduretype_dict['equipment_section'] = EquipmentUsage.construct_html(procedure=self.procedure, child=True)
|
||||
proceduretype_dict['equipment'] = [sanitize_object_for_json(object) for object in proceduretype_dict['equipment']]
|
||||
self.update_equipment = EquipmentUsage.update_equipment
|
||||
regex = re.compile(r".*R\d$")
|
||||
proceduretype_dict['previous'] = [""] + [item.name for item in self.run.procedure if item.proceduretype == self.proceduretype and not bool(regex.match(item.name))]
|
||||
logger.debug(f"Procedure:\n{pformat(self.procedure.__dict__)}")
|
||||
logger.debug(f"ProcedureType:\n{pformat(proceduretype_dict)}")
|
||||
# sys.exit(f"ProcedureDict:\n{pformat(proceduretype_dict)}")
|
||||
html = render_details_template(
|
||||
template_name="procedure_creation",
|
||||
js_in=["procedure_form", "grid_drag", "context_menu"],
|
||||
@@ -88,8 +85,9 @@ class ProcedureCreation(QDialog):
|
||||
self.webview.setHtml(html)
|
||||
|
||||
@pyqtSlot(str, str, str, str)
|
||||
def update_equipment(self, equipmentrole: str, equipment: str, process: str, tips: str):
|
||||
def update_equipment(self, equipmentrole: str, equipment: str, processversion: str, tips: str):
|
||||
from backend.db.models import Equipment, ProcessVersion, TipsLot
|
||||
logger.debug(f"\n\nEquipmentRole: {equipmentrole}, Equipment: {equipment}, Process: {processversion}, Tips: {tips}\n\n")
|
||||
try:
|
||||
equipment_of_interest = next(
|
||||
(item for item in self.procedure.equipment if item.equipmentrole == equipmentrole))
|
||||
@@ -103,9 +101,9 @@ class ProcedureCreation(QDialog):
|
||||
eoi.name = equipment.name
|
||||
eoi.asset_number = equipment.asset_number
|
||||
eoi.nickname = equipment.nickname
|
||||
process_name, version = process.split("-v")
|
||||
process = ProcessVersion.query(name=process_name, version=version, limit=1)
|
||||
eoi.process = process
|
||||
process_name, version = processversion.split("-v")
|
||||
processversion = ProcessVersion.query(name=process_name, version=version, limit=1)
|
||||
eoi.processversion = processversion.to_pydantic()
|
||||
try:
|
||||
tips_manufacturer, tipsref, lot = [item if item != "" else None for item in tips.split("-")]
|
||||
tips = TipsLot.query(manufacturer=tips_manufacturer, ref=tipsref, lot=lot)
|
||||
|
||||
@@ -56,10 +56,15 @@
|
||||
{% endif %}
|
||||
{% if procedure['sample'] %}
|
||||
<button type="button"><h3><u>Procedure Samples:</u></h3></button>
|
||||
{% if procedure['platemap']|length > 5 %}
|
||||
<br>
|
||||
{{ procedure['platemap'] }}
|
||||
{% else %}
|
||||
<p>{% for sample in procedure['sample'] %}
|
||||
<a class="{% if sample['active'] %}data-link {% else %}unused {% endif %}sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id']}}</a><br>
|
||||
<a class="{% if sample['active'] %}data-link {% else %}unused {% endif %}sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id']}}</a><br>
|
||||
{% endfor %}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% if not child %}
|
||||
</body>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<div class="plate" id="plate-container" style="grid-template-columns: repeat({{ plate_columns }}, {{ vw }}vw);grid-template-rows: repeat({{ plate_rows }}, {{ vw }}vw);">
|
||||
{% for sample in samples %}
|
||||
<div class="well" draggable="true" id="{{ sample['well_id'] }}" style="background-color: {{ sample['background_color'] }};">
|
||||
<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}</p>
|
||||
<div class="well" draggable="true" id="{{ sample['sample_id'] }}" style="background-color: {{ sample['background_color'] }};">
|
||||
{% if creation %}
|
||||
<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}</p>
|
||||
{% else %}
|
||||
<a class="data-link sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id']}}</a><br>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ from copy import copy
|
||||
from collections import OrderedDict
|
||||
from datetime import date, datetime, timedelta
|
||||
from json import JSONDecodeError
|
||||
from pprint import pformat
|
||||
from threading import Thread
|
||||
from inspect import getmembers, isfunction, stack
|
||||
from dateutil.easter import easter
|
||||
@@ -1039,7 +1040,7 @@ def flatten_list(input_list: list) -> list:
|
||||
return list(itertools.chain.from_iterable(input_list))
|
||||
|
||||
|
||||
def sanitize_object_for_json(input_dict: dict) -> dict:
|
||||
def sanitize_object_for_json(input_dict: dict) -> dict | str:
|
||||
"""
|
||||
Takes an object and makes sure its components can be converted to JSON
|
||||
|
||||
@@ -1057,8 +1058,12 @@ def sanitize_object_for_json(input_dict: dict) -> dict:
|
||||
try:
|
||||
input_dict = json.dumps(input_dict)
|
||||
except TypeError:
|
||||
input_dict = str(input_dict)
|
||||
return input_dict
|
||||
match input_dict:
|
||||
case str():
|
||||
pass
|
||||
case _:
|
||||
input_dict = str(input_dict)
|
||||
return input_dict.strip('\"')
|
||||
output = {}
|
||||
for key, value in input_dict.items():
|
||||
match value:
|
||||
@@ -1070,7 +1075,13 @@ def sanitize_object_for_json(input_dict: dict) -> dict:
|
||||
try:
|
||||
value = json.dumps(value)
|
||||
except TypeError:
|
||||
value = str(value)
|
||||
match value:
|
||||
case str():
|
||||
pass
|
||||
case _:
|
||||
value = str(value)
|
||||
if isinstance(value, str):
|
||||
value = value.strip('\"')
|
||||
output[key] = value
|
||||
return output
|
||||
|
||||
|
||||
Reference in New Issue
Block a user