Pick the right PDB entry for structure-based virtual screening (SBVS) — with a score you can drill into, not just "the one with the best resolution".
pdb-select pulls every experimental structure of a target from RCSB PDB, ranks them with a
six-dimensional, fully explainable heuristic score, and hands the shortlist to an AI/human review
step that fixes what heuristics can't judge. The output is a ranked table plus a JSON file where
every number traces back to its evidence (per-ligand classification, coverage source, resolution
basis, quality flags).
Selecting a starting structure by resolution alone is a trap. Real failures this tool was built to catch, all found in actual runs:
| Failure | What happens without a structured score |
|---|---|
A 102 Da crystallisation additive outranks a confirmed drug. Malonate (MLI) is not a drug, but "any unlisted organic molecule < 500 Da" scores the same as a validated drug. |
Your top hit is a structure with a buffer molecule in a surface pocket. |
Cryo-EM entries get scored on the X-ray curve. RCSB returns ELECTRON MICROSCOPY as the method string — a naive "em" in method test never fires, so 3 Å cryo-EM silently loses to 3 Å X-ray. |
Cryo-EM structures (often the only ones for large complexes) are systematically under-ranked. |
A single page request truncates the structure list. KRAS has 568 structures; a rows=200 query silently drops 368 of them before scoring even starts. |
The best structure may never be considered. |
| A vague gene query resolves to the wrong protein. Fuzzy gene→UniProt lookup returns an empty gene name and nobody notices. | You rank the wrong target. |
Every one of those is fixed and verified against live endpoints in this version (there is no automated test suite yet — that is on the roadmap, and the examples/ output is a real run).
- Python ≥ 3.9
- Zero third-party dependencies. Standard library only (
urllib,json,argparse). - Network access to
search.rcsb.org,data.rcsb.org,rest.uniprot.org. No API key needed — only public, unauthenticated endpoints are used.
git clone https://github.com/<you>/pdb-select.git
cd pdb-select
# rank every structure of a target by gene symbol (human by default)
python scripts/pdb_scorer.py --gene MYH7 --top 10
# by UniProt accession, straight to a JSON file
python scripts/pdb_scorer.py --uniprot P12883 --output myh7_result.json
# clean JSON on stdout, safe to pipe
python scripts/pdb_scorer.py --gene SOD1 --no-table | jq '.results[0]'| Flag | Meaning |
|---|---|
--gene |
Gene symbol (e.g. MYH7). Resolved with gene_exact + species filter, so it won't drift to the wrong protein. |
--uniprot |
UniProt accession (e.g. P12883). Skips gene resolution. |
--taxon |
NCBI taxonomy id used with --gene. Default 9606 (human). |
--top |
How many rows to print in the terminal table. Default 20. The JSON always contains all scored structures. |
--output |
Write the JSON result to a file. |
--no-table |
Suppress the table; JSON goes to stdout instead (pipe-friendly). |
排名 PDB 分辨率 方法 配体 特异性 覆盖 分辨 质量 总分 标题
------------------------------------------------------------------------------------------------------
1 4PA0 2.25Å X-RAY DIFF +1000 +300 +350 +90 0 +1740 Omecamtiv Mercarbil binding site on the Human Beta
2 9GZ2 2.90Å ELECTRON M +1000 -100 +350 +82 0 +1332 Beta-cardiac heavy meromyosin motor domain in the
3 9YP9 3.00Å ELECTRON M +1000 -100 +350 +80 0 +1330 Cryo-EM structure of human beta-cardiac myosin bou
4 9YR7 3.00Å ELECTRON M +1000 -100 +350 +80 0 +1330 Cryo-EM structure of human beta-cardiac myosin bou
5 9YRH 3.80Å ELECTRON M +1000 -100 +350 +60 0 +1310 (…)
6 9YP4 4.50Å ELECTRON M +1000 -100 +350 +42 0 +1292 (…)
7 9GZ1 3.70Å ELECTRON M +1000 -500 +350 +62 0 +912 Beta-cardiac myosin interacting heads motif comple
8 9HTF 2.48Å X-RAY DIFF +400 +200 +200 +81 0 +881 (…)
It also catches what the eye misses — rank 7 (9GZ1) is a valid, well-resolved cryo-EM structure
that is correctly pushed down as an interacting heads motif assembly (specificity −500), i.e. a
different biological state than a screening-ready motor domain.
The full JSON behind this run is in examples/myh7_result.json.
Total score = sum of six dimensions.
| Dimension | Rule |
|---|---|
| Ligand | Known drug (explicit table) +1000 · Drug-like small molecule (150–600 Da) +700 · Large organic (>600 Da) +500 · Nucleotide/cofactor (ADP, ATP, NAD, FAD, HEM, SAM, …) +400 · Additive/metabolite (<150 Da, e.g. malonate, acetate, DMSO) +200 · Detergent/glycan +200 · None 0 |
| Specificity | Monomer with ligand +300 · Monomer without ligand +200 · Native dimer +100 · Mid-size complex −100 · Large complex −500 · Very large assembly −2000 |
| Coverage | ≥80% +500 · ≥50% +350 · ≥30% +200 · <30% 0 |
| Resolution | X-ray: 100 − (res − 2.0) × 40 · Cryo-EM: 80 − (res − 3.0) × 25 · clamped to [20, 100] |
| Structural quality | R-free > 0.3 −50 · Ramachandran outliers > 5% −50 |
| Ligand placement | Not computed by the script (always 0) — requires 3D geometry + functional-site annotation, so it is explicitly deferred to AI/human review |
- Known drug and "drug-like" are scored separately, on purpose. The single most dangerous scoring bug is letting any unlisted small molecule take the full drug bonus — a 102 Da crystallisation additive then ties with a validated drug, and the whole ranking becomes untrustworthy. Drug evidence must come from an explicit table; molecular weight is only a fallback tier, one step below. Cofactors (nucleotides, hemes, SAM…) get their own tier because they usefully define a pocket without being drug evidence.
- Exclusion lists can never be exhaustive, so they are not the primary mechanism. They only remove things that are definitely not drug evidence (ions, buffers, cryoprotectants); everything else falls through to the molecular-weight + chemistry-type tiering.
- "Has a real ligand" is bound to the tier (≥ drug-like), and it simultaneously drives the specificity branch (+300 vs +200). One definition, no drift.
- Complexity is a negative signal for SBVS. A ligand bound in a very large assembly is weak evidence of a druggable isolated pocket, hence −500 / −2000.
- The two resolution curves are empirical, not physical. They encode "what is good enough for docking", with cryo-EM offset by 1 Å because its nominal resolution overstates local pocket quality. ELECTRON CRYSTALLOGRAPHY intentionally stays on the X-ray curve.
The script is deliberately not the final authority. SKILL.md defines the review pass that
every run should go through:
- Specificity mis-classification — title keywords and chain counts misread domain-only constructs, HMM fragments, interacting-heads motifs and actin-containing filaments.
- Ligand curation — promote unlisted real drugs into
KNOWN_DRUG_LIGANDS, demote additives/buffers, and handle covalent/peptide ligands outside the small-molecule tiers. - Coverage correction — fusions and truncated constructs under-estimate coverage; chains that only resolve part of the protein over-estimate it.
- Ligand placement — the dimension the script deliberately leaves at 0: check that the ligand sits within ~5 Å of a functional-site residue rather than on an irrelevant surface patch.
| Stream | Content |
|---|---|
| stdout | The ranking table (suppressed by --no-table) |
| stderr | Progress and diagnostics |
| JSON | Written to --output FILE; with no --output it goes to stdout only when --no-table is set |
The table and the JSON never share stdout, so | jq is always safe. Each JSON result carries
scores (per-dimension), ligand_hits (every ligand with its tier and score), specificity,
coverage_pct, resolution_basis (xray/cryoem) and quality_detail.
The script makes no assumption about downstream format — read the JSON and do whatever you want with it (write a database, fill your own shortlist tables, batch over many targets).
Add your own drugs and exclusion rules at the top of scripts/pdb_scorer.py:
KNOWN_DRUG_LIGANDS = {
"XB2": "mavacamten",
"MY1": "my-own-inhibitor", # comp_id -> name, scored +1000
}RCSB comp_ids are the PDB chemical component codes (the same ones you see in HETATM records).
The repo root is also a valid Agent Skill:
SKILL.md describes when to use it, how to run the scorer, and the mandatory review pass.
Install it into your agent's skills directory (e.g. ~/.claude/skills/, ~/.hermes/skills/)
and the agent will pick the script up automatically. The script works perfectly well as a plain
CLI too — the skill is an add-on, not a requirement.
- Externalise scoring weights (
--weights/ config file) so the model can be tuned without editing code - Automated smoke test across a few targets
- Optional PDB entry download + ligand-pocket distance check (implements the deferred dimension)
Extracted from the structure-selection step of an internal rare-disease-target virtual screening workflow, cleaned up and released as a standalone tool. Built and tested against live RCSB and UniProt endpoints, not against mocks.
干什么用的: 给定靶点,把 RCSB PDB 上全部相关结构按六维评分排序,挑出最适合做基于结构的 虚拟筛选(SBVS)的起始结构——不只是"分辨率最高的那个",而是一份每个分数都能下钻到依据的排名。
为什么需要它: 按分辨率盲选有三个真坑:① 丙二酸(丙二酸根,MW 102 的结晶添加剂)会和
mavacamten 这种确证药物同分;② RCSB 的冷冻电镜方法字符串是 ELECTRON MICROSCOPY,按 em
子串判断永远不命中,导致 cryo-EM 结构被按 X 光曲线低估;③ 单页查询会把 KRAS 的 568 个结构
截断到 200 个。这三个坑本版本全部修掉并有回归验证。
怎么用: python scripts/pdb_scorer.py --gene MYH7 --top 10,零第三方依赖,只要 Python ≥ 3.9。
设计要点: 脚本只做启发式粗筛,必须再走一遍 SKILL.md 里的 AI 复审(特异性分类修正、
配体补充、覆盖度修正、以及脚本故意不做的配体定位判断——配体是否占据功能位点)。
MIT — see LICENSE.