Structures you enter there are sent to the hosting provider. If you are screening proprietary or unpublished structures, run the application locally instead (see Quick start) — nothing then leaves your machine.
Citing this work. Cite the DOIs above rather than the GitHub URL. The archives are permanent and version-locked; a repository URL can move or be deleted. Cite the version DOI, not the "all versions" one — the latter follows whichever release is newest, so it cannot identify what produced a particular result. The data archive (366 MB) holds the curated dataset, descriptor matrix and trained models that are too large for GitHub. Every published result was produced by code release v1.0.0. v1.0.2 is the release to cite: no pipeline step differs, so it reproduces every result identically, but it corrects the workflow figure's claim that SMOTE was applied and adds the applicability-domain warning for small molecules.
Note on what is and is not in this repository. The code, models needed to run the application, figures, tables and manuscript are all here (~27 MB). The large intermediate arrays and datasets — about 8 GB — are excluded by.gitignoreand are archived separately on Zenodo. Re-running the pipeline regenerates every one of them from PubChem. See Reproducing from scratch below.
.venv. To use the trained model:If you rebuild the environment from scratch, read the Streamlit note under "Documented deviations" first. Two issues stop the web app from starting on a fresh install, and both are already fixed in this copy.
GHS01_Explosive GHS01 Exploding bomb Explosive GHS02_Flammable GHS02 Flame Flammable GHS03_Oxidising GHS03 Flame over circle Oxidiser GHS04_CompressedGas GHS04 Gas cylinder Compressed gas GHS05_Corrosive GHS05 Corrosion Corrosive GHS06_AcuteToxicity GHS06 Skull and crossbones Acute toxicity GHS07_Irritant GHS07 Exclamation mark Irritant / harmful GHS08_HealthHazard GHS08 Health hazard Serious health hazard GHS09_Environmental GHS09 Environment Environmental hazardGHS07_HealthHazard, GHS08_Environmental and GHS09_Irritant, which rotates the descriptive suffixes relative to the UN scheme. The data were always bound to the numeric pictogram code and were therefore always correct — only the three labels were wrong. They have been renamed:GHS07_HealthHazard GHS07_Irritant GHS08_Environmental GHS08_HealthHazard GHS09_Irritant GHS09_Environmentalsrc/migrate_column_names.py and the mapping is preserved in STEP2_ghs_label_schema.csv so that anyone holding an earlier copy of the outputs can translate between the two.src/step1_environment.py Environment setup and verification 2 src/step2_data_collection.py Harvest GHS annotations from PubChem 3 src/step3_data_cleaning.py Validate, deduplicate, reconcile labels 4 src/step4_descriptors.py Compute 1218 molecular descriptors 5 src/step5_scaffold_split.py Bemis-Murcko scaffold split (80/10/10) 6 src/step6_imbalance.py Class weights and metric selection. SMOTE is implemented but was not applied at this dataset size — see STEP6_smote_report.csv 7 src/step7_model_training.py Random Forest, XGBoost, SVM 8 src/step8_hyperparameter_tuning.py RandomizedSearchCV + refit on train 9 src/step9_evaluation.py Metrics, thresholds, bootstrap CIs, plots 10 src/step10_shap_analysis.py SHAP global and per-compound explanation 11 src/step11_malaysia_validation.py Malaysian sectors + Johor 2019 12 app.py, predict_ghs.py Web app and command-line tool 13 src/step13_publication.py Figures, tables, abstract, methodsrun_full_pipeline.py Runs Steps 4–13 on the full 243,323-compound dataset src/controlled_size_experiment.py Does more training data help? Same test set, same hyperparameters, only size varies src/learning_curve.py Learning curve within a fixed dataset src/applicability_domain.py Where the model can be trusted: performance by molecule size src/build_supporting_information.py Compiles Tables S0–S5 and File S1 into one PDF src/redraw_figures_9_10.py Redraws Figures 9 and 10 from saved results, without retraining src/step4_full_from_colab.py Builds the full feature matrix from cached Colab descriptors src/migrate_column_names.py One-off rename of the three mislabelled GHS columns src/verify_interface.py 27 end-to-end checks on the app and CLI src/verify_deliverables.py Audits that every required output exists GHS_full_dataset_colab.ipynb Google Colab notebook for a full-dataset runsrc/step5_scaffold_split.py because both fail silently.logs/ and reproduced in FINAL_PROJECT_SUMMARY_REPORT.pdf.0x3, no admin elevation for the chained MSIs) and conda was unavailable, so the proposal's conda fallback could not be used. A standalone CPython 3.11.15 was installed with uv, which needs no Windows installer.CanonicalSMILES to ConnectivitySMILES in 2025. The new property names are requested explicitly so that isomeric SMILES are obtained.src/controlled_size_experiment.py) showed this was a real limitation, not a harmless one: mean AUC rises from 0.8187 to 0.8738 (+0.0551, four times the confidence interval) on going to the full 194,658-compound training set, and every class improves. All results now use the complete 243,323-compound dataset.max_depth=None for 200 trees × 9 classes would need several GB more than the machine has. Depth is capped and a minimum leaf size imposed.class_weight='balanced', which corrects the imbalance twice. Class weighting is used for the primary models and SMOTE is run as a separate ablation, so the effect is measured rather than assumed.n_iter=30 with 5-fold CV was measured at well over a day. A timing probe sets the largest n_iter that fits a fixed wall-clock budget, with 3-fold CV.RandomizedSearchCV.best_estimator_ is fitted on whatever data the search saw — the validation set. Using it as the final model would waste 90% of the training data and leak validation information into the reported scores. The winning settings are taken from the search and a fresh model is fitted on the training set.~/.streamlit/credentials.toml containing an empty email, and by headless = true in .streamlit/config.toml.starlette is pinned. Streamlit 1.61.0 declares starlette<2,>=0.46.0, but that range is too permissive. Starlette 1.4.0 added a required keyword-only argument thread_minimum_size to GZipResponder.__init__, which Streamlit's own gzip middleware subclasses without passing. The server starts and reports itself healthy, then returns HTTP 500 for every request:pip install streamlit picks the newest Starlette and reproduces the fault. The constraint starlette<1.0 is recorded in STEP1_environment_requirements.txt, and Step 1's smoke tests now check the GZipResponder signature so a broken combination is caught at setup rather than at first page load:.streamlit/config.toml also sets gatherUsageStats = false — this is a chemical safety tool, and no information about the chemicals a user submits should leave the machine.GHS04_CompressedGas Compressed gas 0.998 0.842 GHS03_Oxidising Oxidiser 0.997 0.828 GHS01_Explosive Explosive 0.973 0.534 GHS02_Flammable Flammable 0.956 0.698 GHS05_Corrosive Corrosive 0.868 0.521 GHS08_HealthHazard Serious health hazard 0.838 0.381 GHS09_Environmental Environmental hazard 0.831 0.347 GHS06_AcuteToxicity Acute toxicity 0.783 0.272 GHS07_Irritant Irritant / harmful 0.767 0.228[!#6;!#1]~[!#6;!#1] — two directly bonded heteroatoms — is the top predictor for both explosives (r = +0.96) and oxidisers (r = +0.97). That is the structural signature of nitro, nitrate, peroxide and azide groups, recovered by the model without being told.random, NumPy, scikit-learn, XGBoost and every resampling procedure. The exact environment is pinned in STEP1_environment_requirements.txt. Steps 3 to 10 and 13 are deterministic: re-running them on the saved data reproduces every number exactly.malaysia_validation/STEP11_malaysian_chemicals_raw_*.csv instead of re-querying.Posted Aug 28, 2026
XGBoost model predicting all 9 GHS hazard pictograms from molecular structure, with SHAP explainability. Deployed on Streamlit; now a paid report service.