MitoInteract / recovery /scripts /prepare_bindingdb_gold_kd.py
Ethan Troy
feat: add source-aware BindingDB recovery track
9ae8d85
Raw
History Blame Contribute Delete
3.03 kB
#!/usr/bin/env python3
"""Build BindingDB source records and an exact-Kd gold file from pinned zips.
Reads the publisher-verified BindingDB curated-articles export (main TSV),
the Reactant_set_id -> ENTRYID_ASSAYID mapping, and the Assays export from
their ZIP archives, verifies every archive's SHA-256 against the pinned
config, and writes:
- ``source_records.jsonl``: one versioned JSON envelope per valid
single-chain record/measurement (all measurement types and relations),
- ``gold_exact_kd.jsonl``: only exact '=' Kd records with one chain, a
sequence, a canonicalizable SMILES, a positive nM value, and an assay join,
- ``bindingdb_audit.json``: deterministic audit of counts, coverage, and
every rejection reason.
Nothing is aggregated across rows or assays, and Ki/IC50/EC50 values are
never relabeled as Kd.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from mitointeract_recovery import bindingdb_adapter
DEFAULT_CONFIG = Path(__file__).resolve().parents[1] / "config-bindingdb.json"
def load_config(path: Path) -> dict:
config = json.loads(path.read_text())
files = config.get("files")
if not isinstance(files, dict):
raise ValueError(f"{path}: config must contain a 'files' object")
for key in bindingdb_adapter.INPUT_KEYS:
entry = files.get(key)
if not isinstance(entry, dict):
raise ValueError(f"{path}: config files entry {key!r} missing")
for field in ("url", "member", "sha256"):
if not entry.get(field):
raise ValueError(f"{path}: config files[{key!r}] missing {field!r}")
return config
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--main-zip", type=Path, required=True, help="main articles TSV zip"
)
parser.add_argument(
"--mapping-zip",
type=Path,
required=True,
help="rsid -> ENTRYID_ASSAYID map zip",
)
parser.add_argument(
"--assays-zip", type=Path, required=True, help="assay names/descriptions zip"
)
parser.add_argument(
"--config",
type=Path,
default=DEFAULT_CONFIG,
help="pinned release config (URLs, members, SHA-256 checksums)",
)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument(
"--max-source-rows",
type=int,
default=None,
help="process at most this many main-TSV data rows (for smoke runs)",
)
args = parser.parse_args()
if args.max_source_rows is not None and args.max_source_rows < 1:
parser.error("--max-source-rows must be a positive integer")
config = load_config(args.config)
bindingdb_adapter.run_pipeline(
main_zip=args.main_zip,
mapping_zip=args.mapping_zip,
assays_zip=args.assays_zip,
config=config,
output_dir=args.output_dir,
max_source_rows=args.max_source_rows,
)
if __name__ == "__main__":
main()