Spaces:
Sleeping
Sleeping
earlsab
commited on
Commit
·
b389c69
1
Parent(s):
72bb1f5
working wake
Browse files- app.py +187 -28
- input_outputs.md +13 -2
app.py
CHANGED
|
@@ -1,32 +1,155 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
|
| 3 |
-
from model_skill_extraction import SkillExtractionModel
|
| 4 |
-
from model_section_segmentation import SegmentationModelJobDescription, SegmentationModelResume
|
| 5 |
-
from model_skill_quality_extraction import SkillQualityExtractionModel
|
| 6 |
-
from model_date_extraction import DateExtractionModel
|
| 7 |
-
|
| 8 |
import json
|
| 9 |
-
|
| 10 |
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
def process_job_description(job_description: str) -> Dict:
|
| 20 |
-
"""Process job description and extract skills"""
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
return result
|
| 23 |
|
| 24 |
-
def
|
| 25 |
-
"""Process
|
| 26 |
-
|
| 27 |
-
result =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
return result
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
def create_html_output(job_result: Dict, resume_results: List[Dict]) -> str:
|
| 31 |
"""Create HTML output for the interface"""
|
| 32 |
html = "<div style='font-family: Arial, sans-serif;'>"
|
|
@@ -45,16 +168,47 @@ def create_html_output(job_result: Dict, resume_results: List[Dict]) -> str:
|
|
| 45 |
for i, resume_result in enumerate(resume_results, 1):
|
| 46 |
html += f"<div style='margin-bottom: 20px; padding: 10px; border: 1px solid #ddd; border-radius: 5px;'>"
|
| 47 |
html += f"<h3>Resume {i}</h3>"
|
| 48 |
-
|
| 49 |
-
#
|
| 50 |
-
html += "<p><strong>
|
| 51 |
html += "<div style='background-color: #f0f0f0; padding: 10px; border-radius: 5px;'>"
|
| 52 |
-
|
| 53 |
-
|
| 54 |
html += "</div>"
|
| 55 |
-
|
| 56 |
-
#
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
html += "</div>"
|
| 59 |
|
| 60 |
html += "</div>"
|
|
@@ -83,10 +237,15 @@ def process_inputs(job_description: str, input_type: str, resume_text: str, resu
|
|
| 83 |
return create_html_output(job_result, resume_results)
|
| 84 |
|
| 85 |
# Create Gradio interface
|
| 86 |
-
with gr.Blocks(title="Resume Analysis System") as demo:
|
| 87 |
gr.Markdown("# Beyond Keywords: Job Description and Resume Analyzer")
|
| 88 |
gr.Markdown("Upload a job description and resume(s) to analyze skill matches and quality.")
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
with gr.Row():
|
| 91 |
with gr.Column():
|
| 92 |
job_description = gr.Textbox(
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import requests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import json
|
| 4 |
+
import os
|
| 5 |
import time
|
| 6 |
+
from typing import List, Dict, Any
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
# Load environment variables
|
| 10 |
+
load_dotenv(".env.local")
|
| 11 |
+
|
| 12 |
+
# Load endpoints from JSON file
|
| 13 |
+
with open('endpoints.json', 'r') as f:
|
| 14 |
+
ENDPOINTS = json.load(f)
|
| 15 |
+
|
| 16 |
+
# Get HuggingFace API token from environment variable
|
| 17 |
+
HF_TOKEN = os.getenv('HUGGINGFACE_TOKEN')
|
| 18 |
+
if not HF_TOKEN:
|
| 19 |
+
print("Warning: HUGGINGFACE_TOKEN environment variable not set")
|
| 20 |
+
|
| 21 |
+
# API calling function with retry logic
|
| 22 |
+
def call_api(endpoint_url: str, payload: Dict[str, Any], max_retries: int = 5, retry_delay: int = 2) -> Dict:
|
| 23 |
+
"""Call API endpoint with retry logic"""
|
| 24 |
+
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
|
| 25 |
+
|
| 26 |
+
for attempt in range(max_retries):
|
| 27 |
+
try:
|
| 28 |
+
response = requests.post(
|
| 29 |
+
endpoint_url,
|
| 30 |
+
json=payload,
|
| 31 |
+
headers=headers,
|
| 32 |
+
timeout=30
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
if response.status_code == 200:
|
| 36 |
+
return response.json()
|
| 37 |
+
elif response.status_code == 503:
|
| 38 |
+
print(f"Service temporarily unavailable (503). Retrying... (Attempt {attempt + 1}/{max_retries})")
|
| 39 |
+
time.sleep(retry_delay * (attempt + 1)) # Exponential backoff
|
| 40 |
+
continue
|
| 41 |
+
else:
|
| 42 |
+
print(f"Error calling API: {response.status_code}")
|
| 43 |
+
print(f"Response: {response.text}")
|
| 44 |
+
return {}
|
| 45 |
+
|
| 46 |
+
except requests.exceptions.Timeout:
|
| 47 |
+
print(f"Request timed out. Attempt {attempt + 1}/{max_retries}")
|
| 48 |
+
if attempt < max_retries - 1:
|
| 49 |
+
time.sleep(retry_delay)
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"Exception while calling API: {str(e)}")
|
| 52 |
+
if attempt < max_retries - 1:
|
| 53 |
+
time.sleep(retry_delay)
|
| 54 |
+
|
| 55 |
+
return {}
|
| 56 |
|
| 57 |
+
def wake_servers(progress=gr.Progress()):
|
| 58 |
+
"""Send wake-up requests to all endpoints"""
|
| 59 |
+
results = {}
|
| 60 |
+
|
| 61 |
+
total_endpoints = len(ENDPOINTS)
|
| 62 |
+
for i, (name, url) in enumerate(ENDPOINTS.items()):
|
| 63 |
+
progress(i/total_endpoints, desc=f"Waking up {name} endpoint...")
|
| 64 |
+
print(f"Waking up {name} endpoint...")
|
| 65 |
+
try:
|
| 66 |
+
# Send a small payload just to wake up the server
|
| 67 |
+
minimal_payload = {"inputs": "Hello"}
|
| 68 |
+
response = requests.post(
|
| 69 |
+
url,
|
| 70 |
+
json=minimal_payload,
|
| 71 |
+
headers={"Authorization": f"Bearer {HF_TOKEN}"},
|
| 72 |
+
timeout=45
|
| 73 |
+
)
|
| 74 |
+
results[name] = f"Status: {response.status_code}"
|
| 75 |
+
except Exception as e:
|
| 76 |
+
results[name] = f"Error: {str(e)}"
|
| 77 |
+
|
| 78 |
+
progress(1.0, desc="Complete!")
|
| 79 |
+
status_html = "<h3>Server Wake-up Results:</h3><ul>"
|
| 80 |
+
for name, status in results.items():
|
| 81 |
+
status_color = "green" if "Status: 200" in status else "red"
|
| 82 |
+
status_html += f"<li><strong>{name}</strong>: <span style='color:{status_color}'>{status}</span></li>"
|
| 83 |
+
status_html += "</ul>"
|
| 84 |
+
|
| 85 |
+
return status_html
|
| 86 |
|
| 87 |
def process_job_description(job_description: str) -> Dict:
|
| 88 |
+
"""Process job description and extract skills using the job endpoint"""
|
| 89 |
+
payload = {"inputs": job_description}
|
| 90 |
+
result = call_api(ENDPOINTS["job"], payload)
|
| 91 |
+
|
| 92 |
+
if not result:
|
| 93 |
+
# Return a fallback structure if API call fails
|
| 94 |
+
return {"skills": [], "total_skills": 0}
|
| 95 |
+
|
| 96 |
+
# Format the result to match expected structure
|
| 97 |
+
if "skills" in result:
|
| 98 |
+
# Add a "text" field to each skill for compatibility
|
| 99 |
+
for skill in result["skills"]:
|
| 100 |
+
skill["text"] = skill.get("name", "Unknown Skill")
|
| 101 |
+
|
| 102 |
+
result["total_skills"] = len(result["skills"])
|
| 103 |
+
else:
|
| 104 |
+
result = {"skills": [], "total_skills": 0}
|
| 105 |
+
|
| 106 |
return result
|
| 107 |
|
| 108 |
+
def process_skill_quality(text: str) -> Dict:
|
| 109 |
+
"""Process a sentence through the skill quality endpoint"""
|
| 110 |
+
payload = {"inputs": text}
|
| 111 |
+
result = call_api(ENDPOINTS["skill_quality"], payload)
|
| 112 |
+
|
| 113 |
+
if not result:
|
| 114 |
+
return {"leadership": 0, "leadership_token": "No", "collaboration": 0, "collaboration_token": "No"}
|
| 115 |
+
|
| 116 |
return result
|
| 117 |
|
| 118 |
+
def process_resume(resume_text: str, job_skills: List[str]) -> Dict:
|
| 119 |
+
"""Process resume using the resume endpoint"""
|
| 120 |
+
payload = {"inputs": resume_text}
|
| 121 |
+
result = call_api(ENDPOINTS["resume"], payload)
|
| 122 |
+
|
| 123 |
+
if not result:
|
| 124 |
+
return {"skills": [], "total_skills": 0}
|
| 125 |
+
|
| 126 |
+
# Extract all skills from all job experiences
|
| 127 |
+
all_skills = []
|
| 128 |
+
# Process skill quality for each role description
|
| 129 |
+
for job in result:
|
| 130 |
+
if "skills" in job:
|
| 131 |
+
for skill in job["skills"]:
|
| 132 |
+
# Add a "text" field for compatibility
|
| 133 |
+
skill["text"] = skill.get("name", "Unknown Skill")
|
| 134 |
+
all_skills.append(skill)
|
| 135 |
+
|
| 136 |
+
# Process skill quality for each bullet point in the job description
|
| 137 |
+
if "description" in job:
|
| 138 |
+
quality_scores = []
|
| 139 |
+
for sentence in job.get("description", []):
|
| 140 |
+
quality_score = process_skill_quality(sentence)
|
| 141 |
+
quality_scores.append({"sentence": sentence, "quality": quality_score})
|
| 142 |
+
job["quality_scores"] = quality_scores
|
| 143 |
+
|
| 144 |
+
# Add fields to match expected structure
|
| 145 |
+
formatted_result = {
|
| 146 |
+
"skills": all_skills,
|
| 147 |
+
"total_skills": len(all_skills),
|
| 148 |
+
"roles": result # Keep the original roles data
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
return formatted_result
|
| 152 |
+
|
| 153 |
def create_html_output(job_result: Dict, resume_results: List[Dict]) -> str:
|
| 154 |
"""Create HTML output for the interface"""
|
| 155 |
html = "<div style='font-family: Arial, sans-serif;'>"
|
|
|
|
| 168 |
for i, resume_result in enumerate(resume_results, 1):
|
| 169 |
html += f"<div style='margin-bottom: 20px; padding: 10px; border: 1px solid #ddd; border-radius: 5px;'>"
|
| 170 |
html += f"<h3>Resume {i}</h3>"
|
| 171 |
+
|
| 172 |
+
# Display all skills found in the resume
|
| 173 |
+
html += "<p><strong>Skills Found:</strong></p>"
|
| 174 |
html += "<div style='background-color: #f0f0f0; padding: 10px; border-radius: 5px;'>"
|
| 175 |
+
for skill in resume_result['skills']:
|
| 176 |
+
html += f"<span style='background-color: #e0e0e0; padding: 2px 5px; margin: 2px; border-radius: 3px; display: inline-block;'>{skill['text']}</span>"
|
| 177 |
html += "</div>"
|
| 178 |
+
|
| 179 |
+
# Job roles section
|
| 180 |
+
if 'roles' in resume_result and resume_result['roles']:
|
| 181 |
+
html += "<p><strong>Job Experience:</strong></p>"
|
| 182 |
+
for role in resume_result['roles']:
|
| 183 |
+
html += f"<div style='margin: 10px 0; padding: 10px; background-color: #f9f9f9; border-radius: 5px;'>"
|
| 184 |
+
html += f"<p><strong>Title:</strong> {' '.join(role.get('title', ['Unknown']))}</p>"
|
| 185 |
+
if 'dates' in role and role['dates']:
|
| 186 |
+
html += f"<p><strong>Period:</strong> {role['dates'].get('date_started', 'Unknown')} to {role['dates'].get('date_ended', 'Unknown')}</p>"
|
| 187 |
+
html += f"<p><strong>Role Skills:</strong></p>"
|
| 188 |
+
html += "<div style='margin-left: 20px;'>"
|
| 189 |
+
for skill in role.get('skills', []):
|
| 190 |
+
html += f"<span style='background-color: #e0e0e0; padding: 2px 5px; margin: 2px; border-radius: 3px; display: inline-block;'>{skill.get('name', 'Unknown')}</span>"
|
| 191 |
+
html += "</div>"
|
| 192 |
+
|
| 193 |
+
# Display skill quality analysis
|
| 194 |
+
if 'quality_scores' in role and role['quality_scores']:
|
| 195 |
+
html += "<p><strong>Skill Quality Analysis:</strong></p>"
|
| 196 |
+
html += "<table style='width: 100%; border-collapse: collapse; margin-top: 10px;'>"
|
| 197 |
+
html += "<tr style='background-color: #eee;'><th style='padding: 8px; text-align: left; border: 1px solid #ddd;'>Statement</th><th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Leadership</th><th style='padding: 8px; text-align: center; border: 1px solid #ddd;'>Collaboration</th></tr>"
|
| 198 |
+
|
| 199 |
+
for score in role['quality_scores']:
|
| 200 |
+
quality = score['quality']
|
| 201 |
+
leadership_class = "green-text" if quality.get('leadership', 0) == 1 else "red-text"
|
| 202 |
+
collab_class = "green-text" if quality.get('collaboration', 0) == 1 else "red-text"
|
| 203 |
+
|
| 204 |
+
html += f"<tr><td style='padding: 8px; border: 1px solid #ddd;'>{score['sentence']}</td>"
|
| 205 |
+
html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd; color: {'green' if quality.get('leadership', 0) == 1 else 'red'};'>{quality.get('leadership_token', 'No')}</td>"
|
| 206 |
+
html += f"<td style='padding: 8px; text-align: center; border: 1px solid #ddd; color: {'green' if quality.get('collaboration', 0) == 1 else 'red'};'>{quality.get('collaboration_token', 'No')}</td></tr>"
|
| 207 |
+
|
| 208 |
+
html += "</table>"
|
| 209 |
+
|
| 210 |
+
html += "</div>"
|
| 211 |
+
|
| 212 |
html += "</div>"
|
| 213 |
|
| 214 |
html += "</div>"
|
|
|
|
| 237 |
return create_html_output(job_result, resume_results)
|
| 238 |
|
| 239 |
# Create Gradio interface
|
| 240 |
+
with gr.Blocks(title="Beyond Keywords: Resume Analysis System") as demo:
|
| 241 |
gr.Markdown("# Beyond Keywords: Job Description and Resume Analyzer")
|
| 242 |
gr.Markdown("Upload a job description and resume(s) to analyze skill matches and quality.")
|
| 243 |
|
| 244 |
+
# Wake servers button
|
| 245 |
+
wake_btn = gr.Button("Wake Servers (Do this first!)")
|
| 246 |
+
wake_status = gr.HTML(label="Server Status")
|
| 247 |
+
wake_btn.click(fn=wake_servers, inputs=None, outputs=wake_status)
|
| 248 |
+
|
| 249 |
with gr.Row():
|
| 250 |
with gr.Column():
|
| 251 |
job_description = gr.Textbox(
|
input_outputs.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
|
| 2 |
# Job Description
|
| 3 |
-
|
| 4 |
### Expected INPUT
|
| 5 |
```{
|
| 6 |
"inputs": "About the job\nJob Title: Frontend Developer\nJob Type: Full-time or Part-Time\nLocation: Remote\n\nAbout Us:\nOur mission at micro1 is to match the most talented people in the world with their dream jobs. If you are looking to be at the forefront of AI innovation and work with some of the fastest growing companies in Silicon Valley, we invite you to apply for a role. By joining the micro1 community, your resume will become visible to top industry leaders, unlocking access to the best career opportunities on the market.\n\nJob Summary:\nJoin our dynamic team at micro1 as a Frontend Developer where you will be instrumental in creating engaging and dynamic user experiences for web applications. At micro1, we provide opportunities to work with leading companies in Silicon Valley, ensuring a stable and competitive income in a flexible remote setting. Embrace the opportunity to grow your career, access top industry opportunities, and enjoy a range of great benefits.\n\nKey Responsibilities:\nDevelop high-quality frontend components for web applications.\nOptimize user interfaces to enhance user experiences.\nCollaborate with cross-functional teams to define and design new features.\nEnsure cross-browser compatibility and implement responsive designs.\nMaintain code quality and ensure responsiveness of applications.\nUtilize industry best practices and design patterns.\nParticipate in code reviews to maintain high code quality standards.\n\nRequired Skills and Qualifications:\nProficiency in HTML, CSS, and JavaScript.\nStrong experience with React and Angular frameworks.\nExcellent written and verbal communication skills.\nAbility to work effectively in a remote setting.\nDemonstrated ability to develop and optimize user interfaces.\nExperience with ensuring cross-browser compatibility of applications.\nStrong problem-solving skills and attention to detail.\n\nPreferred Qualifications:\nExperience with responsive design and mobile-first development.\nFamiliarity with version control systems, such as Git.\nUnderstanding of Agile methodologies.\nIt's okay if you don't. Having a Native to C2/C1 level in another language such as German, French, or Spanish is nice to have."
|
|
@@ -158,4 +157,16 @@
|
|
| 158 |
]
|
| 159 |
}
|
| 160 |
]
|
| 161 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
|
| 2 |
# Job Description
|
|
|
|
| 3 |
### Expected INPUT
|
| 4 |
```{
|
| 5 |
"inputs": "About the job\nJob Title: Frontend Developer\nJob Type: Full-time or Part-Time\nLocation: Remote\n\nAbout Us:\nOur mission at micro1 is to match the most talented people in the world with their dream jobs. If you are looking to be at the forefront of AI innovation and work with some of the fastest growing companies in Silicon Valley, we invite you to apply for a role. By joining the micro1 community, your resume will become visible to top industry leaders, unlocking access to the best career opportunities on the market.\n\nJob Summary:\nJoin our dynamic team at micro1 as a Frontend Developer where you will be instrumental in creating engaging and dynamic user experiences for web applications. At micro1, we provide opportunities to work with leading companies in Silicon Valley, ensuring a stable and competitive income in a flexible remote setting. Embrace the opportunity to grow your career, access top industry opportunities, and enjoy a range of great benefits.\n\nKey Responsibilities:\nDevelop high-quality frontend components for web applications.\nOptimize user interfaces to enhance user experiences.\nCollaborate with cross-functional teams to define and design new features.\nEnsure cross-browser compatibility and implement responsive designs.\nMaintain code quality and ensure responsiveness of applications.\nUtilize industry best practices and design patterns.\nParticipate in code reviews to maintain high code quality standards.\n\nRequired Skills and Qualifications:\nProficiency in HTML, CSS, and JavaScript.\nStrong experience with React and Angular frameworks.\nExcellent written and verbal communication skills.\nAbility to work effectively in a remote setting.\nDemonstrated ability to develop and optimize user interfaces.\nExperience with ensuring cross-browser compatibility of applications.\nStrong problem-solving skills and attention to detail.\n\nPreferred Qualifications:\nExperience with responsive design and mobile-first development.\nFamiliarity with version control systems, such as Git.\nUnderstanding of Agile methodologies.\nIt's okay if you don't. Having a Native to C2/C1 level in another language such as German, French, or Spanish is nice to have."
|
|
|
|
| 157 |
]
|
| 158 |
}
|
| 159 |
]
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
# Skill Quality
|
| 163 |
+
### Expected Input
|
| 164 |
+
{"inputs": "I am a leader."}
|
| 165 |
+
|
| 166 |
+
### Expected Output
|
| 167 |
+
{
|
| 168 |
+
"leadership": 1,
|
| 169 |
+
"leadership_token": "Yes",
|
| 170 |
+
"collaboration": 0,
|
| 171 |
+
"collaboration_token": "No"
|
| 172 |
+
}
|