CoolFace
Apppublic

gnudevx/Recommendation-System

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
CV_based.py220 linesDownload Raw Back to root
1from fastapi import FastAPI, UploadFile, File2from fastapi.middleware.cors import CORSMiddleware3import os4from pymongo import MongoClient5import numpy as np6from sklearn.metrics.pairwise import cosine_similarity7from sklearn.preprocessing import normalize8 9from cv_utils import (10    convert_objectid_to_str,11    extract_text_from_file,12    extract_skills,13    extract_location_text,14    extract_city,15    parse_experience_text,16    parse_job_experience,17    prepare_job_skills,18    generate_job_embedding,19    generate_cv_embedding,20    check_location_match,21    model,22)23 24app = FastAPI()25 26app.add_middleware(27    CORSMiddleware,28    allow_origins=["*"],29    allow_credentials=True,30    allow_methods=["*"],31    allow_headers=["*"],32)33 34client = MongoClient(os.getenv("MONGO_URL"))35db = client["ITJOBS"]36job_collection = db["jobs"]37 38@app.post("/recommend")39async def recommend_jobs(file: UploadFile = File(...)):40    try:41        if not any(file.filename.lower().endswith(ext) for ext in [".pdf", ".docx"]):42            return {"error": "File không hợp lệ. Vui lòng tải lên file CV định dạng PDF hoặc DOCX.", "skills_found": [], "recommendations": []}43 44        file_bytes = await file.read()45        text = extract_text_from_file(file_bytes=file_bytes, filename=file.filename)46        if not text or not text.strip():47            return {"error": "Không thể đọc nội dung file. Vui lòng kiểm tra file CV.", "skills_found": [], "recommendations": []}48 49        cv_skills = extract_skills(text)50        cv_skills_set = set(cv_skills)51 52        emb_skill = np.zeros(384) if len(cv_skills) == 0 else model.encode(" ".join(cv_skills))53        emb_full = model.encode(text)54        cv_emb = 0.7 * emb_skill + 0.3 * emb_full55 56        jobs = list(job_collection.find({}, {57            "title": 1,58            "embedding": 1,59            "mustHaveSkills": 1,60            "optionalSkills": 1,61            "domainKnowledge": 1,62            "location": 1,63            "salary_raw": 1,64            "company": 1,65            "requirements": 1,66            "description": 1,67            "experience": 1,68            "experienceLevel": 1,69            "work_location_detail": 170        }))71 72        if not jobs:73            return {"skills_found": cv_skills, "recommendations": [], "message": "Không có công việc nào trong hệ thống"}74 75        job_vectors = []76        fallback_flags = []77        for job in jobs:78            emb = job.get("embedding")79            if emb and len(emb) == 384:80                job_vectors.append(emb)81                fallback_flags.append(False)82            else:83                reqs = " ".join(job.get("requirements", [])) or job.get("description", "")84                job_vectors.append(generate_job_embedding(reqs) if reqs else np.zeros(384).tolist())85                fallback_flags.append(True)86 87        job_vectors = np.array(job_vectors)88        cv_emb = normalize([cv_emb])[0]89        job_vectors = normalize(job_vectors)90        scores = cosine_similarity([cv_emb], job_vectors)[0]91 92        cv_full_location = extract_location_text(text)93        cv_city = extract_city(cv_full_location) if cv_full_location else ""94        cv_experience_years = parse_experience_text(text)95 96        results = []97        for job, score, used_fallback in zip(jobs, scores, fallback_flags):98            skills = prepare_job_skills(job)99            job_all_skills = skills["must_have"] | skills["optional"] | skills["domain"]100            matched_total = cv_skills_set & job_all_skills101            skill_score = (len(matched_total) / len(job_all_skills) * 100) if job_all_skills else 0102 103            job_experience_years = parse_job_experience(job.get("experience") or job.get("experienceLevel"))104            experience_match = None105            experience_score = 50106            if cv_experience_years is not None and job_experience_years is not None:107                experience_match = cv_experience_years >= job_experience_years108                experience_score = 100 if experience_match else 0109 110            parts = []111            location_field = job.get("location")112            if isinstance(location_field, dict):113                parts.append(location_field.get("name", ""))114            elif isinstance(location_field, str):115                parts.append(location_field)116            parts.append(job.get("work_location_detail", ""))117            job_full_location = " | ".join([p for p in parts if p])118            job_city = extract_city(job_full_location) if job_full_location else ""119            location_match = check_location_match(cv_city, job_city) if cv_city and job_city else False120            location_score = 100 if location_match else (50 if (cv_city and job_city) else 0)121 122            match_percentage = round(skill_score * 0.7 + experience_score * 0.15 + location_score * 0.15, 1)123 124            reason_parts = []125            if len(cv_skills_set & skills["must_have"]) > 0:126                reason_parts.append(f"Match {len(cv_skills_set & skills['must_have'])}/{len(skills['must_have'])} kỹ năng bắt buộc")127            if experience_match is True:128                reason_parts.append("Kinh nghiệm đủ yêu cầu")129            elif experience_match is False:130                reason_parts.append("Kinh nghiệm có thể thấp hơn yêu cầu")131            if location_match:132                reason_parts.append("Địa điểm phù hợp")133 134            reason = "; ".join(reason_parts) if reason_parts else "Nội dung CV phù hợp với yêu cầu công việc"135 136            combined_score = float(score) + match_percentage / 100.0137            if experience_match is True:138                combined_score += 0.10139            if location_match:140                combined_score += 0.05141 142            results.append({143                "id": str(job["_id"]),144                "title": str(job.get("title", "")),145                "location": convert_objectid_to_str(job.get("location")),146                "salary": str(job.get("salary_raw", "")),147                "company": str(job.get("company", "")),148                "score": float(score),149                "similarity_percentage": round(float(score) * 100, 1),150                "match_percentage": match_percentage,151                "experience_required": job_experience_years,152                "experience_match": experience_match,153                "cv_experience_years": cv_experience_years,154                "location_match": location_match,155                "cv_location": cv_full_location,156                "cv_city": cv_city,157                "job_location_text": job_full_location,158                "job_city": job_city,159                "combined_score": round(combined_score, 4),160                "matched_skills": {161                    "required": list(cv_skills_set & skills["must_have"]),162                    "optional": list(cv_skills_set & skills["optional"]),163                    "domain": list(cv_skills_set & skills["domain"]),164                    "total_matched": len(matched_total),165                    "total_job_skills": len(job_all_skills),166                },167                "reason": reason,168            })169 170        return convert_objectid_to_str({171            "skills_found": cv_skills,172            "recommendations": sorted(results, key=lambda x: x["combined_score"], reverse=True)[:100],173            "summary": f"Tìm được {len(cv_skills)} kỹ năng trong CV của bạn",174        })175 176    except Exception as e:177        import traceback178        traceback.print_exc()179        return convert_objectid_to_str({"error": f"Lỗi xử lý: {str(e)}", "skills_found": [], "recommendations": []})180 181@app.post("/job-embedding")182async def create_job_embedding(payload: dict):183    try:184        text = payload.get("text", "")185 186        if not text.strip():187            return {"embedding": []}188 189        embedding = model.encode(text)190 191        return {192            "embedding": embedding.tolist()193        }194 195    except Exception as e:196        return {197            "error": str(e)198        }199 200@app.post("/cv-embedding")201async def create_cv_embedding(payload: dict):202 203    raw_text = payload.get("rawText", "")204    skills = payload.get("skills", [])205 206    if not raw_text:207        return {208            "success": False,209            "embedding": []210        }211 212    embedding = generate_cv_embedding(213        raw_text,214        skills215    )216 217    return {218        "success": True,219        "embedding": embedding220    }