File size: 3,926 Bytes
bc2a98e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""Safety rule checks: metal blacklist + PMT pre-filter."""
from pathlib import Path

import yaml
from rdkit import Chem
from rdkit.Chem import Descriptors


def _load_metals_config() -> dict:
    config_path = Path(__file__).resolve().parent.parent / "configs" / "metals.yaml"
    with open(config_path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def _load_pmt_config() -> dict:
    config_path = Path(__file__).resolve().parent.parent / "configs" / "thresholds.yaml"
    with open(config_path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)["pmt_criteria"]


def check_metal_safety(metals: list[str]) -> dict:
    """Check metal nodes against black/grey/white list.

    Args:
        metals: list of element symbols (e.g. ["Zn", "Cu"]).

    Returns:
        {
            "tier": "white" | "grey" | "black" | "unknown",
            "flagged_metals": list[str],
            "details": str,
        }
    """
    cfg = _load_metals_config()
    black = set(cfg.get("black_list", []))
    grey = set(cfg.get("grey_list", []))
    white = set(cfg.get("white_list", []))

    flagged = [m for m in metals if m in black]
    if flagged:
        return {
            "tier": "black",
            "flagged_metals": flagged,
            "details": f"Blacklisted metal(s): {', '.join(flagged)}. Hard reject.",
        }

    grey_found = [m for m in metals if m in grey]
    if grey_found:
        return {
            "tier": "grey",
            "flagged_metals": grey_found,
            "details": f"Grey-list metal(s): {', '.join(grey_found)}. Use with caution.",
        }

    white_found = [m for m in metals if m in white]
    unknown = [m for m in metals if m not in white]
    if unknown:
        return {
            "tier": "unknown",
            "flagged_metals": unknown,
            "details": f"Unknown metal(s): {', '.join(unknown)}. Not in any list.",
        }

    return {
        "tier": "white",
        "flagged_metals": [],
        "details": f"All metals ({', '.join(metals)}) are on the white list.",
    }


def check_pmt_pre_filter(linker_smiles: str | None) -> dict:
    """Screen linker SMILES against ECHA PMT criteria.

    Args:
        linker_smiles: SMILES string or None.

    Returns:
        {
            "pmt_pass": bool,
            "flags": list[str],
            "descriptors": dict | None,
        }
    """
    if linker_smiles is None:
        return {
            "pmt_pass": True,
            "flags": ["No linker SMILES available; PMT check skipped."],
            "descriptors": None,
        }

    mol = Chem.MolFromSmiles(linker_smiles)
    if mol is None:
        return {
            "pmt_pass": False,
            "flags": [f"Invalid SMILES: {linker_smiles}"],
            "descriptors": None,
        }

    pmt = _load_pmt_config()

    logp = Descriptors.MolLogP(mol)
    mw = Descriptors.MolWt(mol)
    hbd = Descriptors.NumHDonors(mol)
    hba = Descriptors.NumHAcceptors(mol)

    descriptors = {
        "logP": round(logp, 2),
        "MW": round(mw, 2),
        "HBD": hbd,
        "HBA": hba,
    }

    flags = []
    if logp > pmt["logP_max"]:
        flags.append(f"logP={logp:.2f} > {pmt['logP_max']} (bioaccumulation risk)")
    if mw > pmt["mw_max"]:
        flags.append(f"MW={mw:.1f} > {pmt['mw_max']} (persistence concern)")
    if hbd > pmt["hbd_max"]:
        flags.append(f"HBD={hbd} > {pmt['hbd_max']}")
    if hba > pmt["hba_max"]:
        flags.append(f"HBA={hba} > {pmt['hba_max']}")

    # PFAS check: presence of C-F bonds
    has_cf = any(
        bond.GetBeginAtom().GetSymbol() == "F" or bond.GetEndAtom().GetSymbol() == "F"
        for bond in mol.GetBonds()
    )
    if has_cf:
        flags.append("Contains C-F bonds (potential PFAS concern)")

    return {
        "pmt_pass": len(flags) == 0,
        "flags": flags if flags else ["All PMT criteria passed."],
        "descriptors": descriptors,
    }