Ram-1103's picture
Update app.py
d0ca720 verified
Raw
History Blame Contribute Delete
9.22 kB
import gradio as gr
import os
import re
from openai import OpenAI
client = OpenAI(
base_url="https://router.huggingface.co/v1",
api_key=os.environ.get("Rejected_tk"),
)
# ---------- FILE READING ----------
def read_file(file):
if file is None:
return ""
path = file.name if hasattr(file, "name") else file
try:
if path.lower().endswith(".pdf"):
from pypdf import PdfReader
reader = PdfReader(path)
return "\n".join((p.extract_text() or "") for p in reader.pages)
elif path.lower().endswith(".docx"):
import docx
d = docx.Document(path)
return "\n".join(p.text for p in d.paragraphs)
else:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
return f.read()
except Exception as e:
return f"[Could not read file: {e}]"
# ---------- DETERMINISTIC KEYWORD ENGINE ----------
# Curated tech/skill vocabulary β€” deterministic, no LLM hallucination
SKILL_VOCAB = [
"python","java","go","golang","rust","c++","scala","javascript","typescript","sql",
"kubernetes","docker","mlflow","airflow","spark","kafka","hadoop","terraform","jenkins",
"aws","gcp","azure","sagemaker","vertex ai","ec2","s3","lambda","bigquery",
"pytorch","tensorflow","keras","scikit-learn","sklearn","jax","onnx","tensorrt",
"horovod","deepspeed","ray","distributed training","quantization","fine-tuning",
"nlp","bert","transformers","llm","huggingface","computer vision","cnn","rnn","gan",
"pandas","numpy","data pipeline","etl","feature engineering","ci/cd","mlops","devops",
"rest api","grpc","microservices","redis","postgresql","mongodb","elasticsearch",
"git","linux","bash","unit testing","system design","production deployment","monitoring",
]
def extract_skills(text):
text_low = text.lower()
found = set()
for skill in SKILL_VOCAB:
if re.search(r"\b" + re.escape(skill) + r"\b", text_low):
found.add(skill)
return found
def keyword_analysis(resume_text, jd_text):
resume_skills = extract_skills(resume_text)
jd_skills = extract_skills(jd_text)
matched = sorted(jd_skills & resume_skills)
missing = sorted(jd_skills - resume_skills)
match_pct = int(100 * len(matched) / len(jd_skills)) if jd_skills else 0
return matched, missing, match_pct, sorted(resume_skills), sorted(jd_skills)
# ---------- LLM PROMPTS ----------
SYSTEM_PROMPT = """You are a brutally honest but helpful senior hiring manager with 15 years of experience.
Tell candidates EXACTLY why they will be rejected β€” a specific, actionable diagnosis, not a vague match score.
Output in this exact structure:
## ❌ Why You Will Likely Be Rejected
[2-3 specific, direct sentences about the core mismatch]
## πŸ” Top 3 Skill Gaps
1. [Gap β€” specific technology]
2. [Gap]
3. [Gap]
## πŸ“‚ Missing Projects To Build
- [Project that would fill the biggest gap]
- [Another project]
- [Another project]
## πŸ“… 30-Day Improvement Plan
**Week 1:** [Specific action]
**Week 2:** [Specific action]
**Week 3:** [Specific action]
**Week 4:** [Specific action β€” a portfolio project]
## πŸ’‘ Honest Verdict
[One sentence: apply now, wait 3 months, or pivot?]
Be specific. Name actual technologies. Do not be vague."""
BULLET_PROMPT = """You are an expert resume writer. Given the candidate's gaps and the target role, write exactly 3 strong, quantified resume bullet points the candidate could truthfully add AFTER building the recommended projects. Each bullet starts with a strong action verb and includes a metric. Output only the 3 bullets, nothing else."""
def call_llm(system, user):
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[{"role": "system", "content": system}, {"role": "user", "content": user}],
max_tokens=1024, temperature=0.7,
)
return response.choices[0].message.content
# ---------- MAIN PIPELINE ----------
def analyze(resume_text, resume_file, jd_text, jd_file):
# File upload overrides text box if a file is provided
resume = read_file(resume_file) if resume_file else resume_text
jd = read_file(jd_file) if jd_file else jd_text
if not resume.strip() or not jd.strip():
return "⚠️ Please provide both a resume and a job description (paste or upload).", ""
# 1. Deterministic keyword engine
matched, missing, match_pct, resume_skills, jd_skills = keyword_analysis(resume, jd)
evidence = f"""## πŸ“Š Evidence-Based Match Analysis
**🎯 Skill Match Score: {match_pct}%**
**βœ… Skills found in BOTH resume & JD ({len(matched)}):**
{', '.join(matched) if matched else 'None β€” major mismatch'}
**❌ Required skills MISSING from your resume ({len(missing)}):**
{', '.join(missing) if missing else 'None β€” strong overlap!'}
---
"""
# 2. LLM rejection report
user_msg = f"Resume:\n{resume}\n\nJob Description:\n{jd}\n\nDETERMINISTIC ANALYSIS β€” Missing skills: {', '.join(missing)}. Match score: {match_pct}%.\n\nGive the full rejection report grounded in these missing skills."
report = call_llm(SYSTEM_PROMPT, user_msg)
# 3. Resume bullet generator
bullet_msg = f"Target role skills missing: {', '.join(missing)}. Candidate background: {resume[:600]}. Write the 3 bullets."
bullets = call_llm(BULLET_PROMPT, bullet_msg)
bullets_section = f"\n\n---\n## ✍️ 'Fix My Resume' β€” 3 Bullets To Add After Building These Projects\n\n{bullets}"
return evidence + report, bullets_section
# ---------- EXAMPLES ----------
EXAMPLE_RESUME = """Name: Priya Sharma
Education: M.S. Computer Science, 2024
Skills: Python, PyTorch, NLP, Transformers, BERT fine-tuning, HuggingFace, scikit-learn, pandas, numpy
Projects:
- Sentiment analysis model on Twitter data using BERT
- Named Entity Recognition system for biomedical text
- Research paper: "Improving low-resource NER with cross-lingual transfer"
Experience:
- ML Research Intern, university NLP lab (6 months)
- TA for Introduction to Machine Learning course"""
EXAMPLE_JD = """Senior ML Engineer β€” Infrastructure
Company: CloudScale Inc.
Requirements:
- 3+ years deploying ML models at scale in production
- Strong knowledge of Kubernetes, Docker, MLflow
- Experience with distributed training (Horovod, DeepSpeed)
- Familiarity with AWS SageMaker or GCP Vertex AI
- Python, Go, or Rust for systems-level work
- Experience with data pipelines: Spark, Kafka, Airflow
- Nice to have: ONNX optimization, TensorRT, model quantization"""
css = """
body { background: #0f0f1a !important; }
.gradio-container { max-width: 1100px !important; margin: auto; }
#title-block { text-align: center; padding: 30px 0 10px 0; }
#title-block h1 { font-size: 2.8em; font-weight: 900; background: linear-gradient(90deg, #ff6b6b, #ff8e53, #ff6bff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
#title-block p { color: #aaa; font-size: 1.05em; }
.gr-button-primary { background: linear-gradient(90deg, #ff416c, #ff4b2b) !important; border: none !important; font-size: 1.1em !important; padding: 14px !important; border-radius: 10px !important; font-weight: 700 !important; }
.gr-textbox textarea { background: #1a1a2e !important; color: #e0e0e0 !important; border: 1px solid #333 !important; border-radius: 10px !important; }
label { color: #ccc !important; font-weight: 600 !important; }
"""
with gr.Blocks(title="Rejected Before Applying", css=css) as demo:
gr.HTML("""
<div id='title-block'>
<h1>πŸ’” Rejected Before Applying</h1>
<p>Find out <b>exactly why</b> your resume won't make it β€” before you waste the application.</p>
<p style='color:#666; font-size:0.85em;'>πŸ“Š Evidence-based keyword engine + AI hiring manager &nbsp;β€’&nbsp; πŸ”’ No data stored</p>
</div>
""")
with gr.Row(equal_height=True):
with gr.Column():
gr.HTML("<b style='color:#ccc;'>πŸ“„ Your Resume</b>")
resume_input = gr.Textbox(label="Paste resume text", lines=12, value=EXAMPLE_RESUME)
resume_file = gr.File(label="...or upload (PDF / DOCX / TXT)", file_types=[".pdf", ".docx", ".txt"])
with gr.Column():
gr.HTML("<b style='color:#ccc;'>πŸ’Ό Job Description</b>")
jd_input = gr.Textbox(label="Paste JD text", lines=12, value=EXAMPLE_JD)
jd_file = gr.File(label="...or upload (PDF / DOCX / TXT)", file_types=[".pdf", ".docx", ".txt"])
analyze_btn = gr.Button("πŸ” Analyse My Rejection", variant="primary", size="lg")
gr.HTML("<div style='margin:10px 0; color:#888; font-size:0.85em; text-align:center;'>⏳ Takes ~20 seconds β€” runs a keyword engine + 2 AI passes</div>")
output = gr.Markdown(value="*Your evidence-based rejection report will appear here...*")
bullets_out = gr.Markdown()
analyze_btn.click(fn=analyze, inputs=[resume_input, resume_file, jd_input, jd_file], outputs=[output, bullets_out])
gr.HTML("<div style='text-align:center; margin-top:24px; color:#555; font-size:0.82em;'>Built for the πŸ€— <b>Build Small Hackathon</b> &nbsp;β€’&nbsp; Engineered pipeline, not just a prompt</div>")
demo.launch()