File size: 9,222 Bytes
6f5a207 75bd1c0 d0ca720 4fee3d2 6f5a207 4fee3d2 5bf89ac 4fee3d2 6f5a207 d0ca720 6f5a207 d0ca720 6f5a207 d0ca720 6f5a207 d0ca720 6f5a207 d0ca720 6f5a207 d0ca720 6f5a207 d0ca720 6f5a207 804e096 6f5a207 d0ca720 6f5a207 d0ca720 804e096 d0ca720 6f5a207 d0ca720 6f5a207 804e096 4fee3d2 804e096 6f5a207 804e096 6f5a207 804e096 d0ca720 6f5a207 804e096 6f5a207 d0ca720 6f5a207 d0ca720 6f5a207 804e096 d0ca720 6f5a207 d0ca720 6f5a207 d0ca720 6f5a207 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | 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 β’ π 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> β’ Engineered pipeline, not just a prompt</div>")
demo.launch() |