initial commit
This commit is contained in:
0
src/submissions/backend/__init__.py
Normal file
0
src/submissions/backend/__init__.py
Normal file
213
src/submissions/backend/db/__init__.py
Normal file
213
src/submissions/backend/db/__init__.py
Normal file
@@ -0,0 +1,213 @@
|
||||
from . import models
|
||||
import pandas as pd
|
||||
# from sqlite3 import IntegrityError
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
import logging
|
||||
import datetime
|
||||
from sqlalchemy import and_
|
||||
import uuid
|
||||
import base64
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_kits_by_use( ctx:dict, kittype_str:str|None) -> list:
|
||||
pass
|
||||
# ctx dict should contain the database session
|
||||
|
||||
|
||||
def store_submission(ctx:dict, base_submission:models.BasicSubmission) -> None:
|
||||
ctx['database_session'].add(base_submission)
|
||||
try:
|
||||
ctx['database_session'].commit()
|
||||
except IntegrityError:
|
||||
ctx['database_session'].rollback()
|
||||
return {"message":"This plate number already exists, so we can't add it."}
|
||||
return None
|
||||
|
||||
|
||||
def store_reagent(ctx:dict, reagent:models.Reagent) -> None:
|
||||
print(reagent.__dict__)
|
||||
ctx['database_session'].add(reagent)
|
||||
ctx['database_session'].commit()
|
||||
|
||||
|
||||
def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmission:
|
||||
query = info_dict['submission_type'].replace(" ", "")
|
||||
model = getattr(models, query)
|
||||
info_dict['submission_type'] = info_dict['submission_type'].replace(" ", "_").lower()
|
||||
instance = model()
|
||||
for item in info_dict:
|
||||
print(f"Setting {item} to {info_dict[item]}")
|
||||
match item:
|
||||
case "extraction_kit":
|
||||
q_str = info_dict[item]
|
||||
print(f"Looking up kit {q_str}")
|
||||
field_value = lookup_kittype_by_name(ctx=ctx, name=q_str)
|
||||
print(f"Got {field_value} for kit {q_str}")
|
||||
case "submitting_lab":
|
||||
q_str = info_dict[item].replace(" ", "_").lower()
|
||||
print(f"looking up organization: {q_str}")
|
||||
field_value = lookup_org_by_name(ctx=ctx, name=q_str)
|
||||
print(f"Got {field_value} for organization {q_str}")
|
||||
case "submitter_plate_num":
|
||||
# Because of unique constraint, the submitter plate number cannot be None, so...
|
||||
if info_dict[item] == None:
|
||||
info_dict[item] = uuid.uuid4().hex.upper()
|
||||
case _:
|
||||
field_value = info_dict[item]
|
||||
try:
|
||||
setattr(instance, item, field_value)
|
||||
except AttributeError:
|
||||
print(f"Could not set attribute: {item} to {info_dict[item]}")
|
||||
continue
|
||||
return instance
|
||||
# looked_up = []
|
||||
# for reagent in reagents:
|
||||
# my_reagent = lookup_reagent(reagent)
|
||||
# print(my_reagent)
|
||||
# looked_up.append(my_reagent)
|
||||
# print(looked_up)
|
||||
# instance.reagents = looked_up
|
||||
# ctx['database_session'].add(instance)
|
||||
# ctx['database_session'].commit()
|
||||
|
||||
def construct_reagent(ctx:dict, info_dict:dict) -> models.Reagent:
|
||||
reagent = models.Reagent()
|
||||
for item in info_dict:
|
||||
print(f"Reagent info item: {item}")
|
||||
match item:
|
||||
case "lot":
|
||||
reagent.lot = info_dict[item].upper()
|
||||
case "expiry":
|
||||
reagent.expiry = info_dict[item]
|
||||
case "type":
|
||||
reagent.type = lookup_reagenttype_by_name(ctx=ctx, rt_name=info_dict[item].replace(" ", "_").lower())
|
||||
try:
|
||||
reagent.expiry = reagent.expiry + reagent.type.eol_ext
|
||||
except TypeError as e:
|
||||
print(f"WE got a type error: {e}.")
|
||||
except AttributeError:
|
||||
pass
|
||||
return reagent
|
||||
|
||||
|
||||
|
||||
def lookup_reagent(ctx:dict, reagent_lot:str):
|
||||
lookedup = ctx['database_session'].query(models.Reagent).filter(models.Reagent.lot==reagent_lot).first()
|
||||
return lookedup
|
||||
|
||||
def get_all_reagenttype_names(ctx:dict) -> list[str]:
|
||||
lookedup = [item.__str__() for item in ctx['database_session'].query(models.ReagentType).all()]
|
||||
return lookedup
|
||||
|
||||
def lookup_reagenttype_by_name(ctx:dict, rt_name:str) -> models.ReagentType:
|
||||
print(f"Looking up ReagentType by name: {rt_name}")
|
||||
lookedup = ctx['database_session'].query(models.ReagentType).filter(models.ReagentType.name==rt_name).first()
|
||||
print(f"Found ReagentType: {lookedup}")
|
||||
return lookedup
|
||||
|
||||
|
||||
def lookup_kittype_by_use(ctx:dict, used_by:str) -> list[models.KitType]:
|
||||
# return [item for item in
|
||||
return ctx['database_session'].query(models.KitType).filter(models.KitType.used_for.contains(used_by))
|
||||
|
||||
def lookup_kittype_by_name(ctx:dict, name:str) -> models.KitType:
|
||||
print(f"Querying kittype: {name}")
|
||||
return ctx['database_session'].query(models.KitType).filter(models.KitType.name==name).first()
|
||||
|
||||
|
||||
def lookup_regent_by_type_name(ctx:dict, type_name:str) -> list[models.ReagentType]:
|
||||
# return [item for item in ctx['database_session'].query(models.Reagent).join(models.Reagent.type, aliased=True).filter(models.ReagentType.name==type_name).all()]
|
||||
return ctx['database_session'].query(models.Reagent).join(models.Reagent.type, aliased=True).filter(models.ReagentType.name==type_name).all()
|
||||
|
||||
|
||||
def lookup_regent_by_type_name_and_kit_name(ctx:dict, type_name:str, kit_name:str) -> list[models.Reagent]:
|
||||
# Hang on, this is going to be a long one.
|
||||
by_type = ctx['database_session'].query(models.Reagent).join(models.Reagent.type, aliased=True).filter(models.ReagentType.name.endswith(type_name))
|
||||
add_in = by_type.join(models.ReagentType.kits).filter(models.KitType.name==kit_name)
|
||||
return add_in
|
||||
|
||||
|
||||
def lookup_all_submissions_by_type(ctx:dict, type:str|None=None):
|
||||
if type == None:
|
||||
subs = ctx['database_session'].query(models.BasicSubmission).all()
|
||||
else:
|
||||
subs = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.submission_type==type).all()
|
||||
return subs
|
||||
|
||||
def lookup_all_orgs(ctx:dict) -> list[models.Organization]:
|
||||
return ctx['database_session'].query(models.Organization).all()
|
||||
|
||||
def lookup_org_by_name(ctx:dict, name:str|None) -> models.Organization:
|
||||
print(f"Querying organization: {name}")
|
||||
return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first()
|
||||
|
||||
def submissions_to_df(ctx:dict, type:str|None=None):
|
||||
print(f"Type: {type}")
|
||||
subs = [item.to_dict() for item in lookup_all_submissions_by_type(ctx=ctx, type=type)]
|
||||
df = pd.DataFrame.from_records(subs)
|
||||
return df
|
||||
|
||||
|
||||
def lookup_submission_by_id(ctx:dict, id:int) -> models.BasicSubmission:
|
||||
return ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.id==id).first()
|
||||
|
||||
|
||||
def create_submission_details(ctx:dict, sub_id:int) -> dict:
|
||||
pass
|
||||
|
||||
|
||||
def lookup_submissions_by_date_range(ctx:dict, start_date:datetime.date, end_date:datetime.date) -> list[models.BasicSubmission]:
|
||||
return ctx['database_session'].query(models.BasicSubmission).filter(and_(models.BasicSubmission.submitted_date > start_date, models.BasicSubmission.submitted_date < end_date)).all()
|
||||
|
||||
|
||||
def get_all_Control_Types_names(ctx:dict) -> list[models.ControlType]:
|
||||
"""
|
||||
Grabs all control type names from db.
|
||||
|
||||
Args:
|
||||
settings (dict): settings passed down from click. Defaults to {}.
|
||||
|
||||
Returns:
|
||||
list: names list
|
||||
"""
|
||||
conTypes = ctx['database_session'].query(models.ControlType).all()
|
||||
conTypes = [conType.name for conType in conTypes]
|
||||
logger.debug(f"Control Types: {conTypes}")
|
||||
return conTypes
|
||||
|
||||
|
||||
def create_kit_from_yaml(ctx:dict, exp:dict) -> None:
|
||||
"""
|
||||
Create and store a new kit in the database based on a .yml file
|
||||
|
||||
Args:
|
||||
ctx (dict): Context dictionary passed down from frontend
|
||||
exp (dict): Experiment dictionary created from yaml file
|
||||
"""
|
||||
if base64.b64encode(exp['password']) != b'cnNsX3N1Ym1pNTVpb25z':
|
||||
print(f"Not the correct password.")
|
||||
return
|
||||
for type in exp:
|
||||
if type == "password":
|
||||
continue
|
||||
for kt in exp[type]['kits']:
|
||||
kit = models.KitType(name=kt, used_for=[type.replace("_", " ").title()], cost_per_run=exp[type]["kits"][kt]["cost"])
|
||||
for r in exp[type]['kits'][kt]['reagenttypes']:
|
||||
look_up = ctx['database_session'].query(models.ReagentType).filter(models.ReagentType.name==r).first()
|
||||
if look_up == None:
|
||||
rt = models.ReagentType(name=r.replace(" ", "_").lower(), eol_ext=datetime.timedelta(30*exp[type]['kits'][kt]['reagenttypes'][r]['eol_ext']), kits=[kit])
|
||||
else:
|
||||
rt = look_up
|
||||
rt.kits.append(kit)
|
||||
ctx['database_session'].add(rt)
|
||||
print(rt.__dict__)
|
||||
print(kit.__dict__)
|
||||
ctx['database_session'].add(kit)
|
||||
ctx['database_session'].commit()
|
||||
|
||||
|
||||
def lookup_all_sample_types(ctx:dict) -> list[str]:
|
||||
uses = [item.used_for for item in ctx['database_session'].query(models.KitType).all()]
|
||||
uses = list(set([item for sublist in uses for item in sublist]))
|
||||
return uses
|
||||
0
src/submissions/backend/db/functions/__init__.py
Normal file
0
src/submissions/backend/db/functions/__init__.py
Normal file
11
src/submissions/backend/db/models/__init__.py
Normal file
11
src/submissions/backend/db/models/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
Base = declarative_base()
|
||||
metadata = Base.metadata
|
||||
|
||||
from .controls import Control, ControlType
|
||||
from .kits import KitType, ReagentType, Reagent
|
||||
from .submissions import BasicSubmission, BacterialCulture, Wastewater
|
||||
from .organizations import Organization, Contact
|
||||
from .samples import Sample
|
||||
36
src/submissions/backend/db/models/controls.py
Normal file
36
src/submissions/backend/db/models/controls.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from . import Base
|
||||
from sqlalchemy import Column, String, TIMESTAMP, text, JSON, INTEGER, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
class ControlType(Base):
|
||||
"""
|
||||
Base class of a control archetype.
|
||||
"""
|
||||
__tablename__ = '_control_types'
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(255), unique=True) #: controltype name (e.g. MCS)
|
||||
targets = Column(JSON) #: organisms checked for
|
||||
# instances_id = Column(INTEGER, ForeignKey("_control_samples.id", ondelete="SET NULL", name="fk_ctype_instances_id"))
|
||||
instances = relationship("Control", back_populates="controltype") #: control samples created of this type.
|
||||
# UniqueConstraint('name', name='uq_controltype_name')
|
||||
|
||||
|
||||
class Control(Base):
|
||||
"""
|
||||
Base class of a control sample.
|
||||
"""
|
||||
|
||||
__tablename__ = '_control_samples'
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
parent_id = Column(String, ForeignKey("_control_types.id", name="fk_control_parent_id")) #: primary key of control type
|
||||
controltype = relationship("ControlType", back_populates="instances", foreign_keys=[parent_id]) #: reference to parent control type
|
||||
name = Column(String(255), unique=True) #: Sample ID
|
||||
submitted_date = Column(TIMESTAMP) #: Date submitted to Robotics
|
||||
contains = Column(JSON) #: unstructured hashes in contains.tsv for each organism
|
||||
matches = Column(JSON) #: unstructured hashes in matches.tsv for each organism
|
||||
kraken = Column(JSON) #: unstructured output from kraken_report
|
||||
# UniqueConstraint('name', name='uq_control_name')
|
||||
submissions = relationship("BacterialCulture", back_populates="control")
|
||||
|
||||
68
src/submissions/backend/db/models/kits.py
Normal file
68
src/submissions/backend/db/models/kits.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from . import Base
|
||||
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
reagenttypes_kittypes = Table("_reagentstypes_kittypes", Base.metadata, Column("reagent_types_id", INTEGER, ForeignKey("_reagent_types.id")), Column("kits_id", INTEGER, ForeignKey("_kits.id")))
|
||||
|
||||
|
||||
class KitType(Base):
|
||||
|
||||
__tablename__ = "_kits"
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(64), unique=True)
|
||||
submissions = relationship("BasicSubmission", back_populates="extraction_kit")
|
||||
used_for = Column(JSON)
|
||||
cost_per_run = Column(FLOAT(2))
|
||||
reagent_types = relationship("ReagentType", back_populates="kits", uselist=True, secondary=reagenttypes_kittypes)
|
||||
reagent_types_id = Column(INTEGER, ForeignKey("_reagent_types.id", ondelete='SET NULL', use_alter=True, name="fk_KT_reagentstype_id"))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ReagentType(Base):
|
||||
|
||||
__tablename__ = "_reagent_types"
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(64))
|
||||
kit_id = Column(INTEGER, ForeignKey("_kits.id", ondelete="SET NULL", use_alter=True, name="fk_RT_kits_id"))
|
||||
kits = relationship("KitType", back_populates="reagent_types", uselist=True, foreign_keys=[kit_id])
|
||||
instances = relationship("Reagent", back_populates="type")
|
||||
# instances_id = Column(INTEGER, ForeignKey("_reagents.id", ondelete='SET NULL'))
|
||||
eol_ext = Column(Interval())
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Reagent(Base):
|
||||
|
||||
__tablename__ = "_reagents"
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
type = relationship("ReagentType", back_populates="instances")
|
||||
type_id = Column(INTEGER, ForeignKey("_reagent_types.id", ondelete='SET NULL', name="fk_reagent_type_id"))
|
||||
name = Column(String(64))
|
||||
lot = Column(String(64))
|
||||
expiry = Column(TIMESTAMP)
|
||||
submissions = relationship("BasicSubmission", back_populates="reagents", uselist=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.lot
|
||||
|
||||
def to_sub_dict(self):
|
||||
try:
|
||||
type = self.type.name.replace("_", " ").title()
|
||||
except AttributeError:
|
||||
type = "Unknown"
|
||||
return {
|
||||
"type": type,
|
||||
"lot": self.lot,
|
||||
"expiry": self.expiry.strftime("%Y-%m-%d")
|
||||
}
|
||||
|
||||
|
||||
|
||||
34
src/submissions/backend/db/models/organizations.py
Normal file
34
src/submissions/backend/db/models/organizations.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from . import Base
|
||||
from sqlalchemy import Column, String, TIMESTAMP, JSON, Float, INTEGER, ForeignKey, UniqueConstraint, Table
|
||||
from sqlalchemy.orm import relationship, validates
|
||||
|
||||
|
||||
orgs_contacts = Table("_orgs_contacts", Base.metadata, Column("org_id", INTEGER, ForeignKey("_organizations.id")), Column("contact_id", INTEGER, ForeignKey("_contacts.id")))
|
||||
|
||||
|
||||
class Organization(Base):
|
||||
|
||||
__tablename__ = "_organizations"
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(64))
|
||||
submissions = relationship("BasicSubmission", back_populates="submitting_lab")
|
||||
cost_centre = Column(String())
|
||||
contacts = relationship("Contact", back_populates="organization", secondary=orgs_contacts)
|
||||
contact_ids = Column(INTEGER, ForeignKey("_contacts.id", ondelete="SET NULL", name="fk_org_contact_id"))
|
||||
|
||||
def __str__(self):
|
||||
return self.name.replace("_", " ").title()
|
||||
|
||||
|
||||
class Contact(Base):
|
||||
|
||||
__tablename__ = "_contacts"
|
||||
|
||||
id = id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(64))
|
||||
email = Column(String(64))
|
||||
phone = Column(String(32))
|
||||
organization = relationship("Organization", back_populates="contacts", uselist=True)
|
||||
# organization_id = Column(INTEGER, ForeignKey("_organizations.id"))
|
||||
|
||||
27
src/submissions/backend/db/models/samples.py
Normal file
27
src/submissions/backend/db/models/samples.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from . import Base
|
||||
from sqlalchemy import Column, String, TIMESTAMP, text, JSON, INTEGER, ForeignKey, FLOAT, BOOLEAN
|
||||
from sqlalchemy.orm import relationship, relationships
|
||||
|
||||
|
||||
class Sample(Base):
|
||||
|
||||
__tablename__ = "_ww_samples"
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
ww_processing_num = Column(String(64))
|
||||
ww_sample_full_id = Column(String(64))
|
||||
rsl_number = Column(String(64))
|
||||
rsl_plate = relationship("Wastewater", back_populates="samples")
|
||||
collection_date = Column(TIMESTAMP) #: Date submission received
|
||||
testing_type = Column(String(64))
|
||||
site_status = Column(String(64))
|
||||
notes = Column(String(2000))
|
||||
ct_n1 = Column(FLOAT(2))
|
||||
ct_n2 = Column(FLOAT(2))
|
||||
seq_submitted = Column(BOOLEAN())
|
||||
ww_seq_run_id = Column(String(64))
|
||||
sample_type = Column(String(8))
|
||||
|
||||
|
||||
|
||||
|
||||
103
src/submissions/backend/db/models/submissions.py
Normal file
103
src/submissions/backend/db/models/submissions.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from . import Base
|
||||
from sqlalchemy import Column, String, TIMESTAMP, text, JSON, INTEGER, ForeignKey, UniqueConstraint, Table
|
||||
from sqlalchemy.orm import relationship, relationships
|
||||
from datetime import datetime as dt
|
||||
|
||||
reagents_submissions = Table("_reagents_submissions", Base.metadata, Column("reagent_id", INTEGER, ForeignKey("_reagents.id")), Column("submission_id", INTEGER, ForeignKey("_submissions.id")))
|
||||
|
||||
class BasicSubmission(Base):
|
||||
|
||||
# TODO: Figure out if I want seperate tables for different sample types.
|
||||
__tablename__ = "_submissions"
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
rsl_plate_num = Column(String(32), unique=True) #: RSL name (e.g. RSL-22-0012)
|
||||
submitter_plate_num = Column(String(127), unique=True) #: The number given to the submission by the submitting lab
|
||||
submitted_date = Column(TIMESTAMP) #: Date submission received
|
||||
submitting_lab = relationship("Organization", back_populates="submissions") #: client
|
||||
submitting_lab_id = Column(INTEGER, ForeignKey("_organizations.id", ondelete="SET NULL"))
|
||||
sample_count = Column(INTEGER) #: Number of samples in the submission
|
||||
extraction_kit = relationship("KitType", back_populates="submissions") #: The extraction kit used
|
||||
extraction_kit_id = Column(INTEGER, ForeignKey("_kits.id", ondelete="SET NULL"))
|
||||
submission_type = Column(String(32))
|
||||
technician = Column(String(64))
|
||||
# Move this into custom types?
|
||||
reagents = relationship("Reagent", back_populates="submissions", secondary=reagents_submissions)
|
||||
reagents_id = Column(String, ForeignKey("_reagents.id", ondelete="SET NULL", name="fk_BS_reagents_id"))
|
||||
|
||||
__mapper_args__ = {
|
||||
"polymorphic_identity": "basic_submission",
|
||||
"polymorphic_on": submission_type,
|
||||
"with_polymorphic": "*",
|
||||
}
|
||||
|
||||
def to_dict(self):
|
||||
print(self.submitting_lab)
|
||||
try:
|
||||
sub_lab = self.submitting_lab.name
|
||||
except AttributeError:
|
||||
sub_lab = None
|
||||
try:
|
||||
sub_lab = sub_lab.replace("_", " ").title()
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
ext_kit = self.extraction_kit.name
|
||||
except AttributeError:
|
||||
ext_kit = None
|
||||
output = {
|
||||
"id": self.id,
|
||||
"Plate Number": self.rsl_plate_num,
|
||||
"Submission Type": self.submission_type.replace("_", " ").title(),
|
||||
"Submitter Plate Number": self.submitter_plate_num,
|
||||
"Submitted Date": self.submitted_date.strftime("%Y-%m-%d"),
|
||||
"Submitting Lab": sub_lab,
|
||||
"Sample Count": self.sample_count,
|
||||
"Extraction Kit": ext_kit,
|
||||
"Technician": self.technician,
|
||||
}
|
||||
return output
|
||||
|
||||
|
||||
def report_dict(self):
|
||||
try:
|
||||
sub_lab = self.submitting_lab.name
|
||||
except AttributeError:
|
||||
sub_lab = None
|
||||
try:
|
||||
sub_lab = sub_lab.replace("_", " ").title()
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
ext_kit = self.extraction_kit.name
|
||||
except AttributeError:
|
||||
ext_kit = None
|
||||
try:
|
||||
cost = self.extraction_kit.cost_per_run
|
||||
except AttributeError:
|
||||
cost = None
|
||||
output = {
|
||||
"id": self.id,
|
||||
"Plate Number": self.rsl_plate_num,
|
||||
"Submission Type": self.submission_type.replace("_", " ").title(),
|
||||
"Submitter Plate Number": self.submitter_plate_num,
|
||||
"Submitted Date": self.submitted_date.strftime("%Y-%m-%d"),
|
||||
"Submitting Lab": sub_lab,
|
||||
"Sample Count": self.sample_count,
|
||||
"Extraction Kit": ext_kit,
|
||||
"Cost": cost
|
||||
}
|
||||
return output
|
||||
|
||||
# Below are the custom submission
|
||||
|
||||
class BacterialCulture(BasicSubmission):
|
||||
control = relationship("Control", back_populates="submissions") #: A control sample added to submission
|
||||
control_id = Column(INTEGER, ForeignKey("_control_samples.id", ondelete="SET NULL", name="fk_BC_control_id"))
|
||||
__mapper_args__ = {"polymorphic_identity": "bacterial_culture", "polymorphic_load": "inline"}
|
||||
|
||||
|
||||
class Wastewater(BasicSubmission):
|
||||
samples = relationship("Sample", back_populates="rsl_plate")
|
||||
sample_id = Column(String, ForeignKey("_ww_samples.id", ondelete="SET NULL", name="fk_WW_sample_id"))
|
||||
__mapper_args__ = {"polymorphic_identity": "wastewater", "polymorphic_load": "inline"}
|
||||
122
src/submissions/backend/excel/parser.py
Normal file
122
src/submissions/backend/excel/parser.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
import re
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
class SheetParser(object):
|
||||
|
||||
def __init__(self, filepath:Path|None = None, **kwargs):
|
||||
for kwarg in kwargs:
|
||||
setattr(self, f"_{kwarg}", kwargs[kwarg])
|
||||
if filepath == None:
|
||||
self.xl = None
|
||||
else:
|
||||
try:
|
||||
self.xl = pd.ExcelFile(filepath.__str__())
|
||||
except ValueError:
|
||||
self.xl = None
|
||||
self.sub = OrderedDict()
|
||||
self.sub['submission_type'] = self._type_decider()
|
||||
parse = getattr(self, f"_parse_{self.sub['submission_type'].lower()}")
|
||||
parse()
|
||||
|
||||
def _type_decider(self):
|
||||
try:
|
||||
for type in self._submission_types:
|
||||
if self.xl.sheet_names == self._submission_types[type]['excel_map']:
|
||||
return type.title()
|
||||
return "Unknown"
|
||||
except:
|
||||
return "Unknown"
|
||||
|
||||
|
||||
def _parse_unknown(self):
|
||||
self.sub = None
|
||||
|
||||
|
||||
def _parse_generic(self, sheet_name:str):
|
||||
submission_info = self.xl.parse(sheet_name=sheet_name)
|
||||
self.sub['submitter_plate_num'] = submission_info.iloc[0][1]
|
||||
self.sub['rsl_plate_num'] = str(submission_info.iloc[10][1])
|
||||
self.sub['submitted_date'] = submission_info.iloc[1][1].date()#.strftime("%Y-%m-%d")
|
||||
self.sub['submitting_lab'] = submission_info.iloc[0][3]
|
||||
self.sub['sample_count'] = str(submission_info.iloc[2][3])
|
||||
self.sub['extraction_kit'] = submission_info.iloc[3][3]
|
||||
return submission_info
|
||||
|
||||
|
||||
def _parse_bacterial_culture(self):
|
||||
# submission_info = self.xl.parse("Sample List")
|
||||
submission_info = self._parse_generic("Sample List")
|
||||
# iloc is [row][column] and the first row is set as header row so -2
|
||||
tech = str(submission_info.iloc[11][1])
|
||||
if tech == "nan":
|
||||
tech = "Unknown"
|
||||
elif len(tech.split(",")) > 1:
|
||||
tech_reg = re.compile(r"[A-Z]{2}")
|
||||
tech = ", ".join(tech_reg.findall(tech))
|
||||
self.sub['technician'] = tech
|
||||
# reagents
|
||||
self.sub['lot_wash_1'] = submission_info.iloc[1][6]
|
||||
self.sub['lot_wash_2'] = submission_info.iloc[2][6]
|
||||
self.sub['lot_binding_buffer'] = submission_info.iloc[3][6]
|
||||
self.sub['lot_magnetic_beads'] = submission_info.iloc[4][6]
|
||||
self.sub['lot_lysis_buffer'] = submission_info.iloc[5][6]
|
||||
self.sub['lot_elution_buffer'] = submission_info.iloc[6][6]
|
||||
self.sub['lot_isopropanol'] = submission_info.iloc[9][6]
|
||||
self.sub['lot_ethanol'] = submission_info.iloc[10][6]
|
||||
self.sub['lot_positive_control'] = submission_info.iloc[103][1]
|
||||
self.sub['lot_plate'] = submission_info.iloc[12][6]
|
||||
|
||||
|
||||
def _parse_wastewater(self):
|
||||
# submission_info = self.xl.parse("WW Submissions (ENTER HERE)")
|
||||
submission_info = self._parse_generic("WW Submissions (ENTER HERE)")
|
||||
enrichment_info = self.xl.parse("Enrichment Worksheet")
|
||||
extraction_info = self.xl.parse("Extraction Worksheet")
|
||||
qprc_info = self.xl.parse("qPCR Worksheet")
|
||||
# iloc is [row][column] and the first row is set as header row so -2
|
||||
# self.sub['submitter_plate_num'] = submission_info.iloc[0][1]
|
||||
# self.sub['rsl_plate_num'] = str(submission_info.iloc[10][1])
|
||||
# self.sub['submitted_date'] = submission_info.iloc[1][1].date()#.strftime("%Y-%m-%d")
|
||||
# self.sub['submitting_lab'] = submission_info.iloc[0][3]
|
||||
# self.sub['sample_count'] = str(submission_info.iloc[2][3])
|
||||
# self.sub['extraction_kit'] = submission_info.iloc[3][3]
|
||||
self.sub['technician'] = f"Enr: {enrichment_info.columns[2]}, Ext: {extraction_info.columns[2]}, PCR: {qprc_info.columns[2]}"
|
||||
# reagents
|
||||
self.sub['lot_lysis_buffer'] = enrichment_info.iloc[0][14]
|
||||
self.sub['lot_proteinase_K'] = enrichment_info.iloc[1][14]
|
||||
self.sub['lot_magnetic_virus_particles'] = enrichment_info.iloc[2][14]
|
||||
self.sub['lot_enrichment_reagent_1'] = enrichment_info.iloc[3][14]
|
||||
self.sub['lot_binding_buffer'] = extraction_info.iloc[0][14]
|
||||
self.sub['lot_magnetic_beads'] = extraction_info.iloc[1][14]
|
||||
self.sub['lot_wash'] = extraction_info.iloc[2][14]
|
||||
self.sub['lot_ethanol'] = extraction_info.iloc[3][14]
|
||||
self.sub['lot_elution_buffer'] = extraction_info.iloc[4][14]
|
||||
self.sub['lot_master_mix'] = qprc_info.iloc[0][14]
|
||||
self.sub['lot_pre_mix_1'] = qprc_info.iloc[1][14]
|
||||
self.sub['lot_pre_mix_2'] = qprc_info.iloc[2][14]
|
||||
self.sub['lot_positive_control'] = qprc_info.iloc[3][14]
|
||||
self.sub['lot_ddh2o'] = qprc_info.iloc[4][14]
|
||||
# tech = str(submission_info.iloc[11][1])
|
||||
# if tech == "nan":
|
||||
# tech = "Unknown"
|
||||
# elif len(tech.split(",")) > 1:
|
||||
# tech_reg = re.compile(r"[A-Z]{2}")
|
||||
# tech = ", ".join(tech_reg.findall(tech))
|
||||
|
||||
|
||||
# self.sub['lot_wash_1'] = submission_info.iloc[1][6]
|
||||
# self.sub['lot_wash_2'] = submission_info.iloc[2][6]
|
||||
# self.sub['lot_binding_buffer'] = submission_info.iloc[3][6]
|
||||
# self.sub['lot_magnetic_beads'] = submission_info.iloc[4][6]
|
||||
# self.sub['lot_lysis_buffer'] = submission_info.iloc[5][6]
|
||||
# self.sub['lot_elution_buffer'] = submission_info.iloc[6][6]
|
||||
# self.sub['lot_isopropanol'] = submission_info.iloc[9][6]
|
||||
# self.sub['lot_ethanol'] = submission_info.iloc[10][6]
|
||||
# self.sub['lot_positive_control'] = None #submission_info.iloc[103][1]
|
||||
# self.sub['lot_plate'] = submission_info.iloc[12][6]
|
||||
13
src/submissions/backend/excel/reports.py
Normal file
13
src/submissions/backend/excel/reports.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from pandas import DataFrame
|
||||
import numpy as np
|
||||
|
||||
def make_report_xlsx(records:list[dict]) -> DataFrame:
|
||||
df = DataFrame.from_records(records)
|
||||
df = df.sort_values("Submitting Lab")
|
||||
# table = df.pivot_table(values="Cost", index=["Submitting Lab", "Extraction Kit"], columns=["Cost", "Sample Count"], aggfunc={'Cost':np.sum,'Sample Count':np.sum})
|
||||
df2 = df.groupby(["Submitting Lab", "Extraction Kit"]).agg({'Cost': ['sum', 'count'], 'Sample Count':['sum']})
|
||||
# df2['Cost'] = df2['Cost'].map('${:,.2f}'.format)
|
||||
print(df2.columns)
|
||||
# df2['Cost']['sum'] = df2['Cost']['sum'].apply('${:,.2f}'.format)
|
||||
df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')] = df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')].applymap('${:,.2f}'.format)
|
||||
return df2
|
||||
Reference in New Issue
Block a user