Mid-code cleanup
This commit is contained in:
@@ -24,8 +24,8 @@ def set_sqlite_pragma(dbapi_connection, connection_record):
|
|||||||
if ctx.database_schema == "sqlite":
|
if ctx.database_schema == "sqlite":
|
||||||
execution_phrase = "PRAGMA foreign_keys=ON"
|
execution_phrase = "PRAGMA foreign_keys=ON"
|
||||||
# cursor.execute(execution_phrase)
|
# cursor.execute(execution_phrase)
|
||||||
elif ctx.database_schema == "mssql+pyodbc":
|
# elif ctx.database_schema == "mssql+pyodbc":
|
||||||
execution_phrase = "SET IDENTITY_INSERT dbo._wastewater ON;"
|
# execution_phrase = "SET IDENTITY_INSERT dbo._wastewater ON;"
|
||||||
else:
|
else:
|
||||||
print("Nothing to execute, returning")
|
print("Nothing to execute, returning")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|||||||
@@ -261,9 +261,9 @@ class KitType(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: Dictionary containing relevant info for SubmissionType construction
|
dict: Dictionary containing relevant info for SubmissionType construction
|
||||||
"""
|
"""
|
||||||
base_dict = dict(name=self.name)
|
base_dict = dict(name=self.name, reagent_roles=[], equipment_roles=[])
|
||||||
base_dict['reagent roles'] = []
|
# base_dict['reagent roles'] = []
|
||||||
base_dict['equipment roles'] = []
|
# base_dict['equipment roles'] = []
|
||||||
for k, v in self.construct_xl_map_for_use(submission_type=submission_type):
|
for k, v in self.construct_xl_map_for_use(submission_type=submission_type):
|
||||||
# logger.debug(f"Value: {v}")
|
# logger.debug(f"Value: {v}")
|
||||||
try:
|
try:
|
||||||
@@ -272,17 +272,17 @@ class KitType(BaseClass):
|
|||||||
continue
|
continue
|
||||||
for kk, vv in assoc.to_export_dict().items():
|
for kk, vv in assoc.to_export_dict().items():
|
||||||
v[kk] = vv
|
v[kk] = vv
|
||||||
base_dict['reagent roles'].append(v)
|
base_dict['reagent_roles'].append(v)
|
||||||
for k, v in submission_type.construct_field_map("equipment"):
|
for k, v in submission_type.construct_field_map("equipment"):
|
||||||
try:
|
try:
|
||||||
assoc = next(item for item in submission_type.submissiontype_equipmentrole_associations if
|
assoc = next(item for item in submission_type.submissiontype_equipmentrole_associations if
|
||||||
item.equipment_role.name == k)
|
item.equipment_role.name == k)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
continue
|
continue
|
||||||
for kk, vv in assoc.to_export_dict(kit_type=self).items():
|
for kk, vv in assoc.to_export_dict(extraction_kit=self).items():
|
||||||
# logger.debug(f"{kk}:{vv}")
|
# logger.debug(f"{kk}:{vv}")
|
||||||
v[kk] = vv
|
v[kk] = vv
|
||||||
base_dict['equipment roles'].append(v)
|
base_dict['equipment_roles'].append(v)
|
||||||
# logger.debug(f"KT returning {base_dict}")
|
# logger.debug(f"KT returning {base_dict}")
|
||||||
return base_dict
|
return base_dict
|
||||||
|
|
||||||
@@ -360,12 +360,12 @@ class ReagentRole(BaseClass):
|
|||||||
assert reagent.role
|
assert reagent.role
|
||||||
# logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}")
|
# logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}")
|
||||||
# logger.debug(f"Kit reagent types: {kit_type.reagent_types}")
|
# logger.debug(f"Kit reagent types: {kit_type.reagent_types}")
|
||||||
result = list(set(kit_type.reagent_roles).intersection(reagent.role))
|
result = set(kit_type.reagent_roles).intersection(reagent.role)
|
||||||
# logger.debug(f"Result: {result}")
|
# logger.debug(f"Result: {result}")
|
||||||
try:
|
# try:
|
||||||
return result[0]
|
return next((item for item in result), None)
|
||||||
except IndexError:
|
# except IndexError:
|
||||||
return None
|
# return None
|
||||||
match name:
|
match name:
|
||||||
case str():
|
case str():
|
||||||
# logger.debug(f"Looking up reagent type by name str: {name}")
|
# logger.debug(f"Looking up reagent type by name str: {name}")
|
||||||
@@ -445,12 +445,8 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
|
|
||||||
if extraction_kit is not None:
|
if extraction_kit is not None:
|
||||||
# NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType
|
# NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType
|
||||||
reagent_role = next((item for item in set(self.role).intersection(extraction_kit.reagent_roles)), self.role[0])
|
reagent_role = next((item for item in set(self.role).intersection(extraction_kit.reagent_roles)),
|
||||||
# try:
|
self.role[0])
|
||||||
# reagent_role = list(set(self.role).intersection(extraction_kit.reagent_roles))[0]
|
|
||||||
# # NOTE: Most will be able to fall back to first ReagentType in itself because most will only have 1.
|
|
||||||
# except:
|
|
||||||
# reagent_role = self.role[0]
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
reagent_role = self.role[0]
|
reagent_role = self.role[0]
|
||||||
@@ -580,11 +576,14 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
for key, value in vars.items():
|
for key, value in vars.items():
|
||||||
match key:
|
match key:
|
||||||
case "expiry":
|
case "expiry":
|
||||||
if not isinstance(value, date):
|
if isinstance(value, str):
|
||||||
field_value = datetime.strptime(value, "%Y-%m-%d").date
|
field_value = datetime.strptime(value, "%Y-%m-%d")
|
||||||
field_value.replace(tzinfo=timezone)
|
# field_value.replace(tzinfo=timezone)
|
||||||
|
elif isinstance(value, date):
|
||||||
|
field_value = datetime.combine(value, datetime.min.time())
|
||||||
else:
|
else:
|
||||||
field_value = value
|
field_value = value
|
||||||
|
field_value.replace(tzinfo=timezone)
|
||||||
case "role":
|
case "role":
|
||||||
continue
|
continue
|
||||||
case _:
|
case _:
|
||||||
@@ -792,6 +791,15 @@ class SubmissionType(BaseClass):
|
|||||||
return self.sample_map
|
return self.sample_map
|
||||||
|
|
||||||
def construct_field_map(self, field: Literal['equipment', 'tip']) -> Generator[(str, dict), None, None]:
|
def construct_field_map(self, field: Literal['equipment', 'tip']) -> Generator[(str, dict), None, None]:
|
||||||
|
"""
|
||||||
|
Make a map of all locations for tips or equipment.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field (Literal['equipment', 'tip']): the field to construct a map for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Generator[(str, dict), None, None]: Generator composing key, locations for each item in the map
|
||||||
|
"""
|
||||||
for item in self.__getattribute__(f"submissiontype_{field}role_associations"):
|
for item in self.__getattribute__(f"submissiontype_{field}role_associations"):
|
||||||
fmap = item.uses
|
fmap = item.uses
|
||||||
if fmap is None:
|
if fmap is None:
|
||||||
@@ -799,6 +807,12 @@ class SubmissionType(BaseClass):
|
|||||||
yield getattr(item, f"{field}_role").name, fmap
|
yield getattr(item, f"{field}_role").name, fmap
|
||||||
|
|
||||||
def get_default_kit(self) -> KitType | None:
|
def get_default_kit(self) -> KitType | None:
|
||||||
|
"""
|
||||||
|
If only one kits exists for this Submission Type, return it.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
KitType | None:
|
||||||
|
"""
|
||||||
if len(self.kit_types) == 1:
|
if len(self.kit_types) == 1:
|
||||||
return self.kit_types[0]
|
return self.kit_types[0]
|
||||||
else:
|
else:
|
||||||
@@ -1379,7 +1393,7 @@ class Equipment(BaseClass):
|
|||||||
else:
|
else:
|
||||||
return {k: v for k, v in self.__dict__.items()}
|
return {k: v for k, v in self.__dict__.items()}
|
||||||
|
|
||||||
def get_processes(self, submission_type: SubmissionType, extraction_kit: str | KitType | None = None) -> List[str]:
|
def get_processes(self, submission_type: str | SubmissionType | None = None, extraction_kit: str | KitType | None = None) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Get all processes associated with this Equipment for a given SubmissionType
|
Get all processes associated with this Equipment for a given SubmissionType
|
||||||
|
|
||||||
@@ -1390,23 +1404,35 @@ class Equipment(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
List[Process]: List of process names
|
List[Process]: List of process names
|
||||||
"""
|
"""
|
||||||
processes = [process for process in self.processes if submission_type in process.submission_types]
|
if isinstance(submission_type, str):
|
||||||
match extraction_kit:
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
case str():
|
if isinstance(extraction_kit, str):
|
||||||
# logger.debug(f"Filtering processes by extraction_kit str {extraction_kit}")
|
extraction_kit = KitType.query(name=extraction_kit)
|
||||||
processes = [process for process in processes if
|
for process in self.processes:
|
||||||
extraction_kit in [kit.name for kit in process.kit_types]]
|
if submission_type not in process.submission_types:
|
||||||
case KitType():
|
continue
|
||||||
# logger.debug(f"Filtering processes by extraction_kit KitType {extraction_kit}")
|
if extraction_kit and extraction_kit not in process.kit_types:
|
||||||
processes = [process for process in processes if extraction_kit in process.kit_types]
|
continue
|
||||||
case _:
|
yield process
|
||||||
pass
|
# processes = (process for process in self.processes if submission_type in process.submission_types)
|
||||||
# NOTE: Convert to strings
|
# match extraction_kit:
|
||||||
processes = [process.name for process in processes]
|
# case str():
|
||||||
assert all([isinstance(process, str) for process in processes])
|
# # logger.debug(f"Filtering processes by extraction_kit str {extraction_kit}")
|
||||||
if len(processes) == 0:
|
# processes = (process for process in processes if
|
||||||
processes = ['']
|
# extraction_kit in [kit.name for kit in process.kit_types])
|
||||||
return processes
|
# case KitType():
|
||||||
|
# # logger.debug(f"Filtering processes by extraction_kit KitType {extraction_kit}")
|
||||||
|
# processes = (process for process in processes if extraction_kit in process.kit_types)
|
||||||
|
# case _:
|
||||||
|
# pass
|
||||||
|
# # NOTE: Convert to strings
|
||||||
|
# # processes = [process.name for process in processes]
|
||||||
|
# # assert all([isinstance(process, str) for process in processes])
|
||||||
|
# # if len(processes) == 0:
|
||||||
|
# # processes = ['']
|
||||||
|
# # return processes
|
||||||
|
# for process in processes:
|
||||||
|
# yield process.name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
@@ -1452,8 +1478,7 @@ class Equipment(BaseClass):
|
|||||||
pass
|
pass
|
||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def to_pydantic(self, submission_type: SubmissionType,
|
def to_pydantic(self, submission_type: SubmissionType, extraction_kit: str | KitType | None = None,
|
||||||
extraction_kit: str | KitType | None = None,
|
|
||||||
role: str = None) -> "PydEquipment":
|
role: str = None) -> "PydEquipment":
|
||||||
"""
|
"""
|
||||||
Creates PydEquipment of this Equipment
|
Creates PydEquipment of this Equipment
|
||||||
@@ -1466,8 +1491,8 @@ class Equipment(BaseClass):
|
|||||||
PydEquipment: pydantic equipment object
|
PydEquipment: pydantic equipment object
|
||||||
"""
|
"""
|
||||||
from backend.validators.pydant import PydEquipment
|
from backend.validators.pydant import PydEquipment
|
||||||
return PydEquipment(
|
processes = self.get_processes(submission_type=submission_type, extraction_kit=extraction_kit)
|
||||||
processes=self.get_processes(submission_type=submission_type, extraction_kit=extraction_kit), role=role,
|
return PydEquipment(processes=processes, role=role,
|
||||||
**self.to_dict(processes=False))
|
**self.to_dict(processes=False))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1603,7 +1628,7 @@ class EquipmentRole(BaseClass):
|
|||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def get_processes(self, submission_type: str | SubmissionType | None,
|
def get_processes(self, submission_type: str | SubmissionType | None,
|
||||||
extraction_kit: str | KitType | None = None) -> List[Process]:
|
extraction_kit: str | KitType | None = None) -> Generator[Process, None, None]:
|
||||||
"""
|
"""
|
||||||
Get processes used by this EquipmentRole
|
Get processes used by this EquipmentRole
|
||||||
|
|
||||||
@@ -1612,30 +1637,38 @@ class EquipmentRole(BaseClass):
|
|||||||
extraction_kit (str | KitType | None, optional): KitType of interest. Defaults to None.
|
extraction_kit (str | KitType | None, optional): KitType of interest. Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Process]: _description_
|
List[Process]: List of processes
|
||||||
"""
|
"""
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
# logger.debug(f"Checking if str {submission_type} exists")
|
# logger.debug(f"Checking if str {submission_type} exists")
|
||||||
submission_type = SubmissionType.query(name=submission_type)
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
if submission_type is not None:
|
if isinstance(extraction_kit, str):
|
||||||
# logger.debug("Getting all processes for this EquipmentRole")
|
extraction_kit = KitType.query(name=extraction_kit)
|
||||||
processes = [process for process in self.processes if submission_type in process.submission_types]
|
for process in self.processes:
|
||||||
else:
|
if submission_type and submission_type not in process.submission_types:
|
||||||
processes = self.processes
|
continue
|
||||||
match extraction_kit:
|
if extraction_kit and extraction_kit not in process.kit_types:
|
||||||
case str():
|
continue
|
||||||
# logger.debug(f"Filtering processes by extraction_kit str {extraction_kit}")
|
yield process.name
|
||||||
processes = [item for item in processes if extraction_kit in [kit.name for kit in item.kit_types]]
|
# if submission_type is not None:
|
||||||
case KitType():
|
# # logger.debug("Getting all processes for this EquipmentRole")
|
||||||
# logger.debug(f"Filtering processes by extraction_kit KitType {extraction_kit}")
|
# processes = [process for process in self.processes if submission_type in process.submission_types]
|
||||||
processes = [item for item in processes if extraction_kit in [kit for kit in item.kit_types]]
|
# else:
|
||||||
case _:
|
# processes = self.processes
|
||||||
pass
|
# match extraction_kit:
|
||||||
output = [item.name for item in processes]
|
# case str():
|
||||||
if len(output) == 0:
|
# # logger.debug(f"Filtering processes by extraction_kit str {extraction_kit}")
|
||||||
return ['']
|
# processes = [item for item in processes if extraction_kit in [kit.name for kit in item.kit_types]]
|
||||||
else:
|
# case KitType():
|
||||||
return output
|
# # logger.debug(f"Filtering processes by extraction_kit KitType {extraction_kit}")
|
||||||
|
# processes = [item for item in processes if extraction_kit in [kit for kit in item.kit_types]]
|
||||||
|
# case _:
|
||||||
|
# pass
|
||||||
|
# output = [item.name for item in processes]
|
||||||
|
# if len(output) == 0:
|
||||||
|
# return ['']
|
||||||
|
# else:
|
||||||
|
# return output
|
||||||
|
|
||||||
def to_export_dict(self, submission_type: SubmissionType, kit_type: KitType):
|
def to_export_dict(self, submission_type: SubmissionType, kit_type: KitType):
|
||||||
"""
|
"""
|
||||||
@@ -1644,8 +1677,8 @@ class EquipmentRole(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: dictionary of Association and related reagent role
|
dict: dictionary of Association and related reagent role
|
||||||
"""
|
"""
|
||||||
return dict(role=self.name,
|
processes = self.get_processes(submission_type=submission_type, extraction_kit=kit_type)
|
||||||
processes=self.get_processes(submission_type=submission_type, extraction_kit=kit_type))
|
return dict(role=self.name, processes=[item for item in processes])
|
||||||
|
|
||||||
|
|
||||||
class SubmissionEquipmentAssociation(BaseClass):
|
class SubmissionEquipmentAssociation(BaseClass):
|
||||||
|
|||||||
@@ -1074,6 +1074,7 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
submission_type: str | SubmissionType | None = None,
|
submission_type: str | SubmissionType | None = None,
|
||||||
|
submission_type_name: str|None = None,
|
||||||
id: int | str | None = None,
|
id: int | str | None = None,
|
||||||
rsl_plate_num: str | None = None,
|
rsl_plate_num: str | None = None,
|
||||||
start_date: date | str | int | None = None,
|
start_date: date | str | int | None = None,
|
||||||
@@ -1173,6 +1174,11 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
match submission_type_name:
|
||||||
|
case str():
|
||||||
|
query = query.filter(model.submission_type_name == submission_type_name)
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
# NOTE: by id (returns only a single value)
|
# NOTE: by id (returns only a single value)
|
||||||
match id:
|
match id:
|
||||||
case int():
|
case int():
|
||||||
@@ -1389,13 +1395,23 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def calculate_turnaround(cls, start_date:date|None=None, end_date:date|None=None) -> Tuple[int|None, bool|None]:
|
def calculate_turnaround(cls, start_date:date|None=None, end_date:date|None=None) -> Tuple[int|None, bool|None]:
|
||||||
|
if 'pytest' not in sys.modules:
|
||||||
|
from tools import ctx
|
||||||
|
else:
|
||||||
|
from test_settings import ctx
|
||||||
if not end_date:
|
if not end_date:
|
||||||
return None, None
|
return None, None
|
||||||
try:
|
try:
|
||||||
delta = np.busday_count(start_date, end_date, holidays=create_holidays_for_year(start_date.year)) + 1
|
delta = np.busday_count(start_date, end_date, holidays=create_holidays_for_year(start_date.year)) + 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None, None
|
return None, None
|
||||||
return delta, delta <= ctx.TaT_threshold
|
try:
|
||||||
|
tat = cls.get_default_info("turnaround_time")
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
tat = None
|
||||||
|
if not tat:
|
||||||
|
tat = ctx.TaT_threshold
|
||||||
|
return delta, delta <= tat
|
||||||
|
|
||||||
|
|
||||||
# Below are the custom submission types
|
# Below are the custom submission types
|
||||||
|
|||||||
@@ -142,18 +142,18 @@ class ReportMaker(object):
|
|||||||
|
|
||||||
class TurnaroundMaker(object):
|
class TurnaroundMaker(object):
|
||||||
|
|
||||||
def __init__(self, start_date: date, end_date: date):
|
def __init__(self, start_date: date, end_date: date, submission_type:str):
|
||||||
self.start_date = start_date
|
self.start_date = start_date
|
||||||
self.end_date = end_date
|
self.end_date = end_date
|
||||||
# NOTE: Set page size to zero to override limiting query size.
|
# NOTE: Set page size to zero to override limiting query size.
|
||||||
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date, page_size=0)
|
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date, submission_type_name=submission_type, page_size=0)
|
||||||
records = [self.build_record(sub) for sub in self.subs]
|
records = [self.build_record(sub) for sub in self.subs]
|
||||||
self.df = DataFrame.from_records(records)
|
self.df = DataFrame.from_records(records)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build_record(cls, sub):
|
def build_record(cls, sub):
|
||||||
days, tat_ok = sub.get_turnaround_time()
|
days, tat_ok = sub.get_turnaround_time()
|
||||||
return dict(name=sub.rsl_plate_num, days=days, submitted_date=sub.submitted_date,
|
return dict(name=str(sub.rsl_plate_num), days=days, submitted_date=sub.submitted_date,
|
||||||
completed_date=sub.completed_date, acceptable=tat_ok)
|
completed_date=sub.completed_date, acceptable=tat_ok)
|
||||||
|
|
||||||
def write_report(self, filename: Path | str, obj: QWidget | None = None):
|
def write_report(self, filename: Path | str, obj: QWidget | None = None):
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from datetime import date, datetime, timedelta
|
|||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
from dateutil.parser import ParserError
|
from dateutil.parser import ParserError
|
||||||
from typing import List, Tuple, Literal
|
from typing import List, Tuple, Literal
|
||||||
|
from types import GeneratorType
|
||||||
from . import RSLNamer
|
from . import RSLNamer
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tools import check_not_nan, convert_nans_to_nones, Report, Result, timezone
|
from tools import check_not_nan, convert_nans_to_nones, Report, Result, timezone
|
||||||
@@ -343,6 +344,8 @@ class PydEquipment(BaseModel, extra='ignore'):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def make_empty_list(cls, value):
|
def make_empty_list(cls, value):
|
||||||
# logger.debug(f"Pydantic value: {value}")
|
# logger.debug(f"Pydantic value: {value}")
|
||||||
|
if isinstance(value, GeneratorType):
|
||||||
|
value = [item.name for item in value]
|
||||||
value = convert_nans_to_nones(value)
|
value = convert_nans_to_nones(value)
|
||||||
if not value:
|
if not value:
|
||||||
value = ['']
|
value = ['']
|
||||||
@@ -380,6 +383,7 @@ class PydEquipment(BaseModel, extra='ignore'):
|
|||||||
assoc = None
|
assoc = None
|
||||||
if assoc is None:
|
if assoc is None:
|
||||||
assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment)
|
assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment)
|
||||||
|
# TODO: This seems precarious. What if there is more than one process?
|
||||||
process = Process.query(name=self.processes[0])
|
process = Process.query(name=self.processes[0])
|
||||||
if process is None:
|
if process is None:
|
||||||
logger.error(f"Found unknown process: {process}.")
|
logger.error(f"Found unknown process: {process}.")
|
||||||
@@ -1122,6 +1126,13 @@ class PydEquipmentRole(BaseModel):
|
|||||||
equipment: List[PydEquipment]
|
equipment: List[PydEquipment]
|
||||||
processes: List[str] | None
|
processes: List[str] | None
|
||||||
|
|
||||||
|
@field_validator("processes", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def expand_processes(cls, value):
|
||||||
|
if isinstance(value, GeneratorType):
|
||||||
|
value = [item for item in value]
|
||||||
|
return value
|
||||||
|
|
||||||
def to_form(self, parent, used: list) -> "RoleComboBox":
|
def to_form(self, parent, used: list) -> "RoleComboBox":
|
||||||
"""
|
"""
|
||||||
Creates a widget for user input into this class.
|
Creates a widget for user input into this class.
|
||||||
|
|||||||
@@ -11,25 +11,33 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
|
|
||||||
class TurnaroundChart(CustomFigure):
|
class TurnaroundChart(CustomFigure):
|
||||||
|
|
||||||
def __init__(self, df: pd.DataFrame, modes: list, settings: dict, ytitle: str | None = None,
|
def __init__(self, df: pd.DataFrame, modes: list, settings: dict, threshold: float | None = None,
|
||||||
|
ytitle: str | None = None,
|
||||||
parent: QWidget | None = None,
|
parent: QWidget | None = None,
|
||||||
months: int = 6):
|
months: int = 6):
|
||||||
super().__init__(df=df, modes=modes, settings=settings)
|
super().__init__(df=df, modes=modes, settings=settings)
|
||||||
|
self.df = df
|
||||||
try:
|
try:
|
||||||
months = int(settings['months'])
|
months = int(settings['months'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
months = 6
|
months = 6
|
||||||
# logger.debug(f"DF: {self.df}")
|
# logger.debug(f"DF: {self.df}")
|
||||||
self.construct_chart(df=df)
|
self.construct_chart()
|
||||||
self.add_hline(y=3.5)
|
if threshold:
|
||||||
|
self.add_hline(y=threshold)
|
||||||
# self.update_xaxes()
|
# self.update_xaxes()
|
||||||
self.update_layout(showlegend=False)
|
self.update_layout(showlegend=False)
|
||||||
|
|
||||||
def construct_chart(self, df: pd.DataFrame):
|
def construct_chart(self, df: pd.DataFrame | None = None):
|
||||||
|
if df:
|
||||||
|
self.df = df
|
||||||
# logger.debug(f"PCR df:\n {df}")
|
# logger.debug(f"PCR df:\n {df}")
|
||||||
df = df.sort_values(by=['submitted_date', 'name'])
|
self.df = self.df[self.df.days.notnull()]
|
||||||
|
self.df = self.df.sort_values(['submitted_date', 'name'], ascending=[True, True]).reset_index(drop=True)
|
||||||
|
self.df = self.df.reset_index().rename(columns={"index": "idx"})
|
||||||
|
# logger.debug(f"DF: {self.df}")
|
||||||
try:
|
try:
|
||||||
scatter = px.scatter(data_frame=df, x='name', y="days",
|
scatter = px.scatter(data_frame=self.df, x='idx', y="days",
|
||||||
hover_data=["name", "submitted_date", "completed_date", "days"],
|
hover_data=["name", "submitted_date", "completed_date", "days"],
|
||||||
color="acceptable", color_discrete_map={True: "green", False: "red"}
|
color="acceptable", color_discrete_map={True: "green", False: "red"}
|
||||||
)
|
)
|
||||||
@@ -37,3 +45,12 @@ class TurnaroundChart(CustomFigure):
|
|||||||
scatter = px.scatter()
|
scatter = px.scatter()
|
||||||
self.add_traces(scatter.data)
|
self.add_traces(scatter.data)
|
||||||
self.update_traces(marker={'size': 15})
|
self.update_traces(marker={'size': 15})
|
||||||
|
tickvals = self.df['idx'].tolist()
|
||||||
|
ticklabels = self.df['name'].tolist()
|
||||||
|
self.update_layout(
|
||||||
|
xaxis=dict(
|
||||||
|
tickmode='array',
|
||||||
|
tickvals=tickvals,
|
||||||
|
ticktext=ticklabels,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from PyQt6.QtWebChannel import QWebChannel
|
|||||||
from PyQt6.QtCore import Qt, pyqtSlot
|
from PyQt6.QtCore import Qt, pyqtSlot
|
||||||
from jinja2 import TemplateNotFound
|
from jinja2 import TemplateNotFound
|
||||||
from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType
|
from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType
|
||||||
from tools import is_power_user, jinja_template_loading
|
from tools import is_power_user, jinja_template_loading, timezone
|
||||||
from .functions import select_save_file
|
from .functions import select_save_file
|
||||||
from .misc import save_pdf
|
from .misc import save_pdf
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -176,7 +176,8 @@ class SubmissionDetails(QDialog):
|
|||||||
if isinstance(submission, str):
|
if isinstance(submission, str):
|
||||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
submission = BasicSubmission.query(rsl_plate_num=submission)
|
||||||
submission.signed_by = getuser()
|
submission.signed_by = getuser()
|
||||||
submission.completed_date = datetime.now().date()
|
submission.completed_date = datetime.now()
|
||||||
|
submission.completed_date.replace(tzinfo=timezone)
|
||||||
submission.save()
|
submission.save()
|
||||||
self.submission_details(submission=self.rsl_plate_num)
|
self.submission_details(submission=self.rsl_plate_num)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from PyQt6.QtCore import QSignalBlocker
|
from PyQt6.QtCore import QSignalBlocker
|
||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QLabel
|
from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QLabel, QComboBox
|
||||||
from .info_tab import InfoPane
|
from .info_tab import InfoPane
|
||||||
from backend.excel.reports import TurnaroundMaker
|
from backend.excel.reports import TurnaroundMaker
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from backend.db import BasicSubmission
|
from backend.db import BasicSubmission, SubmissionType
|
||||||
from frontend.visualizations.turnaround_chart import TurnaroundChart
|
from frontend.visualizations.turnaround_chart import TurnaroundChart
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -17,6 +17,11 @@ class TurnaroundTime(InfoPane):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.chart = None
|
self.chart = None
|
||||||
self.report_object = None
|
self.report_object = None
|
||||||
|
self.submission_typer = QComboBox(self)
|
||||||
|
subs = ["Any"] + [item.name for item in SubmissionType.query()]
|
||||||
|
self.submission_typer.addItems(subs)
|
||||||
|
self.layout.addWidget(self.submission_typer, 1, 1, 1, 3)
|
||||||
|
self.submission_typer.currentTextChanged.connect(self.date_changed)
|
||||||
self.date_changed()
|
self.date_changed()
|
||||||
|
|
||||||
def date_changed(self):
|
def date_changed(self):
|
||||||
@@ -31,6 +36,16 @@ class TurnaroundTime(InfoPane):
|
|||||||
return
|
return
|
||||||
super().date_changed()
|
super().date_changed()
|
||||||
chart_settings = dict(start_date=self.start_date, end_date=self.end_date)
|
chart_settings = dict(start_date=self.start_date, end_date=self.end_date)
|
||||||
self.report_obj = TurnaroundMaker(start_date=self.start_date, end_date=self.end_date)
|
if self.submission_typer.currentText() == "Any":
|
||||||
self.chart = TurnaroundChart(df=self.report_obj.df, settings=chart_settings, modes=[])
|
submission_type = None
|
||||||
|
subtype_obj = None
|
||||||
|
else:
|
||||||
|
submission_type = self.submission_typer.currentText()
|
||||||
|
subtype_obj = SubmissionType.query(name = submission_type)
|
||||||
|
self.report_obj = TurnaroundMaker(start_date=self.start_date, end_date=self.end_date, submission_type=submission_type)
|
||||||
|
if subtype_obj:
|
||||||
|
threshold = subtype_obj.defaults['turnaround_time'] + 0.5
|
||||||
|
else:
|
||||||
|
threshold = None
|
||||||
|
self.chart = TurnaroundChart(df=self.report_obj.df, settings=chart_settings, modes=[], threshold=threshold)
|
||||||
self.webview.setHtml(self.chart.to_html())
|
self.webview.setHtml(self.chart.to_html())
|
||||||
|
|||||||
Reference in New Issue
Block a user