Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions openai_cfps/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def generate_reagent_field(**kwargs):
description = kwargs.get("description", "")
out_of_stock = kwargs.get("out_of_stock", False)
default = kwargs.get("default", 0.0)

# Create the field description for Pydantic
out_of_stock_text = " Out of stock" if out_of_stock else ""
field_description = f"{description}, Components: {components}, Cost: {cost} USD/mL, Concentration: {concentration}, Category: {category}. {out_of_stock_text}"
Expand All @@ -48,6 +48,9 @@ class ReagentList(BaseModel):
# Reagents
# All values are in nanoliters with increments of 25nL

# Control flags (excluded from serialization so they're not treated as reagents)
skip_validation: bool = Field(default=False, exclude=True)

# Core Salt & Buffer Components
potassium_glutamate: float = generate_reagent_field(
concentration="0.85M",
Expand All @@ -59,7 +62,7 @@ class ReagentList(BaseModel):
default=0.0,
out_of_stock=False,
)

magnesium_glutamate: float = generate_reagent_field(
concentration="0.5M",
category="salts",
Expand Down Expand Up @@ -589,12 +592,13 @@ class ReagentList(BaseModel):
def validate_unavailable_reagents(self) -> bool:
unavailable_reagents = [
reagent for reagent, info in self.model_fields.items()
if info.json_schema_extra["out_of_stock"]
if not info.exclude and info.json_schema_extra.get("out_of_stock", False)
]
for reagent in unavailable_reagents:
assert getattr(self, reagent) == 0, (
f"{reagent} is out of stock and must be set to 0"
)
if not self.skip_validation:
for reagent in unavailable_reagents:
assert getattr(self, reagent) == 0, (
f"{reagent} is out of stock and must be set to 0"
)
return self

@model_validator(mode="after")
Expand All @@ -614,6 +618,8 @@ def validate_non_zero_default_values(self) -> bool:
"""
Values for non-zero reagents should match exactly what is defined in the model.
"""
if self.skip_validation:
return self
for key, value in self.model_dump().items():
default_value = self.model_fields[key].default
if default_value > 0:
Expand Down Expand Up @@ -687,7 +693,7 @@ def sample_cost(self) -> float:
return 0.02795 # Calculated from the cost of the reagents in the positive control mix
else:
return 0.0

cost_per_sample = 0.0
for reagent_name, reagent_value in self.reagent_list.model_dump().items():
reagent_field = ReagentList.model_fields[reagent_name]
Expand Down Expand Up @@ -737,7 +743,7 @@ def from_pherastar_data(cls, file_path: str) -> "LuminescenceResults":
"""
well_luminosity = cls.parse_pherastar_well_luminosity(file_path)
return cls(luminescence=well_luminosity)

def titer(self) -> Dict[str, float]:
"""
Calculate the concentration of the sample.
Expand Down Expand Up @@ -837,7 +843,7 @@ def from_pherastar_data(cls, file_path: str) -> "FluorescenceResults":

def titer(self) -> Dict[str, float]:
raise NotImplementedError("Titer calculation for fluorescence is not implemented yet")


class ConcentrationResults(BaseModel):
concentration_g_L: Dict[str, float]
Expand Down Expand Up @@ -997,7 +1003,8 @@ def get_plate_dataframe(self, include_qc=False) -> pd.DataFrame:
"""
Get the plate as a dataframe.
"""
reagent_columns = [f"{k}" for k in ReagentList.model_fields.keys()]
# Exclude non-reagent fields (e.g., control flags) from reagent columns
reagent_columns = [k for k, f in ReagentList.model_fields.items() if not f.exclude]
rows = []
sample_layout = self.get_plate_layout()
if self.luminescence_results:
Expand Down