Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Computational drug-target interaction prediction and virtual screening
.claude/skills/brycewang-stanford-drug-target-interaction/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 70% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 287% | 0% |
A skill for computational prediction of drug-target interactions (DTI), covering molecular docking, machine learning-based binding affinity prediction, compound library screening, and target identification using cheminformatics and structural biology tools.
| Database | Content | Access | |----------|---------|--------| | ChEMBL | 2.4M compounds, 15M bioactivities | REST API, SQL dump | | BindingDB | 2.8M binding data points | Bulk download, REST API | | DrugBank | 15,000+ drug entries with targets | Academic license | | PDB (Protein Data Bank) | 220,000+ 3D structures | Free download, REST API | | UniProt | 250M+ protein sequences | Free, REST API | | STITCH | Chemical-protein interactions | Free academic access |
pythonfrom chembl_webresource_client.new_client import new_client def get_target_bioactivities(target_chembl_id: str, activity_type: str = "IC50", max_nm: float = 10000) -> list[dict]: """ Retrieve bioactivity data for a protein target from ChEMBL. Returns compounds with measured binding/inhibition values. """ activity = new_client.activity results = activity.filter( target_chembl_id=target_chembl_id, standard_type=activity_type, standard_relation="=", standard_units="nM", ).only([ "molecule_chembl_id", "canonical_smiles", "standard_value", "standard_type", "pchembl_value", "assay_description", ]) filtered = [] for r in results: if r.get("standard_value") and float(r["standard_value"]) <= max_nm: filtered.append({ "molecule_id": r["molecule_chembl_id"], "smiles": r["canonical_smiles"], "activity_type": r["standard_type"], "value_nM": float(r["standard_value"]), "pchembl": float(r["pchembl_value"]) if r.get("pchembl_value") else None, }) return filtered
pythonfrom rdkit import Chem from rdkit.Chem import AllChem, Descriptors, rdMolDescriptors import numpy as np def compute_fingerprints(smiles_list: list[str], fp_type: str = "morgan", radius: int = 2, n_bits: int = 2048) -> np.ndarray: """ Compute molecular fingerprints from SMILES strings. fp_type: 'morgan' (ECFP-like), 'maccs', 'rdkit', 'topological' """ fps = [] for smi in smiles_list: mol = Chem.MolFromSmiles(smi) if mol is None: fps.append(np.zeros(n_bits)) continue if fp_type == "morgan": fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius, nBits=n_bits) elif fp_type == "maccs": fp = rdMolDescriptors.GetMACCSKeysFingerprint(mol) elif fp_type == "rdkit": fp = Chem.RDKFingerprint(mol, fpSize=n_bits) else: fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius, nBits=n_bits) arr = np.zeros(len(fp)) Chem.DataStructs.ConvertToNumpyArray(fp, arr) fps.append(arr) return np.array(fps) def compute_descriptors(smiles: str) -> dict: """Compute physicochemical descriptors for a molecule.""" mol = Chem.MolFromSmiles(smiles) if mol is None: return {} return { "molecular_weight": Descriptors.MolWt(mol), "logP": Descriptors.MolLogP(mol), "hbd": Descriptors.NumHDonors(mol), "hba": Descriptors.NumHAcceptors(mol), "tpsa": Descriptors.TPSA(mol), "rotatable_bonds": Descriptors.NumRotatableBonds(mol), "aromatic_rings": Descriptors.NumAromaticRings(mol), "lipinski_violations": sum([ Descriptors.MolWt(mol) > 500, Descriptors.MolLogP(mol) > 5, Descriptors.NumHDonors(mol) > 5, Descriptors.NumHAcceptors(mol) > 10, ]), }
pythonfrom sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score, average_precision_score def train_dti_classifier(compound_fps: np.ndarray, target_features: np.ndarray, labels: np.ndarray) -> dict: """ Train a DTI classifier using compound-target pair features. compound_fps: molecular fingerprints (n_samples, fp_dim) target_features: protein descriptors (n_samples, target_dim) labels: binary interaction labels (1=interacts, 0=no interaction) """ # Concatenate compound and target features X = np.hstack([compound_fps, target_features]) y = labels skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) metrics = {"auroc": [], "auprc": []} for train_idx, test_idx in skf.split(X, y): model = RandomForestClassifier( n_estimators=500, max_depth=20, n_jobs=-1, random_state=42 ) model.fit(X[train_idx], y[train_idx]) pred_proba = model.predict_proba(X[test_idx])[:, 1] metrics["auroc"].append(roc_auc_score(y[test_idx], pred_proba)) metrics["auprc"].append(average_precision_score(y[test_idx], pred_proba)) return { "mean_auroc": np.mean(metrics["auroc"]), "mean_auprc": np.mean(metrics["auprc"]), "model": model, }
Modern DTI prediction architectures:
| Method | Compound Representation | Target Representation | Architecture | |--------|------------------------|----------------------|-------------| | DeepDTA | SMILES (1D CNN) | Protein sequence (1D CNN) | Concatenation + FC | | GraphDTA | Molecular graph (GCN/GAT) | Protein sequence (CNN) | Graph + sequence fusion | | MolTrans | SMILES (Transformer) | Protein sequence (Transformer) | Cross-attention | | DrugBAN | Molecular graph | Protein graph | Bilinear attention |
pythonimport subprocess def run_autodock_vina(receptor_pdbqt: str, ligand_pdbqt: str, center: tuple, box_size: tuple = (20, 20, 20), exhaustiveness: int = 8) -> dict: """ Run AutoDock Vina for molecular docking. receptor_pdbqt: path to prepared receptor file ligand_pdbqt: path to prepared ligand file center: (x, y, z) coordinates of the binding site center Returns docking scores and poses. """ cmd = [ "vina", "--receptor", receptor_pdbqt, "--ligand", ligand_pdbqt, "--center_x", str(center[0]), "--center_y", str(center[1]), "--center_z", str(center[2]), "--size_x", str(box_size[0]), "--size_y", str(box_size[1]), "--size_z", str(box_size[2]), "--exhaustiveness", str(exhaustiveness), "--num_modes", "9", ] result = subprocess.run(cmd, capture_output=True, text=True) # Parse output for binding affinities scores = [] for line in result.stdout.split("\n"): parts = line.split() if len(parts) >= 4 and parts[0].isdigit(): scores.append({ "mode": int(parts[0]), "affinity_kcal_mol": float(parts[1]), "rmsd_lb": float(parts[2]), "rmsd_ub": float(parts[3]), }) return {"scores": scores, "best_affinity": scores[0]["affinity_kcal_mol"] if scores else None}
Standard benchmarks for DTI prediction:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 20,584 | 27,904 | +36% | 1 | 1 | 0% | 4,723 | 6,221 | +32% | 0 | 0 | — |
case-02 | fail→fail | 24,628 | 16,118 | -35% | 1 | 1 | 0% | 5,021 | 5,918 | +18% | 0 | 0 | — |
case-03 | fail→pass | 16,007 | 9,466 | -41% | 1 | 1 | 0% | 3,018 | 4,133 | +37% | 0 | 0 | — |
case-04 | fail→pass | 13,645 | 5,523 | -60% | 1 | 1 | 0% | 2,401 | 3,318 | +38% | 0 | 0 | — |
case-05 | pass→pass | 12,679 | 12,803 | +1% | 1 | 1 | 0% | 2,532 | 4,682 | +85% | 0 | 0 | — |
case-06 | fail→pass | 11,901 | 7,543 | -37% | 1 | 1 | 0% | 2,338 | 3,750 | +60% | 0 | 0 | — |
case-07 | fail→pass | 20,637 | 19,647 | -5% | 1 | 1 | 0% | 3,344 | 5,677 | +70% | 0 | 0 | — |
case-08 | pass→pass | 17,026 | 20,303 | +19% | 1 | 1 | 0% | 2,725 | 5,753 | +111% | 0 | 0 | — |
case-09 | fail→fail | 17,534 | 18,527 | +6% | 1 | 1 | 0% | 2,858 | 5,238 | +83% | 0 | 0 | — |
case-10 | pass→pass | 5,238 | 4,014 | -23% | 1 | 1 | 0% | 963 | 3,034 | +215% | 0 | 0 | — |
case-11 | pass→pass | 18,457 | 19,096 | +3% | 1 | 1 | 0% | 3,027 | 5,339 | +76% | 0 | 0 | — |
case-12 | pass→pass | 17,582 | 11,017 | -37% | 1 | 1 | 0% | 2,666 | 4,203 | +58% | 0 | 0 | — |
case-13 | fail→pass | 4,615 | 4,143 | -10% | 1 | 1 | 0% | 770 | 2,981 | +287% | 0 | 0 | — |
case-14 | fail→pass | 14,388 | 5,243 | -64% | 1 | 1 | 0% | 2,390 | 3,270 | +37% | 0 | 0 | — |
case-15 | pass→pass | 14,379 | 11,126 | -23% | 1 | 1 | 0% | 2,396 | 4,259 | +78% | 0 | 0 | — |
case-16 | pass→pass | 6,993 | 8,228 | +18% | 1 | 1 | 0% | 992 | 3,868 | +290% | 0 | 0 | — |
case-17 | pass→pass | 6,849 | 6,215 | -9% | 1 | 1 | 0% | 1,037 | 3,279 | +216% | 0 | 0 | — |
case-18 | fail→pass | 14,419 | 11,458 | -21% | 1 | 1 | 0% | 2,265 | 4,336 | +91% | 0 | 0 | — |
case-19 | pass→pass | 8,942 | 5,898 | -34% | 1 | 1 | 0% | 1,204 | 3,191 | +165% | 0 | 0 | — |
case-20 | fail→fail | 34,050 | 25,379 | -25% | 1 | 1 | 0% | 5,497 | 7,296 | +33% | 0 | 0 | — |
case-21 | fail→fail | 22,093 | 23,096 | +5% | 1 | 1 | 0% | 3,977 | 6,592 | +66% | 0 | 0 | — |
case-22 | fail→fail | 19,991 | 19,990 | -0% | 1 | 1 | 0% | 3,179 | 5,609 | +76% | 0 | 0 | — |
case-23 | fail→fail | 19,877 | 27,579 | +39% | 1 | 1 | 0% | 3,454 | 7,353 | +113% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +30 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.