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 &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()