firmanaziz commited on
Commit
720a543
Β·
verified Β·
1 Parent(s): 446c80c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -58
app.py CHANGED
@@ -10,7 +10,6 @@ import os
10
  # --- KONFIGURASI API KEY DARI HUGGING FACE SECRETS ---
11
  API_CONFIGURED = False
12
  try:
13
- # PASTIKAN NAMA SECRET ANDA ADALAH 'GEMINI_API_KEY', BUKAN 'GOOGLE_API_KEY'
14
  api_key = os.environ.get('GEMINI_API_KEY')
15
  if api_key:
16
  genai.configure(api_key=api_key)
@@ -33,8 +32,7 @@ def ekstrak_teks_dari_pdf(path_file_pdf):
33
  raise gr.Error(f"Gagal membaca file PDF: {e}")
34
 
35
  def clean_and_parse_json(raw_text):
36
- """Fungsi yang lebih andal untuk membersihkan dan mem-parsing JSON dari teks mentah."""
37
- # Menemukan awal dan akhir blok JSON
38
  start_index = raw_text.find('{')
39
  end_index = raw_text.rfind('}')
40
  if start_index == -1 or end_index == -1:
@@ -43,39 +41,42 @@ def clean_and_parse_json(raw_text):
43
  json_str = raw_text[start_index:end_index+1]
44
  return json.loads(json_str)
45
 
46
- def format_output_markdown(parsed_json, hasil_analisis):
47
- """Menyusun hasil akhir menjadi string Markdown yang rapi."""
48
- nama = parsed_json.get('nama_lengkap', 'N/A')
49
- skor = hasil_analisis.get('skor_kecocokan', 'N/A')
50
- ringkasan = hasil_analisis.get('ringkasan_analisis', 'N/A')
51
- kekuatan = "\n".join([f"- {poin}" for poin in hasil_analisis.get('poin_kekuatan', [])])
52
- kelemahan = "\n".join([f"- {poin}" for poin in hasil_analisis.get('potensi_kelemahan', [])])
53
-
 
 
 
 
 
 
54
  return f"""
55
- ### HASIL ANALISIS KECOCOKAN KANDIDAT
56
- **πŸ‘€ Nama Kandidat:** {nama}
57
  ***
58
- **⭐ Skor Kecocokan:** {skor} / 10
59
-
60
- **πŸ“ Ringkasan Analisis:**
61
- {ringkasan}
62
-
63
- **βœ… Poin Kekuatan Utama:**
64
- {kekuatan}
65
 
66
- **⚠️ Potensi Kelemahan / Pertanyaan Lanjutan:**
67
- {kelemahan}
68
  """
69
 
70
- def analyze_cv(cv_file, deskripsi_pekerjaan):
71
- """Fungsi utama yang dijalankan oleh Gradio saat tombol ditekan."""
72
  if not API_CONFIGURED:
73
  raise gr.Error("API Key Gemini belum terkonfigurasi. Periksa Logs aplikasi di Hugging Face.")
74
- if cv_file is None or not deskripsi_pekerjaan.strip():
75
- raise gr.Error("Mohon upload file CV (PDF) dan isi deskripsi pekerjaan.")
76
 
77
  try:
78
- print("--- Memulai Proses Analisis ---")
79
 
80
  print("1. Mengekstrak teks dari PDF...")
81
  teks_cv = ekstrak_teks_dari_pdf(cv_file.name)
@@ -83,51 +84,62 @@ def analyze_cv(cv_file, deskripsi_pekerjaan):
83
  raise gr.Error("PDF kosong atau tidak dapat dibaca.")
84
  print("βœ… Teks berhasil diekstrak.")
85
 
86
- print("2. Mengirim permintaan ekstraksi JSON ke Gemini...")
87
- prompt_ekstraksi = f"Ekstrak informasi dari teks CV berikut dan sajikan dalam format JSON. Pastikan JSON memiliki struktur: nama_lengkap, kontak, ringkasan, keterampilan, pengalaman_kerja, dan pendidikan. Teks CV:\n---\n{teks_cv}\n---\nOutput harus berupa JSON saja."
88
- response_ekstraksi = model.generate_content(prompt_ekstraksi)
89
- parsed_json = clean_and_parse_json(response_ekstraksi.text)
90
- print("βœ… Ekstraksi JSON berhasil.")
91
-
92
- print("3. Mengirim permintaan analisis kecocokan ke Gemini...")
93
- prompt_analisis = f"Anda adalah Manajer Perekrutan. Analisis data kandidat (JSON) dan bandingkan dengan deskripsi pekerjaan. Deskripsi Pekerjaan: {deskripsi_pekerjaan}. Data Kandidat (JSON): {json.dumps(parsed_json, indent=2)}. Instruksi: Berikan output JSON dengan struktur: skor_kecocokan, ringkasan_analisis, poin_kekuatan, potensi_kelemahan. Output harus JSON saja."
94
- response_analisis = model.generate_content(prompt_analisis)
95
- hasil_analisis = clean_and_parse_json(response_analisis.text)
96
- print("βœ… Analisis kecocokan berhasil.")
97
 
98
- print("4. Menyusun output akhir...")
99
- final_output = format_output_markdown(parsed_json, hasil_analisis)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  print("--- Proses Selesai ---")
101
  return final_output
102
 
103
  except Exception as e:
104
- print(f"πŸ›‘ ERROR DALAM FUNGSI ANALISIS: {e}")
105
  raise gr.Error(f"Terjadi kesalahan: {e}")
106
 
107
  # --- MEMBUAT INTERFACE GRADIO ---
108
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
109
- gr.Markdown("# πŸ€– Aplikasi Analisis CV Otomatis")
110
- gr.Markdown("Upload CV dalam format PDF dan masukkan deskripsi pekerjaan untuk mendapatkan analisis kecocokan secara instan.")
111
 
112
  with gr.Row():
113
  with gr.Column(scale=1):
114
- cv_pdf = gr.File(label="1. Upload CV Anda (PDF)", file_types=[".pdf"])
115
- job_desc = gr.Textbox(lines=10, label="2. Masukkan Deskripsi Pekerjaan", placeholder="Contoh:\nPosisi: Senior Backend Engineer...")
116
- with gr.Row():
117
- clear_button = gr.ClearButton(value="Hapus Semua")
118
- analyze_button = gr.Button("✨ Analisis Sekarang", variant="primary")
119
 
120
  with gr.Column(scale=2):
121
- output_analysis = gr.Markdown(label="Hasil Analisis")
122
-
123
- # Menghubungkan tombol hapus ke semua komponen input dan output
124
- clear_button.add([cv_pdf, job_desc, output_analysis])
125
-
126
- analyze_button.click(
127
- fn=analyze_cv,
128
- inputs=[cv_pdf, job_desc],
129
- outputs=[output_analysis],
130
- show_progress='full' # Menampilkan status proses
131
  )
132
 
133
  if __name__ == "__main__":
 
10
  # --- KONFIGURASI API KEY DARI HUGGING FACE SECRETS ---
11
  API_CONFIGURED = False
12
  try:
 
13
  api_key = os.environ.get('GEMINI_API_KEY')
14
  if api_key:
15
  genai.configure(api_key=api_key)
 
32
  raise gr.Error(f"Gagal membaca file PDF: {e}")
33
 
34
  def clean_and_parse_json(raw_text):
35
+ """Fungsi andal untuk membersihkan dan mem-parsing JSON dari teks mentah."""
 
36
  start_index = raw_text.find('{')
37
  end_index = raw_text.rfind('}')
38
  if start_index == -1 or end_index == -1:
 
41
  json_str = raw_text[start_index:end_index+1]
42
  return json.loads(json_str)
43
 
44
+ def format_output_markdown(hasil_penilaian):
45
+ """Menyusun hasil penilaian menjadi string Markdown yang rapi."""
46
+ skor = hasil_penilaian.get('skor_keseluruhan', 0)
47
+ penilaian = hasil_penilaian.get('penilaian_per_kategori', {})
48
+ kelebihan = "\n".join([f"- {poin}" for poin in hasil_penilaian.get('poin_kelebihan', [])])
49
+ perbaikan = "\n".join([f"- {poin}" for poin in hasil_penilaian.get('poin_perbaikan', [])])
50
+
51
+ # Membuat progress bar untuk setiap kategori
52
+ progress_bars = ""
53
+ for kategori, nilai in penilaian.items():
54
+ # Mengganti underscore dengan spasi dan membuat judul
55
+ nama_kategori = kategori.replace("_", " ").title()
56
+ progress_bars += f"**{nama_kategori}**\n`{'β–ˆ' * int(nilai/10)}{'β–‘' * (10 - int(nilai/10))}` {nilai}/100\n"
57
+
58
  return f"""
59
+ ### HASIL PENILAIAN CV ANDA
60
+ **⭐ Skor Keseluruhan: {skor} / 100**
61
  ***
62
+ {progress_bars}
63
+ ***
64
+ **βœ… Hal yang Sudah Baik:**
65
+ {kelebihan}
 
 
 
66
 
67
+ **πŸ’‘ Poin yang Perlu Diperbaiki:**
68
+ {perbaikan}
69
  """
70
 
71
+ def score_cv(cv_file):
72
+ """Fungsi utama yang dijalankan Gradio untuk menilai CV."""
73
  if not API_CONFIGURED:
74
  raise gr.Error("API Key Gemini belum terkonfigurasi. Periksa Logs aplikasi di Hugging Face.")
75
+ if cv_file is None:
76
+ raise gr.Error("Mohon upload file CV (PDF) Anda.")
77
 
78
  try:
79
+ print("--- Memulai Proses Penilaian CV ---")
80
 
81
  print("1. Mengekstrak teks dari PDF...")
82
  teks_cv = ekstrak_teks_dari_pdf(cv_file.name)
 
84
  raise gr.Error("PDF kosong atau tidak dapat dibaca.")
85
  print("βœ… Teks berhasil diekstrak.")
86
 
87
+ print("2. Mengirim permintaan penilaian ke Gemini...")
88
+ prompt_penilaian = f"""
89
+ Anda adalah seorang career coach dan perekrut profesional. Tugas Anda adalah menilai kualitas sebuah CV berdasarkan kriteria standar industri.
90
+
91
+ Teks CV:
92
+ ---
93
+ {teks_cv}
94
+ ---
 
 
 
95
 
96
+ Lakukan penilaian berdasarkan kriteria berikut dan berikan output dalam format JSON yang ketat.
97
+
98
+ Kriteria Penilaian:
99
+ 1. Kelengkapan_Informasi (0-100): Apakah ada nama, kontak, ringkasan, pengalaman, pendidikan, dan skill?
100
+ 2. Keterbacaan_dan_Format (0-100): Apakah formatnya rapi, mudah dibaca, dan menggunakan bullet points dengan baik?
101
+ 3. Dampak_Pengalaman_Kerja (0-100): Apakah deskripsi pengalaman menggunakan kata kerja aktif dan hasil yang terukur (angka/metrik)? Semakin banyak metrik, semakin tinggi nilainya.
102
+
103
+ Instruksi Output JSON:
104
+ Berikan output dalam format JSON dengan struktur berikut:
105
+ - "skor_keseluruhan": Rata-rata dari ketiga skor kategori (angka 0-100).
106
+ - "penilaian_per_kategori": Objek berisi skor untuk "kelengkapan_informasi", "keterbacaan_dan_format", dan "dampak_pengalaman_kerja".
107
+ - "poin_kelebihan": Sebuah array (list) berisi 2-3 poin positif dari CV ini.
108
+ - "poin_perbaikan": Sebuah array (list) berisi 3-4 poin saran yang paling penting dan actionable untuk meningkatkan kualitas CV.
109
+
110
+ Pastikan output hanya berupa JSON saja tanpa teks tambahan.
111
+ """
112
+ response = model.generate_content(prompt_penilaian)
113
+ hasil_penilaian = clean_and_parse_json(response.text)
114
+ print("βœ… Penilaian berhasil diterima.")
115
+
116
+ print("3. Menyusun output akhir...")
117
+ final_output = format_output_markdown(hasil_penilaian)
118
  print("--- Proses Selesai ---")
119
  return final_output
120
 
121
  except Exception as e:
122
+ print(f"πŸ›‘ ERROR DALAM FUNGSI PENILAIAN: {e}")
123
  raise gr.Error(f"Terjadi kesalahan: {e}")
124
 
125
  # --- MEMBUAT INTERFACE GRADIO ---
126
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
127
+ gr.Markdown("# πŸ’‘ Aplikasi Penilai Kualitas CV (CV Scorer)")
128
+ gr.Markdown("Upload CV Anda dalam format PDF untuk mendapatkan skor dan saran perbaikan instan berdasarkan standar industri.")
129
 
130
  with gr.Row():
131
  with gr.Column(scale=1):
132
+ cv_pdf = gr.File(label="Upload CV Anda (PDF)", file_types=[".pdf"])
133
+ score_button = gr.Button("✨ Nilai CV Saya", variant="primary")
 
 
 
134
 
135
  with gr.Column(scale=2):
136
+ output_score = gr.Markdown(label="Hasil Penilaian")
137
+
138
+ score_button.click(
139
+ fn=score_cv,
140
+ inputs=[cv_pdf],
141
+ outputs=[output_score],
142
+ show_progress='full'
 
 
 
143
  )
144
 
145
  if __name__ == "__main__":