Skill: manuelcorpas/pharmgx-reporter (version 0.2.0)

--- name: clawbio-pharmgx-reporter version: 0.1.0 description: Pharmacogenomic report from DTC genetic data (23andMe/AncestryDNA) author: Manuel Corpas license: MIT tags: - pharmacogenomics - CPIC - DTC-genetics - precision-medicine inputs: - name: input type: file format: [23andme, ancestrydna, tsv] description: Raw genetic data file from 23andMe or AncestryDNA outputs: - name: report type: file format: markdown description: Pharmacogenomic report with gene profiles and drug recommendations metadata: openclaw: category: bioinformatics homepage: https://github.com/ClawBio/ClawBio min_python: "3.9" dependencies: [] --- # 🦖 PharmGx Reporter Generate a pharmacogenomic report from consumer genetic data (23andMe, AncestryDNA). ## What it does 1. Parses raw genetic data files (auto-detects 23andMe or AncestryDNA format) 2. Extracts 31 pharmacogenomic SNPs across 12 genes 3. Calls star alleles and determines metabolizer phenotypes 4. Looks up CPIC drug recommendations for 51 medications 5. Generates a markdown report with gene profiles, drug tables, and alerts ## Genes covered CYP2C19, CYP2D6, CYP2C9, VKORC1, SLCO1B1, DPYD, TPMT, UGT1A1, CYP3A5, CYP2B6, NUDT15, CYP1A2 ## Drug classes Antiplatelet, opioids, statins, anticoagulants, PPIs, antidepressants (TCAs, SSRIs, SNRIs), antipsychotics, NSAIDs, oncology, immunosuppressants, antivirals ## Usage ```bash python pharmgx_reporter.py --input patient_data.txt --output report ``` ## Disclaimer This tool is for research and educational purposes only. It is NOT a diagnostic device. Always consult a healthcare professional before making any medication decisions.

path: demo_patient.txt
content: # This data file generated by ClawBio PharmGx Reporter (demo)
# Synthetic patient data for demonstration purposes only
#
# rsid	chromosome	position	genotype
#
# CYP2C19 - rs4244285 *2 (het: one no-function allele)
rs4244285	10	96541616	AG
# CYP2C19 - rs4986893 *3 (ref: no variant)
rs4986893	10	96540410	GG
# CYP2C19 - rs12248560 *17 (ref: no variant)
rs12248560	10	96522463	CC
# CYP2C19 - rs28399504 *4 (ref: no variant)
rs28399504	10	96521657	AA
#
# CYP2D6 - rs3892097 *4 (hom: two no-function alleles = Poor Metabolizer)
rs3892097	22	42524947	TT
# CYP2D6 - rs5030655 *6 (ref: no variant)
rs5030655	22	42525772	TT
# CYP2D6 - rs16947 *2 (ref: normal function)
rs16947	22	42523943	GG
# CYP2D6 - rs1065852 *10 (ref: no variant)
rs1065852	22	42526694	CC
# CYP2D6 - rs28371725 *41 (ref: no variant)
rs28371725	22	42524175	CC
#
# CYP2C9 - rs1799853 *2 (ref: no variant)
rs1799853	10	96702047	CC
# CYP2C9 - rs1057910 *3 (het: one decreased-function allele)
rs1057910	10	96741053	AC
#
# VKORC1 - rs9923231 -1639G>A (het: intermediate warfarin sensitivity)
rs9923231	16	31107689	GA
#
# SLCO1B1 - rs4149056 *5 (het: intermediate function)
rs4149056	12	21331549	TC
#
# DPYD - rs3918290 *2A (ref: normal)
rs3918290	1	97915614	CC
# DPYD - rs55886062 *13 (ref: normal)
rs55886062	1	98165091	TT
# DPYD - rs67376798 D949V (ref: normal)
rs67376798	1	97981395	TT
#
# TPMT - rs1800460 *3B (ref: normal)
rs1800460	6	18130918	CC
# TPMT - rs1142345 *3C (ref: normal)
rs1142345	6	18139228	AA
# TPMT - rs1800462 *2 (ref: normal)
rs1800462	6	18143955	CC
#
# UGT1A1 - rs8175347 *28 (ref: normal)
rs8175347	2	234668879	CC
# UGT1A1 - rs4148323 *6 (ref: normal)
rs4148323	2	234669144	GG
#
# CYP3A5 - rs776746 *3 (hom: non-expressor)
rs776746	7	99672916	GG
# CYP3A5 - rs10264272 *6 (ref: no variant)
rs10264272	7	99665212	GG
# CYP3A5 - rs41303343 *7 (ref: no variant)
rs41303343	7	99664693	CC
#
# CYP2B6 - rs3745274 *9 (ref: normal)
rs3745274	19	41512841	GG
# CYP2B6 - rs28399499 *18 (ref: normal)
rs28399499	19	41515263	TT
#
# NUDT15 - rs116855232 *3 (ref: normal)
rs116855232	13	48611703	CC
# NUDT15 - rs147390019 *2 (ref: normal)
rs147390019	13	48611695	GG
#
# CYP1A2 - rs762551 *1F (het: one increased-function allele)
rs762551	15	75041917	AC
# CYP1A2 - rs2069514 *1C (ref: no variant)
rs2069514	15	75044934	GG


path: demo_report/report.md
content: # ClawBio PharmGx Report

**Date**: 2026-02-25 19:09 UTC
**Input**: `demo_patient.txt`
**Format detected**: 23andme
**Checksum (SHA-256)**: `4c2e9c8a7fd58de831187dd847e30509fe72c5aee09ff80d617caa4ae8a0eedb`
**Total SNPs in file**: 30
**Pharmacogenomic SNPs found**: 30/30
**Genes profiled**: 12
**Drugs assessed**: 51

---

## Drug Response Summary

| Category | Count |
|----------|-------|
| Standard dosing | 21 |
| Use with caution | 20 |
| Avoid / use alternative | 10 |

### Actionable Alerts

**AVOID / USE ALTERNATIVE:**

- **Codeine** (Tylenol w/ Codeine) [CYP2D6]: Avoid codeine. Select alternative analgesic.
- **Tramadol** (Ultram) [CYP2D6]: Avoid tramadol. Select alternative analgesic.
- **Tamoxifen** (Nolvadex) [CYP2D6]: Consider aromatase inhibitor or higher tamoxifen dose with TDM.
- **Amitriptyline** (Elavil) [CYP2D6]: Avoid TCA. If necessary, reduce dose 50%.
- **Nortriptyline** (Pamelor) [CYP2D6]: Avoid TCA or reduce dose 50%.
- **Desipramine** (Norpramin) [CYP2D6]: Avoid TCA or reduce dose 50%.
- **Imipramine** (Tofranil) [CYP2D6]: Avoid TCA or reduce dose 50%.
- **Doxepin** (Sinequan) [CYP2D6]: Avoid TCA or reduce dose 50%.
- **Trimipramine** (Surmontil) [CYP2D6]: Avoid TCA or reduce dose 50%.
- **Clomipramine** (Anafranil) [CYP2D6]: Avoid TCA or reduce dose 50%.

**USE WITH CAUTION:**

- **Clopidogrel** (Plavix) [CYP2C19]: Consider alternative antiplatelet therapy (prasugrel, ticagrelor).
- **Hydrocodone** (Vicodin) [CYP2D6]: Reduced analgesia likely; consider alternative.
- **Oxycodone** (OxyContin) [CYP2D6]: Reduced analgesia possible; consider alternative.
- **Paroxetine** (Paxil) [CYP2D6]: Consider 50% dose reduction or alternative SSRI.
- **Fluoxetine** (Prozac) [CYP2D6]: Consider 50% dose reduction or alternative.
- **Venlafaxine** (Effexor) [CYP2D6]: Consider 50% dose reduction or switch to desvenlafaxine.
- **Metoprolol** (Lopressor) [CYP2D6]: Consider 50% dose reduction or alternative beta-blocker.
- **Ondansetron** (Zofran) [CYP2D6]: May have reduced antiemetic effect; consider alternative.
- **Risperidone** (Risperdal) [CYP2D6]: Consider 50% dose reduction.
- **Aripiprazole** (Abilify) [CYP2D6]: Reduce dose to 75% of usual.
- **Haloperidol** (Haldol) [CYP2D6]: Reduce dose; monitor for side effects.
- **Atomoxetine** (Strattera) [CYP2D6]: Start at lower dose; 2-fold higher exposure expected.
- **Phenytoin** (Dilantin) [CYP2C9]: Reduce dose 25%. Monitor levels closely.
- **Celecoxib** (Celebrex) [CYP2C9]: Start at lowest dose.
- **Flurbiprofen** (Ansaid) [CYP2C9]: Start at lowest dose.
- **Piroxicam** (Feldene) [CYP2C9]: Start at lowest dose; monitor.
- **Meloxicam** (Mobic) [CYP2C9]: Start at lowest dose.
- **Warfarin** (Coumadin) [CYP2C9+VKORC1]: Reduce initial dose. Use genotype-guided dosing algorithm.
- **Simvastatin** (Zocor) [SLCO1B1]: Lower dose or alternative statin. 4.5x myopathy risk.
- **Atorvastatin** (Lipitor) [SLCO1B1]: Consider CK surveillance.

---

## Gene Profiles

| Gene | Full Name | Diplotype | Phenotype |
|------|-----------|-----------|-----------|
| CYP2C19 | Cytochrome P450 2C19 | *1/*2 | Intermediate Metabolizer |
| CYP2D6 | Cytochrome P450 2D6 | *4/*4 | Poor Metabolizer |
| CYP2C9 | Cytochrome P450 2C9 | *1/*3 | Intermediate Metabolizer |
| VKORC1 | Vitamin K Epoxide Reductase | GA | Intermediate Warfarin Sensitivity |
| SLCO1B1 | Solute Carrier Organic Anion Transporter 1B1 | TC | Intermediate Function |
| DPYD | Dihydropyrimidine Dehydrogenase | Normal/Normal | Normal Metabolizer |
| TPMT | Thiopurine S-Methyltransferase | *1/*1 | Normal Metabolizer |
| UGT1A1 | UDP-Glucuronosyltransferase 1A1 | *1/*1 | Normal Metabolizer |
| CYP3A5 | Cytochrome P450 3A5 | *3/*3 | CYP3A5 Non-expressor |
| CYP2B6 | Cytochrome P450 2B6 | *1/*1 | Normal Metabolizer |
| NUDT15 | Nudix Hydrolase 15 | *1/*1 | Normal Metabolizer |
| CYP1A2 | Cytochrome P450 1A2 | *1/*1F | Normal Metabolizer |

## Detected Variants

| rsID | Gene | Star Allele | Genotype | Effect |
|------|------|-------------|----------|--------|
| rs762551 | CYP1A2 | *1F | AC | increased_function |
| rs2069514 | CYP1A2 | *1C | GG | decreased_function |
| rs3745274 | CYP2B6 | *9 | GG | decreased_function |
| rs28399499 | CYP2B6 | *18 | TT | no_function |
| rs4244285 | CYP2C19 | *2 | AG | no_function |
| rs4986893 | CYP2C19 | *3 | GG | no_function |
| rs12248560 | CYP2C19 | *17 | CC | increased_function |
| rs28399504 | CYP2C19 | *4 | AA | no_function |
| rs1799853 | CYP2C9 | *2 | CC | decreased_function |
| rs1057910 | CYP2C9 | *3 | AC | decreased_function |
| rs3892097 | CYP2D6 | *4 | TT | no_function |
| rs5030655 | CYP2D6 | *6 | TT | no_function |
| rs16947 | CYP2D6 | *2 | GG | normal_function |
| rs1065852 | CYP2D6 | *10 | CC | decreased_function |
| rs28371725 | CYP2D6 | *41 | CC | decreased_function |
| rs776746 | CYP3A5 | *3 | GG | no_function |
| rs10264272 | CYP3A5 | *6 | GG | no_function |
| rs41303343 | CYP3A5 | *7 | CC | no_function |
| rs3918290 | DPYD | *2A | CC | no_function |
| rs55886062 | DPYD | *13 | TT | no_function |
| rs67376798 | DPYD | D949V | TT | decreased_function |
| rs116855232 | NUDT15 | *3 | CC | no_function |
| rs147390019 | NUDT15 | *2 | GG | decreased_function |
| rs4149056 | SLCO1B1 | *5 | TC | decreased_function |
| rs1800460 | TPMT | *3B | CC | no_function |
| rs1142345 | TPMT | *3C | AA | no_function |
| rs1800462 | TPMT | *2 | CC | no_function |
| rs8175347 | UGT1A1 | *28 | CC | decreased_function |
| rs4148323 | UGT1A1 | *6 | GG | decreased_function |
| rs9923231 | VKORC1 | -1639G>A | GA | decreased_expression |

---

## Complete Drug Recommendations

| Drug | Brand | Class | Gene | Status | Recommendation |
|------|-------|-------|------|--------|----------------|
| Amitriptyline | Elavil | Tricyclic Antidepressant | CYP2D6 | AVOID | Avoid TCA. If necessary, reduce dose 50%. |
| Clomipramine | Anafranil | Tricyclic Antidepressant | CYP2D6 | AVOID | Avoid TCA or reduce dose 50%. |
| Codeine | Tylenol w/ Codeine | Opioid Analgesic | CYP2D6 | AVOID | Avoid codeine. Select alternative analgesic. |
| Desipramine | Norpramin | Tricyclic Antidepressant | CYP2D6 | AVOID | Avoid TCA or reduce dose 50%. |
| Doxepin | Sinequan | Tricyclic Antidepressant | CYP2D6 | AVOID | Avoid TCA or reduce dose 50%. |
| Imipramine | Tofranil | Tricyclic Antidepressant | CYP2D6 | AVOID | Avoid TCA or reduce dose 50%. |
| Nortriptyline | Pamelor | Tricyclic Antidepressant | CYP2D6 | AVOID | Avoid TCA or reduce dose 50%. |
| Tamoxifen | Nolvadex | SERM (Oncology) | CYP2D6 | AVOID | Consider aromatase inhibitor or higher tamoxifen dose with TDM. |
| Tramadol | Ultram | Opioid Analgesic | CYP2D6 | AVOID | Avoid tramadol. Select alternative analgesic. |
| Trimipramine | Surmontil | Tricyclic Antidepressant | CYP2D6 | AVOID | Avoid TCA or reduce dose 50%. |
| Aripiprazole | Abilify | Antipsychotic | CYP2D6 | CAUTION | Reduce dose to 75% of usual. |
| Atomoxetine | Strattera | ADHD Medication | CYP2D6 | CAUTION | Start at lower dose; 2-fold higher exposure expected. |
| Atorvastatin | Lipitor | Statin | SLCO1B1 | CAUTION | Consider CK surveillance. |
| Celecoxib | Celebrex | NSAID | CYP2C9 | CAUTION | Start at lowest dose. |
| Clopidogrel | Plavix | Antiplatelet Agent | CYP2C19 | CAUTION | Consider alternative antiplatelet therapy (prasugrel, ticagrelor). |
| Fluoxetine | Prozac | SSRI Antidepressant | CYP2D6 | CAUTION | Consider 50% dose reduction or alternative. |
| Flurbiprofen | Ansaid | NSAID | CYP2C9 | CAUTION | Start at lowest dose. |
| Haloperidol | Haldol | Antipsychotic | CYP2D6 | CAUTION | Reduce dose; monitor for side effects. |
| Hydrocodone | Vicodin | Opioid Analgesic | CYP2D6 | CAUTION | Reduced analgesia likely; consider alternative. |
| Meloxicam | Mobic | NSAID | CYP2C9 | CAUTION | Start at lowest dose. |
| Metoprolol | Lopressor | Beta-Blocker | CYP2D6 | CAUTION | Consider 50% dose reduction or alternative beta-blocker. |
| Ondansetron | Zofran | Antiemetic | CYP2D6 | CAUTION | May have reduced antiemetic effect; consider alternative. |
| Oxycodone | OxyContin | Opioid Analgesic | CYP2D6 | CAUTION | Reduced analgesia possible; consider alternative. |
| Paroxetine | Paxil | SSRI Antidepressant | CYP2D6 | CAUTION | Consider 50% dose reduction or alternative SSRI. |
| Phenytoin | Dilantin | Antiepileptic | CYP2C9 | CAUTION | Reduce dose 25%. Monitor levels closely. |
| Piroxicam | Feldene | NSAID | CYP2C9 | CAUTION | Start at lowest dose; monitor. |
| Risperidone | Risperdal | Antipsychotic | CYP2D6 | CAUTION | Consider 50% dose reduction. |
| Simvastatin | Zocor | Statin | SLCO1B1 | CAUTION | Lower dose or alternative statin. 4.5x myopathy risk. |
| Venlafaxine | Effexor | SNRI Antidepressant | CYP2D6 | CAUTION | Consider 50% dose reduction or switch to desvenlafaxine. |
| Warfarin | Coumadin | Anticoagulant | CYP2C9+VKORC1 | CAUTION | Reduce initial dose. Use genotype-guided dosing algorithm. |
| Atazanavir | Reyataz | Antiretroviral | UGT1A1 | OK | Use recommended dose. |
| Azathioprine | Imuran | Immunosuppressant | TPMT | OK | Use recommended dose. |
| Capecitabine | Xeloda | Antineoplastic | DPYD | OK | Use recommended dose. |
| Citalopram | Celexa | SSRI Antidepressant | CYP2C19 | OK | Use recommended starting dose. |
| Clozapine | Clozaril | Antipsychotic | CYP1A2 | OK | Use recommended dose. |
| Dexlansoprazole | Dexilant | Proton Pump Inhibitor | CYP2C19 | OK | Use recommended starting dose. |
| Efavirenz | Sustiva | Antiretroviral | CYP2B6 | OK | Use recommended dose. |
| Escitalopram | Lexapro | SSRI Antidepressant | CYP2C19 | OK | Use recommended starting dose. |
| Esomeprazole | Nexium | Proton Pump Inhibitor | CYP2C19 | OK | Use recommended starting dose. |
| Fluorouracil | 5-FU | Antineoplastic | DPYD | OK | Use recommended dose. |
| Irinotecan | Camptosar | Antineoplastic | UGT1A1 | OK | Use recommended dose. |
| Lansoprazole | Prevacid | Proton Pump Inhibitor | CYP2C19 | OK | Use recommended starting dose. |
| Mercaptopurine | Purinethol | Immunosuppressant | TPMT | OK | Use recommended dose. |
| Omeprazole | Prilosec | Proton Pump Inhibitor | CYP2C19 | OK | Use recommended starting dose. |
| Pantoprazole | Protonix | Proton Pump Inhibitor | CYP2C19 | OK | Use recommended starting dose. |
| Pravastatin | Pravachol | Statin | SLCO1B1 | OK | Use desired starting dose. |
| Rosuvastatin | Crestor | Statin | SLCO1B1 | OK | Use desired starting dose. |
| Sertraline | Zoloft | SSRI Antidepressant | CYP2C19 | OK | Use recommended starting dose. |
| Tacrolimus | Prograf | Immunosuppressant | CYP3A5 | OK | Use recommended dose (most patients). |
| Thioguanine | Tabloid | Immunosuppressant | TPMT | OK | Use recommended dose. |
| Voriconazole | Vfend | Antifungal | CYP2C19 | OK | Use recommended dose. |

---

## Disclaimer

This report is for **research and educational purposes only**. It is NOT a diagnostic device and should NOT be used to make medication decisions without consulting a qualified healthcare professional.

Pharmacogenomic recommendations are based on CPIC guidelines (cpicpgx.org). DTC genetic tests have limitations: they may not detect all relevant variants, and results should be confirmed by clinical-grade testing before clinical use.

## Methods

- **Tool**: ClawBio PharmGx Reporter v0.1.0
- **SNP panel**: 31 pharmacogenomic variants across 12 genes
- **Star allele calling**: Simplified DTC-compatible algorithm (single-SNP per allele)
- **Phenotype assignment**: CPIC-based diplotype-to-phenotype mapping
- **Drug guidelines**: 51 drugs from CPIC (cpicpgx.org), simplified for DTC context

## Reproducibility

```bash
python pharmgx_reporter.py --input demo_patient.txt --output report
```

**Input checksum**: `4c2e9c8a7fd58de831187dd847e30509fe72c5aee09ff80d617caa4ae8a0eedb`

## References

- Corpas, M. (2026). ClawBio. https://github.com/ClawBio/ClawBio
- CPIC. Clinical Pharmacogenetics Implementation Consortium. https://cpicpgx.org/
- Caudle, K.E. et al. (2014). Standardizing terms for clinical pharmacogenetic test results. Genet Med, 16(9), 655-663.
- PharmGKB. https://www.pharmgkb.org/


path: pharmgx_reporter.py
content: #!/usr/bin/env python3
"""
ClawBio PharmGx Reporter
Pharmacogenomic report generator from DTC genetic data (23andMe/AncestryDNA).

Analyses 31 pharmacogenomic SNPs across 12 genes, calls star alleles and
metabolizer phenotypes, and looks up CPIC drug recommendations for 51 medications.

Usage:
    python pharmgx_reporter.py --input patient_data.txt --output report_dir
"""

import argparse
import hashlib
import os
import re
import sys
from datetime import datetime, timezone
from pathlib import Path

# ---------------------------------------------------------------------------
# 1. PGx SNP definitions (ported from PharmXD snp-parser.js)
# ---------------------------------------------------------------------------

PGX_SNPS = {
    # CYP2C19
    "rs4244285":  {"gene": "CYP2C19", "allele": "*2",  "effect": "no_function"},
    "rs4986893":  {"gene": "CYP2C19", "allele": "*3",  "effect": "no_function"},
    "rs12248560": {"gene": "CYP2C19", "allele": "*17", "effect": "increased_function"},
    "rs28399504": {"gene": "CYP2C19", "allele": "*4",  "effect": "no_function"},
    # CYP2D6
    "rs3892097":  {"gene": "CYP2D6", "allele": "*4",  "effect": "no_function"},
    "rs5030655":  {"gene": "CYP2D6", "allele": "*6",  "effect": "no_function"},
    "rs16947":    {"gene": "CYP2D6", "allele": "*2",  "effect": "normal_function"},
    "rs1065852":  {"gene": "CYP2D6", "allele": "*10", "effect": "decreased_function"},
    "rs28371725": {"gene": "CYP2D6", "allele": "*41", "effect": "decreased_function"},
    # CYP2C9
    "rs1799853":  {"gene": "CYP2C9", "allele": "*2", "effect": "decreased_function"},
    "rs1057910":  {"gene": "CYP2C9", "allele": "*3", "effect": "decreased_function"},
    # VKORC1
    "rs9923231":  {"gene": "VKORC1", "allele": "-1639G>A", "effect": "decreased_expression"},
    # SLCO1B1
    "rs4149056":  {"gene": "SLCO1B1", "allele": "*5", "effect": "decreased_function"},
    # DPYD
    "rs3918290":  {"gene": "DPYD", "allele": "*2A",  "effect": "no_function"},
    "rs55886062": {"gene": "DPYD", "allele": "*13",  "effect": "no_function"},
    "rs67376798": {"gene": "DPYD", "allele": "D949V", "effect": "decreased_function"},
    # TPMT
    "rs1800460":  {"gene": "TPMT", "allele": "*3B", "effect": "no_function"},
    "rs1142345":  {"gene": "TPMT", "allele": "*3C", "effect": "no_function"},
    "rs1800462":  {"gene": "TPMT", "allele": "*2",  "effect": "no_function"},
    # UGT1A1
    "rs8175347":  {"gene": "UGT1A1", "allele": "*28", "effect": "decreased_function"},
    "rs4148323":  {"gene": "UGT1A1", "allele": "*6",  "effect": "decreased_function"},
    # CYP3A5
    "rs776746":    {"gene": "CYP3A5", "allele": "*3", "effect": "no_function"},
    "rs10264272":  {"gene": "CYP3A5", "allele": "*6", "effect": "no_function"},
    "rs41303343":  {"gene": "CYP3A5", "allele": "*7", "effect": "no_function"},
    # CYP2B6
    "rs3745274":  {"gene": "CYP2B6", "allele": "*9",  "effect": "decreased_function"},
    "rs28399499": {"gene": "CYP2B6", "allele": "*18", "effect": "no_function"},
    # NUDT15
    "rs116855232": {"gene": "NUDT15", "allele": "*3", "effect": "no_function"},
    "rs147390019": {"gene": "NUDT15", "allele": "*2", "effect": "decreased_function"},
    # CYP1A2
    "rs762551":   {"gene": "CYP1A2", "allele": "*1F", "effect": "increased_function"},
    "rs2069514":  {"gene": "CYP1A2", "allele": "*1C", "effect": "decreased_function"},
}

# ---------------------------------------------------------------------------
# 2. Gene definitions with phenotype rules (from phenotype.js)
# ---------------------------------------------------------------------------

GENE_DEFS = {
    "CYP2C19": {
        "name": "Cytochrome P450 2C19",
        "function": "Drug metabolism",
        "ref": "*1",
        "variants": {
            "rs4244285":  {"allele": "*2",  "alt": "A", "effect": "no_function"},
            "rs4986893":  {"allele": "*3",  "alt": "A", "effect": "no_function"},
            "rs12248560": {"allele": "*17", "alt": "T", "effect": "increased_function"},
            "rs28399504": {"allele": "*4",  "alt": "G", "effect": "no_function"},
        },
        "phenotypes": {
            "Ultrarapid Metabolizer":  ["*17/*17", "*1/*17"],
            "Normal Metabolizer":      ["*1/*1"],
            "Intermediate Metabolizer": ["*1/*2", "*1/*3", "*2/*17", "*1/*4"],
            "Poor Metabolizer":        ["*2/*2", "*2/*3", "*3/*3", "*2/*4", "*3/*4", "*4/*4"],
        },
    },
    "CYP2D6": {
        "name": "Cytochrome P450 2D6",
        "function": "Drug metabolism (25% of all drugs)",
        "ref": "*1",
        "variants": {
            "rs3892097":  {"allele": "*4",  "alt": "T", "effect": "no_function"},
            "rs5030655":  {"allele": "*6",  "alt": "DEL", "effect": "no_function"},
            "rs16947":    {"allele": "*2",  "alt": "A", "effect": "normal_function"},
            "rs1065852":  {"allele": "*10", "alt": "T", "effect": "decreased_function"},
            "rs28371725": {"allele": "*41", "alt": "T", "effect": "decreased_function"},
        },
        "phenotypes": {
            "Normal Metabolizer":       ["*1/*1", "*1/*2", "*2/*2"],
            "Intermediate Metabolizer": ["*1/*4", "*1/*10", "*1/*41", "*10/*10", "*4/*10", "*10/*41", "*41/*41"],
            "Poor Metabolizer":         ["*4/*4", "*4/*6", "*6/*6", "*4/*41"],
        },
    },
    "CYP2C9": {
        "name": "Cytochrome P450 2C9",
        "function": "Warfarin and NSAID metabolism",
        "ref": "*1",
        "variants": {
            "rs1799853": {"allele": "*2", "alt": "T", "effect": "decreased_function"},
            "rs1057910": {"allele": "*3", "alt": "C", "effect": "decreased_function"},
        },
        "phenotypes": {
            "Normal Metabolizer":       ["*1/*1"],
            "Intermediate Metabolizer": ["*1/*2", "*1/*3", "*2/*2"],
            "Poor Metabolizer":         ["*2/*3", "*3/*3"],
        },
    },
    "VKORC1": {
        "name": "Vitamin K Epoxide Reductase",
        "function": "Warfarin target enzyme",
        "ref": "G",
        "type": "genotype",
        "rsid": "rs9923231",
        "variants": {
            "rs9923231": {"allele": "A", "alt": "A", "effect": "decreased_expression"},
        },
        "phenotypes": {
            "Normal Warfarin Sensitivity":       ["GG"],
            "Intermediate Warfarin Sensitivity":  ["GA", "AG"],
            "High Warfarin Sensitivity":          ["AA"],
        },
    },
    "SLCO1B1": {
        "name": "Solute Carrier Organic Anion Transporter 1B1",
        "function": "Hepatic statin uptake",
        "ref": "T",
        "type": "genotype",
        "rsid": "rs4149056",
        "variants": {
            "rs4149056": {"allele": "*5", "alt": "C", "effect": "decreased_function"},
        },
        "phenotypes": {
            "Normal Function":       ["TT"],
            "Intermediate Function": ["TC", "CT"],
            "Poor Function":         ["CC"],
        },
    },
    "DPYD": {
        "name": "Dihydropyrimidine Dehydrogenase",
        "function": "Fluoropyrimidine metabolism",
        "ref": "Normal",
        "type": "dpyd",
        "variants": {
            "rs3918290":  {"allele": "*2A",  "alt": "T", "effect": "no_function"},
            "rs55886062": {"allele": "*13",  "alt": "C", "effect": "no_function"},
            "rs67376798": {"allele": "D949V", "alt": "A", "effect": "decreased_function"},
        },
        "phenotypes": {
            "Normal Metabolizer":       ["Normal/Normal"],
            "Intermediate Metabolizer": ["Normal/*2A", "Normal/*13", "Normal/D949V"],
            "Poor Metabolizer":         ["*2A/*2A", "*2A/*13", "*13/*13"],
        },
    },
    "TPMT": {
        "name": "Thiopurine S-Methyltransferase",
        "function": "Thiopurine metabolism",
        "ref": "*1",
        "variants": {
            "rs1800460": {"allele": "*3B", "alt": "T", "effect": "no_function"},
            "rs1142345": {"allele": "*3C", "alt": "C", "effect": "no_function"},
            "rs1800462": {"allele": "*2",  "alt": "G", "effect": "no_function"},
        },
        "phenotypes": {
            "Normal Metabolizer":       ["*1/*1"],
            "Intermediate Metabolizer": ["*1/*2", "*1/*3A", "*1/*3B", "*1/*3C"],
            "Poor Metabolizer":         ["*2/*2", "*2/*3A", "*3A/*3A", "*3B/*3B", "*3C/*3C"],
        },
    },
    "UGT1A1": {
        "name": "UDP-Glucuronosyltransferase 1A1",
        "function": "Irinotecan and bilirubin metabolism",
        "ref": "*1",
        "variants": {
            "rs8175347": {"allele": "*28", "alt": "TA7", "effect": "decreased_function"},
            "rs4148323": {"allele": "*6",  "alt": "A", "effect": "decreased_function"},
        },
        "phenotypes": {
            "Normal Metabolizer":       ["*1/*1"],
            "Intermediate Metabolizer": ["*1/*28", "*1/*6", "*6/*28"],
            "Poor Metabolizer":         ["*28/*28", "*6/*6"],
        },
    },
    "CYP3A5": {
        "name": "Cytochrome P450 3A5",
        "function": "Tacrolimus metabolism",
        "ref": "*1",
        "variants": {
            "rs776746":   {"allele": "*3", "alt": "G", "effect": "no_function"},
            "rs10264272": {"allele": "*6", "alt": "A", "effect": "no_function"},
            "rs41303343": {"allele": "*7", "alt": "INS", "effect": "no_function"},
        },
        "phenotypes": {
            "CYP3A5 Expressor":          ["*1/*1"],
            "Intermediate Expressor":     ["*1/*3", "*1/*6", "*1/*7"],
            "CYP3A5 Non-expressor":       ["*3/*3", "*3/*6", "*6/*6", "*3/*7"],
        },
    },
    "CYP2B6": {
        "name": "Cytochrome P450 2B6",
        "function": "Efavirenz and methadone metabolism",
        "ref": "*1",
        "variants": {
            "rs3745274":  {"allele": "*9",  "alt": "T", "effect": "decreased_function"},
            "rs28399499": {"allele": "*18", "alt": "C", "effect": "no_function"},
        },
        "phenotypes": {
            "Normal Metabolizer":       ["*1/*1"],
            "Intermediate Metabolizer": ["*1/*9", "*1/*18", "*9/*18"],
            "Poor Metabolizer":         ["*9/*9", "*18/*18"],
        },
    },
    "NUDT15": {
        "name": "Nudix Hydrolase 15",
        "function": "Thiopurine metabolism",
        "ref": "*1",
        "variants": {
            "rs116855232": {"allele": "*3", "alt": "T", "effect": "no_function"},
            "rs147390019": {"allele": "*2", "alt": "A", "effect": "decreased_function"},
        },
        "phenotypes": {
            "Normal Metabolizer":       ["*1/*1"],
            "Intermediate Metabolizer": ["*1/*2", "*1/*3"],
            "Poor Metabolizer":         ["*2/*2", "*2/*3", "*3/*3"],
        },
    },
    "CYP1A2": {
        "name": "Cytochrome P450 1A2",
        "function": "Caffeine and clozapine metabolism",
        "ref": "*1",
        "variants": {
            "rs762551":  {"allele": "*1F", "alt": "A", "effect": "increased_function"},
            "rs2069514": {"allele": "*1C", "alt": "A", "effect": "decreased_function"},
        },
        "phenotypes": {
            "Ultrarapid Metabolizer":   ["*1F/*1F"],
            "Normal Metabolizer":       ["*1/*1", "*1/*1F"],
            "Intermediate Metabolizer": ["*1/*1C", "*1C/*1F"],
            "Poor Metabolizer":         ["*1C/*1C"],
        },
    },
}

# ---------------------------------------------------------------------------
# 3. CPIC drug guidelines (from cpic-lookup.js, all 51 drugs)
# ---------------------------------------------------------------------------

# Phenotype key mapping for lookup
# For star-allele genes: ultrarapid_metabolizer, normal_metabolizer,
#   intermediate_metabolizer, poor_metabolizer
# For VKORC1: normal_warfarin_sensitivity, intermediate_warfarin_sensitivity,
#   high_warfarin_sensitivity
# For SLCO1B1: normal_function, intermediate_function, poor_function
# For CYP3A5: extensive_metabolizer, intermediate_metabolizer, poor_metabolizer

def _pheno_key(description):
    """Convert phenotype description to lookup key."""
    return description.lower().replace(" ", "_")


GUIDELINES = {
    # --- CYP2C19 drugs ---
    "Clopidogrel": {
        "brand": "Plavix", "class": "Antiplatelet Agent", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("standard", "Use recommended dose."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Consider alternative antiplatelet therapy (prasugrel, ticagrelor)."),
            "poor_metabolizer":       ("avoid", "Use alternative antiplatelet therapy (prasugrel or ticagrelor)."),
        },
    },
    "Omeprazole": {
        "brand": "Prilosec", "class": "Proton Pump Inhibitor", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "For H. pylori: increase dose 50-100%. For GERD: standard dose."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "For chronic therapy >12 wk: consider 50% dose reduction."),
        },
    },
    "Pantoprazole": {
        "brand": "Protonix", "class": "Proton Pump Inhibitor", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "For H. pylori: increase dose 50-100%."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "For chronic therapy >12 wk: consider 50% dose reduction."),
        },
    },
    "Lansoprazole": {
        "brand": "Prevacid", "class": "Proton Pump Inhibitor", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "For H. pylori: increase dose 50-100%."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "For chronic therapy: consider 50% dose reduction."),
        },
    },
    "Esomeprazole": {
        "brand": "Nexium", "class": "Proton Pump Inhibitor", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "For H. pylori: increase dose 50-100%."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "For chronic therapy: consider dose reduction."),
        },
    },
    "Dexlansoprazole": {
        "brand": "Dexilant", "class": "Proton Pump Inhibitor", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "For H. pylori: increase dose 50-100%."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "For chronic therapy: consider dose reduction."),
        },
    },
    "Citalopram": {
        "brand": "Celexa", "class": "SSRI Antidepressant", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "Select alternative SSRI not dependent on CYP2C19."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "Consider 50% reduction. Max 20 mg/day."),
        },
    },
    "Escitalopram": {
        "brand": "Lexapro", "class": "SSRI Antidepressant", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "Select alternative SSRI or titrate to max dose."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "Consider 50% dose reduction."),
        },
    },
    "Sertraline": {
        "brand": "Zoloft", "class": "SSRI Antidepressant", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("standard", "Use recommended starting dose."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("standard", "Use recommended starting dose."),
        },
    },
    "Voriconazole": {
        "brand": "Vfend", "class": "Antifungal", "gene": "CYP2C19",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "Use alternative antifungal; voriconazole may be ineffective."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("standard", "Use recommended dose."),
            "poor_metabolizer":       ("caution", "Increased exposure; consider dose reduction or TDM."),
        },
    },
    # --- CYP2D6 drugs ---
    "Codeine": {
        "brand": "Tylenol w/ Codeine", "class": "Opioid Analgesic", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid codeine. Risk of morphine toxicity."),
            "normal_metabolizer":     ("standard", "Use label-recommended dosing."),
            "intermediate_metabolizer": ("caution", "Use with caution; may have reduced analgesia."),
            "poor_metabolizer":       ("avoid", "Avoid codeine. Select alternative analgesic."),
        },
    },
    "Tramadol": {
        "brand": "Ultram", "class": "Opioid Analgesic", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid tramadol. Increased toxicity risk."),
            "normal_metabolizer":     ("standard", "Use label-recommended dosing."),
            "intermediate_metabolizer": ("caution", "Use with caution; possible reduced analgesia."),
            "poor_metabolizer":       ("avoid", "Avoid tramadol. Select alternative analgesic."),
        },
    },
    "Hydrocodone": {
        "brand": "Vicodin", "class": "Opioid Analgesic", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "Use with caution; increased active metabolite."),
            "normal_metabolizer":     ("standard", "Use label-recommended dosing."),
            "intermediate_metabolizer": ("caution", "May have reduced analgesia; monitor response."),
            "poor_metabolizer":       ("caution", "Reduced analgesia likely; consider alternative."),
        },
    },
    "Oxycodone": {
        "brand": "OxyContin", "class": "Opioid Analgesic", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "Use with caution; increased active metabolite."),
            "normal_metabolizer":     ("standard", "Use label-recommended dosing."),
            "intermediate_metabolizer": ("standard", "Use label-recommended dosing."),
            "poor_metabolizer":       ("caution", "Reduced analgesia possible; consider alternative."),
        },
    },
    "Tamoxifen": {
        "brand": "Nolvadex", "class": "SERM (Oncology)", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("standard", "Use recommended dose."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Reduced efficacy possible. Avoid strong CYP2D6 inhibitors."),
            "poor_metabolizer":       ("avoid", "Consider aromatase inhibitor or higher tamoxifen dose with TDM."),
        },
    },
    "Amitriptyline": {
        "brand": "Elavil", "class": "Tricyclic Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid TCA; likely ineffective."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("caution", "Consider 25% dose reduction."),
            "poor_metabolizer":       ("avoid", "Avoid TCA. If necessary, reduce dose 50%."),
        },
    },
    "Nortriptyline": {
        "brand": "Pamelor", "class": "Tricyclic Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid TCA; reduced efficacy."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("caution", "Consider 25% dose reduction."),
            "poor_metabolizer":       ("avoid", "Avoid TCA or reduce dose 50%."),
        },
    },
    "Desipramine": {
        "brand": "Norpramin", "class": "Tricyclic Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid TCA; likely ineffective."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("caution", "Consider 25% dose reduction."),
            "poor_metabolizer":       ("avoid", "Avoid TCA or reduce dose 50%."),
        },
    },
    "Imipramine": {
        "brand": "Tofranil", "class": "Tricyclic Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid TCA; likely ineffective."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("caution", "Consider 25% dose reduction."),
            "poor_metabolizer":       ("avoid", "Avoid TCA or reduce dose 50%."),
        },
    },
    "Doxepin": {
        "brand": "Sinequan", "class": "Tricyclic Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid TCA; likely ineffective."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("caution", "Consider 25% dose reduction."),
            "poor_metabolizer":       ("avoid", "Avoid TCA or reduce dose 50%."),
        },
    },
    "Trimipramine": {
        "brand": "Surmontil", "class": "Tricyclic Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid TCA; likely ineffective."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("caution", "Consider 25% dose reduction."),
            "poor_metabolizer":       ("avoid", "Avoid TCA or reduce dose 50%."),
        },
    },
    "Clomipramine": {
        "brand": "Anafranil", "class": "Tricyclic Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("avoid", "Avoid TCA; likely ineffective."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("caution", "Consider 25% dose reduction."),
            "poor_metabolizer":       ("avoid", "Avoid TCA or reduce dose 50%."),
        },
    },
    "Paroxetine": {
        "brand": "Paxil", "class": "SSRI Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "Select alternative or titrate to response."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "Consider 50% dose reduction or alternative SSRI."),
        },
    },
    "Fluoxetine": {
        "brand": "Prozac", "class": "SSRI Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "Select alternative or titrate to response."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "Consider 50% dose reduction or alternative."),
        },
    },
    "Venlafaxine": {
        "brand": "Effexor", "class": "SNRI Antidepressant", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("standard", "Use recommended dose."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("standard", "Use recommended dose."),
            "poor_metabolizer":       ("caution", "Consider 50% dose reduction or switch to desvenlafaxine."),
        },
    },
    "Metoprolol": {
        "brand": "Lopressor", "class": "Beta-Blocker", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "May need higher dose or alternative beta-blocker."),
            "normal_metabolizer":     ("standard", "Use recommended starting dose."),
            "intermediate_metabolizer": ("standard", "Use recommended starting dose."),
            "poor_metabolizer":       ("caution", "Consider 50% dose reduction or alternative beta-blocker."),
        },
    },
    "Ondansetron": {
        "brand": "Zofran", "class": "Antiemetic", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "May have reduced antiemetic effect."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("standard", "Use recommended dose."),
            "poor_metabolizer":       ("caution", "May have reduced antiemetic effect; consider alternative."),
        },
    },
    "Risperidone": {
        "brand": "Risperdal", "class": "Antipsychotic", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "May need higher dose."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Consider dose reduction."),
            "poor_metabolizer":       ("caution", "Consider 50% dose reduction."),
        },
    },
    "Aripiprazole": {
        "brand": "Abilify", "class": "Antipsychotic", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("standard", "Use recommended dose."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("standard", "Use recommended dose."),
            "poor_metabolizer":       ("caution", "Reduce dose to 75% of usual."),
        },
    },
    "Haloperidol": {
        "brand": "Haldol", "class": "Antipsychotic", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("caution", "May need higher dose."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Consider dose reduction."),
            "poor_metabolizer":       ("caution", "Reduce dose; monitor for side effects."),
        },
    },
    "Atomoxetine": {
        "brand": "Strattera", "class": "ADHD Medication", "gene": "CYP2D6",
        "recs": {
            "ultrarapid_metabolizer": ("standard", "Use recommended dose."),
            "normal_metabolizer":     ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("standard", "Use recommended dose."),
            "poor_metabolizer":       ("caution", "Start at lower dose; 2-fold higher exposure expected."),
        },
    },
    # --- CYP2C9 drugs ---
    "Phenytoin": {
        "brand": "Dilantin", "class": "Antiepileptic", "gene": "CYP2C9",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Reduce dose 25%. Monitor levels closely."),
            "poor_metabolizer":         ("avoid", "Reduce dose 50%. Consider alternative anticonvulsant."),
        },
    },
    "Celecoxib": {
        "brand": "Celebrex", "class": "NSAID", "gene": "CYP2C9",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Start at lowest dose."),
            "poor_metabolizer":         ("avoid", "Use lowest dose or avoid; consider alternative NSAID."),
        },
    },
    "Flurbiprofen": {
        "brand": "Ansaid", "class": "NSAID", "gene": "CYP2C9",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Start at lowest dose."),
            "poor_metabolizer":         ("avoid", "Use lowest dose or consider alternative."),
        },
    },
    "Piroxicam": {
        "brand": "Feldene", "class": "NSAID", "gene": "CYP2C9",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Start at lowest dose; monitor."),
            "poor_metabolizer":         ("avoid", "Avoid or use lowest dose."),
        },
    },
    "Meloxicam": {
        "brand": "Mobic", "class": "NSAID", "gene": "CYP2C9",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Start at lowest dose."),
            "poor_metabolizer":         ("caution", "Use lowest dose; monitor GI and renal effects."),
        },
    },
    # --- Warfarin (multi-gene) ---
    "Warfarin": {
        "brand": "Coumadin", "class": "Anticoagulant", "genes": ["CYP2C9", "VKORC1"],
        "special": "warfarin",
    },
    # --- SLCO1B1 drugs ---
    "Simvastatin": {
        "brand": "Zocor", "class": "Statin", "gene": "SLCO1B1",
        "recs": {
            "normal_function":       ("standard", "Use desired starting dose."),
            "intermediate_function": ("caution", "Lower dose or alternative statin. 4.5x myopathy risk."),
            "poor_function":         ("avoid", "Use alternative statin (pravastatin, rosuvastatin) or max 20 mg/day."),
        },
    },
    "Atorvastatin": {
        "brand": "Lipitor", "class": "Statin", "gene": "SLCO1B1",
        "recs": {
            "normal_function":       ("standard", "Use desired starting dose."),
            "intermediate_function": ("caution", "Consider CK surveillance."),
            "poor_function":         ("caution", "Lower starting dose or alternative statin."),
        },
    },
    "Rosuvastatin": {
        "brand": "Crestor", "class": "Statin", "gene": "SLCO1B1",
        "recs": {
            "normal_function":       ("standard", "Use desired starting dose."),
            "intermediate_function": ("standard", "Use desired starting dose."),
            "poor_function":         ("standard", "Preferred alternative to simvastatin."),
        },
    },
    "Pravastatin": {
        "brand": "Pravachol", "class": "Statin", "gene": "SLCO1B1",
        "recs": {
            "normal_function":       ("standard", "Use desired starting dose."),
            "intermediate_function": ("standard", "Use desired starting dose."),
            "poor_function":         ("standard", "Preferred alternative to simvastatin."),
        },
    },
    # --- DPYD drugs ---
    "Fluorouracil": {
        "brand": "5-FU", "class": "Antineoplastic", "gene": "DPYD",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Reduce dose 50%. Monitor toxicity closely."),
            "poor_metabolizer":         ("avoid", "Avoid fluorouracil. Select alternative agent."),
        },
    },
    "Capecitabine": {
        "brand": "Xeloda", "class": "Antineoplastic", "gene": "DPYD",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Reduce dose 50%. Monitor toxicity."),
            "poor_metabolizer":         ("avoid", "Avoid capecitabine. Select alternative."),
        },
    },
    # --- TPMT / NUDT15 drugs ---
    "Azathioprine": {
        "brand": "Imuran", "class": "Immunosuppressant", "gene": "TPMT",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Reduce dose 30-70%. Monitor blood counts weekly."),
            "poor_metabolizer":         ("avoid", "Avoid or reduce to 10% dose. Consider alternative."),
        },
    },
    "Mercaptopurine": {
        "brand": "Purinethol", "class": "Immunosuppressant", "gene": "TPMT",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Reduce dose 30-70%. Monitor blood counts."),
            "poor_metabolizer":         ("avoid", "Avoid or reduce to 10% dose."),
        },
    },
    "Thioguanine": {
        "brand": "Tabloid", "class": "Immunosuppressant", "gene": "TPMT",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Reduce dose 30-70%."),
            "poor_metabolizer":         ("avoid", "Avoid or drastically reduce dose."),
        },
    },
    # --- UGT1A1 drug ---
    "Irinotecan": {
        "brand": "Camptosar", "class": "Antineoplastic", "gene": "UGT1A1",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Consider dose reduction if prior toxicity."),
            "poor_metabolizer":         ("avoid", "Reduce initial dose by at least one level."),
        },
    },
    "Atazanavir": {
        "brand": "Reyataz", "class": "Antiretroviral", "gene": "UGT1A1",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("standard", "Use recommended dose. May develop jaundice."),
            "poor_metabolizer":         ("caution", "Higher risk of jaundice. Monitor bilirubin."),
        },
    },
    # --- CYP3A5 drug ---
    "Tacrolimus": {
        "brand": "Prograf", "class": "Immunosuppressant", "gene": "CYP3A5",
        "recs": {
            "extensive_metabolizer":    ("caution", "Increase dose 1.5-2x. Titrate to target trough."),
            "intermediate_metabolizer": ("caution", "Increase dose 1.5x. Titrate to target trough."),
            "poor_metabolizer":         ("standard", "Use recommended dose (most patients)."),
        },
    },
    # --- CYP2B6 drug ---
    "Efavirenz": {
        "brand": "Sustiva", "class": "Antiretroviral", "gene": "CYP2B6",
        "recs": {
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("caution", "Consider dose reduction to 400 mg if toxicity occurs."),
            "poor_metabolizer":         ("caution", "Reduce dose to 400 mg or consider alternative."),
        },
    },
    # --- CYP1A2 drugs ---
    "Clozapine": {
        "brand": "Clozaril", "class": "Antipsychotic", "gene": "CYP1A2",
        "recs": {
            "ultrarapid_metabolizer":   ("caution", "May need higher dose; monitor levels."),
            "normal_metabolizer":       ("standard", "Use recommended dose."),
            "intermediate_metabolizer": ("standard", "Use recommended dose."),
            "poor_metabolizer":         ("caution", "Consider dose reduction; monitor for toxicity."),
        },
    },
}


# ---------------------------------------------------------------------------
# 4. File parser
# ---------------------------------------------------------------------------

def detect_format(lines):
    for line in lines[:20]:
        if line.startswith("# rsid"):
            return "23andme"
        if "RSID" in line and "CHROMOSOME" in line:
            return "ancestrydna"
        if "rsid" in line and "chromosome" in line:
            return "generic"
    for line in lines[:20]:
        if line.startswith("#") or line.strip() == "":
            continue
        parts = line.split("\t")
        if len(parts) >= 4 and parts[0].startswith("rs"):
            return "23andme"
        parts = line.split(",")
        if len(parts) >= 4 and parts[0].startswith("rs"):
            return "ancestrydna"
    return "unknown"


def parse_file(path):
    content = Path(path).read_text()
    lines = content.split("\n")
    fmt = detect_format(lines)

    snps = {}
    for line in lines:
        if line.startswith("#") or line.strip() == "":
            continue
        if "rsid" in line.lower() and "chromosome" in line.lower():
            continue
        parts = line.split("\t") if "\t" in line else line.split(",")
        if len(parts) >= 4:
            rsid = parts[0].strip()
            if not rsid.startswith("rs"):
                continue
            if len(parts) == 5:
                genotype = parts[3].strip() + parts[4].strip()
            else:
                genotype = parts[3].strip()
            if genotype and genotype not in ("--", "00"):
                snps[rsid] = genotype.upper()

    pgx = {}
    for rsid, info in PGX_SNPS.items():
        if rsid in snps:
            pgx[rsid] = {"genotype": snps[rsid], **info}

    return fmt, len(snps), pgx


# ---------------------------------------------------------------------------
# 5. Star allele caller
# ---------------------------------------------------------------------------

def call_diplotype(gene, pgx_snps):
    gdef = GENE_DEFS[gene]

    if gdef.get("type") == "genotype":
        rsid = gdef["rsid"]
        if rsid in pgx_snps:
            return pgx_snps[rsid]["genotype"]
        return gdef["ref"] + gdef["ref"]

    detected = []
    for rsid, vdef in gdef["variants"].items():
        if rsid in pgx_snps:
            gt = pgx_snps[rsid]["genotype"]
            alt = vdef["alt"].upper()
            alt_count = gt.count(alt) if alt != "DEL" and alt != "INS" and alt != "TA7" else 0
            if alt_count > 0:
                detected.append({"rsid": rsid, "allele": vdef["allele"],
                                 "copies": alt_count, "effect": vdef["effect"]})

    if gdef.get("type") == "dpyd":
        if not detected:
            return "Normal/Normal"
        v = detected[0]
        if v["copies"] == 2:
            return f"{v['allele']}/{v['allele']}"
        return f"Normal/{v['allele']}"

    if not detected:
        return f"{gdef['ref']}/{gdef['ref']}"

    detected.sort(key=lambda v: (0 if v["effect"] == "no_function" else 1))

    a1_parts, a2_parts = [], []
    for v in detected:
        if v["copies"] == 2:
            a1_parts.append(v["allele"])
            a2_parts.append(v["allele"])
        elif v["copies"] == 1:
            if not a1_parts:
                a1_parts.append(v["allele"])
            elif not a2_parts:
                a2_parts.append(v["allele"])

    a1 = a1_parts[0] if a1_parts else gdef["ref"]
    a2 = a2_parts[0] if a2_parts else gdef["ref"]
    alleles = sorted([a1, a2])
    return "/".join(alleles)


def call_phenotype(gene, diplotype):
    gdef = GENE_DEFS[gene]
    norm = diplotype.upper()
    for desc, conditions in gdef["phenotypes"].items():
        for cond in conditions:
            if norm == cond.upper():
                return desc
            parts = cond.split("/")
            if len(parts) == 2 and norm == f"{parts[1]}/{parts[0]}".upper():
                return desc
    return "Normal (inferred)"


# ---------------------------------------------------------------------------
# 6. Drug recommendation lookup
# ---------------------------------------------------------------------------

def phenotype_to_key(phenotype_desc):
    """Map phenotype description to GUIDELINES rec key."""
    mapping = {
        "Normal Metabolizer": "normal_metabolizer",
        "Intermediate Metabolizer": "intermediate_metabolizer",
        "Poor Metabolizer": "poor_metabolizer",
        "Ultrarapid Metabolizer": "ultrarapid_metabolizer",
        "Normal (inferred)": "normal_metabolizer",
        "Normal Warfarin Sensitivity": "normal_warfarin_sensitivity",
        "Intermediate Warfarin Sensitivity": "intermediate_warfarin_sensitivity",
        "High Warfarin Sensitivity": "high_warfarin_sensitivity",
        "Normal Function": "normal_function",
        "Intermediate Function": "intermediate_function",
        "Poor Function": "poor_function",
        "CYP3A5 Expressor": "extensive_metabolizer",
        "Intermediate Expressor": "intermediate_metabolizer",
        "CYP3A5 Non-expressor": "poor_metabolizer",
    }
    return mapping.get(phenotype_desc, "normal_metabolizer")


def get_warfarin_rec(profiles):
    cyp2c9 = profiles.get("CYP2C9", {}).get("phenotype", "Normal Metabolizer")
    vkorc1 = profiles.get("VKORC1", {}).get("phenotype", "Normal Warfarin Sensitivity")

    cyp2c9_normal = "normal" in cyp2c9.lower()
    vkorc1_normal = "normal" in vkorc1.lower()

    if cyp2c9_normal and vkorc1_normal:
        return "standard", "Use warfarin dosing algorithm. Standard dose range expected."
    elif "poor" in cyp2c9.lower() or "high" in vkorc1.lower():
        return "avoid", "Significantly reduce dose (50-80% reduction). Consider DOAC alternative."
    else:
        return "caution", "Reduce initial dose. Use genotype-guided dosing algorithm."


def lookup_drugs(profiles):
    results = {"standard": [], "caution": [], "avoid": []}

    for drug_name, drug in GUIDELINES.items():
        if drug.get("special") == "warfarin":
            classification, rec = get_warfarin_rec(profiles)
            results[classification].append({
                "drug": drug_name, "brand": drug["brand"],
                "class": drug["class"], "gene": "CYP2C9+VKORC1",
                "recommendation": rec, "classification": classification,
            })
            continue

        gene = drug["gene"]
        if gene not in profiles:
            continue

        pheno_key = phenotype_to_key(profiles[gene]["phenotype"])
        recs = drug.get("recs", {})

        if pheno_key in recs:
            classification, rec = recs[pheno_key]
        else:
            classification, rec = "standard", "Use recommended dose."

        results[classification].append({
            "drug": drug_name, "brand": drug["brand"],
            "class": drug["class"], "gene": gene,
            "recommendation": rec, "classification": classification,
        })

    return results


# ---------------------------------------------------------------------------
# 7. Report generator
# ---------------------------------------------------------------------------

ICON = {"standard": "OK", "caution": "CAUTION", "avoid": "AVOID"}


def generate_report(input_path, fmt, total_snps, pgx_snps, profiles, drug_results):
    now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
    checksum = hashlib.sha256(Path(input_path).read_bytes()).hexdigest()
    fname = Path(input_path).name

    lines = []
    lines.append("# ClawBio PharmGx Report")
    lines.append("")
    lines.append(f"**Date**: {now}")
    lines.append(f"**Input**: `{fname}`")
    lines.append(f"**Format detected**: {fmt}")
    lines.append(f"**Checksum (SHA-256)**: `{checksum}`")
    lines.append(f"**Total SNPs in file**: {total_snps}")
    lines.append(f"**Pharmacogenomic SNPs found**: {len(pgx_snps)}/{len(PGX_SNPS)}")
    lines.append(f"**Genes profiled**: {len(profiles)}")
    lines.append(f"**Drugs assessed**: {sum(len(v) for v in drug_results.values())}")
    lines.append("")
    lines.append("---")
    lines.append("")

    # Summary counts
    n_std = len(drug_results["standard"])
    n_cau = len(drug_results["caution"])
    n_avo = len(drug_results["avoid"])
    lines.append("## Drug Response Summary")
    lines.append("")
    lines.append(f"| Category | Count |")
    lines.append(f"|----------|-------|")
    lines.append(f"| Standard dosing | {n_std} |")
    lines.append(f"| Use with caution | {n_cau} |")
    lines.append(f"| Avoid / use alternative | {n_avo} |")
    lines.append("")

    # Alert drugs
    if n_avo > 0 or n_cau > 0:
        lines.append("### Actionable Alerts")
        lines.append("")
        if n_avo > 0:
            lines.append("**AVOID / USE ALTERNATIVE:**")
            lines.append("")
            for d in drug_results["avoid"]:
                lines.append(f"- **{d['drug']}** ({d['brand']}) [{d['gene']}]: {d['recommendation']}")
            lines.append("")
        if n_cau > 0:
            lines.append("**USE WITH CAUTION:**")
            lines.append("")
            for d in drug_results["caution"]:
                lines.append(f"- **{d['drug']}** ({d['brand']}) [{d['gene']}]: {d['recommendation']}")
            lines.append("")

    lines.append("---")
    lines.append("")

    # Gene profiles
    lines.append("## Gene Profiles")
    lines.append("")
    lines.append("| Gene | Full Name | Diplotype | Phenotype |")
    lines.append("|------|-----------|-----------|-----------|")
    for gene in GENE_DEFS:
        if gene in profiles:
            p = profiles[gene]
            lines.append(f"| {gene} | {GENE_DEFS[gene]['name']} | {p['diplotype']} | {p['phenotype']} |")
    lines.append("")

    # Detected variants
    lines.append("## Detected Variants")
    lines.append("")
    lines.append("| rsID | Gene | Star Allele | Genotype | Effect |")
    lines.append("|------|------|-------------|----------|--------|")
    for rsid, info in sorted(pgx_snps.items(), key=lambda x: x[1]["gene"]):
        lines.append(f"| {rsid} | {info['gene']} | {info['allele']} | {info['genotype']} | {info['effect']} |")
    lines.append("")

    # Full drug table
    lines.append("---")
    lines.append("")
    lines.append("## Complete Drug Recommendations")
    lines.append("")
    lines.append("| Drug | Brand | Class | Gene | Status | Recommendation |")
    lines.append("|------|-------|-------|------|--------|----------------|")
    for cat in ["avoid", "caution", "standard"]:
        for d in sorted(drug_results[cat], key=lambda x: x["drug"]):
            status = ICON[d["classification"]]
            lines.append(f"| {d['drug']} | {d['brand']} | {d['class']} | {d['gene']} | {status} | {d['recommendation']} |")
    lines.append("")

    # Disclaimer
    lines.append("---")
    lines.append("")
    lines.append("## Disclaimer")
    lines.append("")
    lines.append("This report is for **research and educational purposes only**. "
                 "It is NOT a diagnostic device and should NOT be used to make medication decisions "
                 "without consulting a qualified healthcare professional.")
    lines.append("")
    lines.append("Pharmacogenomic recommendations are based on CPIC guidelines (cpicpgx.org). "
                 "DTC genetic tests have limitations: they may not detect all relevant variants, "
                 "and results should be confirmed by clinical-grade testing before clinical use.")
    lines.append("")

    # Methods
    lines.append("## Methods")
    lines.append("")
    lines.append("- **Tool**: ClawBio PharmGx Reporter v0.1.0")
    lines.append("- **SNP panel**: 31 pharmacogenomic variants across 12 genes")
    lines.append("- **Star allele calling**: Simplified DTC-compatible algorithm (single-SNP per allele)")
    lines.append("- **Phenotype assignment**: CPIC-based diplotype-to-phenotype mapping")
    lines.append("- **Drug guidelines**: 51 drugs from CPIC (cpicpgx.org), simplified for DTC context")
    lines.append("")

    # Reproducibility
    lines.append("## Reproducibility")
    lines.append("")
    lines.append("```bash")
    lines.append(f"python pharmgx_reporter.py --input {fname} --output report")
    lines.append("```")
    lines.append("")
    lines.append(f"**Input checksum**: `{checksum}`")
    lines.append("")

    # References
    lines.append("## References")
    lines.append("")
    lines.append("- Corpas, M. (2026). ClawBio. https://github.com/ClawBio/ClawBio")
    lines.append("- CPIC. Clinical Pharmacogenetics Implementation Consortium. https://cpicpgx.org/")
    lines.append("- Caudle, K.E. et al. (2014). Standardizing terms for clinical pharmacogenetic test results. Genet Med, 16(9), 655-663.")
    lines.append("- PharmGKB. https://www.pharmgkb.org/")
    lines.append("")

    return "\n".join(lines)


# ---------------------------------------------------------------------------
# 8. Main
# ---------------------------------------------------------------------------

def main():
    parser = argparse.ArgumentParser(
        description="ClawBio PharmGx Reporter: pharmacogenomic report from DTC genetic data")
    parser.add_argument("--input", required=True, help="Path to genetic data file (23andMe/AncestryDNA)")
    parser.add_argument("--output", default="pharmgx_report", help="Output directory (default: pharmgx_report)")
    args = parser.parse_args()

    if not Path(args.input).exists():
        print(f"Error: input file not found: {args.input}", file=sys.stderr)
        sys.exit(1)

    print(f"ClawBio PharmGx Reporter v0.1.0")
    print(f"================================")
    print()

    # Parse
    print(f"Parsing: {args.input}")
    fmt, total_snps, pgx_snps = parse_file(args.input)
    print(f"  Format: {fmt}")
    print(f"  Total SNPs: {total_snps}")
    print(f"  PGx SNPs found: {len(pgx_snps)}/{len(PGX_SNPS)}")
    print()

    # Profile genes
    profiles = {}
    for gene in GENE_DEFS:
        diplotype = call_diplotype(gene, pgx_snps)
        phenotype = call_phenotype(gene, diplotype)
        profiles[gene] = {"diplotype": diplotype, "phenotype": phenotype}

    print("Gene Profiles:")
    print(f"  {'Gene':<10} {'Diplotype':<15} {'Phenotype'}")
    print(f"  {'-'*10} {'-'*15} {'-'*30}")
    for gene, p in profiles.items():
        print(f"  {gene:<10} {p['diplotype']:<15} {p['phenotype']}")
    print()

    # Drug lookup
    drug_results = lookup_drugs(profiles)
    n_std = len(drug_results["standard"])
    n_cau = len(drug_results["caution"])
    n_avo = len(drug_results["avoid"])

    print(f"Drug Recommendations ({n_std + n_cau + n_avo} drugs):")
    print(f"  Standard:  {n_std}")
    print(f"  Caution:   {n_cau}")
    print(f"  Avoid:     {n_avo}")
    print()

    if n_avo > 0:
        print("ALERT - Drugs to AVOID:")
        for d in drug_results["avoid"]:
            print(f"  * {d['drug']} ({d['brand']}): {d['recommendation']}")
        print()

    # Generate report
    outdir = Path(args.output)
    outdir.mkdir(parents=True, exist_ok=True)
    report = generate_report(args.input, fmt, total_snps, pgx_snps, profiles, drug_results)
    report_path = outdir / "report.md"
    report_path.write_text(report)

    print(f"Report saved: {report_path}")
    print("Done.")


if __name__ == "__main__":
    main()


path: tests/test_pharmgx.py
content: """
test_pharmgx.py — Automated test suite for PharmGx Reporter

Run with: pytest skills/pharmgx-reporter/tests/test_pharmgx.py -v

Uses the FIXED demo patient (demo_patient.txt) with known genotypes
so that all assertions are deterministic and reproducible.
"""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

from pharmgx_reporter import (
    PGX_SNPS,
    GENE_DEFS,
    GUIDELINES,
    detect_format,
    parse_file,
    call_diplotype,
    call_phenotype,
    phenotype_to_key,
    lookup_drugs,
    generate_report,
)

DEMO = Path(__file__).parent.parent / "demo_patient.txt"


# ── Parsing ────────────────────────────────────────────────────────────────────

def test_detect_format_23andme():
    lines = DEMO.read_text().split("\n")
    assert detect_format(lines) == "23andme"


def test_parse_file_finds_all_pgx_snps():
    fmt, total_snps, pgx_snps = parse_file(str(DEMO))
    assert fmt == "23andme"
    assert total_snps == 30  # 31 data lines but one has genotype "--" or is skipped
    assert len(pgx_snps) == len(PGX_SNPS), (
        f"Expected {len(PGX_SNPS)} PGx SNPs, got {len(pgx_snps)}"
    )


def test_parse_file_genotype_values():
    _, _, pgx = parse_file(str(DEMO))
    # CYP2C19 *2 het
    assert pgx["rs4244285"]["genotype"] == "AG"
    # CYP2D6 *4 hom
    assert pgx["rs3892097"]["genotype"] == "TT"
    # VKORC1 het
    assert pgx["rs9923231"]["genotype"] == "GA"


# ── Star Allele Calling ───────────────────────────────────────────────────────

def _profiles():
    """Build profiles from demo patient for reuse across tests."""
    _, _, pgx = parse_file(str(DEMO))
    profiles = {}
    for gene in GENE_DEFS:
        diplotype = call_diplotype(gene, pgx)
        phenotype = call_phenotype(gene, diplotype)
        profiles[gene] = {"diplotype": diplotype, "phenotype": phenotype}
    return profiles


def test_cyp2c19_diplotype():
    """Demo patient: rs4244285 AG (*2 het), rest ref → *1/*2."""
    p = _profiles()
    assert p["CYP2C19"]["diplotype"] == "*1/*2"


def test_cyp2d6_diplotype():
    """Demo patient: rs3892097 TT (*4 hom) → *4/*4."""
    p = _profiles()
    assert p["CYP2D6"]["diplotype"] == "*4/*4"


def test_vkorc1_genotype():
    """Demo patient: rs9923231 GA → GA diplotype."""
    p = _profiles()
    assert p["VKORC1"]["diplotype"] == "GA"


def test_slco1b1_genotype():
    """Demo patient: rs4149056 TC → TC diplotype."""
    p = _profiles()
    assert p["SLCO1B1"]["diplotype"] == "TC"


def test_cyp3a5_diplotype():
    """Demo patient: rs776746 GG (*3 hom) → *3/*3."""
    p = _profiles()
    assert p["CYP3A5"]["diplotype"] == "*3/*3"


# ── Phenotype Assignment ──────────────────────────────────────────────────────

def test_cyp2c19_intermediate():
    p = _profiles()
    assert p["CYP2C19"]["phenotype"] == "Intermediate Metabolizer"


def test_cyp2d6_poor():
    p = _profiles()
    assert p["CYP2D6"]["phenotype"] == "Poor Metabolizer"


def test_vkorc1_intermediate_sensitivity():
    p = _profiles()
    assert p["VKORC1"]["phenotype"] == "Intermediate Warfarin Sensitivity"


def test_slco1b1_intermediate():
    p = _profiles()
    assert p["SLCO1B1"]["phenotype"] == "Intermediate Function"


def test_cyp3a5_nonexpressor():
    p = _profiles()
    assert p["CYP3A5"]["phenotype"] == "CYP3A5 Non-expressor"


def test_dpyd_normal():
    """All DPYD SNPs are ref → Normal Metabolizer."""
    p = _profiles()
    assert p["DPYD"]["phenotype"] == "Normal Metabolizer"


def test_tpmt_normal():
    p = _profiles()
    assert p["TPMT"]["phenotype"] == "Normal Metabolizer"


# ── Drug Recommendations ──────────────────────────────────────────────────────

def test_drug_lookup_returns_all_categories():
    p = _profiles()
    results = lookup_drugs(p)
    assert "standard" in results
    assert "caution" in results
    assert "avoid" in results
    total = sum(len(v) for v in results.values())
    assert total > 0


def test_clopidogrel_caution_for_intermediate():
    """CYP2C19 *1/*2 → Intermediate → Clopidogrel should be caution."""
    p = _profiles()
    results = lookup_drugs(p)
    clop = [d for d in results["caution"] if d["drug"] == "Clopidogrel"]
    assert len(clop) == 1, "Clopidogrel should be in caution list"


def test_codeine_avoid_for_poor_cyp2d6():
    """CYP2D6 *4/*4 → Poor Metabolizer → Codeine should be avoid."""
    p = _profiles()
    results = lookup_drugs(p)
    codeine = [d for d in results["avoid"] if d["drug"] == "Codeine"]
    assert len(codeine) == 1, "Codeine should be in avoid list for CYP2D6 PM"


def test_simvastatin_caution_for_intermediate_slco1b1():
    """SLCO1B1 TC → Intermediate → Simvastatin should be caution."""
    p = _profiles()
    results = lookup_drugs(p)
    simva = [d for d in results["caution"] if d["drug"] == "Simvastatin"]
    assert len(simva) == 1, "Simvastatin should be in caution list"


# ── Phenotype Key Mapping ─────────────────────────────────────────────────────

def test_phenotype_key_mapping():
    assert phenotype_to_key("Normal Metabolizer") == "normal_metabolizer"
    assert phenotype_to_key("Poor Metabolizer") == "poor_metabolizer"
    assert phenotype_to_key("High Warfarin Sensitivity") == "high_warfarin_sensitivity"
    assert phenotype_to_key("CYP3A5 Non-expressor") == "poor_metabolizer"
    assert phenotype_to_key("Normal (inferred)") == "normal_metabolizer"


# ── Report Generation ─────────────────────────────────────────────────────────

def test_report_contains_key_sections():
    _, _, pgx = parse_file(str(DEMO))
    p = _profiles()
    results = lookup_drugs(p)
    report = generate_report(str(DEMO), "23andme", 31, pgx, p, results)
    assert "# ClawBio PharmGx Report" in report
    assert "Drug Response Summary" in report
    assert "Gene Profiles" in report
    assert "Detected Variants" in report
    assert "Disclaimer" in report
    assert "Methods" in report
    assert "Reproducibility" in report


def test_report_contains_disclaimer():
    _, _, pgx = parse_file(str(DEMO))
    p = _profiles()
    results = lookup_drugs(p)
    report = generate_report(str(DEMO), "23andme", 31, pgx, p, results)
    assert "NOT a diagnostic device" in report


# ── Data Integrity ─────────────────────────────────────────────────────────────

def test_all_genes_have_phenotype_mappings():
    """Every gene in GENE_DEFS must have at least one phenotype."""
    for gene, gdef in GENE_DEFS.items():
        assert "phenotypes" in gdef, f"{gene} missing phenotypes"
        assert len(gdef["phenotypes"]) >= 2, f"{gene} has fewer than 2 phenotypes"


def test_all_guideline_drugs_reference_valid_genes():
    """Every drug in GUIDELINES must reference a gene in GENE_DEFS."""
    for drug, info in GUIDELINES.items():
        if info.get("special") == "warfarin":
            continue
        gene = info["gene"]
        assert gene in GENE_DEFS, f"{drug} references unknown gene {gene}"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (MEDIUM)