Skip to main content

Building a Drug Discovery Screening Pipeline

This walkthrough guides you through building an end-to-end compound screening workflow. You will evaluate drug candidates through molecular analysis, drug-likeness screening, ADMET prediction, toxicity profiling, PK analysis, and clinical trial design.


What You Will Build

A protocol that:

  1. Calculates molecular weight from a chemical formula
  2. Screens compounds against Lipinski's Rule of Five
  3. Classifies compounds using the Biopharmaceutical Classification System
  4. Predicts ADMET properties
  5. Analyzes toxicity (LD50, therapeutic index)
  6. Runs the full screening pipeline on multiple known compounds
  7. Queries the compound database by target and therapeutic class
  8. Profiles PK properties including dose, interactions, and organ-impairment adjustments
  9. Designs a multi-arm clinical trial with power-based sample sizing
  10. De-identifies participant data for regulatory submission

Modules used: med.chem, med.pk, med.research, med.lab


Prerequisites

  • MOISSCode v2.0.0 or later installed
  • Basic familiarity with MOISSCode syntax (see Language Guide)

Step 1: Create the File and Import Modules

Create drug_discovery.moiss and declare your dependencies:

import med.chem;
import med.pk;
import med.research;
import med.lab;

These four modules cover the entire drug development pipeline from lead identification through clinical trial design.


Step 2: Start the Protocol and Analyze Molecular Properties

Open the protocol block and calculate the molecular weight of your candidate compound:

protocol DrugScreeningPipeline {
input: Patient p;

let mw = med.chem.molecular_weight("C17H19NO3");

molecular_weight parses the chemical formula and sums atomic masses. This returns:

[Let] mw = {type: CHEM_MW, formula: C17H19NO3, molecular_weight: 285.34,
composition: {C: 17, H: 19, N: 1, O: 3}}

Why this matters: Molecular weight is the first gate in drug-likeness screening. Compounds over 500 Da have difficulty crossing cell membranes via passive diffusion.


Step 3: Run Lipinski's Rule of Five

Screen the compound for oral bioavailability using Lipinski's criteria:

    let lipinski = med.chem.lipinski_check(285.34, 1.2, 1, 4);

The four parameters are: MW, LogP, hydrogen bond donors (HBD), and hydrogen bond acceptors (HBA).

RuleLimitOur CompoundPass?
MW500 or below285.34Yes
LogP5 or below1.2Yes
HBD5 or below1Yes
HBA10 or below4Yes

Zero violations = drug-like. The function returns:

[Let] lipinski = {violations: 0, drug_like: True, assessment: PASS - drug-like}

Step 4: Classify with BCS

Determine the formulation strategy using the Biopharmaceutical Classification System:

    let bcs = med.chem.bcs_classify("high", "high");
BCS ClassSolubilityPermeabilityOur Result
IHighHighStandard oral formulation
IILowHighMicronization, lipid formulation
IIIHighLowPermeation enhancers
IVLowLowIV route or advanced delivery

Our compound is Class I - the easiest to formulate as an oral tablet.


Step 5: Screen ADMET Properties

Predict absorption, distribution, metabolism, excretion, and toxicity:

    let admet = med.chem.admet_screen(285.34, 1.2, 45.0, 3);

The parameters are: MW, LogP, polar surface area (PSA), and rotatable bonds. The function applies published rule-based filters:

  • PSA under 140 A^2 suggests good absorption
  • LogP between -0.4 and 5 suggests good membrane permeability
  • PSA under 90 A^2 suggests CNS penetration

Step 6: Analyze Toxicity

Run toxicity screening against the compound database:

    let morphine_tox = med.chem.tox_screen("morphine");
let aspirin_tox = med.chem.tox_screen("aspirin");

This returns LD50, therapeutic index, and safety classification:

[Let] morphine_tox = {ld50_mg_kg: 335, therapeutic_index: 22.3,
safety: WIDE therapeutic window}

Therapeutic index = LD50 / effective dose. A TI over 10 means a wide safety margin. Drugs like warfarin (TI around 3-5) require careful monitoring.


Step 7: Run the Full Pipeline

Screen multiple compounds through the complete pipeline in one call each:

    let aspirin_screen = med.chem.screen_compound("aspirin");
let atorvastatin_screen = med.chem.screen_compound("atorvastatin");
let morphine_screen = med.chem.screen_compound("morphine");

screen_compound runs Lipinski, BCS, ADMET, and toxicity checks and returns a combined verdict:

[Let] aspirin_screen = {verdict: GOOD CANDIDATE,
screening_scores: {lipinski: 1, bcs: 1, admet: 1, toxicity: 0}}

Scoring: 1 = pass, 0 = fail. A compound needs to pass most checks to receive a "GOOD CANDIDATE" verdict.


Step 8: Query the Compound Database

Search for compounds by molecular target or therapeutic class:

    let aspirin_profile = med.chem.lookup("aspirin");
let metformin_profile = med.chem.lookup("metformin");
let cox_inhibitors = med.chem.search_by_target("COX");
let nsaids = med.chem.search_by_class("NSAID");
let antibiotics = med.chem.search_by_class("antibiotic");
let all_compounds = med.chem.list_compounds();

The built-in database contains 12 compounds. search_by_target("COX") returns aspirin and ibuprofen - both COX-1/COX-2 inhibitors.

Why this matters: Comparator analysis is essential for regulatory submissions. You need to demonstrate how your candidate compares to existing therapies targeting the same pathway.


Step 9: Profile PK Properties

Use med.pk to analyze pharmacokinetic behavior:

    let morphine_dose = med.pk.calculate_dose("Morphine", 70);
let interactions = med.pk.check_interactions("Morphine", ["Midazolam"]);
let renal_adj = med.pk.renal_adjust("Morphine", 45);
let hepatic_adj = med.pk.hepatic_adjust("Morphine", "B");
let vanco_tdm = med.pk.therapeutic_range("Vancomycin");
let vanco_trough = med.pk.trough_estimate("Vancomycin", 1000, 12, 70);
CheckPurposeResult
calculate_doseWeight-based dosing10 mg IV
check_interactionsDrug-drug interactionMorphine + Midazolam: CNS depression risk
renal_adjusteGFR-based dose reduction50% dose at eGFR 45
hepatic_adjustChild-Pugh based adjustment50% dose for Class B
therapeutic_rangeTDM window for monitoringVancomycin trough 15-20 mcg/mL
trough_estimatePredicted trough levelApprox. 14.2 mcg/mL

Step 10: Design the Clinical Trial

Use med.research to plan the trial:

    let sample = med.research.sample_size(0.4, 0.05, 0.90);
let randomization = med.research.randomize(200, 3, [2, 1, 1]);
let strata = med.research.stratify(200, "age_group", ["18-40", "41-65", "65+"]);

Sample size calculation: For an effect size of 0.4, alpha 0.05, and 90% power:

  • 133 patients per group, 266 total

Randomization: 200 patients across 3 arms with 2:1:1 allocation:

  • Arm A (treatment): 100 patients
  • Arm B (active comparator): 50 patients
  • Arm C (placebo): 50 patients

Stratification ensures balanced enrollment across age groups (18-40, 41-65, 65+).


Step 11: De-identify and Finalize

Prepare data for regulatory submission:

    med.research.deidentify(p);

alert "Drug screening pipeline complete" severity: info;
alert "3 compounds screened, 2 targets queried" severity: info;
alert "Clinical trial: 200 patients, 3 arms, stratified" severity: info;
}

De-identification uses the Safe Harbor method: names are SHA-256 hashed, ages over 89 become "90+", timestamps are fuzzed.


Running the Protocol

moiss run drug_discovery.moiss -v

The protocol generates 25 library calls across 4 modules:

▸ Library Calls (25)
────────────────────────────────────────────────────
◈ med.chem - 15 calls
◈ med.pk - 6 calls
◈ med.research - 4 calls

Output Summary

▸ Clinical Alerts
────────────────────────────────────────────────────
ℹ️ [INFO] Drug screening pipeline complete
ℹ️ [INFO] 3 compounds screened, 2 targets queried
ℹ️ [INFO] Clinical trial: 200 patients, 3 arms, stratified

Complete Source Code

The full source is at examples/drug_discovery.moiss in the repository.