"""Classes to work with quote data"""
import mcol
import mrich
from .price import Price
[docs]
class Quote:
"""Supplier quote for a specific quantity of a :class:`.Compound`.
.. attention::
:class:`.Quote` objects should not be created directly. Instead use :meth:`.Compound.get_quotes`.
"""
_db = None
def __init__(
self,
db: "Database",
id: int,
compound: int,
smiles: str,
supplier: str,
catalogue: str,
entry: str,
amount: float,
price: float,
currency: str,
purity: float,
lead_time: int,
date: str | None = None,
type: str | None = None,
) -> None:
"""Quote initialisation"""
price = Price(price, currency)
self._db = db
self._id = id
self._compound = compound
self._smiles = smiles
self._supplier = supplier
self._catalogue = catalogue
self._entry = entry
self._amount = amount
self._price = price
self._purity = purity
self._lead_time = lead_time
self._date = date
self._type = type
from datetime import datetime
quote_age = (datetime.today() - datetime.strptime(self.date, "%Y-%m-%d")).days
# if quote_age > 30:
# mrich.warning(f'Quote is {quote_age} days old')
# mrich.warning(self)
### FACTORIES
[docs]
@classmethod
def combination(
cls,
required_amount: float,
quotes: list["Quote"],
) -> "Quote":
"""Combine a list of quotes into one :class:`.Quote` object.
* Start with biggest pack
* Estimate by scaling linearly with unit price
:param required_amount: amount in mg
:param quotes: list of quotes to be combined
"""
biggest_pack = sorted(quotes, key=lambda x: x.amount)[-1]
unit_price = biggest_pack.price / biggest_pack.amount
estimated_price = unit_price * required_amount
self = cls.__new__(cls)
self.__init__(
db=biggest_pack.db,
id=None,
compound=biggest_pack.compound,
smiles=biggest_pack.smiles,
supplier=biggest_pack.supplier,
catalogue=biggest_pack.catalogue,
entry=biggest_pack.entry,
amount=required_amount,
price=estimated_price.amount,
currency=biggest_pack.currency,
purity=biggest_pack.purity,
lead_time=biggest_pack.lead_time,
date=biggest_pack.date,
type=f"estimate from quote={biggest_pack.id}",
)
return self
### PROPERTIES
@property
def entry_str(self) -> str:
"""Unformatted string including the supplier, catalogue (if available), and entry name of the quote"""
if self.catalogue:
return f"{self.supplier}:{self.catalogue}:{self.entry}"
else:
return f"{self.supplier}:{self.entry}"
@property
def db(self) -> "Database":
"""Returns a pointer to the parent database"""
return self._db
@property
def id(self) -> int:
"""Returns the quote's database ID"""
return self._id
@property
def compound(self) -> int:
"""Returns the associated :class:`.Compound`"""
return self._compound
@property
def smiles(self) -> str:
"""Returns the catalogue SMILES string"""
return self._smiles
@property
def supplier(self) -> str:
"""Name of the supplier"""
return self._supplier
@property
def catalogue(self) -> str | None:
"""Name of the catalogue"""
return self._catalogue
@property
def entry(self) -> str:
"""Name/ID of the catalogue entry"""
return self._entry
@property
def amount(self) -> float:
"""Amount in mg"""
return self._amount
@property
def price(self) -> "Price":
"""Price"""
return self._price
@property
def currency(self) -> str:
"""Currency of the associated :class:`.Price` object"""
return self.price.currency
@property
def purity(self) -> float:
"""Purity fraction"""
return self._purity
@property
def lead_time(self) -> float:
"""Lead time in days"""
return self._lead_time
@property
def date(self) -> str:
"""Date the quote was registered to the database"""
return self._date
@property
def type(self) -> str:
"""Description of this quote"""
return self._type
@property
def dict(self) -> dict:
"""Dictionary representation of this quote"""
return dict(
id=self.id,
compound=self.compound,
smiles=self.smiles,
supplier=self.supplier,
catalogue=self.catalogue,
entry=self.entry,
amount=self.amount,
price=self.price,
purity=self.purity,
lead_time=self.lead_time,
date=self.date,
type=self.type,
)
@property
def currency_symbol(self) -> str:
"""Currency symbol of the associated :class:`.Price`"""
return self.price.symbol
### DUNDERS
[docs]
def __str__(self):
"""Unformatted string representation"""
if self.purity:
purity = f" @ {self.purity:.0%}"
else:
purity = ""
if self.supplier == "Stock":
return f"C{self.compound} In Stock: {self.amount:}mg{purity}"
elif self.type:
return f"C{self.compound} {self.entry_str} {self.amount:}mg{purity} = {self.price:} ({self.lead_time} days) {self.smiles} [{self.type}]"
else:
return f"C{self.compound} {self.entry_str} {self.amount:}mg{purity} = {self.price:} ({self.lead_time} days) {self.smiles}"
[docs]
def __repr__(self) -> str:
"""ANSI Formatted string representation"""
return f"{mcol.bold}{mcol.underline}{self}{mcol.unbold}{mcol.ununderline}"
def __rich__(self) -> str:
"""Rich Formatted string representation"""
return f"[bold underline]{self}"