RantoG commited on
Commit
9ca7f12
·
1 Parent(s): 9fddb7b

Fix ZeroGPU decorator detection, port 7860 conflict, and sklearn version

Browse files
Files changed (2) hide show
  1. app.py +62 -76
  2. requirements.txt +1 -1
app.py CHANGED
@@ -1,29 +1,22 @@
 
1
  import spaces
2
  import gradio as gr
3
- from fastapi import FastAPI, HTTPException
4
  from fastapi.middleware.cors import CORSMiddleware
5
  from pydantic import BaseModel
6
  import joblib
7
  import pandas as pd
8
  import numpy as np
9
- import uvicorn
10
 
11
- # --- 1. INISIALISASI FASTAPI & CORS (Wajib agar Frontend bisa akses) ---
12
- app = FastAPI(title="DETEKS AI API (ZeroGPU)", version="1.0")
13
-
14
- app.add_middleware(
15
- CORSMiddleware,
16
- allow_origins=["*"], # Mengizinkan akses dari domain frontend mana saja (Vercel/Localhost)
17
- allow_credentials=True,
18
- allow_methods=["*"],
19
- allow_headers=["*"],
20
- )
21
-
22
- # --- 2. LOAD MODEL AI ---
23
  MODEL_PATH = "deteks_isolation_forest.pkl"
 
 
 
24
  try:
25
  ai_model = joblib.load(MODEL_PATH)
26
- print("Model AI berhasil di-load!")
27
  except Exception as e:
28
  ai_model = None
29
  print(f"Error loading model: {e}")
@@ -33,22 +26,8 @@ FITUR_AI = [
33
  'n_event_obat', 'n_faskes_obat', 'qty_30d', 'min_interval', 'overlap_days'
34
  ]
35
 
36
- # --- 3. FUNGSI INFERENSI DENGAN DEKORATOR ZEROGPU (@spaces.GPU) ---
37
  @spaces.GPU
38
- def run_ai_inference(features_dict: dict) -> float:
39
- """
40
- Fungsi inti prediksi anomali yang menggunakan dekorator @spaces.GPU
41
- agar memenuhi syarat eksekusi Hugging Face ZeroGPU.
42
- """
43
- if ai_model is None:
44
- raise RuntimeError("Model belum ter-load. Pastikan file .pkl sudah di-upload.")
45
-
46
- df_input = pd.DataFrame([features_dict])[FITUR_AI]
47
- # Semakin negatif score_samples = semakin anomali, kita balik dengan tanda minus (-)
48
- raw_score = float(-ai_model.score_samples(df_input)[0])
49
- return raw_score
50
-
51
-
52
  def calculate_risk_fusion(
53
  participant_key: str,
54
  n_event_kunjungan: float,
@@ -62,6 +41,9 @@ def calculate_risk_fusion(
62
  overlap_days: float,
63
  rules_score_tambahan: float = 0.0
64
  ) -> dict:
 
 
 
65
  features_dict = {
66
  'n_event_kunjungan': float(n_event_kunjungan),
67
  'n_faskes_kunjungan': float(n_faskes_kunjungan),
@@ -74,24 +56,20 @@ def calculate_risk_fusion(
74
  'overlap_days': float(overlap_days)
75
  }
76
 
77
- # Jalankan fungsi ZeroGPU (dengan fallback aman jika dipanggil langsung via REST API)
78
- try:
79
- raw_score = run_ai_inference(features_dict)
80
- except Exception:
81
- df_input = pd.DataFrame([features_dict])[FITUR_AI]
82
- raw_score = float(-ai_model.score_samples(df_input)[0])
83
 
84
- # Konversi raw_score Isolation Forest (biasanya di rentang 0.35 - 0.75) ke skala persentil 0-100
85
  ai_percentile = float(np.clip((raw_score - 0.35) / 0.40 * 100, 0, 100))
86
 
87
- # Hitung Skor Rules & Normalisasi ke skala 0-100
88
  total_rules = float(same_day_hits) + float(unbundling_hits) + float(rules_score_tambahan)
89
  rules_norm = float(min((total_rules / 140.0) * 100, 100.0))
90
 
91
- # Formula Risk Fusion DETEKS: 60% Rules + 40% AI
92
  review_priority = (0.6 * rules_norm) + (0.4 * ai_percentile)
93
 
94
- # Buat Alasan Terdeteksi (Explainability / Reason Codes)
95
  reasons = []
96
  if same_day_hits > 0:
97
  reasons.append(f"Kunjungan di hari yang sama ({int(same_day_hits)}x)")
@@ -106,7 +84,6 @@ def calculate_risk_fusion(
106
  if not reasons:
107
  reasons.append("Pola terpantau normal / risiko rendah")
108
 
109
- # Tentukan Kategori Risiko (Band)
110
  if review_priority >= 70:
111
  status = "Tinggi"
112
  elif review_priority >= 40:
@@ -127,7 +104,38 @@ def calculate_risk_fusion(
127
  }
128
  }
129
 
130
- # --- 4. MODEL REQUEST & ENDPOINT FASTAPI ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  class PatientFeatures(BaseModel):
132
  participant_key: str = "P-0001"
133
  n_event_kunjungan: float = 2.0
@@ -141,7 +149,16 @@ class PatientFeatures(BaseModel):
141
  overlap_days: float = 4.0
142
  rules_score_tambahan: float = 0.0
143
 
144
- @app.post("/api/v1/analyze_risk")
 
 
 
 
 
 
 
 
 
145
  def analyze_risk_api(patient: PatientFeatures):
146
  try:
147
  return calculate_risk_fusion(
@@ -160,37 +177,6 @@ def analyze_risk_api(patient: PatientFeatures):
160
  except Exception as e:
161
  raise HTTPException(status_code=500, detail=str(e))
162
 
163
- # --- 5. ANTARMUKA GRADIO (Wajib untuk ZeroGPU Space) ---
164
- with gr.Blocks(title="DETEKS AI Engine") as demo:
165
- gr.Markdown("## 🏥 DETEKS - AI Risk Scoring Service (ZeroGPU)")
166
- gr.Markdown("Antarmuka uji coba model **Hybrid Rules + Isolation Forest**. Untuk dokumentasi REST API, buka endpoint `/docs`.")
167
-
168
- with gr.Row():
169
- with gr.Column():
170
- p_key = gr.Textbox(label="ID Peserta (participant_key)", value="P-0951")
171
- n_ev_kunj = gr.Number(label="Total Kunjungan RJTL (n_event_kunjungan)", value=5)
172
- n_fas_kunj = gr.Number(label="Jumlah Faskes Kunjungan (n_faskes_kunjungan)", value=1)
173
- same_day = gr.Number(label="Same Day Hits", value=0)
174
- unbundling = gr.Number(label="Unbundling Hits", value=1)
175
- with gr.Column():
176
- n_ev_obat = gr.Number(label="Frekuensi Tebus Obat (n_event_obat)", value=2)
177
- n_fas_obat = gr.Number(label="Jumlah Faskes Obat (n_faskes_obat)", value=2)
178
- qty_30d = gr.Number(label="Kuantitas Obat 30 Hari (qty_30d)", value=2)
179
- min_int = gr.Number(label="Interval Terpendek Ambil Obat (min_interval)", value=20)
180
- overlap = gr.Number(label="Hari Overlap Obat (overlap_days)", value=5)
181
- rules_ext = gr.Number(label="Skor Rules Obat Tambahan", value=10)
182
-
183
- btn = gr.Button("🔍 Analisis Risiko Pasien", variant="primary")
184
- output_json = gr.JSON(label="Hasil Output DETEKS (JSON)")
185
-
186
- btn.click(
187
- fn=calculate_risk_fusion,
188
- inputs=[p_key, n_ev_kunj, n_fas_kunj, same_day, unbundling, n_ev_obat, n_fas_obat, qty_30d, min_int, overlap, rules_ext],
189
- outputs=output_json
190
- )
191
-
192
- # Gabungkan Gradio ke dalam aplikasi FastAPI
193
- app = gr.mount_gradio_app(app, demo, path="/")
194
-
195
  if __name__ == "__main__":
196
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ import os
2
  import spaces
3
  import gradio as gr
4
+ from fastapi import HTTPException
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from pydantic import BaseModel
7
  import joblib
8
  import pandas as pd
9
  import numpy as np
 
10
 
11
+ # --- 1. LOAD MODEL AI ---
12
+ # Mengecek di root folder atau di dalam folder models/
 
 
 
 
 
 
 
 
 
 
13
  MODEL_PATH = "deteks_isolation_forest.pkl"
14
+ if not os.path.exists(MODEL_PATH) and os.path.exists("models/deteks_isolation_forest.pkl"):
15
+ MODEL_PATH = "models/deteks_isolation_forest.pkl"
16
+
17
  try:
18
  ai_model = joblib.load(MODEL_PATH)
19
+ print(f"Model AI berhasil di-load dari {MODEL_PATH}!")
20
  except Exception as e:
21
  ai_model = None
22
  print(f"Error loading model: {e}")
 
26
  'n_event_obat', 'n_faskes_obat', 'qty_30d', 'min_interval', 'overlap_days'
27
  ]
28
 
29
+ # --- 2. FUNGSI UTAMA DENGAN @spaces.GPU (Terhubung Langsung ke Gradio) ---
30
  @spaces.GPU
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def calculate_risk_fusion(
32
  participant_key: str,
33
  n_event_kunjungan: float,
 
41
  overlap_days: float,
42
  rules_score_tambahan: float = 0.0
43
  ) -> dict:
44
+ if ai_model is None:
45
+ raise RuntimeError("Model belum ter-load. Pastikan file .pkl ada di repositori.")
46
+
47
  features_dict = {
48
  'n_event_kunjungan': float(n_event_kunjungan),
49
  'n_faskes_kunjungan': float(n_faskes_kunjungan),
 
56
  'overlap_days': float(overlap_days)
57
  }
58
 
59
+ df_input = pd.DataFrame([features_dict])[FITUR_AI]
60
+ raw_score = float(-ai_model.score_samples(df_input)[0])
 
 
 
 
61
 
62
+ # Konversi raw_score ke persentil (skala 0 - 100)
63
  ai_percentile = float(np.clip((raw_score - 0.35) / 0.40 * 100, 0, 100))
64
 
65
+ # Hitung & Normalisasi Skor Rules (skala 0 - 100)
66
  total_rules = float(same_day_hits) + float(unbundling_hits) + float(rules_score_tambahan)
67
  rules_norm = float(min((total_rules / 140.0) * 100, 100.0))
68
 
69
+ # Risk Fusion (60% Rules + 40% AI)
70
  review_priority = (0.6 * rules_norm) + (0.4 * ai_percentile)
71
 
72
+ # Reason Codes (Explainability)
73
  reasons = []
74
  if same_day_hits > 0:
75
  reasons.append(f"Kunjungan di hari yang sama ({int(same_day_hits)}x)")
 
84
  if not reasons:
85
  reasons.append("Pola terpantau normal / risiko rendah")
86
 
 
87
  if review_priority >= 70:
88
  status = "Tinggi"
89
  elif review_priority >= 40:
 
104
  }
105
  }
106
 
107
+ # --- 3. ANTARMUKA GRADIO ---
108
+ with gr.Blocks(title="DETEKS AI Engine") as demo:
109
+ gr.Markdown("## 🏥 DETEKS - AI Risk Scoring Service (ZeroGPU)")
110
+ gr.Markdown("Antarmuka uji coba model **Hybrid Rules + Isolation Forest**. Endpoint REST API tersedia di `POST /api/v1/analyze_risk`.")
111
+
112
+ with gr.Row():
113
+ with gr.Column():
114
+ p_key = gr.Textbox(label="ID Peserta (participant_key)", value="P-0951")
115
+ n_ev_kunj = gr.Number(label="Total Kunjungan RJTL (n_event_kunjungan)", value=5)
116
+ n_fas_kunj = gr.Number(label="Jumlah Faskes Kunjungan (n_faskes_kunjungan)", value=1)
117
+ same_day = gr.Number(label="Same Day Hits", value=0)
118
+ unbundling = gr.Number(label="Unbundling Hits", value=1)
119
+ with gr.Column():
120
+ n_ev_obat = gr.Number(label="Frekuensi Tebus Obat (n_event_obat)", value=2)
121
+ n_fas_obat = gr.Number(label="Jumlah Faskes Obat (n_faskes_obat)", value=2)
122
+ qty_30d = gr.Number(label="Kuantitas Obat 30 Hari (qty_30d)", value=2)
123
+ min_int = gr.Number(label="Interval Terpendek Ambil Obat (min_interval)", value=20)
124
+ overlap = gr.Number(label="Hari Overlap Obat (overlap_days)", value=5)
125
+ rules_ext = gr.Number(label="Skor Rules Obat Tambahan", value=10)
126
+
127
+ btn = gr.Button("🔍 Analisis Risiko Pasien", variant="primary")
128
+ output_json = gr.JSON(label="Hasil Output DETEKS (JSON)")
129
+
130
+ # Fungsi @spaces.GPU didaftarkan langsung ke komponen Gradio
131
+ btn.click(
132
+ fn=calculate_risk_fusion,
133
+ inputs=[p_key, n_ev_kunj, n_fas_kunj, same_day, unbundling, n_ev_obat, n_fas_obat, qty_30d, min_int, overlap, rules_ext],
134
+ outputs=output_json,
135
+ api_name="analyze_risk"
136
+ )
137
+
138
+ # --- 4. TAMBAHKAN CUSTOM FASTAPI ENDPOINT KE SERVER GRADIO ---
139
  class PatientFeatures(BaseModel):
140
  participant_key: str = "P-0001"
141
  n_event_kunjungan: float = 2.0
 
149
  overlap_days: float = 4.0
150
  rules_score_tambahan: float = 0.0
151
 
152
+ # Mengaktifkan CORS pada aplikasi FastAPI internal milik Gradio
153
+ demo.app.add_middleware(
154
+ CORSMiddleware,
155
+ allow_origins=["*"],
156
+ allow_credentials=True,
157
+ allow_methods=["*"],
158
+ allow_headers=["*"],
159
+ )
160
+
161
+ @demo.app.post("/api/v1/analyze_risk")
162
  def analyze_risk_api(patient: PatientFeatures):
163
  try:
164
  return calculate_risk_fusion(
 
177
  except Exception as e:
178
  raise HTTPException(status_code=500, detail=str(e))
179
 
180
+ # --- 5. JALANKAN DENGAN DEMO.LAUNCH() ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  if __name__ == "__main__":
182
+ demo.launch()
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
  fastapi
2
  uvicorn
3
- scikit-learn
4
  pandas
5
  numpy
6
  pydantic
 
1
  fastapi
2
  uvicorn
3
+ scikit-learn==1.6.1
4
  pandas
5
  numpy
6
  pydantic