Upgrades to cost calculation methods

This commit is contained in:
Landon Wark
2023-05-02 14:19:45 -05:00
parent dff5a5aa1e
commit 06447f0938
12 changed files with 203 additions and 27 deletions

View File

@@ -164,13 +164,24 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
try:
# ceil(instance.sample_count / 8) will get number of columns
# the cost of a full run multiplied by (that number / 12) is x twelfths the cost of a full run
logger.debug(f"Instance extraction kit details: {instance.extraction_kit.__dict__}")
cols_count = ceil(int(instance.sample_count) / 8)
instance.run_cost = instance.extraction_kit.constant_cost + (instance.extraction_kit.mutable_cost * (cols_count / 12))
logger.debug(f"Calculating costs for procedure...")
# cols_count = ceil(int(instance.sample_count) / 8)
# instance.run_cost = instance.extraction_kit.constant_cost + (instance.extraction_kit.mutable_cost * (cols_count / 12))
instance.calculate_base_cost()
except (TypeError, AttributeError) as e:
logger.debug(f"Looks like that kit doesn't have cost breakdown yet due to: {e}, using full plate cost.")
instance.run_cost = instance.extraction_kit.cost_per_run
logger.debug(f"Calculated base run cost of: {instance.run_cost}")
try:
logger.debug("Checking and applying discounts...")
discounts = [item.amount for item in lookup_discounts_by_org_and_kit(ctx=ctx, kit_id=instance.extraction_kit.id, lab_id=instance.submitting_lab.id)]
logger.debug(f"We got discounts: {discounts}")
discounts = sum(discounts)
instance.run_cost = instance.run_cost - discounts
except Exception as e:
logger.error(f"An unknown exception occurred: {e}")
# We need to make sure there's a proper rsl plate number
logger.debug(f"We've got a total cost of {instance.run_cost}")
try:
logger.debug(f"Constructed instance: {instance.to_string()}")
except AttributeError as e:
@@ -466,7 +477,7 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> dict:
continue
# A submission type may use multiple kits.
for kt in exp[type]['kits']:
kit = models.KitType(name=kt, used_for=[type.replace("_", " ").title()], constant_cost=exp[type]["kits"][kt]["constant_cost"], mutable_cost=exp[type]["kits"][kt]["mutable_cost"])
kit = models.KitType(name=kt, used_for=[type.replace("_", " ").title()], constant_cost=exp[type]["kits"][kt]["constant_cost"], mutable_cost_column=exp[type]["kits"][kt]["mutable_cost_column"])
# A kit contains multiple reagent types.
for r in exp[type]['kits'][kt]['reagenttypes']:
# check if reagent type already exists.
@@ -736,4 +747,10 @@ def update_ww_sample(ctx:dict, sample_obj:dict):
logger.error(f"Unable to find sample {sample_obj['sample']}")
return
ctx['database_session'].add(ww_samp)
ctx["database_session"].commit()
ctx["database_session"].commit()
def lookup_discounts_by_org_and_kit(ctx:dict, kit_id:int, lab_id:int):
return ctx['database_session'].query(models.Discount).join(models.KitType).join(models.Organization).filter(and_(
models.KitType.id==kit_id,
models.Organization.id==lab_id
)).all()

View File

@@ -7,7 +7,7 @@ Base = declarative_base()
metadata = Base.metadata
from .controls import Control, ControlType
from .kits import KitType, ReagentType, Reagent
from .kits import KitType, ReagentType, Reagent, Discount
from .organizations import Organization, Contact
from .samples import WWSample, BCSample
from .submissions import BasicSubmission, BacterialCulture, Wastewater

View File

@@ -25,7 +25,9 @@ class KitType(Base):
submissions = relationship("BasicSubmission", back_populates="extraction_kit") #: submissions this kit was used for
used_for = Column(JSON) #: list of names of sample types this kit can process
cost_per_run = Column(FLOAT(2)) #: dollar amount for each full run of this kit NOTE: depreciated, use the constant and mutable costs instead
mutable_cost = Column(FLOAT(2)) #: dollar amount per plate that can change with number of columns (reagents, tips, etc)
# TODO: Change below to 'mutable_cost_column' and 'mutable_cost_sample' before moving to production.
mutable_cost_column = Column(FLOAT(2)) #: dollar amount per 96 well plate that can change with number of columns (reagents, tips, etc)
mutable_cost_sample = Column(FLOAT(2)) #: dollar amount that can change with number of samples (reagents, tips, etc)
constant_cost = Column(FLOAT(2)) #: dollar amount per plate that will remain constant (plates, man hours, etc)
reagent_types = relationship("ReagentType", back_populates="kits", uselist=True, secondary=reagenttypes_kittypes) #: reagent types this kit contains
reagent_types_id = Column(INTEGER, ForeignKey("_reagent_types.id", ondelete='SET NULL', use_alter=True, name="fk_KT_reagentstype_id")) #: joined reagent type id
@@ -113,13 +115,16 @@ class Reagent(Base):
}
# class Discounts(Base):
# """
# Relationship table for client labs for certain kits.
# """
# __tablename__ = "_discounts"
class Discount(Base):
"""
Relationship table for client labs for certain kits.
"""
__tablename__ = "_discounts"
# id = Column(INTEGER, primary_key=True) #: primary key
# kit = relationship("KitType") #: joined parent reagent type
# kit_id = Column(INTEGER, ForeignKey("_kits.id", ondelete='SET NULL', name="fk_kit_type_id"))
# client = relationship("Organization")
id = Column(INTEGER, primary_key=True) #: primary key
kit = relationship("KitType") #: joined parent reagent type
kit_id = Column(INTEGER, ForeignKey("_kits.id", ondelete='SET NULL', name="fk_kit_type_id"))
client = relationship("Organization") #: joined client lab
client_id = Column(INTEGER, ForeignKey("_organizations.id", ondelete='SET NULL', name="fk_org_id"))
name = Column(String(128))
amount = Column(FLOAT(2))

View File

@@ -8,6 +8,7 @@ from datetime import datetime as dt
import logging
import json
from json.decoder import JSONDecodeError
from math import ceil
logger = logging.getLogger(f"submissions.{__name__}")
@@ -151,6 +152,8 @@ class BasicSubmission(Base):
"Cost": self.run_cost
}
return output
# Below are the custom submission types
@@ -174,6 +177,18 @@ class BacterialCulture(BasicSubmission):
return output
def calculate_base_cost(self):
try:
cols_count_96 = ceil(int(self.sample_count) / 8)
except Exception as e:
logger.error(f"Column count error: {e}")
# cols_count_24 = ceil(int(self.sample_count) / 3)
try:
self.run_cost = self.extraction_kit.constant_cost + (self.extraction_kit.mutable_cost_column * cols_count_96) + (self.extraction_kit.mutable_cost_sample * int(self.sample_count))
except Exception as e:
logger.error(f"Calculation error: {e}")
class Wastewater(BasicSubmission):
"""
derivative submission type from BasicSubmission
@@ -195,4 +210,16 @@ class Wastewater(BasicSubmission):
output['pcr_info'] = json.loads(self.pcr_info)
except TypeError as e:
pass
return output
return output
def calculate_base_cost(self):
try:
cols_count_96 = ceil(int(self.sample_count) / 8) + 1 #: Adding in one column to account for 24 samples + ext negatives
except Exception as e:
logger.error(f"Column count error: {e}")
# cols_count_24 = ceil(int(self.sample_count) / 3)
try:
self.run_cost = self.extraction_kit.constant_cost + (self.extraction_kit.mutable_cost_column * cols_count_96) + (self.extraction_kit.mutable_cost_sample * int(self.sample_count))
except Exception as e:
logger.error(f"Calculation error: {e}")

View File

@@ -11,7 +11,7 @@ import logging
from collections import OrderedDict
import re
import numpy as np
from datetime import date
from datetime import date, datetime
import uuid
from tools import check_not_nan, RSLNamer
@@ -129,8 +129,12 @@ class SheetParser(object):
logger.debug(f"Output variable is {output_var}")
logger.debug(f"Expiry date for imported reagent: {row[3]}")
if check_not_nan(row[3]):
expiry = row[3].date()
try:
expiry = row[3].date()
except AttributeError as e:
expiry = datetime.strptime(row[3], "%Y-%m-%d")
else:
logger.debug(f"Date: {row[3]}")
expiry = date.today()
self.sub[f"lot_{reagent_type}"] = {'lot':output_var, 'exp':expiry}
submission_info = self.parse_generic("Sample List")
@@ -265,8 +269,8 @@ class SampleParser(object):
new_list = []
for sample in self.samples:
new = WWSample()
if check_not_nan(sample["Unnamed: 9"]):
new.rsl_number = sample['Unnamed: 9']
if check_not_nan(sample["Unnamed: 7"]):
new.rsl_number = sample['Unnamed: 7'] # previously Unnamed: 9
else:
logger.error(f"No RSL sample number found for this sample.")
continue
@@ -282,9 +286,9 @@ class SampleParser(object):
new.collection_date = sample['Unnamed: 5']
else:
new.collection_date = date.today()
new.testing_type = sample['Unnamed: 6']
new.site_status = sample['Unnamed: 7']
new.notes = str(sample['Unnamed: 8'])
# new.testing_type = sample['Unnamed: 6']
# new.site_status = sample['Unnamed: 7']
new.notes = str(sample['Unnamed: 6']) # previously Unnamed: 8
new.well_number = sample['Unnamed: 1']
new_list.append(new)
return new_list