appliedai / app.py
curiouscurrent's picture
Update app.py
db61f50 verified
Raw
History Blame Contribute Delete
9.25 kB
import gradio as gr
import pandas as pd
import json
import os
import requests
import re
from functools import lru_cache
# ----------------------------
# CONFIG
# ----------------------------
JSON_FILE = "form-submissions-1.json"
MODEL_ID = "google/flan-t5-small"
# NOTE: HF_API_TOKEN MUST be set in your environment variables/Space secrets.
HF_API_TOKEN = os.environ.get("HF_API_TOKEN")
FILTERED_CSV = "/tmp/filtered_candidates.csv"
OUTPUT_FILE = "/tmp/outputs.csv"
BATCH_SIZE = 50
if not HF_API_TOKEN:
pass
CATEGORIES = {
"AI": [
"AI/ML Ops Engineer","Senior Machine Learning Engineer","Principal Data Scientist",
"Senior Data Scientist","Machine Learning Research Scientist","Senior AI/ML Engineer",
"AI/ML Engineer","Big Data Engineer","AI Research Scientist","AI Research Analyst Consultant",
"AI Analyst","Senior Data Analyst","Automation Engineer","Senior Data Engineer",
"Machine Learning Engineer","Data Engineer","Data Scientist","Data Analyst"
],
"Marketing": ["Marketing Specialist","Sales Agent","Salesman","Sales Associate"],
"CTO": ["Chief Technology Officer","CTO"],
"Legal": ["Legal Specialist","Attorney","Legal Intern","Lawyer"],
"Finance": ["Financial Analyst","Financial Advisor"]
}
# ----------------------------
# LLM Call for Scoring (Focus: Role Experience ONLY)
# ----------------------------
@lru_cache(maxsize=512)
def score_candidate(candidate_str, category_name, job_titles_tuple):
if not HF_API_TOKEN:
print("API Token is missing. Returning score 0.")
return 0
prompt = f"""
You are an HR assistant. Your task is to rate a candidate's suitability based ONLY on their previous job roles.
Rate the suitability of the following candidate on a scale of 1 (Lowest) to 10 (Highest).
The score must reflect how closely the candidate's 'Roles' align with the target job titles.
The target roles for the '{category_name}' category are: {list(job_titles_tuple)}
Candidate JSON: {candidate_str}
**Task**: Respond ONLY with the rating number (an integer from 1 to 10).
"""
headers = {"Authorization": f"Bearer {HF_API_TOKEN}", "Content-Type": "application/json"}
payload = {
"inputs": prompt,
"parameters": {
"max_new_tokens": 5,
"return_full_text": False,
"temperature": 0.1
}
}
try:
response = requests.post(
f"https://api-inference.huggingface.co/models/{MODEL_ID}",
headers=headers,
data=json.dumps(payload),
timeout=60
)
response.raise_for_status()
result = response.json()
generated_text = result[0].get("generated_text", "0").strip()
match = re.search(r'\d+', generated_text)
if match:
score = int(match.group(0))
return max(1, min(10, score))
return 0
except Exception as e:
print(f"LLM scoring call failed for candidate (API/Network Error): {e}")
return 0
# ----------------------------
# Step 1: Filter by roles (Unchanged)
# ----------------------------
def filter_by_roles(category_name):
job_titles = CATEGORIES[category_name]
try:
with open(JSON_FILE, encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
return pd.DataFrame(), f"Error: JSON file '{JSON_FILE}' not found. The LLM can't proceed."
filtered = []
for person in data:
work_exps = person.get("work_experiences", [])
if not work_exps:
continue
non_fullstack_roles = [
exp.get("roleName") for exp in work_exps
if exp.get("roleName") and "full stack developer" not in exp.get("roleName").lower()
]
if not non_fullstack_roles:
continue
if any(role in job_titles for role in non_fullstack_roles):
filtered.append({
"Name": person.get("name"),
"Email": person.get("email"),
"Phone": person.get("phone"),
"Location": person.get("location"),
"Roles": ", ".join(non_fullstack_roles),
"Skills": ", ".join(person.get("skills", [])),
"Salary": person.get("annual_salary_expectation", {}).get("full-time","N/A"),
"Category": category_name
})
if not filtered:
return pd.DataFrame(), f"No candidates found matching roles for category '{category_name}'. The LLM can't proceed."
df = pd.DataFrame(filtered)
df.to_csv(FILTERED_CSV, index=False)
return df, f"{len(df)} candidates filtered by role for category '{category_name}'. Ready for LLM scoring."
# ----------------------------
# Step 2: LLM recommendations (Scoring, Sorting, and Output)
# ----------------------------
def llm_recommendations(category_name):
job_titles = CATEGORIES[category_name]
if not os.path.exists(FILTERED_CSV):
df_filtered, msg = filter_by_roles(category_name)
if df_filtered.empty:
return msg
else:
df_filtered = pd.read_csv(FILTERED_CSV)
df_filtered = df_filtered[df_filtered["Category"] == category_name]
if df_filtered.empty:
return f"No filtered candidates found for category '{category_name}'. Run Step 1 first."
# Prepare for scoring
df_filtered_clean = df_filtered.fillna('N/A')
filtered_candidates = df_filtered_clean.to_dict(orient="records")
scores = []
for person in filtered_candidates:
candidate_info = {
"Name": person.get("Name"),
"Roles": person.get("Roles"),
"Skills": person.get("Skills")
}
candidate_str = json.dumps(candidate_info)
score = score_candidate(candidate_str, category_name, tuple(job_titles))
scores.append(score)
df_filtered["LLM_Score"] = scores
df_recommended = df_filtered[df_filtered["LLM_Score"] > 0].copy()
if df_recommended.empty:
if not HF_API_TOKEN:
return "❌ LLM failed: The HF_API_TOKEN is not set or is invalid. Set the token and try again."
return f"LLM scored all candidates 0. The candidates' roles are deemed irrelevant by the LLM for '{category_name}'."
def parse_salary(s):
try:
return float(str(s).replace("$","").replace(",","").replace("N/A", str(float('inf'))))
except:
return float('inf')
df_recommended["Salary_sort"] = df_recommended["Salary"].apply(parse_salary)
df_top5 = df_recommended.sort_values(
by=['LLM_Score', 'Salary_sort'],
ascending=[False, True]
).head(5)
final_names = df_top5["Name"].tolist()
output_text = f"Top {len(final_names)} Recommended Candidates for the '{category_name}' Category:\n\n"
for i, name in enumerate(final_names):
score = df_top5.iloc[i]['LLM_Score']
output_text += f"{i+1}. {name} (Suitability Score: {score}/10)\n"
output_text += "\nThese candidates were ranked by the LLM based **only on the alignment of their previous job roles** with the target roles, using expected salary as a tie-breaker."
return output_text
# ----------------------------
# Show first 5 raw JSON candidates (Unchanged)
# ----------------------------
def show_first_candidates():
try:
with open(JSON_FILE, encoding="utf-8") as f:
data = json.load(f)
return pd.DataFrame(data[:5])
except FileNotFoundError:
return pd.DataFrame({"Error": [f"JSON file '{JSON_FILE}' not found. Please ensure it is present."]})
except Exception as e:
return pd.DataFrame({"Error": [f"Failed to load JSON: {e}"]})
# ----------------------------
# Gradio interface (Updated Heading and Launch)
# ----------------------------
with gr.Blocks() as app:
# 🚩 CHANGE: Updated Heading
gr.Markdown("# 🤖 Candidate Selection (Role-Based Scoring)")
gr.Markdown("#### 🔍 Raw JSON Preview: First 5 Candidates")
gr.Dataframe(show_first_candidates(), label="First 5 JSON Entries")
gr.Markdown("---")
category_dropdown = gr.Dropdown(list(CATEGORIES.keys()), label="1. Select Category")
# Step 1: Filter by roles
filter_button = gr.Button("2. Filter Candidates by Roles")
filtered_df = gr.Dataframe(label="Filtered Candidates (Preview)")
filter_status = gr.Textbox(label="Filter Status", placeholder="Click 'Filter Candidates by Roles' to start.")
filter_button.click(filter_by_roles, inputs=[category_dropdown], outputs=[filtered_df, filter_status])
gr.Markdown("---")
# Step 2: LLM Recommendations
llm_button = gr.Button("3. Get LLM Recommendations (Role Experience Ranking)")
llm_output_text = gr.Textbox(label="Top Candidate Recommendations Summary", lines=10, placeholder="Click 'Get LLM Recommendations' after Step 2 completes.")
llm_button.click(llm_recommendations, inputs=[category_dropdown], outputs=[llm_output_text])
if __name__ == "__main__":
# 🚩 CHANGE: Set share=True to generate a public link
app.launch(share=True)