Spaces:
Sleeping
Sleeping
| import pdfplumber | |
| from sentence_transformers import SentenceTransformer, util | |
| import gradio as gr | |
| model = SentenceTransformer('all-MiniLM-L6-v2') | |
| def extract_text_from_pdf(uploaded_file): | |
| """ | |
| uploaded_file: Gradio file object or file-like object | |
| Returns extracted text | |
| """ | |
| try: | |
| # Gradio sometimes gives a dict, sometimes a file-like object | |
| if hasattr(uploaded_file, "name"): # file-like object | |
| path = uploaded_file.name | |
| elif isinstance(uploaded_file, dict) and "name" in uploaded_file: | |
| path = uploaded_file["name"] | |
| else: | |
| return "β Cannot read uploaded file." | |
| text = "" | |
| with pdfplumber.open(path) as pdf: | |
| for page in pdf.pages: | |
| page_text = page.extract_text() | |
| if page_text: | |
| text += page_text + "\n" | |
| return text.strip() if text else "β No text found in PDF." | |
| except Exception as e: | |
| return f"β Error reading PDF: {e}" | |
| common_skills = [ | |
| "python","django","flask","fastapi","sql","postgresql","mysql","angular", | |
| "docker","aws","git","rest api","machine learning","pandas","numpy","appian" | |
| ] | |
| def evaluate_fit(cv_file, cv_text, job_text): | |
| print(extract_text_from_pdf(cv_file)) | |
| # Decide which CV to use | |
| if cv_file is not None: | |
| cv_text = extract_text_from_pdf(cv_file) | |
| if cv_text.startswith("β"): # extraction failed | |
| return cv_text | |
| elif cv_text: | |
| cv_text = cv_text | |
| else: | |
| return "β Please provide a CV file or paste text!" | |
| # --- Compute semantic similarity --- | |
| cv_emb = model.encode(cv_text, convert_to_tensor=True) | |
| job_emb = model.encode(job_text, convert_to_tensor=True) | |
| sim = util.cos_sim(cv_emb, job_emb).item() | |
| score = round(sim * 100, 2) | |
| # --- Skill matching --- | |
| job_skills = [s for s in common_skills if s in job_text.lower()] | |
| matched = [s for s in job_skills if s in cv_text.lower()] | |
| missing = [s for s in job_skills if s not in cv_text.lower()] | |
| skill_match = round((len(matched)/len(job_skills))*100, 2) if job_skills else 0 | |
| # --- Build feedback --- | |
| if score > 70 and skill_match > 60: | |
| summary = f"β Strong Match! (Semantic: {score}%, Skills: {skill_match}%)" | |
| elif score > 50: | |
| summary = f"β οΈ Partial Match (Semantic: {score}%, Skills: {skill_match}%)" | |
| else: | |
| summary = f"β Weak Match (Semantic: {score}%, Skills: {skill_match}%)" | |
| result = summary + "\n\n" | |
| if matched: | |
| result += f"β Found skills: {', '.join(matched)}\n" | |
| if missing: | |
| result += f"β Missing skills: {', '.join(missing)}\n" | |
| # --- Extra feedback --- | |
| if skill_match == 100 and score < 60: | |
| result += "\nπ‘ Tip: All required skills found, but CV wording may differ from job duties. Try describing how you used those skills." | |
| return result | |
| demo = gr.Interface( | |
| fn=evaluate_fit, | |
| inputs=[ | |
| gr.File(label="Upload CV (PDF) (optional)"), | |
| gr.Textbox(label="Or paste CV text (optional)", lines=6), | |
| gr.Textbox(label="Paste job description", lines=8) | |
| ], | |
| outputs="text", | |
| title="π§ CV Fit Checker with PDF Upload", | |
| description="Upload your CV PDF or paste text to compare it with a job description using semantic and skill analysis." | |
| ) | |
| demo.launch() | |