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:
- Calculates molecular weight from a chemical formula
- Screens compounds against Lipinski's Rule of Five
- Classifies compounds using the Biopharmaceutical Classification System
- Predicts ADMET properties
- Analyzes toxicity (LD50, therapeutic index)
- Runs the full screening pipeline on multiple known compounds
- Queries the compound database by target and therapeutic class
- Profiles PK properties including dose, interactions, and organ-impairment adjustments
- Designs a multi-arm clinical trial with power-based sample sizing
- 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).
| Rule | Limit | Our Compound | Pass? |
|---|---|---|---|
| MW | 500 or below | 285.34 | Yes |
| LogP | 5 or below | 1.2 | Yes |
| HBD | 5 or below | 1 | Yes |
| HBA | 10 or below | 4 | Yes |
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 Class | Solubility | Permeability | Our Result |
|---|---|---|---|
| I | High | High | Standard oral formulation |
| II | Low | High | Micronization, lipid formulation |
| III | High | Low | Permeation enhancers |
| IV | Low | Low | IV 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);
| Check | Purpose | Result |
|---|---|---|
calculate_dose | Weight-based dosing | 10 mg IV |
check_interactions | Drug-drug interaction | Morphine + Midazolam: CNS depression risk |
renal_adjust | eGFR-based dose reduction | 50% dose at eGFR 45 |
hepatic_adjust | Child-Pugh based adjustment | 50% dose for Class B |
therapeutic_range | TDM window for monitoring | Vancomycin trough 15-20 mcg/mL |
trough_estimate | Predicted trough level | Approx. 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.