File size: 5,964 Bytes
d6edb46
9b39287
d6edb46
 
 
4f4d6f1
 
 
 
 
 
 
d6edb46
 
 
446c80c
4f4d6f1
d6edb46
446c80c
 
d6edb46
446c80c
 
 
 
4f4d6f1
446c80c
4f4d6f1
d6edb46
 
 
 
4f4d6f1
 
d6edb46
4f4d6f1
446c80c
 
4f4d6f1
 
 
 
d6edb46
446c80c
d6edb46
 
 
446c80c
 
d6edb46
 
446c80c
 
d6edb46
 
9b39287
d6edb46
 
 
9b39287
4f4d6f1
446c80c
720a543
 
4f4d6f1
 
d6edb46
 
 
446c80c
 
 
d6edb46
446c80c
d6edb46
720a543
d6edb46
 
 
 
720a543
 
 
446c80c
d6edb46
9b39287
 
 
 
 
 
 
 
 
 
 
d6edb46
9b39287
 
 
 
 
 
d6edb46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b39287
720a543
d6edb46
 
720a543
 
d6edb46
720a543
d6edb46
 
446c80c
 
d6edb46
446c80c
4f4d6f1
d6edb46
 
 
 
 
4f4d6f1
9b39287
d6edb46
 
9b39287
 
 
d6edb46
 
 
 
 
9b39287
d6edb46
720a543
d6edb46
720a543
d6edb46
 
4f4d6f1
 
 
d6edb46
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# ============================================================
# πŸ’Ό CETHA - AI CV REVIEW (FINAL JSON VERSION with SCORE)
# ============================================================

# !pip install -q gradio google-generativeai pymupdf

import gradio as gr
import google.generativeai as genai
import fitz  # PyMuPDF
import json
import os

# ============================================================
# πŸ”‘ KONFIGURASI API KEY
# ============================================================
API_CONFIGURED = False
try:
    api_key = os.environ.get("GEMINI_API_KEY")
    if api_key:
        genai.configure(api_key=api_key)
        model = genai.GenerativeModel("gemini-flash-latest")
        API_CONFIGURED = True
        print("βœ… Konfigurasi API dan model berhasil.")
    else:
        print("πŸ›‘ Secret 'GEMINI_API_KEY' tidak ditemukan.")
except Exception as e:
    print(f"πŸ›‘ Terjadi error saat inisialisasi: {e}")


# ============================================================
# 🧠 FUNGSI PENDUKUNG
# ============================================================

def ekstrak_teks_dari_pdf(path_file_pdf):
    """Membaca seluruh teks dari file PDF."""
    try:
        with fitz.open(path_file_pdf) as dokumen:
            teks_lengkap = "".join(halaman.get_text() for halaman in dokumen)
        return teks_lengkap
    except Exception as e:
        raise gr.Error(f"Gagal membaca file PDF: {e}")


def clean_and_parse_json(raw_text):
    """Membersihkan teks dari output model dan mem-parsing JSON valid."""
    start_index = raw_text.find("{")
    end_index = raw_text.rfind("}")
    if start_index == -1 or end_index == -1:
        raise json.JSONDecodeError("Tidak ditemukan blok JSON valid dalam respons.", raw_text, 0)

    json_str = raw_text[start_index : end_index + 1]
    return json.loads(json_str)


# ============================================================
# 🎯 FUNGSI UTAMA: PENILAIAN CV DENGAN SKOR DAN DETAIL
# ============================================================

def score_cv_json(cv_file):
    """Menilai CV dan mengembalikan hasil dalam format JSON dengan skor dan penjelasan detail."""
    if not API_CONFIGURED:
        raise gr.Error("API Key Gemini belum terkonfigurasi. Periksa Logs aplikasi di Hugging Face.")
    if cv_file is None:
        raise gr.Error("Mohon upload file CV (PDF) Anda.")

    try:
        print("--- πŸš€ Memulai Proses Penilaian CV ---")

        # 1️⃣ Ekstraksi teks dari PDF
        teks_cv = ekstrak_teks_dari_pdf(cv_file.name)
        if not teks_cv:
            raise gr.Error("PDF kosong atau tidak dapat dibaca.")
        print("βœ… Teks berhasil diekstrak dari PDF.")

        # 2️⃣ Prompt ke Gemini untuk analisis
        prompt_penilaian = f"""
        Anda adalah seorang career coach dan perekrut profesional.
        Analisislah CV berikut dan berikan hasil dalam format JSON **valid**.

        CV Kandidat:
        ---
        {teks_cv}
        ---

        Tugas Anda:
        1. Nilai CV berdasarkan 3 kategori (0–100):
           - "kelengkapan_informasi": Apakah CV mencakup nama, kontak, pengalaman, pendidikan, dan keterampilan?
           - "keterbacaan_dan_format": Apakah struktur, urutan, dan tampilan CV rapi dan mudah dibaca?
           - "dampak_pengalaman_kerja": Apakah deskripsi pengalaman menonjolkan hasil terukur (angka, pencapaian)?
        2. Hitung "skor_keseluruhan" sebagai rata-rata dari ketiga kategori.
        3. Temukan 3 KELEBIHAN utama dan 3 HAL YANG DAPAT DITINGKATKAN.
           Setiap poin memiliki:
           - "point": kalimat ringkas yang mewakili kelebihan/perbaikan.
           - "explanation": penjelasan singkat (1–3 kalimat) tentang konteks atau alasannya.

        Format output JSON yang harus dikembalikan:
        {{
          "skor_keseluruhan": <angka>,
          "penilaian_per_kategori": {{
              "kelengkapan_informasi": <angka>,
              "keterbacaan_dan_format": <angka>,
              "dampak_pengalaman_kerja": <angka>
          }},
          "highlights": [
            {{
              "point": "judul kelebihan singkat",
              "explanation": "penjelasan detail"
            }},
            ...
          ],
          "improvements": [
            {{
              "point": "judul perbaikan singkat",
              "explanation": "penjelasan detail"
            }},
            ...
          ]
        }}

        Pastikan output hanya berupa JSON valid tanpa tambahan teks lain.
        """

        print("πŸ“€ Mengirim prompt ke Gemini...")
        response = model.generate_content(prompt_penilaian)
        hasil_penilaian = clean_and_parse_json(response.text)
        print("βœ… Analisis berhasil diterima dan dikonversi ke JSON.")

        # 3️⃣ Mengembalikan hasil JSON rapi
        return json.dumps(hasil_penilaian, indent=2, ensure_ascii=False)

    except Exception as e:
        print(f"πŸ›‘ ERROR: {e}")
        raise gr.Error(f"Terjadi kesalahan: {e}")


# ============================================================
# πŸ’» GRADIO INTERFACE
# ============================================================

with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.Markdown("# πŸ€– CETHA - AI CV Review (JSON Output with Score)")
    gr.Markdown(
        """
        Upload CV Anda dalam format **PDF** untuk mendapatkan hasil analisis otomatis berbasis AI.  
        Sistem akan memberikan **skor keseluruhan**, **penilaian per kategori**,  
        serta **3 poin kelebihan dan 3 poin perbaikan lengkap dengan penjelasan.**
        """
    )

    cv_pdf = gr.File(label="πŸ“„ Upload CV Anda (PDF)", file_types=[".pdf"])
    score_button = gr.Button("πŸš€ Analisis CV Sekarang", variant="primary")
    output_json = gr.JSON(label="πŸ“Š Hasil Analisis (JSON Lengkap)")

    score_button.click(
        fn=score_cv_json,
        inputs=[cv_pdf],
        outputs=[output_json],
        show_progress="full",
    )

if __name__ == "__main__":
    demo.launch()