from typing import Any, Literal, Optional
from drugforge.data.schema.schema_base import DataModelAbstractBase
from drugforge.data.services.postera.manifold_data_validation import TargetTags
from pydantic import BaseModel, ConfigDict, Field, field_validator
[docs]
class LigandIdentifiers(DataModelAbstractBase):
"""
This is a schema for the identifiers associated with a ligand, optional identifiers are used as part of the ASAP
workflow. Non-optional identifiers track the state of the molecule to ensure it is always consistent between the
SDF data and the original input ligand.
Parameters
----------
moonshot_compound_id : Optional[str], optional
Moonshot compound ID, by default None
manifold_api_id : Optional[UUID], optional
Unique ID from Postera Manifold API, by default None
manifold_vc_id : Optional[str], optional
Unique VC ID (virtual compound ID) from Postera Manifold, by default None
compchem_id : Optional[UUID4], optional
Unique ID for P5 compchem reference, unused for now, by default None
"""
moonshot_compound_id: Optional[str] = Field(
None, description="Moonshot compound ID"
)
manifold_api_id: Optional[str] = Field(
None, description="Unique ID from Postera Manifold API"
)
manifold_vc_id: Optional[str] = Field(
None, description="Unique VC ID (virtual compound ID) from Postera Manifold"
)
compchem_id: Optional[str] = Field(
None, description="Unique ID for P5 compchem reference, unused for now"
)
model_config = ConfigDict(frozen=True)
[docs]
@field_validator("manifold_api_id", "compchem_id", mode="before")
def cast_uuids(cls, v):
"""
Cast UUIDS to string
"""
if v is None:
return None
else:
return str(v)
[docs]
class LigandProvenance(DataModelAbstractBase):
model_config = ConfigDict(frozen=True)
isomeric_smiles: str = Field(
..., description="The canonical isomeric smiles pattern for the molecule."
)
inchi: str = Field(..., description="The standard inchi for the input molecule.")
inchi_key: str = Field(
..., description="The standard inchikey for the input molecule."
)
fixed_inchi: str = Field(
...,
description="The non-standard fixed hydrogen layer inchi for the input molecule.",
)
fixed_inchikey: str = Field(
...,
description="The non-standard fixed hydrogen layer inchi key for the input molecule.",
)
[docs]
class TargetIdentifiers(DataModelAbstractBase):
"""
Identifiers for a Target
"""
target_type: Optional[TargetTags] = Field(
None,
description="Tag describing the target type e.g SARS-CoV-2-Mpro, etc.",
)
fragalysis_id: Optional[str] = Field(
None, description="The Fragalysis ID of the target if applicable"
)
pdb_code: Optional[str] = Field(
None, description="The PDB code of the target if applicable"
)
[docs]
class ChargeProvenance(BaseModel):
"""A simple model to record the provenance of the local charging method."""
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
type: Literal["ChargeProvenance"] = "ChargeProvenance"
protocol: dict[str, Any] = Field(
..., description="The protocol and settings used to generate the local charges."
)
provenance: dict[str, str] = Field(
...,
description="The versions of the software used to generate the local charges.",
)
# in case the dictionary of the provenance contains int/float as values, convert them to strings
# as this seems to be the expected behavior
@field_validator("provenance", mode="before")
def cast_provenance_dict(cls, v):
return {k: str(vv) for k, vv in v.items()}
[docs]
class BespokeParameter(BaseModel):
"""
Store the bespoke parameters in a molecule.
Note:
This is for torsions only so far as the units are fixed to kcal / mol.
"""
type: Literal["BespokeParameter"] = "BespokeParameter"
interaction: str = Field(
..., description="The OpenFF interaction type this parameter corresponds to."
)
smirks: str = Field(..., description="The smirks associated with this parameter.")
values: dict[str, float] = Field(
{},
description="The bespoke force field parameters "
"which should be added to the base force field.",
)
units: Literal["kilocalories_per_mole"] = Field(
"kilocalories_per_mole",
description="The OpenFF units unit that should be attached to the values when adding the parameters "
"to the force field.",
)
[docs]
class BespokeParameters(BaseModel):
"""A model to record the bespoke parameters for a ligand."""
type: Literal["BespokeParameters"] = "BespokeParameters"
parameters: list[BespokeParameter] = Field(
[], description="The list of bespoke parameters."
)
base_force_field: str = Field(
...,
description="The name of the base force field these parameters were "
"derived with.",
)