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("""

💔 Rejected Before Applying

Find out exactly why your resume won't make it — before you waste the application.

📊 Evidence-based keyword engine + AI hiring manager  •  🔒 No data stored

""") with gr.Row(equal_height=True): with gr.Column(): gr.HTML("📄 Your Resume") 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("💼 Job Description") 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("
⏳ Takes ~20 seconds — runs a keyword engine + 2 AI passes
") 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("
Built for the 🤗 Build Small Hackathon  •  Engineered pipeline, not just a prompt
") demo.launch()