# CASMI 2026 experiment toolkit Reusable data plumbing and a transparent spectral-library baseline for the [Enveda CASMI 2026 Kaggle competition](https://www.kaggle.com/competitions/enveda-CASMI26-molecule-id-mass-spectra). The competition asks for one ranked list of up to 25 SMILES per molecule, not one prediction per spectrum. Kaggle scores molecule-level MRR@25 after RDKit 2026.03.3 tautomer canonicalization and reduction to InChIKey14. The code here implements that matching rule and aggregates evidence across all spectra for a molecule. ## Environment The existing environment is the WSL/Linux uv venv at `casmi/`. Keep uv pointed at it explicitly; otherwise uv will create `.venv` by default. ```bash cd /mnt/c/CASMI-2026 export UV_PROJECT_ENVIRONMENT="$PWD/casmi" export UV_LINK_MODE=copy uv sync --all-groups uv run casmi --help ``` The project pins `rdkit==2026.3.3` to match Kaggle and uses Polars/PyArrow for the 3 GB parquet files, NumPy/SciPy for sparse spectra, and scikit-learn as the base experiment dependency. Pytest and Ruff are development dependencies. ## What is implemented - Lazy full-data profiling and bounded-memory peak-array validation. - Deterministic structure-disjoint splits keyed by `inchikey14`. - Streamed train subset creation by library, instrument, precursor range, and split. - Configurable spectrum curation: intensity floor, precursor cap, top-N peaks, intensity transform, fixed-width bins, and L2 normalization. - Sharded CSR feature caches with matching parquet metadata and a manifest. - A mass/adduct/mode-filtered cosine spectral-library baseline that combines all query spectra at the molecule level. - Official tautomer/InChIKey14 MRR@25 scoring. - Ranked-prediction deduplication and strict Kaggle submission validation. - Exact recovery of the visible public smoke-test labels and creation of a labeled local dev set. Kaggle replaces this visible set during scoring, so it is only a pipeline test. ## First checks ```bash uv run casmi profile --data-dir . --output reports/data_profile.json uv run casmi validate --data-dir . --train-max-rows 100000 \ --output reports/validation.json uv run casmi make-split --train train.parquet \ --output artifacts/structure_split.parquet --valid-fraction 0.1 --seed 2026 ``` Use `--train-max-rows all` for a complete peak-array validation pass. The full test file is always checked. ## Build an experiment subset Start with an instrument-matched subset instead of immediately featurizing all 2.54 million spectra: ```bash uv run casmi make-subset \ --train train.parquet \ --output artifacts/enveda_train.parquet \ --library enveda-180 \ --split-map artifacts/structure_split.parquet \ --split-name train \ --precursor-mz-min 150 \ --precursor-mz-max 1200 uv run casmi build-features \ artifacts/enveda_train.parquet artifacts/enveda_train_features \ --min-relative-intensity 0.01 --max-peaks 128 --bin-width 0.1 ``` Feature caches are sharded, so a full cache does not require all sparse rows in memory. Each cache records its preprocessing configuration; retrieval refuses to combine mismatched train and query caches. ## Public end-to-end smoke test The checked-in code was exercised with this sequence: ```bash uv run casmi recover-public-labels --data-dir . \ --output artifacts/public_test_labels.parquet uv run casmi make-public-devset --data-dir . \ --labels artifacts/public_test_labels.parquet \ --output artifacts/public_devset.parquet uv run casmi build-features artifacts/public_devset.parquet \ artifacts/public_dev_features uv run casmi build-features test.parquet artifacts/public_query_features uv run casmi retrieve artifacts/public_dev_features \ artifacts/public_query_features artifacts/public_predictions.parquet \ --sample-submission sample_submission.csv \ --submission-output submissions/public_smoke_submission.csv uv run casmi score artifacts/public_test_labels.parquet \ artifacts/public_predictions.parquet uv run casmi check-submission submissions/public_smoke_submission.csv \ --sample sample_submission.csv ``` That deliberately self-retrieving smoke test reaches MRR@25 = 1.0 and produces 400 valid rows with 25 candidates each. It proves the data, aggregation, canonical matching, ranking, and submission path. It is not an estimate of hidden-test performance. ## Evaluation protocols Use the split table differently depending on the experiment: - A same-structure spectrum holdout is appropriate for measuring spectral library search, corresponding to the competition's novelty class 1. - The provided `make-split` output is structure-disjoint and is appropriate for learned spectrum encoders, formula/fingerprint predictors, retrieval against an external structure database, and de novo models. A training-library-only retriever cannot recover a deliberately unseen validation structure. - Keep molecule spectra together in every evaluation and compute MRR at the molecule level. The included retrieval baseline only proposes structures present in its cached training library. It is therefore a useful class-1 baseline and infrastructure check, not a complete class-2/class-3 solution. Known structures without public spectra require a mass/formula-constrained candidate database such as PubChem or COCONUT; genuinely novel structures require a de novo generator. ## Measured local data characteristics See [reports/data_notes.md](reports/data_notes.md) for the measured summary and quality findings. The machine-readable reports are `reports/data_profile.json` and `reports/validation.json`. ## Development ```bash uv run ruff check . uv run pytest ``` Generated feature caches, subsets, and submissions live under `artifacts/` and `submissions/` and are ignored by Git. The original parquet files remain untouched and are also ignored so the 3 GB training file is not committed.