Cristian Martinez commited on
Commit
fcd80f3
Β·
0 Parent(s):

Initial hackathon demo

Browse files
Files changed (6) hide show
  1. .gitignore +12 -0
  2. .python-version +1 -0
  3. README.md +0 -0
  4. app.py +125 -0
  5. pyproject.toml +12 -0
  6. requirements.txt +5 -0
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+ uv.lock
12
+ .env
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
README.md ADDED
File without changes
app.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ import os, json
4
+ from dotenv import load_dotenv
5
+ from openai import OpenAI
6
+ from pypdf import PdfReader
7
+ import gradio as gr
8
+ import re, json
9
+
10
+ def extract_json(text: str) -> str:
11
+ """(You already have this) Strip ``` fences so we can parse JSON."""
12
+ text = re.sub(r"```(?:json)?\s*", "", text)
13
+ text = re.sub(r"\s*```", "", text)
14
+ start = text.find("{"); end = text.rfind("}")
15
+ return text[start:end+1] if start!=-1 and end!=-1 else text
16
+
17
+ def format_results_as_md(res: dict) -> str:
18
+ """Turn the parsed dict into a nice Markdown string."""
19
+ if "error" in res:
20
+ return f"**Error:** {res['error']}\n\n```\n{res.get('raw','')}\n```"
21
+
22
+ md = ["## 🎯 Top Skill Gaps"]
23
+ for gap in res.get("skill_gaps", []):
24
+ md.append(f"- {gap}")
25
+
26
+ md.append("\n## πŸ“š Course Recommendations")
27
+ for course in res.get("course_recommendations", []):
28
+ name = course.get("course_name")
29
+ provider = course.get("provider")
30
+ link = course.get("link")
31
+ md.append(f"- [{name}]({link}) _(Provider: {provider})_")
32
+
33
+ md.append("\n## πŸ… Certification Recommendations")
34
+ for cert in res.get("certification_recommendations", []):
35
+ md.append(f"- {cert}")
36
+
37
+ return "\n".join(md)
38
+
39
+ # ─── 1) Load keys ───────────────────────────────────────────────────────────────
40
+
41
+ load_dotenv() # expects OPENAI_API_KEY in your .env
42
+ openai = OpenAI()
43
+
44
+ # ─── 2) Helpers ─────────────────────────────────────────────────────────────────
45
+
46
+ def read_resume(file_obj):
47
+ """Extract text from a PDF or return raw text."""
48
+ name = file_obj.name.lower()
49
+ if name.endswith(".pdf"):
50
+ reader = PdfReader(file_obj.name)
51
+ return "\n".join(page.extract_text() or "" for page in reader.pages)
52
+ else:
53
+ return file_obj.read().decode("utf-8")
54
+
55
+ def analyze_skills_and_recs(resume_text, titles_list):
56
+ prompt = {
57
+ "role":"user",
58
+ "content":(
59
+ "You are an expert career coach.\n\n"
60
+ "Here is a candidate's resume (first 2k chars):\n```\n"
61
+ + resume_text[:2000] + "\n```\n\n"
62
+ "They want to apply for these roles:\n β€’ "
63
+ + "\n β€’ ".join(titles_list) + "\n\n"
64
+ "Please reply _only_ in JSON with these fields:\n"
65
+ ' "skill_gaps": [ "…" ],\n'
66
+ ' "course_recommendations": [\n'
67
+ ' { "course_name": "…", "provider": "…", "link": "…" }\n'
68
+ ' ],\n'
69
+ ' "certification_recommendations": [ "…" ]\n'
70
+ )
71
+ }
72
+ resp = openai.chat.completions.create(
73
+ model="gpt-4o-mini",
74
+ messages=[prompt]
75
+ )
76
+ raw = resp.choices[0].message.content
77
+ cleaned = extract_json(raw)
78
+ try:
79
+ data = json.loads(cleaned)
80
+ except json.JSONDecodeError:
81
+ return {
82
+ "error": "Still could not parse after stripping fences",
83
+ "raw": raw
84
+ }
85
+ return format_results_as_md(data)
86
+
87
+ # ─── 3) Pipeline ────────────────────────────────────────────────────────────────
88
+
89
+ def run_agent(resume_file, titles_str):
90
+ resume = read_resume(resume_file)
91
+ titles = [t.strip() for t in titles_str.split(",") if t.strip()]
92
+ if not titles:
93
+ return {"error": "Please enter at least one job title."}
94
+ return analyze_skills_and_recs(resume, titles)
95
+
96
+ # ─── 4) Gradio UI ───────────────────────────────────────────────────────────────
97
+
98
+ app = gr.Blocks()
99
+
100
+ with app:
101
+ gr.Markdown("## πŸŽ“ Skill Gap & Course Advisor")
102
+ gr.Markdown(
103
+ "Upload your resume, enter the roles you want (comma-separated), "
104
+ "and get back:\n"
105
+ "1. Your top skill gaps\n"
106
+ "2. 5 course recommendations (with names, providers, links)\n"
107
+ "3. 3 certification recommendations"
108
+ )
109
+
110
+ with gr.Row():
111
+ resume_input = gr.File(label="πŸ“„ Resume (PDF or TXT)")
112
+ titles_input = gr.Textbox(
113
+ label="🎯 Target Job Titles",
114
+ placeholder="e.g. Data Scientist, ML Engineer"
115
+ )
116
+
117
+ run_btn = gr.Button("πŸš€ Analyze & Recommend")
118
+ output = gr.Markdown(label="πŸ“‹ Recommendations")
119
+ run_btn.click(run_agent, [resume_input, titles_input], output)
120
+
121
+ if __name__ == "__main__":
122
+ # # Local:
123
+ # app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
124
+ # For hackathon submission (MCP mode):
125
+ app.launch(mcp_server=True, server_name="0.0.0.0", server_port=7860)
pyproject.toml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "hackathon-job-agent"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "gradio>=5.33.0",
9
+ "openai>=1.84.0",
10
+ "pypdf>=5.6.0",
11
+ "python-dotenv>=1.1.0",
12
+ ]
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ openai
3
+ pypdf
4
+ python-dotenv
5
+ requests