Mandr1 commited on
Commit
bea9034
ยท
verified ยท
1 Parent(s): 62a1d70

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +170 -176
app.py CHANGED
@@ -1,40 +1,45 @@
 
1
  import gradio as gr
2
  import matplotlib.pyplot as plt
3
  import pandas as pd
4
  import joblib
5
- # from pyspark.sql import SparkSession # No longer needed for inference
6
- # from pyspark.sql.functions import col, max as spark_max # No longer needed for inference
7
- from pyspark.sql.types import StringType, IntegerType, StructType, StructField # Still needed for schema definition if Spark is used elsewhere in the app.py, but not for this specific prediction path.
8
 
9
- # ==================================================
10
- # BAGIAN 0: INITIAL SETUP & LOAD SAVED MODELS
11
- # ==================================================
12
- print("Loading saved scikit-learn models and preprocessor...")
13
-
14
- # Load the saved preprocessor
15
- try:
16
- preprocessor = joblib.load('preprocessor.pkl')
17
- print("โœ… Preprocessor loaded successfully.")
18
- except FileNotFoundError:
19
- print("โŒ Error: 'preprocessor.pkl' not found. Please ensure it's in the same directory.")
20
- exit()
21
-
22
- # Load the trained scikit-learn models
23
- try:
24
- lr_model = joblib.load('lr_model.pkl')
25
- dt_model = joblib.load('dt_model.pkl')
26
- rf_model = joblib.load('rf_model.pkl')
27
- loaded_models = {
28
- 'Linear Regression': lr_model,
29
- 'Decision Tree': dt_model,
30
- 'Random Forest': rf_model
31
- }
32
- print("โœ… Scikit-learn models loaded successfully.")
33
- except FileNotFoundError:
34
- print("โŒ Error: One or more model .pkl files not found. Please ensure they are in the same directory.")
35
- exit()
36
 
37
- # Load and clean job_salary_mean.csv using pandas for benchmarks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  try:
39
  pd_df_raw = pd.read_csv('job_salary_mean.csv')
40
  pd_df_clean = pd_df_raw.rename(columns={
@@ -43,46 +48,29 @@ try:
43
  "Lokasi": "lokasi",
44
  "Gaji_Rata2": "gaji"
45
  })
46
- pd_df_clean['judul_clean'] = pd_df_clean['judul'].str.lower()
47
- pd_df_clean['lokasi_clean'] = pd_df_clean['lokasi'].str.lower()
48
- pd_df_clean = pd_df_clean.dropna()
49
  print(f"โœ… Pandas DataFrame for benchmarks loaded and cleaned. Total rows: {len(pd_df_clean)}")
50
  except FileNotFoundError:
51
  print("โŒ Error: 'job_salary_mean.csv' not found. Please ensure it's in the same directory.")
52
- exit()
53
-
54
- # ==================================================
55
- # BAGIAN 6 (FINAL): DASHBOARD DENGAN DATABASE WILAYAH RESMI
56
- # ==================================================
57
-
58
- # ---------------------------------------------------------
59
- # A. PERSIAPAN MASTER DATA WILAYAH (Dari File CSV Baru)
60
- # ---------------------------------------------------------
61
- print("Sedang memproses Database Wilayah Indonesia...")
62
 
63
- # 1. Baca Dataset Kabupaten/Kota
 
64
  try:
65
  geo_df = pd.read_csv('dataset kabupaten indonesia.csv')
66
-
67
- # Rename kolom agar jelas: 'name' -> 'kota', 'Unnamed: 3' -> 'provinsi'
68
  geo_df = geo_df[['name', 'Unnamed: 3']].rename(columns={'name': 'kota', 'Unnamed: 3': 'provinsi'})
69
-
70
- # Bersihkan Nama Kota (Hapus "KABUPATEN " dan "KOTA ") & Lowercase
71
- # Contoh: "KABUPATEN ACEH BARAT" -> "aceh barat"
72
- geo_df['kota_clean'] = geo_df['kota'].astype(str).str.replace('KABUPATEN ', '').str.replace('KOTA ', '').str.lower().str.strip()
73
  geo_df['provinsi'] = geo_df['provinsi'].astype(str).str.upper().str.strip()
74
-
75
- # Buat Kamus Pencarian (Dictionary)
76
- # Format: {'aceh barat': 'ACEH', 'surabaya': 'JAWA TIMUR', ...}
77
  kamus_wilayah = pd.Series(geo_df.provinsi.values, index=geo_df.kota_clean).to_dict()
78
-
79
  print(f"โœ… Berhasil memuat {len(kamus_wilayah)} wilayah administrasi Indonesia.")
80
-
81
  except FileNotFoundError:
82
- print("โŒ ERROR: File 'dataset kabupaten indonesia.csv' tidak ditemukan. Upload dulu!")
83
  kamus_wilayah = {}
84
 
85
- # 2. Mapping Provinsi ke Pulau (Logic Tambahan)
86
  def get_pulau_from_provinsi(provinsi):
87
  p = provinsi.upper()
88
  if any(x in p for x in ['JAWA', 'DKI', 'BANTEN', 'YOGYAKARTA']): return "PULAU JAWA"
@@ -93,135 +81,141 @@ def get_pulau_from_provinsi(provinsi):
93
  if any(x in p for x in ['PAPUA', 'MALUKU']): return "PAPUA & MALUKU"
94
  return "INDONESIA (LAINNYA)"
95
 
96
- # ---------------------------------------------------------
97
- # B. FUNGSI CERDAS: DETEKSI LOKASI USER
98
- # ---------------------------------------------------------
99
  def deteksi_info_lokasi(input_user):
100
- text = input_user.lower().strip()
101
-
102
- # Cek apakah input user mengandung nama kota yang ada di database
103
- provinsi_terdeteksi = "INDONESIA" # Default
104
-
105
  for kota_db, prov_db in kamus_wilayah.items():
106
- # Jika user ngetik "Simeulue" dan di db ada "simeulue", maka ketemu!
107
  if kota_db in text:
108
  provinsi_terdeteksi = prov_db
109
  break
110
-
111
  pulau_terdeteksi = get_pulau_from_provinsi(provinsi_terdeteksi)
112
  return provinsi_terdeteksi, pulau_terdeteksi
113
 
114
- # ---------------------------------------------------------
115
- # C. FUNGSI ANALISIS UTAMA
116
- # ---------------------------------------------------------
117
  def analisis_gaji_final(judul_input, lokasi_input, model_choice):
118
- # 1. Prediksi ML (Menggunakan Scikit-learn model)
119
- model_pipeline = loaded_models[model_choice]
120
-
121
- # Prepare input for scikit-learn pipeline (pandas DataFrame)
122
- input_df = pd.DataFrame({
123
- 'judul_clean': [judul_input.lower()],
124
- 'lokasi_clean': [lokasi_input.lower()],
125
- 'perusahaan': ['unknown_company_for_prediction'] # Placeholder for 'perusahaan'
126
- })
127
-
128
  try:
129
- prediksi_user = model_pipeline.predict(input_df)[0]
130
- # Ensure prediction is non-negative
131
- prediksi_user = max(0, prediksi_user)
132
- except Exception as e:
133
- return f"<h1>โš ๏ธ Error during prediction: {e}</h1>", None
134
-
135
- # 2. Deteksi Wilayah Cerdas
136
- provinsi_found, pulau_found = deteksi_info_lokasi(lokasi_input)
137
-
138
- # 3. Logika Benchmark (Pembanding) - Menggunakan pd_df_clean
139
- judul_lower = judul_input.lower()
140
-
141
- # A. Max Gaji Pekerjaan (Nasional)
142
- # Filter jobs where judul_clean contains the input judul_lower
143
- filtered_jobs = pd_df_clean[pd_df_clean['judul_clean'].str.contains(judul_lower, na=False)]
144
- if not filtered_jobs.empty:
145
- max_gaji_job = filtered_jobs['gaji'].max()
146
- else:
147
- max_gaji_job = prediksi_user * 1.2 # Fallback if no matching jobs found
148
-
149
- # B. Max Gaji Regional (Berdasarkan Pulau yang ditemukan)
150
- keyword_pencarian = pulau_found.replace("PULAU ", "").lower() # Misal "jawa", "sumatera"
151
- # Filter locations where lokasi_clean contains the keyword_pencarian
152
- filtered_locations = pd_df_clean[pd_df_clean['lokasi_clean'].str.contains(keyword_pencarian, na=False)]
153
- if not filtered_locations.empty:
154
- max_gaji_region = filtered_locations['gaji'].max()
155
- else:
156
- max_gaji_region = prediksi_user * 1.5 # Fallback if no matching locations found
157
-
158
- # 4. Visualisasi Matplotlib
159
- plt.style.use('seaborn-v0_8-whitegrid')
160
- fig, ax = plt.subplots(figsize=(10, 5.5))
161
-
162
- labels = [f"Estimasi Anda\n({lokasi_input})", f"Max Posisi '{judul_input}'\n(Nasional)", f"Max Regional\n({pulau_found})"]
163
- values = [prediksi_user, max_gaji_job, max_gaji_region]
164
- colors = ['#0ea5e9', '#94a3b8', '#f59e0b'] # Biru Langit, Abu, Oranye
165
-
166
- bars = ax.bar(labels, values, color=colors, edgecolor='black', alpha=0.9)
167
-
168
- # Garis referensi gaji user
169
- ax.axhline(y=prediksi_user, color='#0ea5e9', linestyle='--', linewidth=2, label="Posisi Anda")
170
-
171
- for bar in bars:
172
- height = bar.get_height()
173
- ax.text(bar.get_x() + bar.get_width()/2., height + (height*0.015),
174
- f'Rp {height/1000000:.1f} Jt',
175
- ha='center', va='bottom', fontweight='bold', fontsize=11)
176
-
177
- ax.set_title(f"Analisis Gaji: {judul_input} @ {provinsi_found} (Model: {model_choice}) ", fontsize=14, fontweight='bold', pad=15)
178
- ax.set_ylabel("Gaji (Rupiah)")
179
- ax.grid(axis='y', linestyle='--', alpha=0.5)
180
-
181
- # 5. Generate Output HTML
182
- html_output = f"""
183
- <div style="font-family: sans-serif; padding: 20px; border: 1px solid #e2e8f0; border-radius: 12px; background: linear-gradient(to right, #f8fafc, #ffffff);">
184
- <h2 style="color: #0f172a; margin-bottom: 5px;">๐Ÿ’ฐ Estimasi: Rp {int(prediksi_user):,.0f}</h2>
185
- <span style="background-color: #e0f2fe; color: #0369a1; padding: 4px 10px; border-radius: 20px; font-size: 0.85em; font-weight: bold;">
186
- ๐Ÿ“ {provinsi_found} / {pulau_found}
187
- </span>
188
- <p style="margin-top: 15px; color: #475569; line-height: 1.5;">
189
- Sistem mendeteksi lokasi Anda berada di provinsi <b>{provinsi_found}</b>.
190
- Berdasarkan data historis, standar gaji pasar untuk <b>{judul_input}</b> di wilayah ini adalah seperti di atas.
191
- </p>
192
- <div style="margin-top: 15px; padding: 10px; background-color: #fff7ed; border-left: 4px solid #f97316; color: #9a3412; font-size: 0.9em;">
193
- ๐Ÿ’ก <b>Insight Regional:</b> Batas atas gaji tertinggi (semua sektor) di {pulau_found} tercatat mencapai <b>Rp {int(max_gaji_region):,.0f}</b>.
 
 
 
 
194
  </div>
195
- </div>
196
- """
197
-
198
- return html_output, fig
199
-
200
- # ---------------------------------------------------------
201
- # D. INTERFACE GRADIO
202
- # ---------------------------------------------------------
203
- theme = gr.themes.Soft(primary_hue="cyan", secondary_hue="slate")
204
-
205
- with gr.Blocks(theme=theme, title="Salary AI") as demo:
206
- gr.Markdown("# ๐Ÿ‡ฎ๐Ÿ‡ฉ AI Salary Predictor & Geo-Intelligence")
207
- gr.Markdown("Prediksi gaji menggunakan Scikit-learn Models + Database Wilayah BPS Indonesia.")
208
-
209
- with gr.Row():
210
- with gr.Column():
211
- t1 = gr.Textbox(label="Posisi Pekerjaan", placeholder="Contoh: Guru, Driver, Manager")
212
- t2 = gr.Textbox(label="Kabupaten / Kota", placeholder="Contoh: Simeulue, Surakarta, Malang")
213
- model_selector = gr.Dropdown(
214
- label="Pilih Model Prediksi",
215
- choices=list(loaded_models.keys()),
216
- value='Decision Tree' # Default selected model
217
- )
218
- btn = gr.Button("๐Ÿ” Analisis Sekarang", variant="primary")
219
- with gr.Column():
220
- out_html = gr.HTML(label="Hasil Analisis")
221
-
222
- out_plot = gr.Plot(label="Grafik Komparasi")
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  btn.click(analisis_gaji_final, inputs=[t1, t2, model_selector], outputs=[out_html, out_plot])
225
 
226
- print("Menjalankan Aplikasi Final...")
227
- demo.launch(share=True, debug=True)
 
 
1
+ # app.py
2
  import gradio as gr
3
  import matplotlib.pyplot as plt
4
  import pandas as pd
5
  import joblib
6
+ import traceback
 
 
7
 
8
+ # ------------------------------
9
+ # Helper: safe load joblib with message
10
+ # ------------------------------
11
+ def safe_load(path, name):
12
+ try:
13
+ obj = joblib.load(path)
14
+ print(f"โœ… {name} loaded from {path}")
15
+ return obj
16
+ except FileNotFoundError:
17
+ print(f"โŒ Error: '{path}' not found. Please ensure it's in the same directory.")
18
+ raise
19
+ except Exception as e:
20
+ print(f"โŒ Error loading {path}: {e}")
21
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
+ # ==============================
24
+ # BAGIAN 0: LOAD PREPROCESSOR & MODELS
25
+ # ==============================
26
+ print("Loading saved scikit-learn models and preprocessor...")
27
+ preprocessor = safe_load('preprocessor.pkl', 'Preprocessor')
28
+
29
+ lr_model = safe_load('lr_model.pkl', 'Linear Regression model')
30
+ dt_model = safe_load('dt_model.pkl', 'Decision Tree model')
31
+ rf_model = safe_load('rf_model.pkl', 'Random Forest model')
32
+
33
+ loaded_models = {
34
+ 'Linear Regression': lr_model,
35
+ 'Decision Tree': dt_model,
36
+ 'Random Forest': rf_model
37
+ }
38
+
39
+ # ==============================
40
+ # BAGIAN: LOAD BENCHMARK CSV & WILAYAH
41
+ # ==============================
42
+ pd_df_clean = None
43
  try:
44
  pd_df_raw = pd.read_csv('job_salary_mean.csv')
45
  pd_df_clean = pd_df_raw.rename(columns={
 
48
  "Lokasi": "lokasi",
49
  "Gaji_Rata2": "gaji"
50
  })
51
+ pd_df_clean['judul_clean'] = pd_df_clean['judul'].astype(str).str.lower()
52
+ pd_df_clean['lokasi_clean'] = pd_df_clean['lokasi'].astype(str).str.lower()
53
+ pd_df_clean = pd_df_clean.dropna(subset=['judul_clean','lokasi_clean','gaji'])
54
  print(f"โœ… Pandas DataFrame for benchmarks loaded and cleaned. Total rows: {len(pd_df_clean)}")
55
  except FileNotFoundError:
56
  print("โŒ Error: 'job_salary_mean.csv' not found. Please ensure it's in the same directory.")
57
+ pd_df_clean = pd.DataFrame(columns=['judul_clean','lokasi_clean','gaji'])
 
 
 
 
 
 
 
 
 
58
 
59
+ # Load wilayah
60
+ kamus_wilayah = {}
61
  try:
62
  geo_df = pd.read_csv('dataset kabupaten indonesia.csv')
 
 
63
  geo_df = geo_df[['name', 'Unnamed: 3']].rename(columns={'name': 'kota', 'Unnamed: 3': 'provinsi'})
64
+ geo_df['kota_clean'] = geo_df['kota'].astype(str).str.replace('KABUPATEN ', '', regex=False)\
65
+ .str.replace('KOTA ', '', regex=False)\
66
+ .str.lower().str.strip()
 
67
  geo_df['provinsi'] = geo_df['provinsi'].astype(str).str.upper().str.strip()
 
 
 
68
  kamus_wilayah = pd.Series(geo_df.provinsi.values, index=geo_df.kota_clean).to_dict()
 
69
  print(f"โœ… Berhasil memuat {len(kamus_wilayah)} wilayah administrasi Indonesia.")
 
70
  except FileNotFoundError:
71
+ print("โŒ WARNING: File 'dataset kabupaten indonesia.csv' tidak ditemukan. Fitur deteksi lokasi manual masih bisa digunakan.")
72
  kamus_wilayah = {}
73
 
 
74
  def get_pulau_from_provinsi(provinsi):
75
  p = provinsi.upper()
76
  if any(x in p for x in ['JAWA', 'DKI', 'BANTEN', 'YOGYAKARTA']): return "PULAU JAWA"
 
81
  if any(x in p for x in ['PAPUA', 'MALUKU']): return "PAPUA & MALUKU"
82
  return "INDONESIA (LAINNYA)"
83
 
 
 
 
84
  def deteksi_info_lokasi(input_user):
85
+ text = str(input_user).lower().strip()
86
+ provinsi_terdeteksi = "INDONESIA"
 
 
 
87
  for kota_db, prov_db in kamus_wilayah.items():
 
88
  if kota_db in text:
89
  provinsi_terdeteksi = prov_db
90
  break
 
91
  pulau_terdeteksi = get_pulau_from_provinsi(provinsi_terdeteksi)
92
  return provinsi_terdeteksi, pulau_terdeteksi
93
 
94
+ # ==============================
95
+ # FUNGSI UTAMA: analisis_gaji_final
96
+ # ==============================
97
  def analisis_gaji_final(judul_input, lokasi_input, model_choice):
 
 
 
 
 
 
 
 
 
 
98
  try:
99
+ # Safety for empty inputs
100
+ if not judul_input or not lokasi_input:
101
+ return ("<div style='color:#9a1f1f; padding:12px;'><b>Masukkan posisi dan lokasi terlebih dahulu.</b></div>", None)
102
+
103
+ model_pipeline = loaded_models.get(model_choice)
104
+ if model_pipeline is None:
105
+ return (f"<div style='color:#9a1f1f; padding:12px;'><b>Model '{model_choice}' tidak tersedia.</b></div>", None)
106
+
107
+ input_df = pd.DataFrame({
108
+ 'judul_clean': [str(judul_input).lower()],
109
+ 'lokasi_clean': [str(lokasi_input).lower()],
110
+ 'perusahaan': ['unknown_company_for_prediction']
111
+ })
112
+
113
+ # If your preprocessor expects different feature names, ensure alignment here.
114
+ try:
115
+ prediksi_user = model_pipeline.predict(input_df)[0]
116
+ prediksi_user = max(0, float(prediksi_user))
117
+ except Exception as e:
118
+ tb = traceback.format_exc()
119
+ print("Prediction error:", tb)
120
+ return (f"<div style='color:#9a1f1f; padding:12px;'><b>Gagal memprediksi:</b> {str(e)}</div>", None)
121
+
122
+ # Benchmark logic
123
+ judul_lower = str(judul_input).lower()
124
+ filtered_jobs = pd_df_clean[pd_df_clean['judul_clean'].str.contains(judul_lower, na=False)]
125
+ if not filtered_jobs.empty:
126
+ max_gaji_job = float(filtered_jobs['gaji'].max())
127
+ else:
128
+ max_gaji_job = prediksi_user * 1.2
129
+
130
+ provinsi_found, pulau_found = deteksi_info_lokasi(lokasi_input)
131
+ keyword_pencarian = pulau_found.replace("PULAU ", "").lower()
132
+ filtered_locations = pd_df_clean[pd_df_clean['lokasi_clean'].str.contains(keyword_pencarian, na=False)]
133
+ if not filtered_locations.empty:
134
+ max_gaji_region = float(filtered_locations['gaji'].max())
135
+ else:
136
+ max_gaji_region = prediksi_user * 1.5
137
+
138
+ # Visualisasi (matplotlib)
139
+ fig, ax = plt.subplots(figsize=(9,4.6))
140
+ labels = [f"Estimasi Anda\n({lokasi_input})", f"Max Posisi\n(Nasional)", f"Max Regional\n({pulau_found})"]
141
+ values = [prediksi_user, max_gaji_job, max_gaji_region]
142
+ # subtle colors
143
+ colors = ['#60a5fa', '#94a3b8', '#fbbf24']
144
+
145
+ bars = ax.bar(labels, values, color=colors, edgecolor='none', alpha=0.95)
146
+ ax.axhline(y=prediksi_user, color='#2563eb', linestyle='--', linewidth=1)
147
+ ax.set_ylabel("Gaji (Rupiah)")
148
+ ax.set_title(f"Analisis Gaji: {judul_input} โ€” {provinsi_found} | Model: {model_choice}", fontsize=12)
149
+ ax.grid(axis='y', linestyle='--', alpha=0.4)
150
+ for bar in bars:
151
+ height = bar.get_height()
152
+ ax.text(bar.get_x() + bar.get_width()/2., height + (max(values)*0.015),
153
+ f'Rp {int(height):,}', ha='center', va='bottom', fontsize=9)
154
+
155
+ # HTML card hasil
156
+ html_output = f"""
157
+ <div style="font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
158
+ padding:18px; border-radius:12px; background: linear-gradient(180deg, #ffffff 0%, #fbfbfc 100%);
159
+ box-shadow: 0px 6px 20px rgba(16,24,40,0.04); color:#0f172a;">
160
+ <h2 style="margin:0 0 6px 0; font-size:18px; color:#0f172a;">๐Ÿ’ฐ Estimasi Gaji: <span style="color:#0b6fb7;">Rp {int(prediksi_user):,}</span></h2>
161
+ <div style="font-size:13px; color:#475569; margin-bottom:10px;">
162
+ ๐Ÿ“ <b>{provinsi_found}</b> / {pulau_found} &nbsp; โ€ข &nbsp; Model: <b>{model_choice}</b>
163
+ </div>
164
+ <div style="padding:10px; border-radius:8px; background:#f8fafc; color:#0f172a; font-size:13px;">
165
+ Berdasarkan data historis, batas atas untuk posisi <b>{judul_input}</b> (nasional) mencapai <b>Rp {int(max_gaji_job):,}</b>.
166
+ Untuk regional ({pulau_found}) tertinggi tercatat Rp <b>{int(max_gaji_region):,}</b>.
167
+ </div>
168
  </div>
169
+ """
170
+ plt.tight_layout()
171
+ return html_output, fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
+ except Exception as e:
174
+ tb = traceback.format_exc()
175
+ print("Unhandled error in analisis_gaji_final:", tb)
176
+ return (f"<div style='color:#9a1f1f; padding:12px;'><b>Terjadi kesalahan:</b> {str(e)}</div>", None)
177
+
178
+ # ==============================
179
+ # GRADIO UI - with custom CSS for subtle/elegant look
180
+ # ==============================
181
+ custom_css = """
182
+ :root{
183
+ --primary:#0b6fb7;
184
+ --muted:#94a3b8;
185
+ --card-bg: #ffffff;
186
+ --accent: #f8fafc;
187
+ }
188
+ body { font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; }
189
+ .gradio-container { max-width: 1100px; margin: 18px auto; }
190
+ .header { display:flex; align-items:center; gap:12px; margin-bottom:8px; }
191
+ .small-brand { font-weight:700; color:var(--primary); font-size:20px; }
192
+ .description { color:var(--muted); margin-bottom:14px; }
193
+ .input-box .gr-textbox { border-radius:10px; }
194
+ .gr-button { border-radius:10px; padding:10px 14px; font-weight:600; }
195
+ .result-card { border-radius:12px; padding:6px; }
196
+ """
197
+
198
+ with gr.Blocks(title="Salary AI โ€” Elegant", css=custom_css) as demo:
199
+ with gr.Column():
200
+ with gr.Row(elem_id="top-row"):
201
+ with gr.Column(scale=2):
202
+ gr.Markdown("<div class='header'><div class='small-brand'>๐Ÿ‡ฎ๐Ÿ‡ฉ Salary AI</div></div>")
203
+ gr.Markdown("<div class='description'>Prediksi gaji berbasis machine learning + data benchmark wilayah Indonesia. Masukkan posisi pekerjaan dan kabupaten/kota untuk analisis.</div>")
204
+ with gr.Row():
205
+ t1 = gr.Textbox(label="Posisi Pekerjaan", placeholder="Contoh: Guru, Driver, Manager", elem_id="t1")
206
+ t2 = gr.Textbox(label="Kabupaten / Kota", placeholder="Contoh: Simeulue, Surakarta, Malang", elem_id="t2")
207
+ model_selector = gr.Dropdown(label="Pilih Model Prediksi",
208
+ choices=list(loaded_models.keys()),
209
+ value='Random Forest',
210
+ interactive=True)
211
+ btn = gr.Button("๐Ÿ” Analisis Sekarang", variant="primary")
212
+ with gr.Column(scale=1):
213
+ gr.Markdown("### Hasil")
214
+ out_html = gr.HTML()
215
+ out_plot = gr.Plot()
216
+ # Connect
217
  btn.click(analisis_gaji_final, inputs=[t1, t2, model_selector], outputs=[out_html, out_plot])
218
 
219
+ if __name__ == "__main__":
220
+ print("Menjalankan Aplikasi Final...")
221
+ demo.launch(share=True, debug=True)