Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

drug-interaction-checker

Python 3.10+ License: MIT Runtime: stdlib only Tests: 40 offline

A drug-drug interaction (DDI) checking skill for any LLM agent — opencode, Claude Code, Codex, and others. It parses a free-text drug list (Chinese or English names, brand names, D numbers), computes shared mechanism elements from KEGG drug entries, looks up severity in DDInter (~150k pairs), and hands the agent a structured JSON result with a bilingual term dictionary, from which the agent writes an interpretation report. The skill is plain Markdown plus Python: SKILL.md defines the workflow, scripts/ does the computation.


Scope, limitations, and disclaimer

Read this section first; it defines what this tool is and is not.

Not medical advice. The tool analyzes data and generates reports for reference only. It never provides dose adjustments, dose percentages, blood-concentration targets, or guideline citations — its data contains none — and every report ends with a fixed disclaimer. It is not suitable for clinical decision support (prescription screening, dose adjustment, or contraindication enforcement). For clinical decisions, consult a qualified professional.

Coverage is bounded by its data sources. Only drugs in KEGG/DDInter can be checked (the name index holds ~12.9k drugs; DDInter ~1,806 drugs). Unrecognized names are reported explicitly as unmatched, never silently skipped. Chinese names cover common drugs well via a Wikidata-derived table (~8.5k entries), with a curated residue for brand names, combination products, and traditional Chinese medicines — the tool does not cover TCM-herb or herb-drug interactions.

The mechanism signal is weak. Shared elements explain, they do not prove: backtested precision is 25.2% against a 14.0% random baseline, and recall is 24.5%. A missing DDInter record does not imply no interaction, and a shared element does not imply a clinical effect.

The model is intentionally shallow. Element graphs carry only KEGG-annotated fields (targets, metabolic enzymes, CYP inhibition, transporters, pathways). Dose, route of administration, blood concentrations, metabolites, and special populations are not modeled — so concentration- dependent or route-dependent interactions are out of scope by design.

Prodrug direction is handled, not guaranteed. A curated list (data/prodrugs.json, ~24 common prodrugs) inverts the interpretation when a prodrug's activation enzyme is shared with an inhibitor (e.g. clopidogrel × fluconazole: efficacy may fall, not toxicity rise). The list is curated and incomplete; prodrugs not listed follow the default direction. Interpretation is rule-based — pattern cards are domain heuristics, not validated pharmacology.

Snapshots age. The shipped ddinter.sqlite and drug_index.json are snapshots of their sources at build time (built_at, surfaced in --json output); KEGG and DDInter update on their own cadence. Rebuild periodically to stay current.


What it does

  • Agent-agnostic — any agent that reads SKILL.md and runs shell commands can drive it; no LLM API or external service is called by the scripts.
  • Bilingual by design — the output language follows the input list: Chinese input produces a Chinese report, English input an English one.
  • Two data layers — a mechanism layer (KEGG element graphs) explains why two drugs might interact; a fact layer (DDInter) decides whether they are recorded as interacting and how severe.
  • Interpretation, not transcription — a curated bilingual term dictionary resolves into per-pair element and reasoning-pattern cards, so reports explain shared mechanisms instead of restating raw data.
  • Prodrug-aware direction — when a prodrug's activation enzyme is shared with an inhibitor, the report is told to invert the usual direction (efficacy may fall) instead of claiming raised exposure.
  • Works out of the box — the interaction database and name index ship as committed, timestamped snapshots; no build step is required to run checks.

Workflow

User drug list (free text: Chinese/English names, brand names, D numbers)
        │
        ▼
① List parsing     tokenize → normalize (parentheses/salt forms) →
                   Chinese alias table → name index → D numbers
② Drug graph       batch-fetch KEGG entries (throttled + cached) →
                   parse into "element graphs"
③ All-pair         per pair: shared-element intersection
   compute          + DDInter fact lookup (severity / guidance)
                   + term dictionary → per-pair term cards
        │
        ▼
output: console summary + result.json  (--json)
        │
        ▼
agent writes the natural-language report from result.json
(mechanism interpretation grounded in data + term dictionary; no fabrication)

Shared elements are a weak but real signal: backtested precision is 25.2% against a 14.0% random baseline, and recall 24.5% — roughly three quarters of known interactions carry no annotatable shared mechanism. The report therefore judges by the fact layer, explains via the mechanism layer, and states this caveat explicitly.

Scripts

Two scripts drive the skill; usage details live in SKILL.md:

  • scripts/check_interactions.py — the computation entry point: parses the drug list, computes all-pair overlaps, resolves the term dictionary, and emits a console summary plus an optional --json full result.
  • scripts/build_data.py — one-time data build: DDInter database, KEGG drug name index, and the Wikidata-generated Chinese-name mapping.

Python 3.10+, pure standard library, zero third-party dependencies at runtime.

Data

data/ mixes committed artifacts with build products:

File Source Purpose
ddinter.sqlite DDInter 8 CSVs fact layer: 150,420 pairs × severity/guidance (committed snapshot)
drug_index.json KEGG list/drug (~12.9k drugs) name → D number index + fuzzy prefix buckets (committed snapshot)
chinese_names.json Wikidata (via KEGG names + PubChem CIDs), Unihan trad→simp Chinese alias table (~8.5k entries, committed)
chinese_names_overlay.json hand-curated (17 entries) residue for Wikidata gaps (committed)
trad_simp.json Unicode Unihan trad→simp char table (committed)
terms.json curated, bilingual term dictionary: concepts / patterns / levels / element cards (committed)
prodrugs.json curated (~24 entries) prodrug direction notes: name → activation enzymes (committed)
cache.sqlite runtime KEGG entry cache + PubChem CIDs
wikidata_zh_cache.json build-time resumable Wikidata build

ddinter.sqlite and drug_index.json ship as committed snapshots so the skill works out of the box. Every build stamps them with a UTC built_at timestamp (an meta table row in the sqlite, a built_at field in the index JSON), surfaced in --json results as data.drug_index_built_at / data.ddinter_built_at; legacy files without a stamp report null. Rebuild any time to refresh the snapshots.

Element graphs are parsed from KEGG flat-file fields: TARGET → target/binder, METABOLISM → enzyme/substrate, INTERACTION → enzyme/inhibitor (CYP), TRANSPORTER → transporter/substrate, PATHWAY → pathway/involved.

Repository layout

.
├── SKILL.md                  # skill definition: triggers + workflow + report rules
├── README.md                 # this document
├── LICENSE                   # MIT license
├── pyproject.toml            # dev tooling: ruff + pytest config
├── scripts/
│   ├── check_interactions.py # CLI entry point (arg parsing + console rendering)
│   ├── build_data.py         # one-time data build
│   └── lib/                  # shared library, one module per domain
│       ├── config.py         # paths + network policy constants
│       ├── errors.py         # typed errors
│       ├── http.py           # throttled/retried fetch (KEGG, Wikidata)
│       ├── db.py             # sqlite connection lifecycle
│       ├── kegg.py           # KEGG entry fetch (single/batch) + parsing + cache
│       ├── names.py          # normalization + drug-list matching
│       ├── ddinter.py        # severity lookup
│       ├── terms.py          # term dictionary loading + resolution
│       ├── overlap.py        # pair analysis (shared elements + fact lookup)
│       ├── texts.py          # bilingual console strings + caveat
│       └── service.py        # pure compute(): orchestration, no I/O side effects
├── tests/                    # offline pytest suite (recorded KEGG fixtures)
│   ├── conftest.py           # tmp data dir + fixture helpers
│   └── test_*.py             # kegg / names / terms / overlap / compute
└── data/                     # committed files marked ✓, build artifacts rest
    ├── chinese_names.json         # Chinese → English INN mapping ✓
    ├── chinese_names_overlay.json # curated residue (17 entries) ✓
    ├── trad_simp.json             # Unihan trad→simp char table ✓
    ├── terms.json                 # bilingual term dictionary ✓
    ├── prodrugs.json              # prodrug direction notes ✓
    ├── ddinter.sqlite             # interaction database (committed snapshot, built_at ✓)
    ├── drug_index.json            # drug-name index (committed snapshot, built_at ✓)
    ├── cache.sqlite               # KEGG entry cache + PubChem links (runtime)
    └── wikidata_zh_cache.json     # Wikidata build cache (resume support)

Development

Runtime is pure stdlib; the test suite (tests/, fully offline, recorded KEGG fixtures) and linting (ruff) run via a local venv. service.compute() is the single seam — pure, typed errors, injectable batch-fetch — which is what keeps the suite offline.

License

MIT — see LICENSE.

About

A drug-drug interaction (DDI) checking skill for agent

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages