Spaces:
Build error
Build error
| """ | |
| 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() | |