Moments before disaster
This commit is contained in:
@@ -245,7 +245,6 @@ class BasicSubmission(BaseClass):
|
|||||||
case _:
|
case _:
|
||||||
return SubmissionType.query(cls.__mapper_args__['polymorphic_identity'])
|
return SubmissionType.query(cls.__mapper_args__['polymorphic_identity'])
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def construct_info_map(cls, submission_type: SubmissionType | None = None,
|
def construct_info_map(cls, submission_type: SubmissionType | None = None,
|
||||||
mode: Literal["read", "write"] = "read") -> dict:
|
mode: Literal["read", "write"] = "read") -> dict:
|
||||||
@@ -424,7 +423,8 @@ class BasicSubmission(BaseClass):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Column count error: {e}")
|
logger.error(f"Column count error: {e}")
|
||||||
# NOTE: Get kit associated with this submission
|
# NOTE: Get kit associated with this submission
|
||||||
assoc = next((item for item in self.extraction_kit.kit_submissiontype_associations if item.submission_type == self.submission_type),
|
assoc = next((item for item in self.extraction_kit.kit_submissiontype_associations if
|
||||||
|
item.submission_type == self.submission_type),
|
||||||
None)
|
None)
|
||||||
# logger.debug(f"Came up with association: {assoc}")
|
# logger.debug(f"Came up with association: {assoc}")
|
||||||
# NOTE: If every individual cost is 0 this is probably an old plate.
|
# NOTE: If every individual cost is 0 this is probably an old plate.
|
||||||
@@ -464,15 +464,23 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
str: html output string.
|
str: html output string.
|
||||||
"""
|
"""
|
||||||
output_samples = []
|
# output_samples = []
|
||||||
# logger.debug("Setting locations.")
|
# logger.debug("Setting locations.")
|
||||||
for column in range(1, plate_columns + 1):
|
# for column in range(1, plate_columns + 1):
|
||||||
for row in range(1, plate_rows + 1):
|
# for row in range(1, plate_rows + 1):
|
||||||
try:
|
# try:
|
||||||
well = next((item for item in sample_list if item['row'] == row and item['column'] == column), dict(name="", row=row, column=column, background_color="#ffffff"))
|
# well = next((item for item in sample_list if item['row'] == row and item['column'] == column), dict(name="", row=row, column=column, background_color="#ffffff"))
|
||||||
except StopIteration:
|
# except StopIteration:
|
||||||
well = dict(name="", row=row, column=column, background_color="#ffffff")
|
# well = dict(name="", row=row, column=column, background_color="#ffffff")
|
||||||
output_samples.append(well)
|
# output_samples.append(well)
|
||||||
|
rows = range(1, plate_rows + 1)
|
||||||
|
columns = range(1, plate_columns + 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
|
||||||
|
output_samples = [next((item for item in sample_list if item['row'] == row and item['column'] == column),
|
||||||
|
dict(name="", row=row, column=column, background_color="#ffffff"))
|
||||||
|
for row in rows
|
||||||
|
for column in columns]
|
||||||
env = jinja_template_loading()
|
env = jinja_template_loading()
|
||||||
template = env.get_template("plate_map.html")
|
template = env.get_template("plate_map.html")
|
||||||
html = template.render(samples=output_samples, PLATE_ROWS=plate_rows, PLATE_COLUMNS=plate_columns)
|
html = template.render(samples=output_samples, PLATE_ROWS=plate_rows, PLATE_COLUMNS=plate_columns)
|
||||||
@@ -510,16 +518,17 @@ class BasicSubmission(BaseClass):
|
|||||||
df = pd.DataFrame.from_records(subs)
|
df = pd.DataFrame.from_records(subs)
|
||||||
# logger.debug(f"Column names: {df.columns}")
|
# logger.debug(f"Column names: {df.columns}")
|
||||||
# NOTE: Exclude sub information
|
# NOTE: Exclude sub information
|
||||||
excluded = ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents',
|
exclude = ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents',
|
||||||
'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls',
|
'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls',
|
||||||
'source_plates', 'pcr_technician', 'ext_technician', 'artic_technician', 'cost_centre',
|
'source_plates', 'pcr_technician', 'ext_technician', 'artic_technician', 'cost_centre',
|
||||||
'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact',
|
'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact',
|
||||||
'tips', 'gel_image_path', 'custom']
|
'tips', 'gel_image_path', 'custom']
|
||||||
for item in excluded:
|
df = df.loc[:, ~df.columns.isin(exclude)]
|
||||||
try:
|
# for item in excluded:
|
||||||
df = df.drop(item, axis=1)
|
# try:
|
||||||
except:
|
# df = df.drop(item, axis=1)
|
||||||
logger.warning(f"Couldn't drop '{item}' column from submissionsheet df.")
|
# except:
|
||||||
|
# logger.warning(f"Couldn't drop '{item}' column from submissionsheet df.")
|
||||||
if chronologic:
|
if chronologic:
|
||||||
try:
|
try:
|
||||||
df.sort_values(by="id", axis=0, inplace=True, ascending=False)
|
df.sort_values(by="id", axis=0, inplace=True, ascending=False)
|
||||||
@@ -599,7 +608,7 @@ class BasicSubmission(BaseClass):
|
|||||||
field_value = value.strip()
|
field_value = value.strip()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
field_value = value
|
field_value = value
|
||||||
# insert into field
|
# NOTE: insert into field
|
||||||
try:
|
try:
|
||||||
self.__setattr__(key, field_value)
|
self.__setattr__(key, field_value)
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
@@ -616,16 +625,17 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
Result: _description_
|
Result: _description_
|
||||||
"""
|
"""
|
||||||
# assoc = [item for item in self.submission_sample_associations if item.sample == sample][0]
|
|
||||||
try:
|
try:
|
||||||
assoc = next(item for item in self.submission_sample_associations if item.sample == sample)
|
assoc = next(item for item in self.submission_sample_associations if item.sample == sample)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
report = Report()
|
report = Report()
|
||||||
report.add_result(Result(msg=f"Couldn't find submission sample association for {sample.submitter_id}", status="Warning"))
|
report.add_result(
|
||||||
|
Result(msg=f"Couldn't find submission sample association for {sample.submitter_id}", status="Warning"))
|
||||||
return report
|
return report
|
||||||
for k, v in input_dict.items():
|
for k, v in input_dict.items():
|
||||||
try:
|
try:
|
||||||
setattr(assoc, k, v)
|
setattr(assoc, k, v)
|
||||||
|
# NOTE: for some reason I don't think assoc.__setattr__(k, v) doesn't work here.
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"Can't set {k} to {v}")
|
logger.error(f"Can't set {k} to {v}")
|
||||||
result = assoc.save()
|
result = assoc.save()
|
||||||
@@ -702,15 +712,7 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
re.Pattern: Regular expression pattern to discriminate between submission types.
|
re.Pattern: Regular expression pattern to discriminate between submission types.
|
||||||
"""
|
"""
|
||||||
# rstring = rf'{"|".join([item.get_regex() for item in cls.__subclasses__()])}'
|
res = [st.defaults['regex'] for st in SubmissionType.query() if st.defaults]
|
||||||
# rstring = rf'{"|".join([item.defaults["regex"] for item in SubmissionType.query()])}'
|
|
||||||
res = []
|
|
||||||
for item in SubmissionType.query():
|
|
||||||
try:
|
|
||||||
res.append(item.defaults['regex'])
|
|
||||||
except TypeError:
|
|
||||||
logger.error(f"Problem: {item.__dict__}")
|
|
||||||
continue
|
|
||||||
rstring = rf'{"|".join(res)}'
|
rstring = rf'{"|".join(res)}'
|
||||||
regex = re.compile(rstring, flags=re.IGNORECASE | re.VERBOSE)
|
regex = re.compile(rstring, flags=re.IGNORECASE | re.VERBOSE)
|
||||||
return regex
|
return regex
|
||||||
@@ -737,21 +739,21 @@ class BasicSubmission(BaseClass):
|
|||||||
match polymorphic_identity:
|
match polymorphic_identity:
|
||||||
case str():
|
case str():
|
||||||
try:
|
try:
|
||||||
logger.info(f"Recruiting: {cls}")
|
|
||||||
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
|
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission")
|
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission")
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
if attrs is None or len(attrs) == 0:
|
# if attrs is None or len(attrs) == 0:
|
||||||
return model
|
# logger.info(f"Recruiting: {cls}")
|
||||||
if any([not hasattr(cls, attr) for attr in attrs.keys()]):
|
# return model
|
||||||
|
if attrs and any([not hasattr(cls, attr) for attr in attrs.keys()]):
|
||||||
# looks for first model that has all included kwargs
|
# looks for first model that has all included kwargs
|
||||||
try:
|
try:
|
||||||
model = [subclass for subclass in cls.__subclasses__() if
|
model = next(subclass for subclass in cls.__subclasses__() if
|
||||||
all([hasattr(subclass, attr) for attr in attrs.keys()])][0]
|
all([hasattr(subclass, attr) for attr in attrs.keys()]))
|
||||||
except IndexError as e:
|
except StopIteration as e:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}")
|
f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}")
|
||||||
logger.info(f"Recruiting model: {model}")
|
logger.info(f"Recruiting model: {model}")
|
||||||
@@ -785,15 +787,19 @@ class BasicSubmission(BaseClass):
|
|||||||
input_dict['custom'][k] = ws.cell(row=v['read']['row'], column=v['read']['column']).value
|
input_dict['custom'][k] = ws.cell(row=v['read']['row'], column=v['read']['column']).value
|
||||||
case "range":
|
case "range":
|
||||||
ws = xl[v['sheet']]
|
ws = xl[v['sheet']]
|
||||||
input_dict['custom'][k] = []
|
# input_dict['custom'][k] = []
|
||||||
if v['start_row'] != v['end_row']:
|
if v['start_row'] != v['end_row']:
|
||||||
v['end_row'] = v['end_row'] + 1
|
v['end_row'] = v['end_row'] + 1
|
||||||
|
rows = range(v['start_row'], v['end_row'])
|
||||||
if v['start_column'] != v['end_column']:
|
if v['start_column'] != v['end_column']:
|
||||||
v['end_column'] = v['end_column'] + 1
|
v['end_column'] = v['end_column'] + 1
|
||||||
for ii in range(v['start_row'], v['end_row']):
|
columns = range(v['start_column'], v['end_column'])
|
||||||
for jj in range(v['start_column'], v['end_column'] + 1):
|
input_dict['custom'][k] = [dict(value=ws.cell(row=row, column=column).value, row=row, column=column)
|
||||||
input_dict['custom'][k].append(
|
for row in rows for column in columns]
|
||||||
dict(value=ws.cell(row=ii, column=jj).value, row=ii, column=jj))
|
# for ii in range(v['start_row'], v['end_row']):
|
||||||
|
# for jj in range(v['start_column'], v['end_column'] + 1):
|
||||||
|
# input_dict['custom'][k].append(
|
||||||
|
# dict(value=ws.cell(row=ii, column=jj).value, row=ii, column=jj))
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -907,38 +913,43 @@ class BasicSubmission(BaseClass):
|
|||||||
else:
|
else:
|
||||||
outstr = instr
|
outstr = instr
|
||||||
if re.search(rf"{data['abbreviation']}", outstr, flags=re.IGNORECASE) is None:
|
if re.search(rf"{data['abbreviation']}", outstr, flags=re.IGNORECASE) is None:
|
||||||
|
# NOTE: replace RSL- with RSL-abbreviation-
|
||||||
outstr = re.sub(rf"RSL-?", rf"RSL-{data['abbreviation']}-", outstr, flags=re.IGNORECASE)
|
outstr = re.sub(rf"RSL-?", rf"RSL-{data['abbreviation']}-", outstr, flags=re.IGNORECASE)
|
||||||
try:
|
try:
|
||||||
|
# NOTE: remove dashes from date
|
||||||
outstr = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\1\2\3", outstr)
|
outstr = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\1\2\3", outstr)
|
||||||
|
# NOTE: insert dash between abbreviation and date
|
||||||
outstr = re.sub(rf"{data['abbreviation']}(\d{6})", rf"{data['abbreviation']}-\1", outstr,
|
outstr = re.sub(rf"{data['abbreviation']}(\d{6})", rf"{data['abbreviation']}-\1", outstr,
|
||||||
flags=re.IGNORECASE).upper()
|
flags=re.IGNORECASE).upper()
|
||||||
except (AttributeError, TypeError) as e:
|
except (AttributeError, TypeError) as e:
|
||||||
logger.error(f"Error making outstr: {e}, sending to RSLNamer to make new plate name.")
|
logger.error(f"Error making outstr: {e}, sending to RSLNamer to make new plate name.")
|
||||||
outstr = RSLNamer.construct_new_plate_name(data=data)
|
outstr = RSLNamer.construct_new_plate_name(data=data)
|
||||||
try:
|
try:
|
||||||
|
# NOTE: Grab plate number
|
||||||
plate_number = re.search(r"(?:(-|_)\d)(?!\d)", outstr).group().strip("_").strip("-")
|
plate_number = re.search(r"(?:(-|_)\d)(?!\d)", outstr).group().strip("_").strip("-")
|
||||||
# logger.debug(f"Plate number is: {plate_number}")
|
# logger.debug(f"Plate number is: {plate_number}")
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
plate_number = "1"
|
plate_number = "1"
|
||||||
|
# NOTE: insert dash between date and plate number
|
||||||
outstr = re.sub(r"(\d{8})(-|_)?\d?(R\d?)?", rf"\1-{plate_number}\3", outstr)
|
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}")
|
# logger.debug(f"After addition of plate number the plate name is: {outstr}")
|
||||||
try:
|
try:
|
||||||
|
# NOTE: grab repeat number
|
||||||
repeat = re.search(r"-\dR(?P<repeat>\d)?", outstr).groupdict()['repeat']
|
repeat = re.search(r"-\dR(?P<repeat>\d)?", outstr).groupdict()['repeat']
|
||||||
if repeat is None:
|
if repeat is None:
|
||||||
repeat = "1"
|
repeat = "1"
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
repeat = ""
|
repeat = ""
|
||||||
|
# NOTE: Insert repeat number?
|
||||||
outstr = re.sub(r"(-\dR)\d?", rf"\1 {repeat}", outstr).replace(" ", "")
|
outstr = re.sub(r"(-\dR)\d?", rf"\1 {repeat}", outstr).replace(" ", "")
|
||||||
# abb = cls.get_default_info('abbreviation')
|
# NOTE: This should already have been done. Do I dare remove it?
|
||||||
# outstr = re.sub(rf"RSL{abb}", rf"RSL-{abb}", outstr)
|
|
||||||
# return re.sub(rf"{abb}(\d)", rf"{abb}-\1", outstr)
|
|
||||||
outstr = re.sub(rf"RSL{data['abbreviation']}", rf"RSL-{data['abbreviation']}", outstr)
|
outstr = re.sub(rf"RSL{data['abbreviation']}", rf"RSL-{data['abbreviation']}", outstr)
|
||||||
return re.sub(rf"{data['abbreviation']}(\d)", rf"{data['abbreviation']}-\1", outstr)
|
return re.sub(rf"{data['abbreviation']}(\d)", rf"{data['abbreviation']}-\1", outstr)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_pcr(cls, xl: Workbook, rsl_plate_num: str) -> list:
|
def parse_pcr(cls, xl: Workbook, rsl_plate_num: str) -> list:
|
||||||
"""
|
"""
|
||||||
Perform custom parsing of pcr info.
|
Perform parsing of pcr info. Since most of our PC outputs are the same format, this should work for most.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
xl (pd.DataFrame): pcr info form
|
xl (pd.DataFrame): pcr info form
|
||||||
@@ -959,7 +970,6 @@ class BasicSubmission(BaseClass):
|
|||||||
for k, v in fields.items():
|
for k, v in fields.items():
|
||||||
sheet = xl[v['sheet']]
|
sheet = xl[v['sheet']]
|
||||||
sample[k] = sheet.cell(row=idx, column=v['column']).value
|
sample[k] = sheet.cell(row=idx, column=v['column']).value
|
||||||
# yield sample
|
|
||||||
samples.append(sample)
|
samples.append(sample)
|
||||||
return samples
|
return samples
|
||||||
|
|
||||||
@@ -974,19 +984,20 @@ class BasicSubmission(BaseClass):
|
|||||||
"""
|
"""
|
||||||
return "{{ rsl_plate_num }}"
|
return "{{ rsl_plate_num }}"
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int:
|
# def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int:
|
||||||
"""
|
# """
|
||||||
Updates row information
|
# Updates row information
|
||||||
|
#
|
||||||
Args:
|
# Args:
|
||||||
sample (_type_): _description_
|
# sample (_type_): _description_
|
||||||
worksheet (Workbook): _description_
|
# worksheet (Workbook): _description_
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
int: New row number
|
# int: New row number
|
||||||
"""
|
# """
|
||||||
return None
|
# logger.debug(f"Sample from args: {sample}")
|
||||||
|
# return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def adjust_autofill_samples(cls, samples: List[Any]) -> List[Any]:
|
def adjust_autofill_samples(cls, samples: List[Any]) -> List[Any]:
|
||||||
@@ -1232,7 +1243,10 @@ class BasicSubmission(BaseClass):
|
|||||||
except (SQLIntegrityError, SQLOperationalError, AlcIntegrityError, AlcOperationalError) as e:
|
except (SQLIntegrityError, SQLOperationalError, AlcIntegrityError, AlcOperationalError) as e:
|
||||||
self.__database_session__.rollback()
|
self.__database_session__.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
try:
|
||||||
obj.setData()
|
obj.setData()
|
||||||
|
except AttributeError:
|
||||||
|
logger.debug("App will not refresh data at this time.")
|
||||||
|
|
||||||
def show_details(self, obj):
|
def show_details(self, obj):
|
||||||
"""
|
"""
|
||||||
@@ -1408,29 +1422,29 @@ class BacterialCulture(BasicSubmission):
|
|||||||
pos_control_reg['missing'] = False
|
pos_control_reg['missing'] = False
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int:
|
# def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int:
|
||||||
"""
|
# """
|
||||||
Extends parent
|
# Extends parent
|
||||||
"""
|
# """
|
||||||
# logger.debug(f"Checking {sample.well}")
|
# # logger.debug(f"Checking {sample.well}")
|
||||||
# logger.debug(f"here's the worksheet: {worksheet}")
|
# # logger.debug(f"here's the worksheet: {worksheet}")
|
||||||
row = super().custom_sample_autofill_row(sample, worksheet)
|
# row = super().custom_sample_autofill_row(sample, worksheet)
|
||||||
df = pd.DataFrame(list(worksheet.values))
|
# df = pd.DataFrame(list(worksheet.values))
|
||||||
# logger.debug(f"Here's the dataframe: {df}")
|
# # logger.debug(f"Here's the dataframe: {df}")
|
||||||
idx = df[df[0] == sample.well]
|
# idx = df[df[0] == sample.well]
|
||||||
if idx.empty:
|
# if idx.empty:
|
||||||
new = f"{sample.well[0]}{sample.well[1:].zfill(2)}"
|
# new = f"{sample.well[0]}{sample.well[1:].zfill(2)}"
|
||||||
# logger.debug(f"Checking: {new}")
|
# # logger.debug(f"Checking: {new}")
|
||||||
idx = df[df[0] == new]
|
# idx = df[df[0] == new]
|
||||||
# logger.debug(f"Here is the row: {idx}")
|
# # logger.debug(f"Here is the row: {idx}")
|
||||||
row = idx.index.to_list()[0]
|
# row = idx.index.to_list()[0]
|
||||||
return row + 1
|
# return row + 1
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None, custom_fields: dict = {}) -> dict:
|
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None, custom_fields: dict = {}) -> dict:
|
||||||
input_dict = super().custom_info_parser(input_dict=input_dict, xl=xl, custom_fields=custom_fields)
|
input_dict = super().custom_info_parser(input_dict=input_dict, xl=xl, custom_fields=custom_fields)
|
||||||
logger.debug(f"\n\nInfo dictionary:\n\n{pformat(input_dict)}\n\n")
|
# logger.debug(f"\n\nInfo dictionary:\n\n{pformat(input_dict)}\n\n")
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
|
|
||||||
@@ -1561,20 +1575,20 @@ class Wastewater(BasicSubmission):
|
|||||||
samples = [item for item in samples if not item.submitter_id.startswith("EN")]
|
samples = [item for item in samples if not item.submitter_id.startswith("EN")]
|
||||||
return samples
|
return samples
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int:
|
# def custom_sample_autofill_row(cls, sample, worksheet: Worksheet) -> int:
|
||||||
"""
|
# """
|
||||||
Extends parent
|
# Extends parent
|
||||||
"""
|
# """
|
||||||
# logger.debug(f"Checking {sample.well}")
|
# # logger.debug(f"Checking {sample.well}")
|
||||||
# logger.debug(f"here's the worksheet: {worksheet}")
|
# # logger.debug(f"here's the worksheet: {worksheet}")
|
||||||
row = super().custom_sample_autofill_row(sample, worksheet)
|
# row = super().custom_sample_autofill_row(sample, worksheet)
|
||||||
df = pd.DataFrame(list(worksheet.values))
|
# df = pd.DataFrame(list(worksheet.values))
|
||||||
# logger.debug(f"Here's the dataframe: {df}")
|
# # logger.debug(f"Here's the dataframe: {df}")
|
||||||
idx = df[df[1] == sample.sample_location]
|
# idx = df[df[1] == sample.sample_location]
|
||||||
# logger.debug(f"Here is the row: {idx}")
|
# # logger.debug(f"Here is the row: {idx}")
|
||||||
row = idx.index.to_list()[0]
|
# row = idx.index.to_list()[0]
|
||||||
return row + 1
|
# return row + 1
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_details_template(cls, base_dict: dict) -> Tuple[dict, Template]:
|
def get_details_template(cls, base_dict: dict) -> Tuple[dict, Template]:
|
||||||
@@ -1816,7 +1830,7 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
datum['values'].append(d)
|
datum['values'].append(d)
|
||||||
data.append(datum)
|
data.append(datum)
|
||||||
input_dict['gel_info'] = data
|
input_dict['gel_info'] = data
|
||||||
logger.debug(f"Wastewater Artic custom info:\n\n{pformat(input_dict)}")
|
# logger.debug(f"Wastewater Artic custom info:\n\n{pformat(input_dict)}")
|
||||||
egel_image_section = custom_fields['image_range']
|
egel_image_section = custom_fields['image_range']
|
||||||
img: Image = scrape_image(wb=xl, info_dict=egel_image_section)
|
img: Image = scrape_image(wb=xl, info_dict=egel_image_section)
|
||||||
if img is not None:
|
if img is not None:
|
||||||
@@ -2259,18 +2273,19 @@ class BasicSample(BaseClass):
|
|||||||
|
|
||||||
def to_sub_dict(self, full_data: bool = False) -> dict:
|
def to_sub_dict(self, full_data: bool = False) -> dict:
|
||||||
"""
|
"""
|
||||||
gui friendly dictionary, extends parent method.
|
gui friendly dictionary
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
full_data (bool): Whether to use full object or truncated. Defaults to False
|
full_data (bool): Whether to use full object or truncated. Defaults to False
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
|
dict: submitter id and sample type and linked submissions if full data
|
||||||
"""
|
"""
|
||||||
# logger.debug(f"Converting {self} to dict.")
|
# logger.debug(f"Converting {self} to dict.")
|
||||||
sample = {}
|
sample = dict(
|
||||||
sample['submitter_id'] = self.submitter_id
|
submitter_id=self.submitter_id,
|
||||||
sample['sample_type'] = self.sample_type
|
sample_type=self.sample_type
|
||||||
|
)
|
||||||
if full_data:
|
if full_data:
|
||||||
sample['submissions'] = sorted([item.to_sub_dict() for item in self.sample_submission_associations],
|
sample['submissions'] = sorted([item.to_sub_dict() for item in self.sample_submission_associations],
|
||||||
key=itemgetter('submitted_date'))
|
key=itemgetter('submitted_date'))
|
||||||
@@ -2307,22 +2322,22 @@ class BasicSample(BaseClass):
|
|||||||
polymorphic_identity = polymorphic_identity['value']
|
polymorphic_identity = polymorphic_identity['value']
|
||||||
if polymorphic_identity is not None:
|
if polymorphic_identity is not None:
|
||||||
try:
|
try:
|
||||||
# return [item for item in cls.__subclasses__() if
|
|
||||||
# item.__mapper_args__['polymorphic_identity'] == polymorphic_identity][0]
|
|
||||||
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
|
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}")
|
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}")
|
||||||
model = cls
|
model = cls
|
||||||
|
logger.info(f"Recruiting model: {model}")
|
||||||
|
return model
|
||||||
else:
|
else:
|
||||||
model = cls
|
model = cls
|
||||||
if attrs is None or len(attrs) == 0:
|
if attrs is None or len(attrs) == 0:
|
||||||
return model
|
return model
|
||||||
if any([not hasattr(cls, attr) for attr in attrs.keys()]):
|
if any([not hasattr(cls, attr) for attr in attrs.keys()]):
|
||||||
# looks for first model that has all included kwargs
|
# NOTE: looks for first model that has all included kwargs
|
||||||
try:
|
try:
|
||||||
model = [subclass for subclass in cls.__subclasses__() if
|
model = next(subclass for subclass in cls.__subclasses__() if
|
||||||
all([hasattr(subclass, attr) for attr in attrs.keys()])][0]
|
all([hasattr(subclass, attr) for attr in attrs.keys()]))
|
||||||
except IndexError as e:
|
except StopIteration as e:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}")
|
f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}")
|
||||||
logger.info(f"Recruiting model: {model}")
|
logger.info(f"Recruiting model: {model}")
|
||||||
@@ -2343,7 +2358,7 @@ class BasicSample(BaseClass):
|
|||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_details_template(cls, base_dict: dict) -> Tuple[dict, Template]:
|
def get_details_template(cls) -> Template:
|
||||||
"""
|
"""
|
||||||
Get the details jinja template for the correct class
|
Get the details jinja template for the correct class
|
||||||
|
|
||||||
@@ -2353,7 +2368,6 @@ class BasicSample(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
Tuple(dict, Template): (Updated dictionary, Template to be rendered)
|
Tuple(dict, Template): (Updated dictionary, Template to be rendered)
|
||||||
"""
|
"""
|
||||||
base_dict['excluded'] = ['submissions', 'excluded', 'colour', 'tooltip']
|
|
||||||
env = jinja_template_loading()
|
env = jinja_template_loading()
|
||||||
temp_name = f"{cls.__name__.lower()}_details.html"
|
temp_name = f"{cls.__name__.lower()}_details.html"
|
||||||
# logger.debug(f"Returning template: {temp_name}")
|
# logger.debug(f"Returning template: {temp_name}")
|
||||||
@@ -2362,7 +2376,7 @@ class BasicSample(BaseClass):
|
|||||||
except TemplateNotFound as e:
|
except TemplateNotFound as e:
|
||||||
logger.error(f"Couldn't find template {e}")
|
logger.error(f"Couldn't find template {e}")
|
||||||
template = env.get_template("basicsample_details.html")
|
template = env.get_template("basicsample_details.html")
|
||||||
return base_dict, template
|
return template
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
@@ -2459,6 +2473,7 @@ class BasicSample(BaseClass):
|
|||||||
search = f"%{v}%"
|
search = f"%{v}%"
|
||||||
try:
|
try:
|
||||||
attr = getattr(model, k)
|
attr = getattr(model, k)
|
||||||
|
# NOTE: the secret sauce is in attr.like
|
||||||
query = query.filter(attr.like(search))
|
query = query.filter(attr.like(search))
|
||||||
except (ArgumentError, AttributeError) as e:
|
except (ArgumentError, AttributeError) as e:
|
||||||
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
||||||
@@ -2488,8 +2503,6 @@ class BasicSample(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
pd.DataFrame: Dataframe all samples
|
pd.DataFrame: Dataframe all samples
|
||||||
"""
|
"""
|
||||||
if not isinstance(sample_list, list):
|
|
||||||
sample_list = [sample_list]
|
|
||||||
try:
|
try:
|
||||||
samples = [sample.to_sub_dict() for sample in sample_list]
|
samples = [sample.to_sub_dict() for sample in sample_list]
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
@@ -2497,12 +2510,9 @@ class BasicSample(BaseClass):
|
|||||||
return None
|
return None
|
||||||
df = pd.DataFrame.from_records(samples)
|
df = pd.DataFrame.from_records(samples)
|
||||||
# NOTE: Exclude sub information
|
# NOTE: Exclude sub information
|
||||||
for item in ['concentration', 'organism', 'colour', 'tooltip', 'comments', 'samples', 'reagents',
|
exclude = ['concentration', 'organism', 'colour', 'tooltip', 'comments', 'samples', 'reagents',
|
||||||
'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls']:
|
'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls']
|
||||||
try:
|
df = df.loc[:, ~df.columns.isin(exclude)]
|
||||||
df = df.drop(item, axis=1)
|
|
||||||
except KeyError as e:
|
|
||||||
logger.warning(f"Couldn't drop '{item}' column from submissionsheet df due to {e}.")
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def show_details(self, obj):
|
def show_details(self, obj):
|
||||||
@@ -2570,7 +2580,7 @@ class WastewaterSample(BasicSample):
|
|||||||
gui friendly dictionary, extends parent method.
|
gui friendly dictionary, extends parent method.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
|
dict: sample id, type, received date, collection date
|
||||||
"""
|
"""
|
||||||
sample = super().to_sub_dict(full_data=full_data)
|
sample = super().to_sub_dict(full_data=full_data)
|
||||||
sample['ww_processing_num'] = self.ww_processing_num
|
sample['ww_processing_num'] = self.ww_processing_num
|
||||||
@@ -2713,7 +2723,7 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: Updated dictionary with row, column and well updated
|
dict: Updated dictionary with row, column and well updated
|
||||||
"""
|
"""
|
||||||
# Get sample info
|
# NOTE: Get associated sample info
|
||||||
# logger.debug(f"Running {self.__repr__()}")
|
# logger.debug(f"Running {self.__repr__()}")
|
||||||
sample = self.sample.to_sub_dict()
|
sample = self.sample.to_sub_dict()
|
||||||
# logger.debug("Sample conversion complete.")
|
# logger.debug("Sample conversion complete.")
|
||||||
@@ -2791,8 +2801,6 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
model = cls
|
model = cls
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
# output = [item for item in cls.__subclasses__() if
|
|
||||||
# item.__mapper_args__['polymorphic_identity'] == polymorphic_identity][0]
|
|
||||||
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
|
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}")
|
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}")
|
||||||
@@ -2974,11 +2982,10 @@ class WastewaterAssociation(SubmissionSampleAssociation):
|
|||||||
if scaler == 0.0:
|
if scaler == 0.0:
|
||||||
scaler = 45
|
scaler = 45
|
||||||
bg = (45 - scaler) * 17
|
bg = (45 - scaler) * 17
|
||||||
red = min([128 + bg, 255])
|
red = min([64 + bg, 255])
|
||||||
grn = max([255 - bg, 0])
|
grn = max([255 - bg, 0])
|
||||||
blu = 128
|
blu = 128
|
||||||
rgb = f"rgb({red}, {grn}, {blu})"
|
sample['background_color'] = f"rgb({red}, {grn}, {blu})"
|
||||||
sample['background_color'] = rgb
|
|
||||||
try:
|
try:
|
||||||
sample[
|
sample[
|
||||||
'tooltip'] += f"<br>- ct N1: {'{:.2f}'.format(self.ct_n1)} ({self.n1_status})<br>- ct N2: {'{:.2f}'.format(self.ct_n2)} ({self.n2_status})"
|
'tooltip'] += f"<br>- ct N1: {'{:.2f}'.format(self.ct_n1)} ({self.n1_status})<br>- ct N2: {'{:.2f}'.format(self.ct_n2)} ({self.n2_status})"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
'''
|
'''
|
||||||
contains parser objects for pulling values from client generated submission sheets.
|
contains parser objects for pulling values from client generated submission sheets.
|
||||||
'''
|
'''
|
||||||
import sys
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from getpass import getuser
|
from getpass import getuser
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
@@ -12,9 +11,7 @@ from backend.db.models import *
|
|||||||
from backend.validators import PydSubmission, PydReagent, RSLNamer, PydSample, PydEquipment, PydTips
|
from backend.validators import PydSubmission, PydReagent, RSLNamer, PydSample, PydEquipment, PydTips
|
||||||
import logging, re
|
import logging, re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import date
|
from tools import check_not_nan, convert_nans_to_nones, is_missing, check_key_or_attr
|
||||||
from dateutil.parser import parse, ParserError
|
|
||||||
from tools import check_not_nan, convert_nans_to_nones, is_missing, remove_key_from_list_of_dicts, check_key_or_attr
|
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -70,34 +67,24 @@ class SheetParser(object):
|
|||||||
check = info['submission_type']['value'] not in [None, "None", "", " "]
|
check = info['submission_type']['value'] not in [None, "None", "", " "]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
logger.debug(f"Checking old submission type: {self.submission_type.name} against new: {info['submission_type']['value']}")
|
logger.info(
|
||||||
|
f"Checking for updated submission type: {self.submission_type.name} against new: {info['submission_type']['value']}")
|
||||||
if self.submission_type.name != info['submission_type']['value']:
|
if self.submission_type.name != info['submission_type']['value']:
|
||||||
# logger.debug(f"info submission type: {info}")
|
# logger.debug(f"info submission type: {info}")
|
||||||
if check:
|
if check:
|
||||||
self.submission_type = SubmissionType.query(name=info['submission_type']['value'])
|
self.submission_type = SubmissionType.query(name=info['submission_type']['value'])
|
||||||
logger.debug(f"Updated self.submission_type to {self.submission_type}. Rerunning parse.")
|
logger.info(f"Updated self.submission_type to {self.submission_type}. Rerunning parse.")
|
||||||
self.parse_info()
|
self.parse_info()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.submission_type = RSLNamer.retrieve_submission_type(filename=self.filepath)
|
self.submission_type = RSLNamer.retrieve_submission_type(filename=self.filepath)
|
||||||
self.parse_info()
|
self.parse_info()
|
||||||
|
|
||||||
|
|
||||||
for k, v in info.items():
|
for k, v in info.items():
|
||||||
match k:
|
match k:
|
||||||
# NOTE: exclude samples.
|
# NOTE: exclude samples.
|
||||||
case "sample":
|
case "sample":
|
||||||
continue
|
continue
|
||||||
# case "submission_type":
|
|
||||||
# self.sub[k] = v
|
|
||||||
# # NOTE: Rescue submission type using scraped values to be used in Sample, Reagents, etc.
|
|
||||||
# if v not in [None, "None", "", " "]:
|
|
||||||
# self.submission_type = SubmissionType.query(name=v)
|
|
||||||
# logger.debug(f"Updated self.submission_type to {self.submission_type}")
|
|
||||||
case _:
|
case _:
|
||||||
self.sub[k] = v
|
self.sub[k] = v
|
||||||
# print(f"\n\n {self.sub} \n\n")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_reagents(self, extraction_kit: str | None = None):
|
def parse_reagents(self, extraction_kit: str | None = None):
|
||||||
"""
|
"""
|
||||||
@@ -111,7 +98,7 @@ class SheetParser(object):
|
|||||||
# logger.debug(f"Parsing reagents for {extraction_kit}")
|
# logger.debug(f"Parsing reagents for {extraction_kit}")
|
||||||
parser = ReagentParser(xl=self.xl, submission_type=self.submission_type,
|
parser = ReagentParser(xl=self.xl, submission_type=self.submission_type,
|
||||||
extraction_kit=extraction_kit)
|
extraction_kit=extraction_kit)
|
||||||
self.sub['reagents'] = [item for item in parser.parse_reagents()]
|
self.sub['reagents'] = [reagent for reagent in parser.parse_reagents()]
|
||||||
|
|
||||||
def parse_samples(self):
|
def parse_samples(self):
|
||||||
"""
|
"""
|
||||||
@@ -125,14 +112,14 @@ class SheetParser(object):
|
|||||||
Calls equipment parser to pull info from the excel sheet
|
Calls equipment parser to pull info from the excel sheet
|
||||||
"""
|
"""
|
||||||
parser = EquipmentParser(xl=self.xl, submission_type=self.submission_type)
|
parser = EquipmentParser(xl=self.xl, submission_type=self.submission_type)
|
||||||
self.sub['equipment'] = parser.parse_equipment()
|
self.sub['equipment'] = [equipment for equipment in parser.parse_equipment()]
|
||||||
|
|
||||||
def parse_tips(self):
|
def parse_tips(self):
|
||||||
"""
|
"""
|
||||||
Calls tips parser to pull info from the excel sheet
|
Calls tips parser to pull info from the excel sheet
|
||||||
"""
|
"""
|
||||||
parser = TipParser(xl=self.xl, submission_type=self.submission_type)
|
parser = TipParser(xl=self.xl, submission_type=self.submission_type)
|
||||||
self.sub['tips'] = parser.parse_tips()
|
self.sub['tips'] = [tip for tip in parser.parse_tips()]
|
||||||
|
|
||||||
def import_kit_validation_check(self):
|
def import_kit_validation_check(self):
|
||||||
"""
|
"""
|
||||||
@@ -297,16 +284,18 @@ class ReagentParser(object):
|
|||||||
Object to pull reagents from excel sheet.
|
Object to pull reagents from excel sheet.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, xl: Workbook, submission_type: str, extraction_kit: str,
|
def __init__(self, xl: Workbook, submission_type: str | SubmissionType, extraction_kit: str,
|
||||||
sub_object: BasicSubmission | None = None):
|
sub_object: BasicSubmission | None = None):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||||
submission_type (str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
submission_type (str|SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
||||||
extraction_kit (str): Extraction kit used.
|
extraction_kit (str): Extraction kit used.
|
||||||
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
|
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
|
||||||
"""
|
"""
|
||||||
# logger.debug("\n\nHello from ReagentParser!\n\n")
|
# logger.debug("\n\nHello from ReagentParser!\n\n")
|
||||||
|
if isinstance(submission_type, str):
|
||||||
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
self.submission_type_obj = submission_type
|
self.submission_type_obj = submission_type
|
||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
if isinstance(extraction_kit, dict):
|
if isinstance(extraction_kit, dict):
|
||||||
@@ -408,7 +397,8 @@ class SampleParser(object):
|
|||||||
self.submission_type = submission_type.name
|
self.submission_type = submission_type.name
|
||||||
self.submission_type_obj = submission_type
|
self.submission_type_obj = submission_type
|
||||||
if sub_object is None:
|
if sub_object is None:
|
||||||
logger.warning(f"Sample parser attempting to fetch submission class with polymorphic identity: {self.submission_type}")
|
logger.warning(
|
||||||
|
f"Sample parser attempting to fetch submission class with polymorphic identity: {self.submission_type}")
|
||||||
sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
|
||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
self.sample_info_map = self.fetch_sample_info_map(submission_type=submission_type, sample_map=sample_map)
|
self.sample_info_map = self.fetch_sample_info_map(submission_type=submission_type, sample_map=sample_map)
|
||||||
@@ -521,18 +511,16 @@ class SampleParser(object):
|
|||||||
new_samples.append(PydSample(**translated_dict))
|
new_samples.append(PydSample(**translated_dict))
|
||||||
return result, new_samples
|
return result, new_samples
|
||||||
|
|
||||||
def reconcile_samples(self) -> List[dict]:
|
def reconcile_samples(self) -> Generator[dict, None, None]:
|
||||||
"""
|
"""
|
||||||
Merges sample info from lookup table and plate map.
|
Merges sample info from lookup table and plate map.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[dict]: Reconciled samples
|
List[dict]: Reconciled samples
|
||||||
"""
|
"""
|
||||||
# TODO: Move to pydantic validator?
|
|
||||||
if self.plate_map_samples is None or self.lookup_samples is None:
|
if self.plate_map_samples is None or self.lookup_samples is None:
|
||||||
self.samples = self.lookup_samples or self.plate_map_samples
|
self.samples = self.lookup_samples or self.plate_map_samples
|
||||||
return
|
return
|
||||||
samples = []
|
|
||||||
merge_on_id = self.sample_info_map['lookup_table']['merge_on_id']
|
merge_on_id = self.sample_info_map['lookup_table']['merge_on_id']
|
||||||
plate_map_samples = sorted(copy(self.plate_map_samples), key=lambda d: d['id'])
|
plate_map_samples = sorted(copy(self.plate_map_samples), key=lambda d: d['id'])
|
||||||
lookup_samples = sorted(copy(self.lookup_samples), key=lambda d: d[merge_on_id])
|
lookup_samples = sorted(copy(self.lookup_samples), key=lambda d: d[merge_on_id])
|
||||||
@@ -561,9 +549,11 @@ class SampleParser(object):
|
|||||||
if not check_key_or_attr(key='submitter_id', interest=new, check_none=True):
|
if not check_key_or_attr(key='submitter_id', interest=new, check_none=True):
|
||||||
new['submitter_id'] = psample['id']
|
new['submitter_id'] = psample['id']
|
||||||
new = self.sub_object.parse_samples(new)
|
new = self.sub_object.parse_samples(new)
|
||||||
samples.append(new)
|
del new['id']
|
||||||
samples = remove_key_from_list_of_dicts(samples, "id")
|
yield new
|
||||||
return sorted(samples, key=lambda k: (k['row'], k['column']))
|
# samples.append(new)
|
||||||
|
# samples = remove_key_from_list_of_dicts(samples, "id")
|
||||||
|
# return sorted(samples, key=lambda k: (k['row'], k['column']))
|
||||||
|
|
||||||
|
|
||||||
class EquipmentParser(object):
|
class EquipmentParser(object):
|
||||||
@@ -643,13 +633,13 @@ class EquipmentParser(object):
|
|||||||
eq = Equipment.query(name=asset)
|
eq = Equipment.query(name=asset)
|
||||||
process = ws.cell(row=v['process']['row'], column=v['process']['column']).value
|
process = ws.cell(row=v['process']['row'], column=v['process']['column']).value
|
||||||
try:
|
try:
|
||||||
output.append(
|
# output.append(
|
||||||
dict(name=eq.name, processes=[process], role=k, asset_number=eq.asset_number,
|
yield dict(name=eq.name, processes=[process], role=k, asset_number=eq.asset_number,
|
||||||
nickname=eq.nickname))
|
nickname=eq.nickname)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"Unable to add {eq} to list.")
|
logger.error(f"Unable to add {eq} to list.")
|
||||||
# logger.debug(f"Here is the output so far: {pformat(output)}")
|
# logger.debug(f"Here is the output so far: {pformat(output)}")
|
||||||
return output
|
# return output
|
||||||
|
|
||||||
|
|
||||||
class TipParser(object):
|
class TipParser(object):
|
||||||
@@ -710,12 +700,12 @@ class TipParser(object):
|
|||||||
# logger.debug(f"asset: {asset}")
|
# logger.debug(f"asset: {asset}")
|
||||||
eq = Tips.query(lot=lot, name=asset, limit=1)
|
eq = Tips.query(lot=lot, name=asset, limit=1)
|
||||||
try:
|
try:
|
||||||
output.append(
|
# output.append(
|
||||||
dict(name=eq.name, role=k, lot=lot))
|
yield dict(name=eq.name, role=k, lot=lot)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"Unable to add {eq} to PydTips list.")
|
logger.error(f"Unable to add {eq} to PydTips list.")
|
||||||
# logger.debug(f"Here is the output so far: {pformat(output)}")
|
# logger.debug(f"Here is the output so far: {pformat(output)}")
|
||||||
return output
|
# return output
|
||||||
|
|
||||||
|
|
||||||
class PCRParser(object):
|
class PCRParser(object):
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
|
|
||||||
@field_validator("samples")
|
@field_validator("samples")
|
||||||
@classmethod
|
@classmethod
|
||||||
def assign_ids(cls, value, values):
|
def assign_ids(cls, value):
|
||||||
starting_id = SubmissionSampleAssociation.autoincrement_id()
|
starting_id = SubmissionSampleAssociation.autoincrement_id()
|
||||||
output = []
|
output = []
|
||||||
for iii, sample in enumerate(value, start=starting_id):
|
for iii, sample in enumerate(value, start=starting_id):
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class ObjectSelector(QDialog):
|
|||||||
"""
|
"""
|
||||||
dialog to input BaseClass type manually
|
dialog to input BaseClass type manually
|
||||||
"""
|
"""
|
||||||
def __init__(self, title:str, message:str, obj_type:str|models.BaseClass):
|
def __init__(self, title:str, message:str, obj_type:str|type[models.BaseClass]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
self.widget = QComboBox()
|
self.widget = QComboBox()
|
||||||
|
|||||||
@@ -96,14 +96,18 @@ class SubmissionDetails(QDialog):
|
|||||||
if isinstance(sample, str):
|
if isinstance(sample, str):
|
||||||
sample = BasicSample.query(submitter_id=sample)
|
sample = BasicSample.query(submitter_id=sample)
|
||||||
base_dict = sample.to_sub_dict(full_data=True)
|
base_dict = sample.to_sub_dict(full_data=True)
|
||||||
base_dict, template = sample.get_details_template(base_dict=base_dict)
|
exclude = ['submissions', 'excluded', 'colour', 'tooltip']
|
||||||
|
try:
|
||||||
|
base_dict['excluded'] += exclude
|
||||||
|
except KeyError:
|
||||||
|
base_dict['excluded'] = exclude
|
||||||
|
template = sample.get_details_template(base_dict=base_dict)
|
||||||
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
|
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
|
||||||
with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
||||||
css = f.read()
|
css = f.read()
|
||||||
html = template.render(sample=base_dict, css=css)
|
html = template.render(sample=base_dict, css=css)
|
||||||
self.webview.setHtml(html)
|
self.webview.setHtml(html)
|
||||||
self.setWindowTitle(f"Sample Details - {sample.submitter_id}")
|
self.setWindowTitle(f"Sample Details - {sample.submitter_id}")
|
||||||
# self.btn.setEnabled(False)
|
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def reagent_details(self, reagent: str | Reagent, kit: str | KitType):
|
def reagent_details(self, reagent: str | Reagent, kit: str | KitType):
|
||||||
|
|||||||
Reference in New Issue
Block a user