Spaces:
Running
Running
File size: 47,840 Bytes
a04c9e9 819adf9 a04c9e9 819adf9 04e78e6 a04c9e9 f32824f a04c9e9 f32824f a04c9e9 f32824f 819adf9 a04c9e9 819adf9 a04c9e9 04e78e6 a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 819adf9 a04c9e9 56881ad a04c9e9 819adf9 a04c9e9 04e78e6 819adf9 04e78e6 56881ad 04e78e6 56881ad 04e78e6 819adf9 a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 56881ad a04c9e9 f32824f 56881ad f32824f 56881ad f32824f 56881ad f32824f 56881ad f32824f 56881ad f32824f 56881ad f32824f 56881ad f32824f 56881ad f32824f 56881ad a04c9e9 56881ad a04c9e9 d8264d4 a04c9e9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 |
import gradio as gr
from typing import Dict, Any
from datetime import datetime, timedelta
from brand_to_generic import brand_lookup, set_pbs_data
from dbi_mcp import dbi_mcp, dbi_mcp_mixed_routes
from clinical_calculators import (
cockcroft_gault_creatinine_clearance,
ckd_epi_egfr,
child_pugh_score,
bmi_calculator,
ideal_body_weight,
dosing_weight_recommendation,
creatinine_conversion,
)
from caching import with_caching, api_cache
from utils import with_error_handling, standardize_response, format_json_output
from drug_data_endpoints import (
search_adverse_events,
fetch_event_details,
drug_label_warnings,
drug_recalls,
drug_pregnancy_lactation,
drug_dose_adjustments,
drug_livertox_summary,
)
from adr_analysis import (
enhanced_faers_search,
calculate_naranjo_score,
disproportionality_analysis,
find_similar_cases,
temporal_analysis,
)
import time
import sys
import logging
from apscheduler.schedulers.background import BackgroundScheduler
import pandas as pd
try:
from datasets import load_dataset
HAVE_DATASETS = True
except ImportError:
HAVE_DATASETS = False
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def load_pbs_data():
"""Load PBS data from Hugging Face Hub, with fallback to previous month."""
if not HAVE_DATASETS:
logger.warning("`datasets` library not installed. Skipping PBS data load.")
set_pbs_data(pd.DataFrame())
return
today = datetime.now()
current_month_str = today.strftime("%Y-%m")
first_day_current_month = today.replace(day=1)
last_day_last_month = first_day_current_month - timedelta(days=1)
last_month_str = last_day_last_month.strftime("%Y-%m")
loaded = False
for month_str in [current_month_str, last_month_str]:
try:
logger.info(f"Attempting to load PBS data for {month_str}")
ds = load_dataset("cmcmaster/pbs_items", month_str, trust_remote_code=True)
if 'train' in ds:
pbs_df = ds['train'].to_pandas()
set_pbs_data(pbs_df)
logger.info(f"Successfully loaded PBS data for {month_str}. Shape: {pbs_df.shape}")
loaded = True
break
else:
logger.error(f"No 'train' split found in dataset for month {month_str}")
except Exception as e:
logger.warning(f"Failed to load PBS data for {month_str}: {e}")
if not loaded:
logger.error(f"Failed to load PBS data for both {current_month_str} and {last_month_str}. PBS lookups will be disabled.")
set_pbs_data(pd.DataFrame())
# Initial load on startup
logger.info("Performing initial load of PBS data...")
load_pbs_data()
# Schedule daily refresh
scheduler = BackgroundScheduler(daemon=True)
scheduler.add_job(load_pbs_data, 'interval', days=1)
scheduler.start()
@with_error_handling
def _brand_lookup_gradio(brand_name: str, prefer_countries_str: str = ""):
"""Brand to generic lookup for single input."""
prefer_countries_list = (
[c.strip().upper() for c in prefer_countries_str.split(",") if c.strip()]
if prefer_countries_str
else None
)
result = brand_lookup(brand_name, prefer_countries=prefer_countries_list)
return standardize_response(result, "brand_to_generic")
@with_error_handling
def _dbi_mcp_mixed_routes_gradio(text_block: str):
result = dbi_mcp_mixed_routes(text_block, ref_csv="dbi_reference_by_route.csv")
return standardize_response(result, "dbi_calculator_mixed_routes")
@with_error_handling
def _cockcroft_gault_gradio(
age: int, weight_kg: float, serum_creatinine: float, is_female: bool
):
result = cockcroft_gault_creatinine_clearance(
age, weight_kg, serum_creatinine, is_female
)
return standardize_response(result, "cockcroft_gault_calculator")
@with_error_handling
def _ckd_epi_gradio(age: int, serum_creatinine: float, is_female: bool, is_black: bool):
result = ckd_epi_egfr(age, serum_creatinine, is_female, is_black)
return standardize_response(result, "ckd_epi_calculator")
@with_error_handling
def _child_pugh_gradio(
bilirubin: float, albumin: float, inr: float, ascites: str, encephalopathy: str
):
result = child_pugh_score(bilirubin, albumin, inr, ascites, encephalopathy)
return standardize_response(result, "child_pugh_calculator")
@with_error_handling
def _bmi_gradio(weight_kg: float, height_cm: float):
result = bmi_calculator(weight_kg, height_cm)
return standardize_response(result, "bmi_calculator")
@with_error_handling
def _ideal_body_weight_gradio(height_cm: float, is_male: bool):
result = ideal_body_weight(height_cm, is_male)
return standardize_response(result, "ideal_body_weight_calculator")
@with_error_handling
def _dosing_weight_gradio(actual_weight: float, height_cm: float, is_male: bool):
result = dosing_weight_recommendation(actual_weight, height_cm, is_male)
return standardize_response(result, "dosing_weight_calculator")
@with_error_handling
def _creatinine_conversion_gradio(value: float, from_unit: str, to_unit: str):
result = creatinine_conversion(value, from_unit, to_unit)
return standardize_response(result, "creatinine_conversion")
@with_error_handling
@with_caching(ttl=1800)
def search_adverse_events_mcp(drug_name: str, limit: str = "5") -> str:
"""
Searches the FDA Adverse Event Reporting System (FAERS) database for adverse events associated with a specific drug. This tool is useful for initial investigation into a drug's safety profile by retrieving summaries of reported adverse event cases. It provides a quick overview of potential side effects reported by healthcare professionals and the public.
Args:
drug_name (str): Generic or brand name to search (case-insensitive)
limit (str): Maximum number of FAERS safety reports to return (default: "5")
Returns:
str: JSON string with adverse event contexts and metadata
"""
limit_int = int(limit) if limit.isdigit() else 5
result = search_adverse_events(drug_name, limit_int)
return format_json_output(result)
@with_error_handling
@with_caching(ttl=3600)
def fetch_event_details_mcp(event_id: str) -> str:
"""
Retrieves the complete details of a specific adverse event case from the FDA Adverse Event Reporting System (FAERS) using its unique safety report ID. Use this tool when you need to dive deep into a particular case found via 'search_adverse_events_mcp' to understand the full context, including patient demographics, concomitant medications, and the full narrative of the event.
Args:
event_id (str): Numeric FAERS safetyreportid string
Returns:
str: JSON string with structured case data including drugs, reactions, and full record
"""
result = fetch_event_details(event_id)
return format_json_output(result)
@with_error_handling
@with_caching(ttl=7200)
def drug_label_warnings_mcp(drug_name: str) -> str:
"""
Fetches critical safety information from the official FDA drug label. This includes boxed warnings (the most serious type), contraindications (situations where the drug should not be used), and known drug interactions. This is a primary tool for assessing a drug's major safety risks before prescribing or dispensing.
Args:
drug_name (str): Generic drug name preferred
Returns:
str: JSON string with boxed warnings, contraindications, and interaction data
"""
result = drug_label_warnings(drug_name)
return format_json_output(result)
@with_error_handling
@with_caching(ttl=3600)
def drug_recalls_mcp(drug_name: str, limit: str = "5") -> str:
"""
Searches for recent FDA-issued recall events for a specific drug product. This is critical for ensuring patient safety by identifying if a drug or specific batch has been recalled due to manufacturing defects, contamination, or other safety concerns. The results include details on the recall reason, status, and affected lots.
Args:
drug_name (str): Free-text search string for the drug
limit (str): Maximum number of recall notices to return (default: "5")
Returns:
str: JSON string with recall notices including recall number, status, and reason
"""
limit_int = int(limit) if limit.isdigit() else 5
result = drug_recalls(drug_name, limit_int)
return format_json_output(result)
@with_error_handling
@with_caching(ttl=7200)
def drug_pregnancy_lactation_mcp(drug_name: str) -> str:
"""
Retrieves specific sections from the FDA drug label related to use during pregnancy and lactation. This tool is essential for assessing the safety of a medication for patients who are pregnant, planning to become pregnant, or breastfeeding. It provides the official guidance and available data on potential risks.
Args:
drug_name (str): Generic drug name preferred
Returns:
str: JSON string with pregnancy text, lactation text, and reproductive potential information
"""
result = drug_pregnancy_lactation(drug_name)
return format_json_output(result)
@with_error_handling
@with_caching(ttl=7200) # 2 hours cache
def drug_dose_adjustments_mcp(drug_name: str) -> str:
"""
Extracts dosing adjustment recommendations for patients with kidney (renal) or liver (hepatic) impairment from the official FDA drug label. This is a crucial tool for safe and effective dosing in special populations where standard doses may be harmful or ineffective.
Args:
drug_name (str): Generic drug name
Returns:
str: JSON string with renal and hepatic dosing excerpts
"""
result = drug_dose_adjustments(drug_name)
return format_json_output(result)
@with_error_handling
@with_caching(ttl=1800) # 30 minutes cache
def drug_livertox_summary_mcp(drug_name: str) -> str:
"""
Queries the NIH LiverTox database to retrieve a summary of a drug's potential for causing liver injury (hepatotoxicity). This tool is valuable for investigating or assessing the risk of drug-induced liver damage, providing information on the mechanism of injury, and clinical management advice.
Args:
drug_name (str): Drug name to search for (case-insensitive)
Returns:
str: JSON string with hepatotoxicity data, mechanism of injury, and management information
"""
result = drug_livertox_summary(drug_name)
return format_json_output(result)
@with_error_handling
def brand_to_generic_lookup_mcp(brand_name: str) -> str:
"""
Converts a drug brand name to its generic (active ingredient) name. It can also provide information on the countries where the brand name is marketed. This tool is fundamental for identifying the active component of a branded medication, which is necessary for most other clinical lookups and to avoid therapeutic duplication.
Args:
brand_name (str): Brand name to look up
Returns:
str: JSON string with generic drug information and country-specific data
"""
result = _brand_lookup_gradio(brand_name)
return format_json_output(result)
@with_error_handling
def calculate_drug_burden_index_mcp(drug_list: str) -> str:
"""
Calculates the Drug Burden Index (DBI) for a patient's medication list. The DBI is a measure of a person's total exposure to anticholinergic and sedative drugs, which are associated with an increased risk of falls and cognitive impairment, especially in the elderly.
This intelligent version automatically detects the route of administration for each medication (e.g., oral, transdermal patches, parenteral injections) and uses the appropriate reference data for each route, making it suitable for complex, real-world medication regimens.
Args:
drug_list (str): Drug list (one per line, include dose and frequency - also write "prn" if the drug is a PRN medication)
Examples:
- "Fentanyl 25mcg/hr patch daily"
- "Amitriptyline 25mg tablet twice daily"
- "Morphine 10mg injection PRN"
Returns:
str: JSON string with DBI calculation results broken down by route and individual drug contributions
"""
result = _dbi_mcp_mixed_routes_gradio(drug_list)
return format_json_output(result)
@with_error_handling
def calculate_creatinine_clearance_mcp(
age: str, weight_kg: str, serum_creatinine: str, is_female: str
) -> str:
"""
Calculates a patient's creatinine clearance (CrCl) using the Cockcroft-Gault equation. CrCl is an estimate of the glomerular filtration rate (GFR) and is widely used to determine appropriate dose adjustments for drugs that are cleared by the kidneys. This is a standard clinical calculator for renal function assessment.
Args:
age (str): Patient's age in years
weight_kg (str): Patient's weight in kilograms
serum_creatinine (str): Patient's serum creatinine in mg/dL
is_female (str): "true" if patient is female, "false" if male
Returns:
str: JSON string with creatinine clearance calculation and interpretation
"""
age_int = int(age)
weight_float = float(weight_kg)
creat_float = float(serum_creatinine)
is_female_bool = is_female.lower() == "true"
result = _cockcroft_gault_gradio(
age_int, weight_float, creat_float, is_female_bool
)
return format_json_output(result)
@with_error_handling
def calculate_egfr_mcp(
age: str, serum_creatinine: str, is_female: str, is_black: str
) -> str:
"""
Calculates the estimated Glomerular Filtration Rate (eGFR) using the CKD-EPI 2021 equation. eGFR is a key indicator of kidney function and is used to diagnose, stage, and manage Chronic Kidney Disease (CKD). This is the preferred method for assessing kidney function in many clinical guidelines.
Args:
age (str): Patient's age in years
serum_creatinine (str): Patient's serum creatinine in mg/dL
is_female (str): "true" if patient is female, "false" if male
is_black (str): "true" if patient is Black, "false" otherwise
Returns:
str: JSON string with eGFR calculation and CKD stage interpretation
"""
age_int = int(age)
creat_float = float(serum_creatinine)
is_female_bool = is_female.lower() == "true"
is_black_bool = is_black.lower() == "true"
result = _ckd_epi_gradio(age_int, creat_float, is_female_bool, is_black_bool)
return format_json_output(result)
@with_error_handling
def calculate_child_pugh_score_mcp(
bilirubin: str, albumin: str, inr: str, ascites: str, encephalopathy: str
) -> str:
"""
Calculates the Child-Pugh score, a well-established tool for assessing the prognosis of chronic liver disease, primarily cirrhosis. The score is used to determine the severity of liver disease and to guide dosage adjustments for drugs that are metabolized by the liver.
Args:
bilirubin (str): Total bilirubin in mg/dL
albumin (str): Serum albumin in g/dL
inr (str): INR value
ascites (str): Ascites level ("none", "mild", "moderate-severe")
encephalopathy (str): Encephalopathy grade ("none", "grade-1-2", "grade-3-4")
Returns:
str: JSON string with Child-Pugh score, class, and prognosis information
"""
bilirubin_float = float(bilirubin)
albumin_float = float(albumin)
inr_float = float(inr)
result = _child_pugh_gradio(
bilirubin_float, albumin_float, inr_float, ascites, encephalopathy
)
return format_json_output(result)
@with_error_handling
def calculate_bmi_mcp(weight_kg: str, height_cm: str) -> str:
"""
Calculates a person's Body Mass Index (BMI) based on their weight and height. BMI is a widely used screening tool to categorize weight status (e.g., underweight, normal weight, overweight, obese) and identify potential health risks associated with weight.
Args:
weight_kg (str): Weight in kilograms
height_cm (str): Height in centimeters
Returns:
str: JSON string with BMI calculation and weight category classification
"""
weight_float = float(weight_kg)
height_float = float(height_cm)
result = _bmi_gradio(weight_float, height_float)
return format_json_output(result)
@with_error_handling
def calculate_ideal_body_weight_mcp(height_cm: str, is_male: str) -> str:
"""
Calculates a patient's Ideal Body Weight (IBW) using the Devine formula. IBW is used in various clinical contexts, including for calculating the dose of certain medications (e.g., aminophylline, digoxin) and for assessing nutritional status.
Args:
height_cm (str): Patient's height in cm
is_male (str): "true" if patient is male, "false" if female
Returns:
str: JSON string with IBW calculation.
"""
height_float = float(height_cm)
is_male_bool = is_male.lower() == "true"
result = _ideal_body_weight_gradio(height_float, is_male_bool)
return format_json_output(result)
@with_error_handling
def recommend_dosing_weight_mcp(
actual_weight: str, height_cm: str, is_male: str
) -> str:
"""
Recommends the most appropriate weight to use for medication dosing calculations (i.e., actual, ideal, or adjusted body weight) based on the patient's actual weight and height. This is crucial for obese or underweight patients, as using the wrong weight can lead to sub-therapeutic or toxic drug levels for certain medications.
Args:
actual_weight (str): Patient's actual weight in kg
height_cm (str): Patient's height in cm
is_male (str): "true" if patient is male, "false" if female
Returns:
str: JSON string with dosing weight recommendation and rationale
"""
weight_float = float(actual_weight)
height_float = float(height_cm)
is_male_bool = is_male.lower() == "true"
result = _dosing_weight_gradio(weight_float, height_float, is_male_bool)
return format_json_output(result)
@with_error_handling
def convert_creatinine_units_mcp(value: str, from_unit: str, to_unit: str) -> str:
"""
Converts serum creatinine values between the two standard units of measurement: milligrams per deciliter (mg/dL) and micromoles per liter (μmol/L). This is essential for interoperability, as different laboratories and clinical calculators may use different units.
Args:
value (str): The creatinine value to convert
from_unit (str): The original unit ("mg_dl" or "umol_l")
to_unit (str): The target unit ("mg_dl" or "umol_l")
Returns:
str: JSON string with converted creatinine value and conversion factor
"""
value_float = float(value)
result = _creatinine_conversion_gradio(value_float, from_unit, to_unit)
return format_json_output(result)
@with_error_handling
def get_cache_stats_mcp() -> str:
"""
Retrieves statistics about the application's internal cache. This is a debugging and monitoring tool to assess the performance and health of the MCP server, showing metrics like hit rate and cache size. It is not typically used for clinical queries.
Returns:
str: JSON string with cache hit rates, size, and other metrics
"""
stats = api_cache.get_stats()
expired_cleared = api_cache.clear_expired()
result = {
**stats,
"expired_entries_cleared": expired_cleared,
"cache_health": "good" if stats.get("hit_rate", 0) > 0.3 else "poor"
}
return format_json_output(standardize_response(result, "cache_stats"))
@with_error_handling
def health_check_mcp() -> str:
"""
Performs a health check on the MCP server to ensure its core components are operational. This tool is used for system monitoring to verify that the server is running, the cache is working, and basic calculations can be performed. It's not intended for clinical use.
Returns:
str: JSON string with server health information
"""
# Test basic functionality
try:
# Test cache
cache_stats = api_cache.get_stats()
# Test a simple calculation
test_calc = cockcroft_gault_creatinine_clearance(65, 70, 1.2, False)
calc_working = test_calc.get("creatinine_clearance_ml_min") is not None
# Check if reference data is loaded
from pathlib import Path
ref_file_exists = Path("dbi_reference_by_route.csv").exists()
health_status = {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"uptime_info": {
"python_version": sys.version.split()[0],
"cache_working": cache_stats is not None,
"calculations_working": calc_working,
"reference_data_available": ref_file_exists
},
"cache_stats": cache_stats,
"version": "1.1.0"
}
# Determine overall health
if not calc_working or not ref_file_exists:
health_status["status"] = "degraded"
except Exception as e:
health_status = {
"status": "unhealthy",
"timestamp": datetime.now().isoformat(),
"error": str(e),
"version": "1.1.0"
}
return format_json_output(standardize_response(health_status, "health_check"))
# ===== NEW ADR ANALYSIS ENDPOINTS =====
@with_error_handling
def enhanced_faers_search_mcp(
drug_name: str,
adverse_event: str = "",
age_range: str = "",
gender: str = "",
serious_only: str = "false",
limit: str = "100"
) -> str:
"""
Performs an advanced search of the FDA Adverse Event Reporting System (FAERS) database with powerful filtering options. This tool is designed for in-depth pharmacovigilance analysis, allowing users to narrow down adverse event reports by patient age, gender, and the seriousness of the event. It's ideal for identifying trends and patterns in drug safety data.
Use this tool particularly if the user asks about a specific adverse event or reaction.
Args:
drug_name (str): Drug name to search for
adverse_event (str): Specific adverse event/reaction to filter by (optional)
age_range (str): Age range filter like "18-65" or ">65" (optional)
gender (str): Gender filter "1" (male) or "2" (female) (optional)
serious_only (str): "true" to only return serious adverse events
limit (str): Maximum number of results (default "100")
Returns:
str: JSON string with enhanced case data including demographics and outcomes
"""
limit_int = int(limit) if limit.isdigit() else 100
serious_bool = serious_only.lower() == "true"
# Convert empty strings to None
adverse_event = adverse_event if adverse_event.strip() else None
age_range = age_range if age_range.strip() else None
gender = gender if gender.strip() in ["1", "2"] else None
result = enhanced_faers_search(
drug_name=drug_name,
adverse_event=adverse_event,
age_range=age_range,
gender=gender,
serious_only=serious_bool,
limit=limit_int
)
return format_json_output(standardize_response(result, "enhanced_faers_search"))
@with_error_handling
def calculate_naranjo_score_mcp(
adverse_reaction_after_drug: str,
reaction_improved_after_stopping: str,
reaction_reappeared_after_readministration: str,
alternative_causes_exist: str,
reaction_when_placebo_given: str,
drug_detected_in_blood: str,
reaction_worse_with_higher_dose: str,
similar_reaction_to_drug_before: str,
adverse_event_confirmed_objectively: str,
reaction_appeared_after_suspected_drug_given: str
) -> str:
"""
Calculates the Naranjo score, a standardized and widely used causality assessment tool to determine the probability that an adverse event is related to a specific drug. The score helps clinicians and researchers classify the likelihood of an adverse drug reaction (ADR) as doubtful, possible, probable, or definite.
Sometimes the user might not have provided all the information, so you will need to ask for the missing information (remember to give them the option to say "unknown" if they don't know the answer)
Args:
adverse_reaction_after_drug (str): "yes", "no", "unknown"
reaction_improved_after_stopping (str): "yes", "no", "unknown"
reaction_reappeared_after_readministration (str): "yes", "no", "unknown"
alternative_causes_exist (str): "yes", "no", "unknown"
reaction_when_placebo_given (str): "yes", "no", "unknown"
drug_detected_in_blood (str): "yes", "no", "unknown"
reaction_worse_with_higher_dose (str): "yes", "no", "unknown"
similar_reaction_to_drug_before (str): "yes", "no", "unknown"
adverse_event_confirmed_objectively (str): "yes", "no", "unknown"
reaction_appeared_after_suspected_drug_given (str): "yes", "no", "unknown"
Returns:
str: JSON string with score, probability category, and detailed breakdown
"""
result = calculate_naranjo_score(
adverse_reaction_after_drug=adverse_reaction_after_drug,
reaction_improved_after_stopping=reaction_improved_after_stopping,
reaction_reappeared_after_readministration=reaction_reappeared_after_readministration,
alternative_causes_exist=alternative_causes_exist,
reaction_when_placebo_given=reaction_when_placebo_given,
drug_detected_in_blood=drug_detected_in_blood,
reaction_worse_with_higher_dose=reaction_worse_with_higher_dose,
similar_reaction_to_drug_before=similar_reaction_to_drug_before,
adverse_event_confirmed_objectively=adverse_event_confirmed_objectively,
reaction_appeared_after_suspected_drug_given=reaction_appeared_after_suspected_drug_given
)
return format_json_output(standardize_response(result, "naranjo_score"))
@with_error_handling
def disproportionality_analysis_mcp(
drug_name: str,
adverse_event: str,
background_limit: str = "10000"
) -> str:
"""
Performs a disproportionality analysis (also known as signal detection) on adverse event data. This statistical method compares the reporting rate of a specific drug-event combination to the reporting rate of that event for all other drugs in the database. It calculates metrics like Proportional Reporting Ratio (PRR) and Reporting Odds Ratio (ROR) to identify potential safety signals that may warrant further investigation.
Args:
drug_name (str): Drug of interest
adverse_event (str): Adverse event of interest
background_limit (str): Number of background cases to sample (default "10000")
Returns:
str: JSON string with PRR, ROR, IC values and statistical significance
"""
background_limit_int = int(background_limit) if background_limit.isdigit() else 10000
result = disproportionality_analysis(
drug_name=drug_name,
adverse_event=adverse_event,
background_limit=background_limit_int
)
return format_json_output(standardize_response(result, "disproportionality_analysis"))
@with_error_handling
def find_similar_cases_mcp(
reference_case_id: str,
similarity_threshold: str = "0.7",
limit: str = "50"
) -> str:
"""
Identifies and retrieves adverse event cases from the FAERS database that are similar to a given reference case. Similarity is calculated based on a combination of patient demographics (age, gender), reported reactions, and concomitant drugs. This tool is useful for contextualizing a specific case and identifying potential case series for further review.
Args:
reference_case_id (str): FAERS safety report ID to use as reference
similarity_threshold (str): Minimum similarity score 0-1 (default "0.7")
limit (str): Maximum number of similar cases to return (default "50")
Returns:
str: JSON string with similar cases and similarity scores
"""
try:
similarity_threshold_float = float(similarity_threshold)
except ValueError:
similarity_threshold_float = 0.7
limit_int = int(limit) if limit.isdigit() else 50
result = find_similar_cases(
reference_case_id=reference_case_id,
similarity_threshold=similarity_threshold_float,
limit=limit_int
)
return format_json_output(standardize_response(result, "similar_cases"))
@with_error_handling
def temporal_analysis_mcp(
drug_name: str,
adverse_event: str = "",
limit: str = "500"
) -> str:
"""
Analyzes the temporal relationship between drug administration and the onset of adverse events. This tool provides insights into the typical time-to-onset for a specific drug-associated adverse event, which can be a critical factor in causality assessment. It helps determine if the timing of the event is consistent with the drug's known pharmacology.
Args:
drug_name (str): Drug to analyze
adverse_event (str): Specific adverse event (optional)
limit (str): Maximum cases to analyze (default "500")
Returns:
str: JSON string with temporal patterns and time-to-onset analysis
"""
adverse_event = adverse_event if adverse_event.strip() else None
limit_int = int(limit) if limit.isdigit() else 500
result = temporal_analysis(
drug_name=drug_name,
adverse_event=adverse_event,
limit=limit_int
)
return format_json_output(standardize_response(result, "temporal_analysis"))
with gr.Blocks(
theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
title="Pharmacist MCP",
analytics_enabled=False
) as demo:
gr.Markdown(
"""
<div style="text-align: center; padding: 20px;">
<h1 style="color: #0B579E;">💊 Pharmacist MCP</h1>
<p style="font-size: 1.1em;">
<strong>A suite of tools for pharmacists and clinicians.</strong>
</p>
<p>
Access critical drug information, perform clinical calculations, and analyze adverse drug reaction data efficiently.
</p>
</div>
"""
)
with gr.Tabs():
with gr.TabItem("Drug Information & Safety"):
with gr.Tabs():
with gr.TabItem("AE Search"):
gr.Interface(
fn=search_adverse_events_mcp,
inputs=[gr.Text(label="Drug Name"), gr.Text(label="Limit", value="5")],
outputs=gr.JSON(label="Output"),
title="AE Search",
api_name="ae_search",
description="Search FAERS for adverse events.",
)
with gr.TabItem("AE Details"):
gr.Interface(
fn=fetch_event_details_mcp,
inputs=gr.Text(label="FAERS Event ID"),
outputs=gr.JSON(label="Output"),
title="AE Details",
api_name="ae_details",
description="Fetch a full FAERS case by safety-report ID.",
)
with gr.TabItem("Label Warnings"):
gr.Interface(
fn=drug_label_warnings_mcp,
inputs=gr.Text(label="Drug Name"),
outputs=gr.JSON(label="Output"),
title="Label Warnings",
api_name="label_warnings",
description="Get FDA label warnings.",
)
with gr.TabItem("Recalls"):
gr.Interface(
fn=drug_recalls_mcp,
inputs=[gr.Text(label="Drug"), gr.Text(label="Limit", value="5")],
outputs=gr.JSON(label="Output"),
title="Drug Recalls",
api_name="drug_recalls",
description="Return recent FDA recall events for a drug.",
)
with gr.TabItem("Pregnancy & Lactation"):
gr.Interface(
fn=drug_pregnancy_lactation_mcp,
inputs=gr.Text(label="Drug"),
outputs=gr.JSON(label="Output"),
title="Pregnancy & Lactation",
api_name="pregnancy_lactation",
description="Return Pregnancy & Lactation text from FDA label.",
)
with gr.TabItem("Dose Adjustments"):
gr.Interface(
fn=drug_dose_adjustments_mcp,
inputs=gr.Text(label="Drug"),
outputs=gr.JSON(label="Output"),
title="Dose Adjustments",
api_name="dose_adjustments",
description="Return renal & hepatic dosing excerpts from FDA label.",
)
with gr.TabItem("LiverTox Summary"):
gr.Interface(
fn=drug_livertox_summary_mcp,
inputs=gr.Text(label="Drug Name"),
outputs=gr.JSON(label="Output"),
title="LiverTox Summary",
api_name="livertox_summary",
description="Get hepatotoxicity information.",
)
with gr.TabItem("Brand to Generic"):
gr.Interface(
fn=brand_to_generic_lookup_mcp,
inputs=[gr.Text(label="Brand Name")],
outputs=gr.JSON(label="Output"),
title="Brand to Generic",
api_name="brand_to_generic",
description="Look up generic drug information.",
)
with gr.TabItem("Clinical Calculators"):
with gr.Tabs():
with gr.TabItem("DBI Calculator"):
gr.Interface(
fn=calculate_drug_burden_index_mcp,
inputs=[
gr.Textbox(
label="Drug List (one per line, include dose and frequency)",
lines=10,
placeholder="e.g., Fentanyl 25mcg/hr patch daily\nAmitriptyline 25mg tablet twice daily\nMorphine 10mg injection PRN",
),
],
outputs=gr.JSON(label="DBI Calculation with Route Detection"),
title="DBI Calculator",
api_name="dbi_calculator",
description="Intelligent DBI calculator that automatically detects routes (oral, patches, injections, etc.) and uses appropriate reference data for each medication.",
)
with gr.TabItem("Creatinine Clearance"):
gr.Interface(
fn=calculate_creatinine_clearance_mcp,
inputs=[
gr.Text(label="Age (years)", value="65"),
gr.Text(label="Weight (kg)", value="70"),
gr.Text(label="Serum Creatinine (mg/dL)", value="1.2"),
gr.Radio(["true", "false"], label="Is Female", value="false"),
],
outputs=gr.JSON(label="Creatinine Clearance"),
title="Cockcroft-Gault Calculator",
api_name="cockcroft_gault",
description="Calculate creatinine clearance using Cockcroft-Gault equation for dose adjustments.",
)
with gr.TabItem("eGFR"):
gr.Interface(
fn=calculate_egfr_mcp,
inputs=[
gr.Text(label="Age (years)", value="65"),
gr.Text(label="Serum Creatinine (mg/dL)", value="1.2"),
gr.Radio(["true", "false"], label="Is Female", value="false"),
gr.Radio(["true", "false"], label="Is Black", value="false"),
],
outputs=gr.JSON(label="eGFR"),
title="CKD-EPI eGFR Calculator",
api_name="ckd_epi",
description="Calculate estimated glomerular filtration rate using CKD-EPI equation.",
)
with gr.TabItem("Child-Pugh Score"):
gr.Interface(
fn=calculate_child_pugh_score_mcp,
inputs=[
gr.Text(label="Total Bilirubin (mg/dL)", value="1.5"),
gr.Text(label="Serum Albumin (g/dL)", value="3.5"),
gr.Text(label="INR", value="1.3"),
gr.Dropdown(["none", "mild", "moderate-severe"], value="none", label="Ascites"),
gr.Dropdown(["none", "grade-1-2", "grade-3-4"], value="none", label="Encephalopathy"),
],
outputs=gr.JSON(label="Child-Pugh Score"),
title="Child-Pugh Score Calculator",
api_name="child_pugh",
description="Calculate Child-Pugh score for liver function assessment and dose adjustments.",
)
with gr.TabItem("BMI"):
gr.Interface(
fn=calculate_bmi_mcp,
inputs=[
gr.Text(label="Weight (kg)", value="70"),
gr.Text(label="Height (cm)", value="170"),
],
outputs=gr.JSON(label="BMI Calculation"),
title="BMI Calculator",
api_name="bmi_calculator",
description="Calculate Body Mass Index and weight category assessment.",
)
with gr.TabItem("Ideal Body Weight"):
gr.Interface(
fn=calculate_ideal_body_weight_mcp,
inputs=[
gr.Text(label="Height (cm)", value="170"),
gr.Radio(["true", "false"], label="Is Male", value="true"),
],
outputs=gr.JSON(label="Ideal Body Weight Calculation"),
title="Ideal Body Weight (IBW) Calculator",
api_name="ideal_body_weight",
description="Calculate Ideal Body Weight using the Devine formula.",
)
with gr.TabItem("Dosing Weight"):
gr.Interface(
fn=recommend_dosing_weight_mcp,
inputs=[
gr.Text(label="Actual Weight (kg)", value="85"),
gr.Text(label="Height (cm)", value="170"),
gr.Radio(["true", "false"], label="Is Male", value="true"),
],
outputs=gr.JSON(label="Dosing Weight Recommendation"),
title="Dosing Weight Calculator",
api_name="dosing_weight",
description="Recommend appropriate weight for medication dosing calculations.",
)
with gr.TabItem("Creatinine Converter"):
gr.Interface(
fn=convert_creatinine_units_mcp,
inputs=[
gr.Text(label="Creatinine Value", value="1.2"),
gr.Dropdown(["mg_dl", "umol_l"], value="mg_dl", label="From Unit"),
gr.Dropdown(["mg_dl", "umol_l"], value="umol_l", label="To Unit"),
],
outputs=gr.JSON(label="Converted Value"),
title="Creatinine Unit Converter",
api_name="creatinine_converter",
description="Convert creatinine values between mg/dL and μmol/L.",
)
with gr.TabItem("Pharmacovigilance Tools"):
with gr.Tabs():
with gr.TabItem("Enhanced FAERS Search"):
gr.Interface(
fn=enhanced_faers_search_mcp,
inputs=[
gr.Text(label="Drug Name", placeholder="e.g., lisinopril"),
gr.Text(label="Adverse Event (optional)", placeholder="e.g., cough"),
gr.Text(label="Age Range (optional)", placeholder="e.g., 18-65 or >65"),
gr.Radio(["", "1", "2"], label="Gender (optional)", info="1=Male, 2=Female", value=""),
gr.Radio(["true", "false"], label="Serious Events Only", value="false"),
gr.Number(label="Limit", value=100),
],
outputs=gr.JSON(label="Output"),
title="Enhanced FAERS Search",
api_name="enhanced_faers_search",
description="Enhanced FAERS search with filtering capabilities for pharmacovigilance analysis.",
)
with gr.TabItem("Naranjo Score"):
gr.Interface(
fn=calculate_naranjo_score_mcp,
inputs=[
gr.Radio(["yes", "no", "unknown"], label="Did the adverse reaction appear after the drug was given?", value="unknown", info="Question 2"),
gr.Radio(["yes", "no", "unknown"], label="Did the reaction improve after stopping the drug?", value="unknown", info="Question 3"),
gr.Radio(["yes", "no", "unknown"], label="Did the reaction reappear upon readministration?", value="unknown", info="Question 4"),
gr.Radio(["yes", "no", "unknown"], label="Are there alternative causes for the reaction?", value="unknown", info="Question 5"),
gr.Radio(["yes", "no", "unknown"], label="Did the reaction reappear with a placebo?", value="unknown", info="Question 6"),
gr.Radio(["yes", "no", "unknown"], label="Was the drug detected in blood/fluids at a toxic level?", value="unknown", info="Question 7"),
gr.Radio(["yes", "no", "unknown"], label="Did the reaction change with dose alterations?", value="unknown", info="Question 8"),
gr.Radio(["yes", "no", "unknown"], label="Did the patient have a similar reaction previously?", value="unknown", info="Question 9"),
gr.Radio(["yes", "no", "unknown"], label="Was the adverse event confirmed objectively?", value="unknown", info="Question 10"),
# This argument seems to be a duplicate or a more specific version of the first one. Let's provide a clear label.
gr.Radio(["yes", "no", "unknown"], label="Are there conclusive reports on this reaction?", value="unknown", info="Question 1"),
],
outputs=gr.JSON(label="Naranjo Score"),
title="Naranjo Score Calculator",
api_name="naranjo_score",
description="Calculate the Naranjo Adverse Drug Reaction Probability Scale.",
)
with gr.TabItem("Disproportionality Analysis"):
gr.Interface(
fn=disproportionality_analysis_mcp,
inputs=[
gr.Text(label="Drug Name", placeholder="e.g., atorvastatin"),
gr.Text(label="Adverse Event", placeholder="e.g., myalgia"),
gr.Number(label="Background Limit", value=10000),
],
outputs=gr.JSON(label="Output"),
title="Disproportionality Analysis (Signal Detection)",
api_name="disproportionality_analysis",
description="Perform disproportionality analysis (PRR, ROR, IC) to detect potential drug-adverse event signals.",
)
with gr.TabItem("Similar Case Finder"):
gr.Interface(
fn=find_similar_cases_mcp,
inputs=[
gr.Text(label="Reference FAERS Case ID"),
gr.Slider(0, 1, value=0.7, label="Similarity Threshold"),
gr.Number(label="Limit", value=50),
],
outputs=gr.JSON(label="Output"),
title="Find Similar Cases",
api_name="find_similar_cases",
description="Find cases similar to a reference case based on patient characteristics, drugs, and adverse events.",
)
with gr.TabItem("Temporal Analysis"):
gr.Interface(
fn=temporal_analysis_mcp,
inputs=[
gr.Text(label="Drug Name"),
gr.Text(label="Adverse Event (optional)"),
gr.Number(label="Limit", value=500),
],
outputs=gr.JSON(label="Output"),
title="Temporal Analysis",
api_name="temporal_analysis",
description="Analyze temporal patterns and time-to-onset of adverse events for a drug.",
)
# Video Resources Section
with gr.Row():
gr.HTML(
"""
<div style="text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #e0e0e0;">
<h3 style="color: #0B579E; margin-bottom: 20px;">📹 Example Usage</h3>
<div style="display: flex; justify-content: center; gap: 20px; flex-wrap: wrap;">
<div style="flex: 1; min-width: 300px; max-width: 400px;">
<iframe width="100%" height="225" src="https://www.youtube.com/embed/ufPnoJ6TZNI"
frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen style="border-radius: 8px;"></iframe>
</div>
<div style="flex: 1; min-width: 300px; max-width: 400px;">
<iframe width="100%" height="225" src="https://www.youtube.com/embed/Ku2EOHTnPYs"
frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen style="border-radius: 8px;"></iframe>
</div>
</div>
</div>
"""
)
if __name__ == "__main__":
demo.launch(mcp_server=True, show_error=True)
|