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