nanasmuda's picture
commit
53c04a5 verified
Raw
History Blame Contribute Delete
19.8 kB
"""
PHARMABOT - Asisten Medis Digital
Deploy: Hugging Face Spaces (Gradio SDK)
Dataset: drugs_side_effects_drugs_com.csv (Kaggle - Drugs.com)
"""
# ── INSTALASI (jika belum tersedia di environment) ──────────────────────────
import subprocess, sys
def pip_install(pkg):
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", pkg])
try:
import deep_translator
except ImportError:
pip_install("deep-translator")
# ── IMPORT ────────────────────────────────────────────────────────────────────
import re
import time
import warnings
import numpy as np
import pandas as pd
import gradio as gr
import nltk
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from deep_translator import GoogleTranslator
warnings.filterwarnings("ignore")
nltk.download("punkt", quiet=True)
nltk.download("punkt_tab", quiet=True)
nltk.download("stopwords", quiet=True)
nltk.download("wordnet", quiet=True)
# ── LOAD DATASET ──────────────────────────────────────────────────────────────
df_raw = pd.read_csv("drugs_side_effects_drugs_com.csv")
# ── MAPPING KATEGORI ──────────────────────────────────────────────────────────
CATEGORY_MAP = {
"Acne": "kulit", "Eczema": "kulit", "Psoriasis": "kulit", "Hair Loss": "kulit",
"Colds & Flu": "pernapasan", "Asthma": "pernapasan", "Hayfever": "pernapasan",
"Bronchitis": "pernapasan", "COPD": "pernapasan", "Swine Flu": "pernapasan",
"Covid 19": "pernapasan", "Allergies": "pernapasan",
"Hypertension": "kardiovaskular", "Angina": "kardiovaskular",
"Cholesterol": "kardiovaskular", "Stroke": "kardiovaskular",
"Diabetes (Type 2)": "metabolik", "Diabetes (Type 1)": "metabolik",
"Hypothyroidism": "metabolik", "Obesity": "metabolik", "Gout": "metabolik",
"Weight Loss": "metabolik", "Osteoporosis": "metabolik",
"Pain": "nyeri", "Osteoarthritis": "nyeri", "Rheumatoid Arthritis": "nyeri",
"Migraine": "nyeri", "Fibromyalgia": "nyeri",
"AIDS/HIV": "infeksi", "Pneumonia": "infeksi", "UTI": "infeksi",
"Herpes": "infeksi", "Gastrointestinal": "infeksi",
"GERD (Heartburn)": "pencernaan", "Constipation": "pencernaan",
"Diarrhea": "pencernaan", "IBD (Bowel)": "pencernaan", "Incontinence": "pencernaan",
"Depression": "mental", "Anxiety": "mental", "Insomnia": "mental",
"ADHD": "mental", "Bipolar Disorder": "mental", "Schizophrenia": "mental",
"Seizures": "mental",
"Alzheimer's": "saraf",
"Erectile Dysfunction": "reproduksi", "Menopause": "reproduksi",
"Cancer": "onkologi",
}
df_raw["category"] = df_raw["medical_condition"].map(CATEGORY_MAP).fillna("umum")
# ── KAMUS TERJEMAHAN INDONESIA / GAUL β†’ INGGRIS ───────────────────────────────
TRANSLATION_DICT = {
"jerawat": "acne", "berjerawat": "acne", "kulit berminyak": "acne",
"bruntusan": "acne", "komedo": "acne", "eksim": "eczema",
"kulit gatal gatal": "eczema", "kulit bersisik": "psoriasis eczema",
"psoriasis": "psoriasis", "rambut rontok": "hair loss",
"kebotakan": "hair loss", "botak": "hair loss",
"flu": "colds flu", "pilek": "colds flu", "batuk": "colds flu bronchitis",
"batuk berdahak": "bronchitis", "batuk kering": "colds flu",
"hidung meler": "colds flu hayfever", "hidung tersumbat": "colds flu hayfever",
"bersin bersin": "hayfever allergies", "alergi": "hayfever allergies",
"sesak napas": "asthma", "asma": "asthma", "napas bunyi ngik": "asthma",
"covid": "covid 19", "corona": "covid 19", "positif covid": "covid 19",
"tekanan darah tinggi": "hypertension", "hipertensi": "hypertension",
"darah tinggi": "hypertension", "kolesterol": "cholesterol",
"kolesterol tinggi": "cholesterol", "lemak darah tinggi": "cholesterol",
"nyeri dada": "angina", "dada sakit": "angina", "stroke": "stroke",
"diabetes": "diabetes", "gula darah tinggi": "diabetes",
"kencing manis": "diabetes", "gula darah": "diabetes",
"tiroid": "hypothyroidism", "kelenjar tiroid": "hypothyroidism",
"kegemukan": "obesity weight loss", "obesitas": "obesity",
"badan gemuk": "obesity weight loss", "asam urat": "gout",
"osteoporosis": "osteoporosis", "tulang rapuh": "osteoporosis",
"tulang keropos": "osteoporosis",
"nyeri": "pain", "sakit": "pain", "kesakitan": "pain",
"nyeri sendi": "osteoarthritis pain", "sendi sakit": "osteoarthritis",
"radang sendi": "rheumatoid arthritis", "rematik": "rheumatoid arthritis",
"encok": "rheumatoid arthritis", "migrain": "migraine",
"sakit kepala sebelah": "migraine", "kepala berdenyut": "migraine",
"sakit kepala": "migraine pain", "kepala pusing": "migraine pain", "pusing": "migraine pain",
"hiv": "aids hiv", "aids": "aids hiv", "paru paru": "pneumonia",
"radang paru": "pneumonia", "infeksi saluran kemih": "uti", "isk": "uti",
"anyang anyangan": "uti", "sering pipis": "uti incontinence",
"herpes": "herpes", "cacar": "herpes",
"maag": "gerd heartburn", "lambung": "gerd heartburn gastrointestinal",
"asam lambung": "gerd heartburn", "asam lambung naik": "gerd heartburn",
"perut perih": "gerd heartburn", "perut mulas": "gerd diarrhea",
"sembelit": "constipation", "susah buang air besar": "constipation",
"susah bab": "constipation", "bab susah": "constipation",
"diare": "diarrhea", "mencret": "diarrhea", "berak cair": "diarrhea",
"buang air besar terus": "diarrhea", "radang usus": "ibd bowel",
"usus bermasalah": "ibd bowel",
"depresi": "depression", "sedih terus": "depression",
"nggak semangat": "depression", "ga semangat": "depression", "murung": "depression",
"cemas": "anxiety", "kecemasan": "anxiety", "overthinking": "anxiety",
"panik terus": "anxiety", "gelisah": "anxiety",
"susah tidur": "insomnia", "tidak bisa tidur": "insomnia",
"insomnia": "insomnia", "susah bobok": "insomnia",
"adhd": "adhd", "susah fokus": "adhd", "bipolar": "bipolar disorder",
"mood swing": "bipolar disorder", "skizofrenia": "schizophrenia",
"kejang": "seizures", "epilepsi": "seizures",
"alzheimer": "alzheimer", "pikun": "alzheimer", "lupa terus": "alzheimer",
"disfungsi ereksi": "erectile dysfunction", "impotensi": "erectile dysfunction",
"menopause": "menopause", "kanker": "cancer", "tumor": "cancer",
"obat apa": "drug treatment", "obat yang cocok": "drug treatment recommended",
"obat untuk": "drug treatment for", "rekomendasi obat": "recommended drug treatment",
"rekomendasiin obat": "recommended drug treatment", "rekomendasiin": "recommended",
"minum obat apa": "drug treatment", "mengobati": "treatment",
"menyembuhkan": "treatment cure", "mengatasi": "treatment", "ngatasi": "treatment",
"efek samping": "side effects", "bahaya": "side effects risk",
"berbahaya": "side effects risk", "risiko": "side effects risk",
"aman nggak": "safe side effects", "aman ga": "safe side effects",
"saya sedang mengalami": "", "aku sedang mengalami": "",
"lagi ngalamin": "", "lagi kena": "", "aku kena": "", "aku punya": "",
"saya punya": "", "aku menderita": "", "saya menderita": "",
"gimana cara": "", "bagaimana cara": "", "buat": "for",
"dong": "", "ya": "", "nih": "", "sih": "", "deh": "", "tuh": "",
}
# ── TERJEMAHAN KOLOM ──────────────────────────────────────────────────────────
_translator = GoogleTranslator(source="en", target="id")
def translate_to_id(text: str, max_chars: int = 4500) -> str:
if pd.isna(text) or str(text).strip() == "":
return ""
text = re.sub(r"\s+", " ", str(text)).strip()
if len(text) > max_chars:
cut = text[:max_chars].rfind(".")
text = text[:cut + 1] if cut > 0 else text[:max_chars]
try:
result = _translator.translate(text)
return result if result else text
except Exception:
return text
print("βš™οΈ Menerjemahkan kolom side_effects & description ke Bahasa Indonesia...")
translated_side_effects = []
translated_descriptions = []
for i, row in df_raw.iterrows():
translated_side_effects.append(translate_to_id(row.get("side_effects", "")))
translated_descriptions.append(translate_to_id(row.get("medical_condition_description", "")))
if (i + 1) % 100 == 0:
print(f" [{i + 1}/{len(df_raw)}] selesai...")
time.sleep(0.05)
df_raw["side_effects_id"] = translated_side_effects
df_raw["medical_condition_description_id"] = translated_descriptions
print("βœ… Terjemahan selesai!")
# ── BUILD Q&A DATA ────────────────────────────────────────────────────────────
RX_LABEL_ID = {
"Rx": "Obat Keras β€” perlu resep dokter πŸ”΄",
"OTC": "Obat Bebas β€” bisa beli tanpa resep 🟒",
"Rx/OTC": "Tersedia dengan atau tanpa resep 🟑",
}
def build_question(row):
return f"efek samping {str(row['drug_name']).strip().lower()} untuk {str(row['medical_condition']).strip().lower()}"
def build_answer_id(row):
drug = str(row["drug_name"]).strip()
cond = str(row["medical_condition"]).strip()
side_fx_id = str(row.get("side_effects_id", "")).strip()
desc_id = str(row.get("medical_condition_description_id", "")).strip()
rx_label = RX_LABEL_ID.get(str(row.get("rx_otc", "")), "")
rating = row.get("rating", "")
drug_class = re.sub(r"\s+", " ", str(row.get("drug_classes", ""))).strip()
brand = re.sub(r"\s+", " ", str(row.get("brand_names", ""))).strip()
if len(drug_class) > 120: drug_class = drug_class[:120].rsplit(",", 1)[0] + "..."
if len(brand) > 120: brand = brand[:120].rsplit(",", 1)[0] + "..."
if len(side_fx_id) > 500:
cut = side_fx_id[:500].rfind(".")
side_fx_id = side_fx_id[:cut + 1] if cut > 0 else side_fx_id[:500]
if len(desc_id) > 350:
cut = desc_id[:350].rfind(".")
desc_id = desc_id[:cut + 1] if cut > 0 else desc_id[:350]
lines = [f"**Nama Obat** : {drug}", f"**Kondisi** : {cond}"]
if rx_label: lines.append(f"**Status** : {rx_label}")
if drug_class and drug_class != "nan": lines.append(f"**Kelas Obat** : {drug_class}")
if brand and brand != "nan": lines.append(f"**Nama Merek** : {brand}")
if rating and not pd.isna(rating): lines.append(f"**Rating Pengguna** : {rating}")
if side_fx_id and side_fx_id != "nan": lines.append(f"\n**Efek Samping :**\n{side_fx_id}")
if desc_id and desc_id != "nan": lines.append(f"\n**Tentang {cond} :**\n{desc_id}")
lines.append("\n> βš•οΈ *Informasi ini hanya untuk referensi. Selalu konsultasikan dengan dokter atau apoteker sebelum mengonsumsi obat apapun.*")
return "\n".join(lines)
medical_qa_data = []
for _, row in df_raw.iterrows():
medical_qa_data.append({
"category": row["category"],
"question": build_question(row),
"answer": build_answer_id(row),
"_drug_name": row["drug_name"],
"_medical_condition": row["medical_condition"],
"_rx_otc": row.get("rx_otc", ""),
"_rating": row.get("rating", np.nan),
})
df_qa = pd.DataFrame(medical_qa_data)
print(f"βœ… Q&A data siap! Total: {len(df_qa)} entri")
# ── NLP PREPROCESSING & TF-IDF ───────────────────────────────────────────────
ENGLISH_COMMON_WORDS = {
"what","which","how","where","when","why","who","the","is","are","for",
"drug","medicine","treatment","side","effect","symptom","disease","my",
"have","i","can","you","tell","me","give","recommend","best","please",
"help","pain","acne","diabetes","flu",
}
def normalize(text: str) -> str:
text = text.lower().strip()
text = re.sub(r"[^\w\s]", " ", text)
return re.sub(r"\s+", " ", text)
def is_english(text: str) -> bool:
words = set(normalize(text).split())
return len(words & ENGLISH_COMMON_WORDS) >= 2
def translate_to_en(text: str) -> str:
normalized = normalize(text)
sorted_keys = sorted(TRANSLATION_DICT.keys(), key=lambda k: len(k), reverse=True)
for key in sorted_keys:
if key in normalized:
normalized = normalized.replace(key, TRANSLATION_DICT[key])
return normalized.strip()
docs_for_index = (
df_qa["question"] + " " + df_qa["category"] + " " + df_qa["_medical_condition"]
).str.lower()
vectorizer = TfidfVectorizer(ngram_range=(1, 2), min_df=1)
tfidf_matrix = vectorizer.fit_transform(docs_for_index)
print(f"βœ… TF-IDF index siap! ({tfidf_matrix.shape[0]} dokumen, {tfidf_matrix.shape[1]} fitur)")
def retrieve(user_input: str, top_k: int = 3, min_score: float = 0.05) -> list:
translated = translate_to_en(user_input)
user_vec = vectorizer.transform([translated])
scores = cosine_similarity(user_vec, tfidf_matrix).flatten()
top_idx = scores.argsort()[::-1][:top_k]
results = []
for idx in top_idx:
if scores[idx] >= min_score:
row = df_qa.iloc[idx]
results.append({
"drug": row["_drug_name"],
"condition": row["_medical_condition"],
"category": row["category"],
"answer": row["answer"],
"score": round(float(scores[idx]), 4),
})
return results
# ── FORMAT RESPONS ────────────────────────────────────────────────────────────
INTENT_KEYWORDS = {
"rekomendasi_obat": ["obat apa","obat yang","rekomendasi","rekomendasiin",
"cocok","mengobati","menyembuhkan","mengatasi",
"ngatasi","minum obat","obat untuk","obat buat"],
"efek_samping": ["efek samping","bahaya","efek","dampak","risiko",
"aman","berbahaya","efek negatif","aman nggak","aman ga"],
}
def detect_intent(text: str) -> str:
lower = text.lower()
for intent, kws in INTENT_KEYWORDS.items():
if any(kw in lower for kw in kws):
return intent
return "rekomendasi_obat"
CATEGORY_LABEL_ID = {
"kulit": "Kulit & Kecantikan 🧴",
"pernapasan": "Pernapasan & Paru 🫁",
"kardiovaskular": "Jantung & Pembuluh Darah ❀️",
"metabolik": "Metabolik & Endokrin 🩸",
"nyeri": "Nyeri & Sendi 🦴",
"infeksi": "Infeksi & Imunitas 🦠",
"pencernaan": "Pencernaan & GI πŸ«ƒ",
"mental": "Kesehatan Mental 🧠",
"saraf": "Saraf & Otak 🧬",
"reproduksi": "Kesehatan Reproduksi πŸ‘Ά",
"onkologi": "Onkologi & Kanker πŸŽ—οΈ",
"umum": "Umum πŸ’Š",
}
MSG_NOT_FOUND = """❌ Maaf, aku tidak menemukan informasi obat yang relevan untuk pertanyaanmu.
**πŸ₯ Saran kami:**
Kondisi yang kamu sebutkan belum ada dalam basis data chatbot ini. Untuk penanganan yang tepat dan aman, silakan:
1. πŸ‘¨β€βš•οΈ Konsultasi langsung ke **dokter umum atau spesialis**
2. πŸ’Š Kunjungi **apotek terdekat** dan minta saran apoteker
3. πŸ“ž Hubungi layanan kesehatan: **Halo Kemkes 1500567** (bebas pulsa)
> βš•οΈ *Jangan sembarangan mengonsumsi obat tanpa rekomendasi tenaga medis.*"""
MSG_ENGLISH = """πŸ€– Hei! Chatbot ini hanya menerima pertanyaan dalam **Bahasa Indonesia** ya.
Coba ulangi pertanyaanmu dalam Bahasa Indonesia, contohnya:
- "obat untuk jerawat apa?"
- "efek samping obat diabetes"
- "aku susah tidur, ada obatnya?"
- "rekomendasiin obat flu dong" """
def chatbot_response(user_input: str, top_k: int = 3) -> str:
if is_english(user_input):
return MSG_ENGLISH
intent = detect_intent(user_input)
results = retrieve(user_input, top_k=top_k, min_score=0.05)
if not results:
return MSG_NOT_FOUND
category = results[0]["category"]
cat_label = CATEGORY_LABEL_ID.get(category, "Medis πŸ’Š")
condition = results[0]["condition"]
if intent == "efek_samping":
intro = f"πŸ€– Berikut informasi efek samping obat untuk **{condition}** (kategori: {cat_label}):\n\n"
else:
intro = f"πŸ€– Berikut rekomendasi obat untuk **{condition}** (kategori: {cat_label}):\n\n"
parts = []
for i, r in enumerate(results):
score_pct = int(r["score"] * 100)
header = f"---\n### Pilihan {i+1}: {r['drug']} (relevansi: {score_pct}%)\n"
parts.append(header + r["answer"])
footer = (
"\n\n---\n"
"> βš•οΈ **Disclaimer:** Informasi ini hanya untuk referensi medis awal.\n"
"> Selalu konsultasikan kondisi kesehatanmu dengan dokter atau apoteker sebelum memulai pengobatan apapun."
)
return intro + "\n\n".join(parts) + footer
# ── GRADIO UI ─────────────────────────────────────────────────────────────────
with gr.Blocks(theme=gr.themes.Soft(), title="Pharmabot - Asisten Medis") as demo:
gr.Markdown(
"""
# πŸ€– Pharmabot: Asisten Medis Digital
Selamat datang di **Pharmabot**! Masukkan keluhan kesehatan atau nama obat untuk mendapatkan
informasi terkait **rekomendasi** dan **efek samping** obat-obatan.
> ⚠️ **Disclaimer:** Informasi ini hanya untuk referensi medis awal. Selalu konsultasikan dengan dokter atau apoteker.
"""
)
with gr.Row():
with gr.Column(scale=1):
input_text = gr.Textbox(
label="Tanyakan sesuatu (Bahasa Indonesia):",
placeholder="Contoh: 'obat flu yang ampuh' atau 'efek samping ibuprofen'...",
lines=3,
)
btn = gr.Button("πŸ” Cari Informasi", variant="primary")
gr.Examples(
examples=[
"obat untuk jerawat apa yang cocok?",
"apa efek samping obat hipertensi?",
"rekomendasiin obat buat susah tidur dong",
"lagi kena anyang anyangan nih, obatnya apa?",
"saya punya kolesterol tinggi, obat apa yang cocok?",
"aku sedang mengalami depresi, ada obatnya?",
],
inputs=input_text,
)
with gr.Column(scale=2):
output_text = gr.Markdown(label="Hasil Analisis Pharmabot")
btn.click(fn=chatbot_response, inputs=input_text, outputs=output_text)
input_text.submit(fn=chatbot_response, inputs=input_text, outputs=output_text)
gr.Markdown(
"""
---
**Dataset:** [Drugs Side Effects & Medical Condition - Kaggle](https://www.kaggle.com/datasets/jithinanievarghese/drugs-side-effects-and-medical-condition) |
**Model:** TF-IDF + Cosine Similarity + Google Translate
"""
)
if __name__ == "__main__":
demo.launch()