""" Forward Chaining Expert System - 7 Rules Berdasarkan PERDOSKI 2024 (Panduan Praktik Klinis) Referensi: Tabel 1. Rekomendasi Terapi Akne IAEM 2015 Structure: Layer 1: Treatment (7 Rules) - Prioritas utama Layer 2: Daily Skincare (Pendukung) - Facial wash, moisturizer, sunscreen Input: - skin_type: "berminyak" | "kering" | "sensitif" | "kombinasi" - severity_level: "clear" | "mild" | "moderate" | "severe" - dominant_lesion: "comedone" | "papula_pustula" | "nodul" Klasifikasi Derajat (PERDOSKI 2024): - Ringan: Komedo <20 ATAU lesi inflamasi <15 ATAU total lesi 30 - Sedang: Komedo 20-100 ATAU lesi inflamasi 15-50 ATAU total lesi 30-125 - Berat: Kista >5 ATAU komedo >100 ATAU lesi inflamasi >50 ATAU total lesi >125 Keterangan singkatan (PERDOSKI 2024): RA = Retinoic Acid (termasuk Adapalene) BPO = Benzoyl Peroxide SA = Salicylic Acid AB = Antibiotik (Doksisiklin/Minosiklin) AA = Azelaic Acid Dox = Doxicyclin Status Legalitas Indonesia (BPOM): - Salicylic Acid ≤2%: Obat Bebas (OTC), batas skincare BPOM maks 2% - Adapalene 0.1%: Obat Keras (Resep Dokter), walaupun di US sudah OTC - Benzoyl Peroxide 2.5-5%: Obat Bebas Terbatas (OTC terbatas) - Azelaic Acid 15-20%: Obat Keras (Resep Dokter), ASEAN Cosmetic Directive - Niacinamide 2-10%: Kosmetik (Bebas), tidak termasuk obat """ VALID_SKIN_TYPES = ["berminyak", "kering", "sensitif", "kombinasi"] SEVERITY_TO_DEGREE = { "clear": "bersih", "mild": "ringan", "moderate": "sedang", "severe": "berat", } # ═══════════════════════════════════════════════════════════════════ # REFERENSI BAHAN AKTIF (untuk metadata) # ═══════════════════════════════════════════════════════════════════ ACTIVE_INGREDIENT_INFO = { "salicylic_acid": { "name": "Salicylic Acid (BHA)", "otc_status": "Obat Bebas (OTC)", "prescription_required": False, "pregnancy_category": "Hati-hati (konsultasi dokter)", "max_concentration_otc": "2%", "drug_interactions": [ "Hindari penggunaan bersamaan dengan Adapalene (risiko iritasi berlebih)", "BPO + SA: gunakan bergantian waktu (pagi/malam), jangan bersamaan", ], }, "adapalene": { "name": "Adapalene 0.1%", "otc_status": "Obat Keras (Resep Dokter)", "prescription_required": True, "pregnancy_category": "Kategori C (Hindari saat hamil)", "max_concentration_otc": "N/A (obat keras di Indonesia)", "drug_interactions": [ "JANGAN dicampur dengan Salicylic Acid (iritasi berlebih)", "HINDARI penggunaan bersamaan dengan Retinoid lain, Sulfur, atau AHA", "Adapalene + BPO: hanya dalam produk kombinasi (Epiduo) di bawah pengawasan dokter", ], }, "benzoyl_peroxide": { "name": "Benzoyl Peroxide (BPO)", "otc_status": "Obat Bebas Terbatas", "prescription_required": False, "pregnancy_category": "Hati-hati (konsultasi dokter)", "max_concentration_otc": "5%", "drug_interactions": [ "HINDARI dengan Tretinoin, Isotretinoin, Tazarotene (menurunkan efektivitas + iritasi)", "Hindari dengan Sulfonamida (perubahan warna kulit sementara)", "BPO + SA: gunakan bergantian waktu, jangan bersamaan", ], }, "azelaic_acid": { "name": "Azelaic Acid 15-20%", "otc_status": "Obat Keras (Resep Dokter)", "prescription_required": True, "pregnancy_category": "Kategori B (Relatif aman, konsultasi dokter)", "max_concentration_otc": "N/A (obat keras di Indonesia/ASEAN)", "drug_interactions": [ "HINDARI dengan Retinol/Isotretinoin (meningkatkan iritasi)", "HINDARI dengan produk astringent atau alkohol kuat", "AA + SA: aman, bahkan ada produk kombinasi", ], }, "niacinamide": { "name": "Niacinamide 2-10%", "otc_status": "Kosmetik (Bebas)", "prescription_required": False, "pregnancy_category": "Aman", "max_concentration_otc": "Tidak dibatasi (produk kosmetik)", "drug_interactions": [ "Aman dengan hampir semua bahan aktif", "Tidak bereaksi dengan bahan sunscreen (oxybenzone, avobenzone, titanium dioxide)", "Meningkatkan efektivitas Vitamin C dan sunscreen", ], }, } # ═══════════════════════════════════════════════════════════════════ # LAYER 1: TREATMENT (7 Rules - PERDOSKI 2024 Tabel 1) # ═══════════════════════════════════════════════════════════════════ RULES = [ # ── RINGAN: Komedo + Berminyak/Kombinasi ── # PERDOSKI: Topikal lini 1 → SA (2B) atau RA (1A) { "rule": 1, "condition": { "degree": ["ringan"], "dominant_lesion": ["comedone"], "skin_type": ["berminyak", "kombinasi"], }, "recommendation": { "active_ingredient": "Salicylic Acid (BHA) 0.5-2%", "product_type": "Treatment", "dosage": "2-3 tetes atau secukupnya", "frequency": "1-2x sehari (pagi dan/atau malam)", "time_of_day": "pagi_malam", "instructions": ( "Oleskan tipis pada area jerawat setelah facial wash. " "BHA (Salicylic Acid) larut dalam minyak, membersihkan pori-pori tersumbat dari dalam. " "Dapat digunakan pagi atau malam. Mulai dari konsentrasi rendah (0.5%) lalu tingkatkan." ), "evidence": "2B (PERDOSKI 2024)", "prescription_required": False, "otc_status": "Obat Bebas (OTC) - tersedia tanpa resep", "pregnancy_category": "Hati-hati - konsultasi dokter sebelum digunakan", "drug_interactions": [ "Hindari penggunaan bersamaan dengan Adapalene (risiko iritasi berlebih)", "Jangan digunakan bersamaan dengan BPO di waktu yang sama", ], }, "contraindications": [], }, # ── RINGAN: Komedo + Sensitif/Kering ── # PERDOSKI: Topikal lini 1 → RA (1A) atau SA (2B), pilih yang lebih lembut { "rule": 2, "condition": { "degree": ["ringan"], "dominant_lesion": ["comedone"], "skin_type": ["sensitif", "kering"], }, "recommendation": { "active_ingredient": "Dermokosmetik Multitarget (BHA lembut + Niacinamide)", "product_type": "Treatment", "dosage": "Secukupnya", "frequency": "1-2x sehari (pagi dan malam)", "time_of_day": "pagi_malam", "instructions": ( "Gunakan produk multitarget dengan bahan aktif lembut. " "Formula hypoallergenic untuk kulit sensitif. " "Dapat mengandung BHA lembut (0.5%) atau Niacinamide untuk eksfoliasi ringan tanpa iritasi." ), "evidence": "1A-2B (PERDOSKI 2024)", "prescription_required": False, "otc_status": "Kosmetik/Obat Bebas - tersedia tanpa resep", "pregnancy_category": "Aman (Niacinamide) / Hati-hati (BHA rendah)", "drug_interactions": [], }, "contraindications": [], }, # ── RINGAN: Papula/Pustula + Berminyak ── # PERDOSKI: Topikal lini 1 → RA (1A) dan/atau BPO (1A) { "rule": 3, "condition": { "degree": ["ringan"], "dominant_lesion": ["papula_pustula"], "skin_type": ["berminyak", "kombinasi"], }, "recommendation": { "active_ingredient": "Adapalene 0.1% (Retinoid) atau Benzoyl Peroxide 2.5-5%", "product_type": "Treatment", "dosage": "Pea-size untuk seluruh wajah (Adapalene) atau secukupnya (BPO)", "frequency": "1x sehari (malam)", "time_of_day": "malam", "instructions": ( "Pilihan 1: Adapalene 0.1% malam hari. Mulai 2-3x seminggu, tingkatkan bertahap. " "Pilihan 2: Benzoyl Peroxide 2.5-5% malam hari. " "Gunakan pelembap setelahnya. Hindari paparan sinar UV langsung." ), "evidence": "1A (PERDOSKI 2024)", "prescription_required": True, "otc_status": "Adapalene: Obat Keras (Resep Dokter). BPO: Obat Bebas Terbatas.", "pregnancy_category": "Adapalene: Kategori C (HINDARI saat hamil). BPO: Hati-hati.", "drug_interactions": [ "Adapalene: JANGAN dicampur dengan SA, Retinoid lain, atau Sulfur", "Adapalene + BPO: hanya dalam produk kombinasi (Epiduo) di bawah pengawasan dokter", "BPO: Hindari dengan Tretinoin/Isotretinoin", ], }, "contraindications": [ "ADAPALENE: Hanya dengan resep dokter", "Tidak untuk ibu hamil (Adapalene - Kategori C)", "Hindari paparan sinar UV langsung", "Jangan dicampur sendiri Adapalene dengan bahan aktif lain", ], }, # ── RINGAN: Papula/Pustula + Sensitif/Kering ── # PERDOSKI: Topikal lini 1 → RA (1A) atau SA (2B) { "rule": 4, "condition": { "degree": ["ringan"], "dominant_lesion": ["papula_pustula"], "skin_type": ["sensitif", "kering"], }, "recommendation": { "active_ingredient": "Dermokosmetik (pagi) + Adapalene 0.1% (malam)", "product_type": "Treatment", "dosage": "Secukupnya (dermokosmetik), pea-size (Adapalene)", "frequency": "2x sehari (pagi dermokosmetik, malam adapalene)", "time_of_day": "pagi_malam", "instructions": ( "Pagi: Dermokosmetik multitarget untuk menenangkan kulit. " "Malam: Adapalene 0.1% mulai 2-3x seminggu, tingkatkan bertahap. " "Gunakan pelembap setelah adapalene." ), "evidence": "1A (PERDOSKI 2024)", "prescription_required": True, "otc_status": "Adapalene: Obat Keras (Resep Dokter)", "pregnancy_category": "Adapalene: Kategori C (HINDARI saat hamil)", "drug_interactions": [ "Adapalene: JANGAN dicampur dengan SA, Retinoid lain, Sulfur, atau AHA", "Hindari produk iritatif lainnya saat menggunakan Adapalene", ], }, "contraindications": [ "ADAPALENE: Hanya dengan resep dokter", "Tidak untuk ibu hamil (Adapalene - Kategori C)", "Hindari produk iritatif lainnya", "Jangan dicampur sendiri Adapalene dengan bahan aktif lain", ], }, # ── SEDANG: Papula/Pustula + Berminyak/Kombinasi ── # PERDOSKI: Topikal lini 1 → RA (1A) dan/atau BPO (1A) dan AB (1A) { "rule": 5, "condition": { "degree": ["sedang"], "dominant_lesion": ["papula_pustula"], "skin_type": ["berminyak", "kombinasi"], }, "recommendation": { "active_ingredient": "Adapalene 0.1% + Benzoyl Peroxide 2.5% (kombinasi)", "product_type": "Treatment", "dosage": "Pea-size untuk seluruh wajah", "frequency": "1x sehari (malam)", "time_of_day": "malam", "instructions": ( "Gunakan produk kombinasi Adapalene 0.1% + Benzoyl Peroxide 2.5% (misal: Epiduo). " "Oleskan tipis pada seluruh wajah malam hari. " "Mulai 3-4x seminggu, tingkatkan bertahap. " "Gunakan pelembap 20 menit setelahnya." ), "evidence": "1A (PERDOSKI 2024)", "prescription_required": True, "otc_status": "Obat Keras (Resep Dokter) - produk kombinasi hanya dengan resep", "pregnancy_category": "Kategori C (HINDARI saat hamil)", "drug_interactions": [ "JANGAN gunakan bersamaan dengan produk retinoid lainnya", "Hindari penggunaan bersamaan dengan SA atau Sulfur", "Hanya gunakan produk kombinasi jadi (Epiduo), JANGAN dicampur sendiri", ], }, "contraindications": [ "PRODUK KOMBINASI: Hanya dengan resep dokter", "Tidak untuk ibu hamil (Adapalene - Kategori C)", "Hindari paparan sinar UV langsung", "JANGAN dicampur sendiri dengan produk retinoid lain", ], }, # ── SEDANG: Papula/Pustula + Sensitif/Kering ── # PERDOSKI: Topikal lini 2 → AA (1B) atau SA (1B) { "rule": 6, "condition": { "degree": ["sedang"], "dominant_lesion": ["papula_pustula"], "skin_type": ["sensitif", "kering"], }, "recommendation": { "active_ingredient": "Azelaic Acid 15-20% (pagi) + Dermokosmetik (malam)", "product_type": "Treatment", "dosage": "Pea-size untuk seluruh wajah", "frequency": "2x sehari (pagi AA, malam dermokosmetik)", "time_of_day": "pagi_malam", "instructions": ( "Pagi: Azelaic Acid 15-20% mengurangi inflamasi dan bakteri. " "Malam: Dermokosmetik multitarget untuk perbaikan sawar kulit. " "Azelaic Acid aman untuk kulit sensitif dan ibu hamil." ), "evidence": "1B (PERDOSKI 2024)", "prescription_required": True, "otc_status": "Obat Keras (Resep Dokter) - ASEAN Cosmetic Directive", "pregnancy_category": "Kategori B (Relatif aman, konsultasi dokter)", "drug_interactions": [ "HINDARI dengan Retinol/Isotretinoin (meningkatkan iritasi)", "HINDARI dengan produk astringent atau alkohol kuat", ], }, "contraindications": [ "AZELAIC ACID: Hanya dengan resep dokter", "Hindari penggunaan bersamaan dengan Retinol atau Isotretinoin", ], }, # ── BERAT: Nodul atau Komedo/Papula banyak + Semua tipe kulit ── # PERDOSKI: Rujuk dokter. Oral Isotretinoin (1B) atau AB (1A) { "rule": 7, "condition": { "degree": ["berat"], "dominant_lesion": ["nodul", "papula_pustula", "comedone"], "skin_type": ["berminyak", "kering", "sensitif", "kombinasi"], }, "recommendation": { "active_ingredient": "STOP - Rujuk Dokter Spesialis Kulit", "product_type": "Konsultasi Medis", "dosage": "N/A", "frequency": "Segera", "time_of_day": "segera", "instructions": ( "HENTIKAN pengobatan mandiri. Jerawat derajat berat membutuhkan penanganan medis profesional. " "Kemungkinan memerlukan: Isotretinoin oral (1-2 mg/kg/hari), " "Antibiotik oral (Doksisiklin 2x100 mg/hari), " "atau terapi kombinasi sesuai indikasi dokter." ), "evidence": "1B (PERDOSKI 2024)", "prescription_required": True, "otc_status": "Semua terapi memerlukan resep dokter", "pregnancy_category": "Isotretinoin: TERATOGENIK (Dilarang saat hamil)", "drug_interactions": [ "Semua terapi harus diawasi oleh dokter spesialis kulit", "Isotretinoin: Dilarang saat hamil dan menyusui", ], }, "contraindications": [ "Semua terapi: HANYA dengan resep dokter spesialis kulit", "Jangan memencet jerawat nodul", "Isotretinoin: TERATOGENIK - Dilarang saat hamil", ], }, # ── SEDANG/RINGAN: Nodul + Semua tipe kulit ── # PERDOSKI: Nodul = selalu rujuk dokter, terlepas dari derajat lain { "rule": 8, "condition": { "degree": ["sedang", "ringan"], "dominant_lesion": ["nodul"], "skin_type": ["berminyak", "kering", "sensitif", "kombinasi"], }, "recommendation": { "active_ingredient": "STOP - Rujuk Dokter Spesialis Kulit", "product_type": "Konsultasi Medis", "dosage": "N/A", "frequency": "Segera", "time_of_day": "segera", "instructions": ( "Terdeteksi jerawat nodul yang memerlukan penanganan medis. " "Nodul adalah bentuk jerawat dalam yang meninggalkan bekas permanen jika tidak ditangani. " "Segera konsultasi ke dokter spesialis kulit untuk evaluasi dan rencana pengobatan." ), "evidence": "1B (PERDOSKI 2024)", "prescription_required": True, "otc_status": "Semua terapi memerlukan resep dokter", "pregnancy_category": "Konsultasi dokter", "drug_interactions": [ "Semua terapi harus diawasi oleh dokter spesialis kulit", ], }, "contraindications": [ "Semua terapi: HANYA dengan resep dokter spesialis kulit", "Jangan memencet jerawat nodul", ], }, # ── SEDANG: Komedo saja (banyak) + Berminyak/Kombinasi ── # PERDOSKI: Komedo 20-100 tanpa lesi inflamasi → BHA kuat + Adapalene { "rule": 9, "condition": { "degree": ["sedang"], "dominant_lesion": ["comedone"], "skin_type": ["berminyak", "kombinasi"], }, "recommendation": { "active_ingredient": "Salicylic Acid (BHA) 2% + Adapalene 0.1%", "product_type": "Treatment", "dosage": "2-3 tetes BHA (pagi), pea-size Adapalene (malam)", "frequency": "2x sehari (pagi BHA, malam Adapalene)", "time_of_day": "pagi_malam", "instructions": ( "Pagi: Salicylic Acid (BHA) 2% untuk eksfoliasi pori. " "Malam: Adapalene 0.1% mulai 3-4x seminggu, tingkatkan bertahap. " "Komedo yang banyak memerlukan kombinasi eksfolian + retinoid untuk hasil optimal. " "Gunakan pelembap sebagai penghalang di antara aplikasi." ), "evidence": "1A-2B (PERDOSKI 2024)", "prescription_required": True, "otc_status": "Adapalene: Obat Keras (Resep Dokter). BHA: Obat Bebas (OTC).", "pregnancy_category": "Adapalene: Kategori C (HINDARI saat hamil). BHA: Hati-hati.", "drug_interactions": [ "JANGAN gunakan BHA dan Adapalene pada waktu yang sama", "Pagi: BHA saja. Malam: Adapalene saja.", "Adapalene: JANGAN dicampur dengan Sulfur atau AHA", ], }, "contraindications": [ "ADAPALENE: Hanya dengan resep dokter", "Tidak untuk ibu hamil (Adapalene - Kategori C)", "Jangan aplikasikan BHA dan Adapalene bersamaan", ], }, # ── SEDANG: Komedo saja (banyak) + Sensitif/Kering ── # PERDOSKI: Komedo 20-100 tanpa lesi inflamasi → SA lembut + AA { "rule": 10, "condition": { "degree": ["sedang"], "dominant_lesion": ["comedone"], "skin_type": ["sensitif", "kering"], }, "recommendation": { "active_ingredient": "Salicylic Acid (BHA) 0.5-1% + Azelaic Acid 15%", "product_type": "Treatment", "dosage": "Secukupnya", "frequency": "2x sehari (pagi AA, malam BHA rendah)", "time_of_day": "pagi_malam", "instructions": ( "Pagi: Azelaic Acid 15% untuk anti-inflamasi dan eksfoliasi lembut. " "Malam: BHA rendah (0.5-1%) untuk membersihkan pori tanpa iritasi. " "Untuk kulit sensitif, gunakan frekuensi rendah dulu lalu tingkatkan. " "Pelembap wajib setiap kali setelah treatment." ), "evidence": "1B-2B (PERDOSKI 2024)", "prescription_required": True, "otc_status": "Azelaic Acid: Obat Keras (Resep Dokter). BHA: Obat Bebas (OTC).", "pregnancy_category": "Azelaic Acid: Kategori B (Relatif aman). BHA: Hati-hati.", "drug_interactions": [ "JANGAN gunakan BHA dan Adapalene bersamaan", "Azelaic Acid: HINDARI dengan Retinol/Isotretinoin", ], }, "contraindications": [ "AZELAIC ACID: Hanya dengan resep dokter", "Hindari penggunaan bersamaan dengan Retinol atau Isotretinoin", ], }, ] # ═══════════════════════════════════════════════════════════════════ # LAYER 2: DAILY SKINCARE (Berdasarkan tipe kulit) # PERDOSKI 2024: Adjuvan - KIE dan SC (1A), tabir surya (1C) # ═══════════════════════════════════════════════════════════════════ DAILY_SKINCARE = { "berminyak": { "facial_wash": { "name": "Facial Wash Gel/Foam pH Balanced", "description": "Pembersih berbentuk gel atau busa, pH balanced, bebas alkohol", "ingredients": "Tea tree, salicylic acid ringan, niacinamide", "usage": "2x sehari (pagi dan malam)", }, "moisturizer": { "name": "Moisturizer Oil-Free / Non-Comedogenic", "description": "Pelembap ringan tanpa minyak, tidak menyumbat pori", "ingredients": "Hyaluronic acid, niacinamide, aloe vera", "usage": "2x sehari setelah treatment", }, "sunscreen": { "name": "Sunscreen SPF 30-50 PA+++", "description": "Pelindung UV ringan, tidak lengket, non-comedogenic", "ingredients": "SPF 30-50, PA+++, formula gel atau air", "usage": "Pagi hari, 15 menit sebelum keluar rumah, reapply tiap 2-3 jam", }, }, "kering": { "facial_wash": { "name": "Facial Wash Cream/Milk pH Balanced", "description": "Pembersih lembut berbentuk krim atau susu, tidak mengikis kelembapan", "ingredients": "Ceramide, glycerin, centella asiatica", "usage": "2x sehari (pagi dan malam)", }, "moisturizer": { "name": "Moisturizer Hydrating / Ceramide", "description": "Pelembap kaya nutrisi untuk mengembalikan sawar kulit", "ingredients": "Ceramide, hyaluronic acid, squalane, shea butter", "usage": "2x sehari setelah treatment", }, "sunscreen": { "name": "Sunscreen SPF 30-50 PA+++ Hydrating", "description": "Pelindung UV dengan formula melembapkan", "ingredients": "SPF 30-50, PA+++, dengan hyaluronic acid", "usage": "Pagi hari, 15 menit sebelum keluar rumah, reapply tiap 2-3 jam", }, }, "sensitif": { "facial_wash": { "name": "Facial Wash Gentle / Fragrance-Free", "description": "Pembersih sangat lembut, bebas pewangi dan alkohol", "ingredients": "Centella asiatica, panthenol, allantoin", "usage": "2x sehari (pagi dan malam)", }, "moisturizer": { "name": "Moisturizer Calming / Centella", "description": "Pelembap menenangkan, mengurangi kemerahan dan iritasi", "ingredients": "Aloe vera, centella asiatica, madecassoside, panthenol", "usage": "2x sehari setelah treatment", }, "sunscreen": { "name": "Sunscreen Mineral SPF 30-50", "description": "Pelindung UV minimal iritasi, formula fisik (mineral)", "ingredients": "SPF 30-50, PA+++, zinc oxide/titanium dioxide", "usage": "Pagi hari, 15 menit sebelum keluar rumah, reapply tiap 2-3 jam", }, }, "kombinasi": { "facial_wash": { "name": "Facial Wash Gentle Foam pH Balanced", "description": "Pembersih ringan berbentuk busa lembut", "ingredients": "Glycerin, niacinamide ringan, centella asiatica", "usage": "2x sehari (pagi dan malam)", }, "moisturizer": { "name": "Moisturizer Gel-Cream Oil-Free", "description": "Pelembap bertekstur gel-cream, ringan tapi melembapkan", "ingredients": "Hyaluronic acid, niacinamide, aloe vera", "usage": "2x sehari setelah treatment", }, "sunscreen": { "name": "Sunscreen SPF 30-50 PA+++ Gel", "description": "Pelindung UV ringan, tidak lengket di T-zone", "ingredients": "SPF 30-50, PA+++, formula gel atau air", "usage": "Pagi hari, 15 menit sebelum keluar rumah, reapply tiap 2-3 jam", }, }, } # ═══════════════════════════════════════════════════════════════════ # FUNGSI EVALUASI FORWARD CHAINING # ═══════════════════════════════════════════════════════════════════ def _get_dominant_lesion(summary: dict) -> str: """Menentukan jenis lesi dominan dari summary deteksi. Note: Nodul TIDAK dijadikan dominant lesion karena: - Penanganan nodul selalu rujuk dokter (tidak ada self-treatment) - Kita tetap ingin match rule untuk lesi LAIN yang bisa ditreatment - Nodul ditangani terpisah via nodul_alert """ if not summary: return "comedone" comedone_count = summary.get("comedone", 0) papula_count = summary.get("papules", 0) pustule_count = summary.get("pustules", 0) papula_pustula_total = papula_count + pustule_count if papula_pustula_total > comedone_count: return "papula_pustula" return "comedone" def _has_nodul(summary: dict) -> bool: """Cek apakah ada nodul dalam deteksi.""" return summary.get("nodules", 0) > 0 def _evaluate_rules(degree: str, dominant_lesion: str, skin_type: str) -> list: """Evaluasi semua rules dan kembalikan rules yang cocok.""" matched_rules = [] for rule in RULES: cond = rule["condition"] if degree not in cond["degree"]: continue if dominant_lesion not in cond["dominant_lesion"]: continue if skin_type not in cond["skin_type"]: continue matched_rules.append(rule) return matched_rules def get_expert_recommendation( detected_classes: list, summary: dict, skin_type: str, severity_level: str, ) -> dict: """ Forward Chaining: evaluasi rules dan kembalikan rekomendasi. Berdasarkan PERDOSKI 2024: - Ringan: Topikal lini 1 (SA/RA/BPO) - Sedang: Topikal lini 1 + Oral (Dox) - Berat: Rujuk dokter, oral Isotretinoin/AB Returns: dict berisi: - expert_rule: info rule yang aktif - recommendation: rekomendasi treatment (Layer 1) - daily_skincare: rekomendasi daily skincare (Layer 2) - nonmedikamentosa: tips non obat (PERDOSKI 2024) - maintenance: tips pemeliharaan (PERDOSKI 2024) - active_ingredient_info: info legalitas bahan aktif - nodul_alert: peringatan nodul (jika ada nodul terdeteksi) """ if skin_type not in VALID_SKIN_TYPES: skin_type = "berminyak" dominant_lesion = _get_dominant_lesion(summary) # ── Jika ada nodul, hitung severity TANPA nodul untuk treatment lesi lain ── has_nodul = _has_nodul(summary) if has_nodul: # Buat summary tanpa nodul non_nodul_summary = {k: v for k, v in summary.items() if k != "nodules"} non_nodul_score = sum( count * {"comedone": 1, "papules": 2, "pustules": 2}.get(cls, 1) for cls, count in non_nodul_summary.items() ) # Map skor non-nodul ke severity if non_nodul_score == 0: non_nodul_severity = "clear" elif non_nodul_score <= 9: non_nodul_severity = "mild" elif non_nodul_score <= 19: non_nodul_severity = "moderate" else: non_nodul_severity = "severe" treatment_degree = SEVERITY_TO_DEGREE.get(non_nodul_severity, "ringan") else: treatment_degree = SEVERITY_TO_DEGREE.get(severity_level, "ringan") matched_rules = _evaluate_rules(treatment_degree, dominant_lesion, skin_type) if matched_rules: active_rule = matched_rules[0] recommendation = active_rule["recommendation"] contraindications = active_rule.get("contraindications", []) rule_number = active_rule["rule"] else: recommendation = { "active_ingredient": "Konsultasi Dokter Kulit", "product_type": "Konsultasi Medis", "dosage": "N/A", "frequency": "Segera", "time_of_day": "segera", "instructions": "Tidak ada rekomendasi spesifik. Silakan konsultasi dengan dokter kulit.", "evidence": "-", "prescription_required": True, "otc_status": "Konsultasi dokter", "pregnancy_category": "Konsultasi dokter", "drug_interactions": [], } contraindications = [] rule_number = 0 daily_skincare = DAILY_SKINCARE.get(skin_type, DAILY_SKINCARE["berminyak"]) # Nonmedikamentosa (PERDOSKI 2024) nonmedikamentosa = [ "Cuci muka 2x sehari dengan sabun pH balanced, bebas alkohol, non-abrasif", "Hindari penggunaan kosmetik yang bersifat oklusif", "Batasi konsumsi makanan indeks glikemik tinggi (karbohidrat, makanan berlemak, produk olahan susu)", "Identifikasi dan kelola stres psikologis", ] # Maintenance (PERDOSKI 2024) maintenance = [ "Setelah remisi, lanjutkan retinoid topikal <0.01-0.025% untuk pencegahan kekambuhan", "Gunakan sabun wajah, pelembap, dan tabir surya sesuai kondisi kulit", "Evaluasi respons pengobatan tiap 8-12 minggu", ] # Info bahan aktif berdasarkan rekomendasi active_ingredient_info = _get_active_ingredient_info(recommendation["active_ingredient"]) # Nodul alert: selalu tampil jika nodul terdeteksi (terlepas dari rule) nodul_alert = None if _has_nodul(summary): nodule_count = summary.get("nodules", 0) nodul_alert = { "detected": True, "count": nodule_count, "title": "Nodul Terdeteksi", "message": ( f"Terdeteksi {nodule_count} nodul pada wajah. " "Nodul adalah jerawat dalam yang tidak dapat ditangani dengan skincare atau obat bebas. " "Diperlukan penanganan medis oleh dokter spesialis kulit." ), "treatments": [ "Injeksi triamsinolon acetonide (TAIL) intralesi — dosis 0,03 mg per lesi", "Ekstraksi lesi oleh dokter", "Isotretinoin oral (untuk kasus berat) — hanya dengan resep dokter", ], "urgency": "Segera — risiko bekas permanen jika tidak ditangani", } return { "expert_rule": { "rule_number": rule_number, "degree": treatment_degree, "dominant_lesion": dominant_lesion, "skin_type": skin_type, "input_summary": { "severity_level": severity_level, "detected_classes": detected_classes, "lesion_counts": summary, "has_nodul": has_nodul, }, }, "recommendation": { **recommendation, "contraindications": contraindications, }, "daily_skincare": daily_skincare, "nonmedikamentosa": nonmedikamentosa, "maintenance": maintenance, "active_ingredient_info": active_ingredient_info, "nodul_alert": nodul_alert, } def _get_active_ingredient_info(ingredient_str: str) -> list: """Ambil info bahan aktif berdasarkan string rekomendasi.""" info = [] ingredient_lower = ingredient_str.lower() if "salicylic" in ingredient_lower or "bha" in ingredient_lower: info.append(ACTIVE_INGREDIENT_INFO["salicylic_acid"]) if "adapalene" in ingredient_lower: info.append(ACTIVE_INGREDIENT_INFO["adapalene"]) if "benzoyl" in ingredient_lower or "bpo" in ingredient_lower: info.append(ACTIVE_INGREDIENT_INFO["benzoyl_peroxide"]) if "azelaic" in ingredient_lower: info.append(ACTIVE_INGREDIENT_INFO["azelaic_acid"]) if "niacinamide" in ingredient_lower: info.append(ACTIVE_INGREDIENT_INFO["niacinamide"]) return info