| 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"), |
| ) |
|
|
| |
| 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}]" |
|
|
| |
| |
| 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) |
|
|
| |
| 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 |
|
|
| |
| def analyze(resume_text, resume_file, jd_text, jd_file): |
| |
| 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).", "" |
|
|
| |
| 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!'} |
| |
| --- |
| """ |
|
|
| |
| 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) |
|
|
| |
| 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 |
|
|
| |
| 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() |