talent-patch / app.py
rca123456's picture
Update app.py
e38a317 verified
import gradio as gr
from langchain_community.document_loaders.pdf import PyPDFLoader
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_groq import ChatGroq
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from sklearn.metrics.pairwise import cosine_similarity
from dotenv import load_dotenv
from fpdf import FPDF
from collections import Counter
import numpy as np
import tempfile
import os
load_dotenv()
groq_api_key = os.getenv("GROQ_API_KEY")
os.environ["GROQ_API_KEY"] = groq_api_key
def extract_text_from_pdf(pdf_file):
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp:
temp.write(pdf_file)
temp.flush()
loader = PyPDFLoader(temp.name)
pages = loader.load_and_split()
return " ".join([page.page_content for page in pages])
def extract_skills(text):
skills_list = ["Python", "SQL", "Machine Learning", "Deep Learning", "NLP", "Data Visualization", "Cloud", "TensorFlow", "PyTorch", "Statistics", "Java", "C++", "HTML", "CSS", "JavaScript"]
return [skill for skill in skills_list if skill.lower() in text.lower()]
def generate_learning_resources(missing_skills):
suggestions = []
for skill in missing_skills:
search_link = f"https://www.google.com/search?q={skill}+online+course"
youtube_link = f"https://www.youtube.com/results?search_query={skill}+tutorial"
suggestions.append(f"πŸ”Ž [{skill} Courses on Google]({search_link})\n▢️ [YouTube Tutorials]({youtube_link})\n")
return "\n\n".join(suggestions)
def suggest_certifications(missing_skills):
cert_mapping = {
"Python": "Python for Everybody (Coursera)",
"Machine Learning": "Machine Learning by Andrew Ng (Coursera)",
"Cloud": "AWS Certified Solutions Architect",
"SQL": "Google Data Analytics Certificate",
"TensorFlow": "TensorFlow Developer Certificate",
"NLP": "Natural Language Processing Specialization (DeepLearning.AI)",
"Java": "Oracle Certified Java Programmer",
"C++": "C++ Nanodegree (Udacity)"
}
suggestions = []
for skill in missing_skills:
if skill in cert_mapping:
suggestions.append(f"{skill}: {cert_mapping[skill]}")
return "\n".join(suggestions) if suggestions else "No specific certifications recommended."
def generate_circular_progress(percentage):
html_code = f"""
<style>
.circular-progress {{
position: relative;
width: 160px;
height: 160px;
border-radius: 50%;
background: conic-gradient(#00c6ff 0%, #003366 0%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 20px rgba(0, 198, 255, 0.5);
animation: fillAnimation 2s ease-out forwards;
}}
.circular-progress::after {{
content: "";
position: absolute;
width: 120px;
height: 120px;
background: #1b263b;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}}
.circular-progress span {{
position: absolute;
color: #00f7ff;
font-size: 1.8em;
font-weight: bold;
text-shadow: 0 0 5px rgba(0,255,255,0.8);
z-index: 2;
}}
@keyframes fillAnimation {{
from {{
background: conic-gradient(#00c6ff 0%, #003366 0%);
}}
to {{
background: conic-gradient(#00c6ff {percentage}%, #003366 {percentage}% 100%);
}}
}}
</style>
<div class='circular-progress' style='--progress: {percentage};'>
<span>{percentage}%</span>
</div>
"""
return html_code
def generate_skill_gap_report(user_skills, job_skills, missing_skills, match_percent):
llm = ChatGroq(model="llama3-8b-8192", temperature=0.2)
template = """
User Skills: {user_skills}
Job Requirements: {job_skills}
Missing Skills: {missing_skills}
Match Percentage: {match_percent}%
Generate a short, friendly skill gap report. Suggest next steps for the user to improve their chances.
"""
prompt = PromptTemplate.from_template(template)
chain = prompt | llm | StrOutputParser()
report = chain.invoke({
"user_skills": ", ".join(user_skills),
"job_skills": ", ".join(job_skills),
"missing_skills": ", ".join(missing_skills),
"match_percent": match_percent
})
return report
def create_pdf(full_report_text):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", size=12)
pdf.multi_cell(0, 10, full_report_text)
output_path = "multi_jd_skill_gap_report.pdf"
pdf.output(output_path)
return output_path
def process_skill_gap(resume_pdf, jd_pdfs):
if resume_pdf is None or jd_pdfs is None:
return "", "", "", "", "", None, "", "", ""
resume_text = extract_text_from_pdf(resume_pdf)
user_skills = extract_skills(resume_text)
all_missing_skills = []
full_report = ""
for idx, jd_pdf in enumerate(jd_pdfs, start=1):
jd_text = extract_text_from_pdf(jd_pdf)
job_skills = extract_skills(jd_text)
common = set(user_skills) & set(job_skills)
match_percent = (len(common) / len(job_skills)) * 100 if job_skills else 0
missing_skills = list(set(job_skills) - set(user_skills))
all_missing_skills.extend(missing_skills)
embed_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
vectors = embed_model.embed_documents([resume_text, jd_text])
similarity_score = cosine_similarity([vectors[0]], [vectors[1]])[0][0]
similarity_percent = round(similarity_score * 100, 2)
ai_report = generate_skill_gap_report(user_skills, job_skills, missing_skills, match_percent)
full_report += f"\nJD {idx}:\nSkill Match: {match_percent}%\nMissing Skills: {', '.join(missing_skills) if missing_skills else 'None'}\nSimilarity Score: {similarity_percent}%\nAI Report:\n{ai_report}\n-------------------------\n"
resources = generate_learning_resources(list(set(all_missing_skills)))
certifications = suggest_certifications(all_missing_skills)
most_common_skills = Counter(all_missing_skills).most_common(3)
top_missing_skills_text = "Top Missing Skills Across JDs: " + ", ".join(
[f"{skill} ({count} times)" for skill, count in most_common_skills]
) if most_common_skills else "No missing skills detected."
overall_match = round(
(sum([len(set(user_skills) & set(extract_skills(extract_text_from_pdf(jd)))) for jd in jd_pdfs]) / (len(user_skills) * len(jd_pdfs))) * 100,
2
) if user_skills else 0
full_report_clean = full_report.encode('ascii', 'ignore').decode('ascii')
pdf_path = create_pdf(full_report_clean)
progress_display = generate_circular_progress(overall_match)
return progress_display, "βœ… Analysis done across all JDs", ", ".join(set(all_missing_skills)), "Multi-JD Comparison Completed", full_report, pdf_path, top_missing_skills_text, resources, certifications
with gr.Blocks() as demo:
gr.HTML("""
<style>
body {
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
background-size: 300% 300%;
animation: gradientShift 15s ease infinite;
font-family: 'Segoe UI', 'Roboto', sans-serif;
color: #ffffff;
margin: 0;
padding: 0;
}
@keyframes gradientShift {
0% {background-position: 0% 50%;}
50% {background-position: 100% 50%;}
100% {background-position: 0% 50%;}
}
.floating-circle {
position: fixed;
border-radius: 50%;
background: rgba(0,255,255,0.07);
box-shadow: 0 0 30px rgba(0,255,255,0.2);
animation: floatUp linear infinite;
z-index: -1;
filter: blur(2px);
}
@keyframes floatUp {
0% {transform: translateY(100vh); opacity: 0;}
10% {opacity: 0.2;}
90% {opacity: 0.2;}
100% {transform: translateY(-200px); opacity: 0;}
}
.floating-circle:nth-child(1) { width: 80px; height: 80px; left: 15%; animation-duration: 25s; }
.floating-circle:nth-child(2) { width: 100px; height: 100px; left: 50%; animation-duration: 30s; animation-delay: 5s; }
.floating-circle:nth-child(3) { width: 60px; height: 60px; left: 70%; animation-duration: 20s; animation-delay: 10s; }
.floating-circle:nth-child(4) { width: 90px; height: 90px; left: 35%; animation-duration: 35s; animation-delay: 15s; }
.floating-circle:nth-child(5) { width: 120px; height: 120px; left: 80%; animation-duration: 40s; animation-delay: 20s; }
input, textarea, .gr-textbox, .gr-slider, .gr-button, .gr-file, .gr-markdown, .gr-image {
background: rgba(255, 255, 255, 0.05) !important;
color: #ffffff !important;
border-radius: 10px !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
backdrop-filter: blur(8px);
padding: 10px !important;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
transition: all 0.3s ease;
}
.gr-button {
background: linear-gradient(135deg, #00c6ff, #0072ff) !important;
color: #ffffff !important;
font-weight: bold;
text-transform: uppercase;
cursor: pointer;
border: none !important;
box-shadow: 0 4px 15px rgba(0, 198, 255, 0.4);
}
.gr-button:hover {
background: linear-gradient(135deg, #0072ff, #00c6ff) !important;
box-shadow: 0 6px 20px rgba(0, 198, 255, 0.6);
transform: translateY(-2px);
}
textarea, .gr-textbox {
min-height: 120px !important;
line-height: 1.6;
}
h1, h2, h3, p, label {
color: #00f7ff !important;
text-shadow: 0 0 8px rgba(0, 255, 255, 0.3);
}
.circular-progress {
position: relative;
width: 160px;
height: 160px;
border-radius: 50%;
background: conic-gradient(#00c6ff 0%, #003366 0%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 20px rgba(0, 198, 255, 0.5);
animation: fillAnimation 2s ease-out forwards;
}
.circular-progress::after {
content: "";
position: absolute;
width: 120px;
height: 120px;
background: #1b263b;
border-radius: 50%;
}
.circular-progress span {
position: absolute;
color: #00f7ff;
font-size: 1.8em;
font-weight: bold;
text-shadow: 0 0 5px rgba(0,255,255,0.8);
}
@keyframes fillAnimation {
from { background: conic-gradient(#00c6ff 0%, #003366 0%); }
to { background: conic-gradient(#00c6ff var(--progress)%, #003366 var(--progress)% 100%); }
}
</style>
<div class='floating-circle'></div>
<div class='floating-circle'></div>
<div class='floating-circle'></div>
<div class='floating-circle'></div>
<div class='floating-circle'></div>
""")
gr.Markdown("# 🧠 TALENTPATCH - Multi-JD AI Skill Gap Checker")
resume_file = gr.File(label="πŸ“„ Upload Resume (PDF)", type="binary")
jd_files = gr.File(label="πŸ“„ Upload Multiple Job Descriptions (PDFs)", type="binary", file_types=[".pdf"], file_count="multiple")
match_progress = gr.HTML(label="Skill Match Progress")
skill_match_text = gr.Textbox(label="Status", interactive=False)
missing_skills_text = gr.Textbox(label="All Missing Skills", interactive=False)
similarity_text = gr.Textbox(label="Status Message", interactive=False)
report_output = gr.Textbox(label="AI-Generated Multi-JD Skill Gap Report", lines=20, interactive=False)
download_pdf = gr.File(label="πŸ“₯ Download Full Report as PDF")
top_skills_output = gr.Textbox(label="Top Missing Skills Across JDs", interactive=False)
learning_resources = gr.Markdown(label="πŸ“š AI Learning Resource Recommendations")
certification_output = gr.Textbox(label="πŸŽ“ Recommended Certifications", interactive=False)
submit_btn = gr.Button("πŸš€ Analyze Skill Gap")
submit_btn.click(
fn=process_skill_gap,
inputs=[resume_file, jd_files],
outputs=[
match_progress,
skill_match_text,
missing_skills_text,
similarity_text,
report_output,
download_pdf,
top_skills_output,
learning_resources,
certification_output
]
)
demo.launch()