# Enveda-180: High throughput generation of a large open multimodal small molecule LC-MS/MS and ion mobility library

Zenodo License Python: >=3.11 Linting: Ruff

This repository contains the code and notebooks for the Enveda-180 project, which generates a large open multimodal MS/MS + ion mobility spectral library for drug-like small molecules from the Enamine REAL space. The project is a collaboration between Enveda and the scientific community, and we welcome contributions and feedback. ## Citation > Krettler, C., Kind, T., DeBloois, E., Killian, M., Prince, J., Taylor, J., > Gadiya, Y., Simpson, P., Domingo-Fernández, D., Allen, A., Colluru, V., > Healey, D. (2026). Enveda-180: generation of a large open multimodal MS/MS > and ion mobility spectral library for drug-like small molecules. > *ChemRxiv*. https://doi.org/10.26434/chemrxiv.15004319/v1 ## Repository layout ``` enveda-180/ data_processing/ # Python package: ms_prepper + extraction + curation scripts/ # CLI runners (build manifest, run ms_prepper, run pipeline) notebooks/ # process.ipynb — papermill-driven pipeline entry point tests/ # Unit tests docs/ # Design / agent notes pyproject.toml # Hatchling build; the package installs as `data_processing` README.md, LICENSE ``` The package lives at `data_processing/` and is installed under the import name `data_processing`. ## Install ```bash uv sync # or, with pip: pip install -e . ``` Python 3.11–3.13 is supported. ## Pipeline overview The `data_processing` package has two halves: 1. **`data_processing.ms_prepper`** — read a Bruker `.d` folder and write four per-msrun parquets (`ms1`, `ms2`, `ms2_event`, `frame`). 2. **`data_processing.{extraction, curation, pipeline}`** — targeted extraction of MS/MS spectra for each compound-pool member across all common adducts, plus quality-curation helpers. ### Adducts targeted by the extraction pipeline The default adduct list is surfaced at the package root: ```python from data_processing import COMMON_ADDUCTS, CATION_ADDUCTS ``` Positive mode: `[M+H]+`, `[M+Na]+`, `[M+NH4]+`, `[M+K]+`, `[2M+Na]+`, `[2M+H]+`, `[M-H2O+H]+`, `[M-H4O2+H]+` Negative mode: `[M-H]-`, `[M+Cl]-`, `[M+FA-H]-`, `[M+Br]-`, `[M+Hac-H]-`, `[2M-H]-`, `[2M+FA-H]-`, `[2M+Hac-H]-`, `[M-H2O-H]-` Cation (natively charged) mode: `[M]+` All exact monoisotopic offsets are computed from the adduct name strings via `molmass` in `data_processing.extraction.load.compute_adduct_metadata`. To add an adduct, append its name to `COMMON_ADDUCTS` (or `CATION_ADDUCTS`) in [data_processing/extraction/load.py](data_processing/extraction/load.py). ## Quick start ### 1. Convert a Bruker `.d` to parquets ```bash create-signal-tables --input /path/to/sample.d --output-dir /data/enamine_prep ``` or from Python: ```python from data_processing.ms_prepper import pipeline pipeline.run(input_path="/path/to/sample.d", output_dir="/data/enamine_prep") ``` This writes four parquets named `{msrun_id}.{ms1,ms2,ms2_event,frame}.parquet`. ### 2. Configure where the inputs and outputs live Set environment variables (a `.env` file at the repo root is auto-loaded): ```bash ENAMINE_PROCESSING_DATA_PATH=/data/data_processing ENAMINE_PREP_DATA_PATH=/data/data_processing/input/enamine_prep ENAMINE_PROCESSING_LOG_LEVEL=INFO ``` Expected input layout: ``` ${ENAMINE_PROCESSING_DATA_PATH}/ input/ enamine_prep/ {msrun_id}.ms1.parquet {msrun_id}.ms2.parquet {msrun_id}.ms2_event.parquet {msrun_id}.frame.parquet preprocessed/ / compound_metadata.parquet compound_pool_id_to_msrun_ids_df.parquet output/ / / extracted/... annotated/... annotated_matched/... final/... ``` ### 3. Run the extraction + curation pipeline ```python import asyncio from data_processing import pipeline asyncio.run(pipeline.run( name="my-batch", version="v0.1", log_path="/tmp/enamine-processing-run", )) ``` Or call the headline extraction function directly: ```python from pathlib import Path from data_processing.pipeline import extract_spectra spectra = extract_spectra( compound_pool_id_to_msrun_ids_df_path=Path(".../compound_pool_id_to_msrun_ids_df.parquet"), compound_metadata_df_path=Path(".../compound_metadata.parquet"), export_folder=Path(".../output/my-batch/v0.1"), version="v0.1", ) ``` ## Running the 14 enamine batches end-to-end `scripts/` contains the end-to-end runners used to produce the Enveda-180 library: ```bash # 1. Build a manifest mapping MSB ids -> raw .d filenames for every batch. # (Enveda-internal: loads data from Databricks.) python scripts/build_run_manifest.py --all # 2. Download the .d folders listed in the manifest to a local directory. # (Use whatever tool your environment provides — azcopy, gsutil, rclone, ...) # 3. Run ms_prepper on every .d folder for one batch. python scripts/run_ms_prepper.py \ --batch enamine-part1 \ --raw-dir /scratch/enamine/raw \ --output-dir $ENAMINE_PREP_DATA_PATH \ --skip-existing # 4. Run the extraction + curation pipeline. python scripts/run_pipeline.py --batch enamine-part1 --version v0.1 ``` External users without Enveda Databricks access should skip step 1 and hand-author `raw_file_manifest.parquet` with at least the columns `msrun_id, raw_file`. ## Notebooks [notebooks/process.ipynb](notebooks/process.ipynb) is the canonical papermill-driven entry point for running the extraction + curation pipeline against one preprocessed batch. It is the notebook-flavoured equivalent of `scripts/run_pipeline.py` — pick whichever you prefer. ```bash name="my-batch" version=$(date +%b-%d-%Y) folder_path="$ENAMINE_PROCESSING_DATA_PATH/logs/${name}" log_path="${folder_path}/${version}" mkdir -p "$folder_path" papermill notebooks/process.ipynb "${log_path}.ipynb" \ -p name "$name" \ -p version "$version" \ -p log_path "$log_path" ``` ## Public configuration surface Every tunable knob is exposed in `data_processing.config` and re-exported from the package root: | Name | Source | Description | | --- | --- | --- | | `COMMON_ADDUCTS` | `extraction.load` | Adducts searched per compound. | | `CATION_ADDUCTS` | `extraction.load` | Adducts for natively charged species. | | `DATA_PATH` | `extraction.constants` | Root data directory (`$ENAMINE_PROCESSING_DATA_PATH`). | | `ENAMINE_PREP_DATA_PATH` | `extraction.constants` | ms_prepper parquet directory. | | `ENAMINE_PREP_VERSION` | `extraction.constants` | Stamped on every extracted spectrum. | | `INDEX_COLUMNS` | `curation.constants` | `["msrun_id", "msms_aggregate_id"]`. | | `HASH_PRECISION` | `curation.constants` | Spectrum-hash mz rounding (default `1`). | | `TOP_N`, `TOLERANCE_DA` | `curation.constants` | Library-match knobs. | | `EXPORT_KEYS`, `DS8_EXPORT_KEYS` | `curation.constants` | Columns written to disk. | ## SIRIUS annotation SIRIUS annotation is not invoked automatically. Run SIRIUS yourself () on the `extracted` jsonl output and merge the resulting columns into the dataframe before continuing. All downstream curation code reads SIRIUS columns defensively, so the pipeline still completes without them. ## License MIT. See [LICENSE](LICENSE).