PraneshJs commited on
Commit
d01d66d
·
verified ·
1 Parent(s): 666c166

adding files to hf space

Browse files
Files changed (3) hide show
  1. app.py +136 -0
  2. requirements.txt +6 -0
  3. resume_ai.py +37 -0
app.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import json
3
+ from typing import Optional
4
+
5
+ import gradio as gr
6
+ import PyPDF2
7
+
8
+ from resume_ai import score, improve
9
+
10
+ def extract_text_from_pdf(file_obj: io.IOBase) -> str:
11
+ """Extract text from a PDF file-like object."""
12
+ try:
13
+ reader = PyPDF2.PdfReader(file_obj)
14
+ text_chunks = []
15
+ for page in reader.pages:
16
+ page_text = page.extract_text() or ""
17
+ text_chunks.append(page_text)
18
+ text = "\n".join(text_chunks).strip()
19
+ if not text:
20
+ raise ValueError("No extractable text found in PDF.")
21
+ return text
22
+ except Exception as e:
23
+ raise ValueError(f"Error reading PDF: {e}")
24
+
25
+ def read_resume_to_text(resume_file_path) -> str:
26
+ """
27
+ Accepts a file path and returns text content.
28
+ Supports PDF and plain text files.
29
+ """
30
+ if resume_file_path is None:
31
+ raise ValueError("Please upload a resume file.")
32
+
33
+ filename = str(resume_file_path).lower()
34
+ if filename.endswith(".pdf"):
35
+ with open(resume_file_path, "rb") as f:
36
+ return extract_text_from_pdf(f)
37
+ else:
38
+ with open(resume_file_path, "rb") as f:
39
+ data = f.read()
40
+ if not data:
41
+ raise ValueError("Uploaded file is empty.")
42
+ try:
43
+ return data.decode("utf-8").strip()
44
+ except UnicodeDecodeError:
45
+ return data.decode("latin-1").strip()
46
+
47
+ def score_fn(resume_file_path, job_desc: str) -> str:
48
+ try:
49
+ if not job_desc or not job_desc.strip():
50
+ raise ValueError("Please paste a job description.")
51
+ resume_text = read_resume_to_text(resume_file_path)
52
+ result = score(resume_text, job_desc)
53
+ return json.dumps(result, indent=2, ensure_ascii=False)
54
+ except Exception as e:
55
+ return f"Error: {e}"
56
+
57
+ def improve_fn(resume_file_path, job_desc: Optional[str]) -> str:
58
+ try:
59
+ resume_text = read_resume_to_text(resume_file_path)
60
+ jd_text = job_desc if job_desc and job_desc.strip() else None
61
+ suggestions = improve(resume_text, jd_text)
62
+ if isinstance(suggestions, (list, tuple)):
63
+ bullets = "\n".join(f"- {s}" for s in suggestions)
64
+ return f"### Suggestions\n{bullets}"
65
+ elif isinstance(suggestions, dict):
66
+ return "```json\n" + json.dumps(suggestions, indent=2, ensure_ascii=False) + "\n```"
67
+ else:
68
+ return str(suggestions)
69
+ except Exception as e:
70
+ return f"Error: {e}"
71
+
72
+ def format_score_display(result_json) -> str:
73
+ """
74
+ Takes the result JSON (as dict or str), parses it, and returns a Markdown string for display.
75
+ """
76
+ if isinstance(result_json, str):
77
+ try:
78
+ result = json.loads(result_json)
79
+ except Exception:
80
+ return f"```\n{result_json}\n```"
81
+ else:
82
+ result = result_json
83
+
84
+ md = f"## 🏆 ATS Compatibility Score: **{result.get('overall_score', 0)}%**\n\n"
85
+ md += "### Category Scores\n"
86
+ md += "| Skills | Experience | Education |\n"
87
+ md += "|--------|------------|-----------|\n"
88
+ cs = result.get("category_scores", {})
89
+ md += f"| {cs.get('skills',0)}% | {cs.get('experience',0)}% | {cs.get('education',0)}% |\n\n"
90
+
91
+ gaps = result.get("top_skill_gaps", [])
92
+ if gaps:
93
+ md += "### 🚩 Top Skill Gaps\n"
94
+ for gap in gaps:
95
+ md += f"- {gap}\n"
96
+ return md
97
+
98
+ # ...existing code...
99
+
100
+ with gr.Blocks(title="Resume AI (Score & Improve)") as demo:
101
+ gr.Markdown(
102
+ """
103
+ # 📄 Resume AI — Score & Improve
104
+ Upload your resume (PDF or TXT), paste a Job Description, and get:
105
+ - **Score**: A formatted breakdown of your resume's ATS compatibility
106
+ - **Improve**: A healthy set of suggestions to enhance your resume
107
+ """
108
+ )
109
+
110
+ with gr.Row():
111
+ resume = gr.File(label="Upload Resume (PDF or TXT)", file_types=[".pdf", ".txt"], type="filepath")
112
+ jd = gr.Textbox(label="Job Description (paste here)", lines=10, placeholder="Paste JD text...")
113
+
114
+ with gr.Row():
115
+ score_btn = gr.Button("⚖️ Score Resume", variant="primary")
116
+ improve_btn = gr.Button("✨ Improve Resume")
117
+
118
+ score_out = gr.Markdown(label="Score (Formatted)")
119
+ improve_out = gr.Markdown(label="Improvement Suggestions")
120
+
121
+ def score_fn_display(resume_file_path, job_desc: str) -> str:
122
+ try:
123
+ if not job_desc or not job_desc.strip():
124
+ raise ValueError("Please paste a job description.")
125
+ resume_text = read_resume_to_text(resume_file_path)
126
+ result = score(resume_text, job_desc)
127
+ return format_score_display(result)
128
+ except Exception as e:
129
+ return f"Error: {e}"
130
+
131
+ score_btn.click(fn=score_fn_display, inputs=[resume, jd], outputs=score_out)
132
+ improve_btn.click(fn=improve_fn, inputs=[resume, jd], outputs=improve_out)
133
+
134
+
135
+ if __name__ == "__main__":
136
+ demo.queue().launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ flask
2
+ flask-cors
3
+ python-dotenv
4
+ openai
5
+ PyPDF2
6
+ gradio
resume_ai.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, json
2
+ from openai import AzureOpenAI
3
+ from dotenv import load_dotenv
4
+
5
+
6
+ load_dotenv()
7
+ client = AzureOpenAI(
8
+ api_key=os.getenv("AZURE_OPENAI_KEY"),
9
+ azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
10
+ api_version=os.getenv("AZURE_OPENAI_VERSION")
11
+ )
12
+ DEPLOY = os.getenv("AZURE_OPENAI_DEPLOYMENT")
13
+
14
+ def score(resume, jd):
15
+ r = client.chat.completions.create(
16
+ model=DEPLOY,
17
+ max_completion_tokens=800,
18
+ temperature=0.8,
19
+ messages=[
20
+ {"role":"system","content":"Act as an ATS. Compare résumé with JD. Return JSON: overall_score(0-100) and category_scores{skills,experience,education}, plus top_skill_gaps list."},
21
+ {"role":"user","content":f"JOB_DESCRIPTION:\n{jd}\n\nRESUME:\n{resume}"}
22
+ ]
23
+ )
24
+ return json.loads(r.choices[0].message.content.strip())
25
+
26
+ def improve(resume, jd=None):
27
+ p = ("Provide bullet suggestions to raise the score. " + ("Tailor to this JD:\n"+jd+"\n" if jd else "") + "Here is the résumé:\n"+resume)
28
+ r = client.chat.completions.create(
29
+ model=DEPLOY,
30
+ max_completion_tokens=800,
31
+ temperature=0.7,
32
+ messages=[
33
+ {"role":"system","content":"You are a résumé coach."},
34
+ {"role":"user","content":p}
35
+ ]
36
+ )
37
+ return r.choices[0].message.content.strip()