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

Deploy DETEKS AI Engine and FastAPI endpoint

Browse files
Files changed (3) hide show
  1. app.py +196 -0
  2. deteks_isolation_forest.pkl +3 -0
  3. requirements.txt +9 -0
app.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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}")
30
+
31
+ FITUR_AI = [
32
+ 'n_event_kunjungan', 'n_faskes_kunjungan', 'same_day_hits', 'unbundling_hits',
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,
55
+ n_faskes_kunjungan: float,
56
+ same_day_hits: float,
57
+ unbundling_hits: float,
58
+ n_event_obat: float,
59
+ n_faskes_obat: float,
60
+ qty_30d: float,
61
+ min_interval: float,
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),
68
+ 'same_day_hits': float(same_day_hits),
69
+ 'unbundling_hits': float(unbundling_hits),
70
+ 'n_event_obat': float(n_event_obat),
71
+ 'n_faskes_obat': float(n_faskes_obat),
72
+ 'qty_30d': float(qty_30d),
73
+ 'min_interval': float(min_interval),
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)")
98
+ if unbundling_hits > 0:
99
+ reasons.append(f"Indikasi unbundling kunjungan ({int(unbundling_hits)}x)")
100
+ if min_interval < 25:
101
+ reasons.append(f"Early refill obat (interval {int(min_interval)} hari)")
102
+ if overlap_days >= 3:
103
+ reasons.append(f"Overlap stok obat tinggi ({int(overlap_days)} hari)")
104
+ if n_faskes_obat > 1:
105
+ reasons.append(f"Pengambilan obat lintas faskes ({int(n_faskes_obat)} faskes)")
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:
113
+ status = "Sedang"
114
+ else:
115
+ status = "Rendah"
116
+
117
+ return {
118
+ "participant_key": str(participant_key),
119
+ "review_priority": round(review_priority, 2),
120
+ "risk_band": status,
121
+ "reasons": reasons,
122
+ "detail_scores": {
123
+ "total_rules_raw": round(total_rules, 2),
124
+ "rules_score_normalized": round(rules_norm, 2),
125
+ "ai_anomaly_percentile": round(ai_percentile, 2),
126
+ "raw_if_score": round(raw_score, 4)
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
134
+ n_faskes_kunjungan: float = 1.0
135
+ same_day_hits: float = 0.0
136
+ unbundling_hits: float = 0.0
137
+ n_event_obat: float = 2.0
138
+ n_faskes_obat: float = 1.0
139
+ qty_30d: float = 2.0
140
+ min_interval: float = 27.0
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(
148
+ participant_key=patient.participant_key,
149
+ n_event_kunjungan=patient.n_event_kunjungan,
150
+ n_faskes_kunjungan=patient.n_faskes_kunjungan,
151
+ same_day_hits=patient.same_day_hits,
152
+ unbundling_hits=patient.unbundling_hits,
153
+ n_event_obat=patient.n_event_obat,
154
+ n_faskes_obat=patient.n_faskes_obat,
155
+ qty_30d=patient.qty_30d,
156
+ min_interval=patient.min_interval,
157
+ overlap_days=patient.overlap_days,
158
+ rules_score_tambahan=patient.rules_score_tambahan
159
+ )
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)
deteks_isolation_forest.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:173903eb60b9637cb795219af348d8ad41d552dd775f7ed8048ba1de77b64835
3
+ size 793181
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ scikit-learn
4
+ pandas
5
+ numpy
6
+ pydantic
7
+ joblib
8
+ gradio
9
+ spaces