Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """CV Matching Engine""" | |
| import os,re,json,warnings,tempfile | |
| import fitz,spacy,gradio as gr | |
| import numpy as np,pandas as pd | |
| import plotly.graph_objects as go | |
| from collections import defaultdict | |
| from typing import Dict,List | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| from sentence_transformers import SentenceTransformer | |
| warnings.filterwarnings("ignore") | |
| _nlp=None;_embed_model=None | |
| def get_nlp(): | |
| global _nlp | |
| if _nlp is None: _nlp=spacy.load("en_core_web_sm") | |
| return _nlp | |
| def get_embed_model(): | |
| global _embed_model | |
| if _embed_model is None: _embed_model=SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") | |
| return _embed_model | |
| print("[INFO] App baslatildi.") | |
| SKILL_ONTOLOGY: Dict[str, List[str]] = { | |
| # ── 1. PROGRAMLAMA DİLLERİ ─────────────────────────────────────────────── | |
| "python" : ["python3", "python 3", "py", "cpython"], | |
| "java" : ["java se", "java ee", "java 8", "java 11", "java 17", "jvm", "openjdk"], | |
| "c#" : ["csharp", "c sharp", ".net c#", "dotnet csharp"], | |
| "c++" : ["cpp", "c plus plus", "c/c++", "iso c++"], | |
| "c" : ["c language", "c programming", "ansi c", "embedded c"], | |
| "javascript" : ["js", "ecmascript", "es6", "es2015", "vanilla js"], | |
| "typescript" : ["ts", "typed javascript"], | |
| "kotlin" : ["kotlin jvm", "kotlin android"], | |
| "swift" : ["swift ios", "swift ui", "apple swift"], | |
| "go" : ["golang", "go language"], | |
| "rust" : ["rust lang", "rust programming"], | |
| "php" : ["php7", "php8", "hypertext preprocessor"], | |
| "ruby" : ["ruby on rails", "ror", "rails"], | |
| "scala" : ["scala jvm", "akka"], | |
| "r" : ["rstudio", "r programming", "r language", "r stats"], | |
| "matlab" : ["matlab/simulink", "simulink", "mathworks"], | |
| "python" : ["python3", "py"], | |
| "dart" : ["dart flutter", "dart language"], | |
| "perl" : ["perl scripting"], | |
| "bash" : ["shell scripting", "bash scripting", "shell script", "sh", "zsh"], | |
| "powershell" : ["ps1", "windows powershell", "pwsh"], | |
| "groovy" : ["groovy jvm", "grails"], | |
| "lua" : ["lua scripting"], | |
| "haskell" : ["functional programming haskell"], | |
| "elixir" : ["elixir phoenix", "phoenix framework"], | |
| "clojure" : ["clojure jvm"], | |
| "assembly" : ["asm", "x86 assembly", "arm assembly"], | |
| "vba" : ["visual basic for applications", "excel vba", "office vba"], | |
| "visual basic" : ["vb.net", "vb6", "visual basic .net"], | |
| "cobol" : ["cobol mainframe"], | |
| "fortran" : ["fortran 90", "fortran 95"], | |
| # ── 2. WEB FRONTEND ───────────────────────────────────────────────────── | |
| "react" : ["reactjs", "react.js", "react native", "react hooks", "next.js", "nextjs"], | |
| "angular" : ["angularjs", "angular 2+", "angular cli", "ng"], | |
| "vue" : ["vuejs", "vue.js", "vue 3", "nuxt", "nuxt.js"], | |
| "svelte" : ["sveltekit"], | |
| "html" : ["html5", "hypertext markup language"], | |
| "css" : ["css3", "cascading style sheets", "scss", "sass", "less"], | |
| "tailwind" : ["tailwindcss", "tailwind css"], | |
| "bootstrap" : ["bootstrap 5", "bootstrap css"], | |
| "jquery" : ["jquery js"], | |
| "webpack" : ["webpack bundler", "vite", "rollup", "parcel"], | |
| "graphql" : ["graphql api", "apollo graphql"], | |
| # ── 3. WEB BACKEND / API ───────────────────────────────────────────────── | |
| "node.js" : ["nodejs", "node js", "express", "express.js", "expressjs"], | |
| "django" : ["django rest", "drf", "django framework"], | |
| "flask" : ["flask api", "flask python"], | |
| "fastapi" : ["fast api", "fastapi python"], | |
| "spring boot" : ["spring", "spring framework", "spring mvc", "spring security", "spring data"], | |
| "asp.net" : ["asp net", "asp.net core", ".net core", "dotnet core", "aspnet", "mvc .net"], | |
| "laravel" : ["laravel php", "laravel framework"], | |
| "nestjs" : ["nest.js", "nest js"], | |
| "rest api" : ["restful api", "rest", "restful", "rest services", "web api", "http api"], | |
| "microservices" : ["micro services", "service oriented architecture", "soa", "distributed systems"], | |
| "grpc" : ["grpc api", "protocol buffers", "protobuf"], | |
| "websocket" : ["websockets", "socket.io", "real-time communication"], | |
| "oauth" : ["oauth2", "openid connect", "jwt", "json web token", "oidc"], | |
| # ── 4. MOBİL GELİŞTİRME ───────────────────────────────────────────────── | |
| "android" : ["android development", "android studio", "android sdk", "android kotlin"], | |
| "ios" : ["ios development", "xcode", "swift ios", "objective-c", "ios sdk"], | |
| "flutter" : ["flutter dart", "flutter mobile", "flutter sdk"], | |
| "react native" : ["react-native", "rn mobile"], | |
| "xamarin" : ["xamarin forms", "xamarin android", "xamarin ios"], | |
| "ionic" : ["ionic framework", "ionic angular"], | |
| # ── 5. VERİTABANI — İLİŞKİSEL ──────────────────────────────────────────── | |
| "sql" : ["structured query language", "ansi sql", "sql dili"], | |
| "mysql" : ["mysql database", "mysql server", "mysql 8"], | |
| "postgresql" : ["postgres", "psql", "postgresql database"], | |
| "mssql" : ["sql server", "microsoft sql server", "t-sql", "tsql", "ms sql"], | |
| "oracle" : ["oracle database", "oracle db", "oracle sql", "pl/sql", "plsql"], | |
| "sqlite" : ["sqlite3", "sqlite database"], | |
| "mariadb" : ["maria db", "mariadb server"], | |
| "db2" : ["ibm db2", "db2 database"], | |
| # ── 6. VERİTABANI — NoSQL ──────────────────────────────────────────────── | |
| "mongodb" : ["mongo", "mongo db", "mongodb atlas", "nosql mongodb"], | |
| "redis" : ["redis cache", "redis server", "in-memory database"], | |
| "cassandra" : ["apache cassandra", "cql"], | |
| "elasticsearch" : ["elastic search", "elk stack", "opensearch"], | |
| "dynamodb" : ["aws dynamodb", "amazon dynamodb"], | |
| "neo4j" : ["graph database", "neo4j db", "cypher query"], | |
| "couchdb" : ["apache couchdb"], | |
| "firebase" : ["firebase realtime", "firestore", "firebase db"], | |
| "influxdb" : ["time series database", "influx db"], | |
| # ── 7. BULUT PLATFORMLARI ───────────────────────────────────────────────── | |
| "aws" : ["amazon web services", "amazon aws", "aws cloud", "ec2", "s3", "lambda", | |
| "rds", "eks", "ecs", "sagemaker", "cloudwatch", "cloudformation"], | |
| "azure" : ["microsoft azure", "azure cloud", "azure devops", "azure functions", | |
| "azure kubernetes", "aks", "azure blob", "azure sql", "azure ad"], | |
| "google cloud" : ["gcp", "google cloud platform", "gke", "bigquery", "cloud run", | |
| "cloud functions", "vertex ai", "firebase"], | |
| "alibaba cloud" : ["aliyun", "alibaba cloud computing"], | |
| "oracle cloud" : ["oci", "oracle cloud infrastructure"], | |
| # ── 8. DevOps / SRE / CI-CD ────────────────────────────────────────────── | |
| "docker" : ["containerization", "container", "dockerfile", "docker-compose", | |
| "docker swarm", "container runtime"], | |
| "kubernetes" : ["k8s", "k8", "container orchestration", "helm", "kubectl", | |
| "kube", "openshift"], | |
| "git" : ["github", "gitlab", "bitbucket", "version control", "git flow", | |
| "source control", "git branching"], | |
| "jenkins" : ["jenkins ci", "jenkins pipeline", "jenkins ci/cd"], | |
| "github actions" : ["gh actions", "github ci", "github workflow"], | |
| "gitlab ci" : ["gitlab cicd", "gitlab pipeline"], | |
| "terraform" : ["terraform iac", "hashicorp terraform", "infrastructure as code", "iac"], | |
| "ansible" : ["ansible playbook", "ansible automation", "configuration management"], | |
| "prometheus" : ["prometheus monitoring", "grafana prometheus"], | |
| "grafana" : ["grafana dashboard", "grafana monitoring"], | |
| "nginx" : ["nginx server", "nginx reverse proxy", "nginx load balancer"], | |
| "apache" : ["apache http", "apache server", "httpd"], | |
| "linux" : ["ubuntu", "centos", "debian", "red hat", "rhel", "fedora", | |
| "linux server", "unix", "linux administration"], | |
| "vagrant" : ["vagrant vm", "hashicorp vagrant"], | |
| "puppet" : ["puppet configuration"], | |
| "chef" : ["chef infra", "chef automation"], | |
| # ── 9. VERİ MÜHENDİSLİĞİ / BIG DATA ───────────────────────────────────── | |
| "apache spark" : ["spark", "pyspark", "spark streaming", "apache spark sql"], | |
| "hadoop" : ["apache hadoop", "hdfs", "mapreduce", "hive", "hbase"], | |
| "kafka" : ["apache kafka", "kafka streaming", "kafka broker", "event streaming"], | |
| "airflow" : ["apache airflow", "airflow dag", "workflow orchestration"], | |
| "dbt" : ["data build tool", "dbt core", "dbt cloud"], | |
| "etl" : ["extract transform load", "data pipeline", "data integration", | |
| "data ingestion", "elt"], | |
| "data warehouse" : ["veri ambarı", "snowflake", "redshift", "bigquery", | |
| "azure synapse", "data lake", "lakehouse", "delta lake"], | |
| "data analysis" : ["veri analizi", "data analytics", "exploratory data analysis", "eda", | |
| "veri madenciliği", "data mining"], | |
| "pandas" : ["pandas dataframe", "pandas python"], | |
| "numpy" : ["numpy array", "numerical python"], | |
| "tableau" : ["tableau desktop", "tableau server", "tableau public"], | |
| "power bi" : ["powerbi", "microsoft power bi", "power bi desktop"], | |
| "qlik" : ["qliksense", "qlik sense", "qlikview"], | |
| "looker" : ["looker studio", "google looker", "data studio"], | |
| # ── 10. YAPAY ZEKA / MAKİNE ÖĞRENMESİ ─────────────────────────────────── | |
| "machine learning": ["ml", "makine öğrenmesi", "makine öğrenimi", | |
| "supervised learning", "unsupervised learning", "classification", | |
| "regression", "clustering", "makine öğrenimi"], | |
| "deep learning" : ["dl", "derin öğrenme", "neural network", "ann", "dnn", | |
| "convolutional neural network", "cnn", "rnn", "lstm", "gru"], | |
| "natural language processing": ["nlp", "doğal dil işleme", "text mining", | |
| "transformers", "text classification", "named entity recognition", | |
| "ner", "sentiment analysis", "text generation"], | |
| "computer vision" : ["cv", "image processing", "görüntü işleme", "opencv", | |
| "object detection", "image classification", "yolo", "segmentation"], | |
| "reinforcement learning": ["rl", "pekiştirmeli öğrenme", "dqn", "ppo", "a3c"], | |
| "large language model": ["llm", "gpt", "bert", "llama", "fine-tuning", "rag", | |
| "retrieval augmented generation", "prompt engineering", | |
| "chatgpt", "claude", "gemini", "mistral"], | |
| "tensorflow" : ["tf", "keras", "tensorflow 2"], | |
| "pytorch" : ["torch", "pytorch lightning"], | |
| "scikit-learn" : ["sklearn", "scikit learn"], | |
| "hugging face" : ["huggingface", "transformers library", "hf hub"], | |
| "langchain" : ["lang chain", "langchain python", "langchain agents"], | |
| "mlflow" : ["ml flow", "mlflow tracking", "model registry"], | |
| "feature engineering": ["özellik mühendisliği", "feature selection", "feature extraction"], | |
| "xgboost" : ["extreme gradient boosting", "xgb", "lightgbm", "lgbm", "catboost"], | |
| "time series" : ["zaman serisi", "forecasting", "talep tahmini", | |
| "arima", "sarima", "prophet", "lstm forecasting"], | |
| # ── 11. SİBER GÜVENLİK ─────────────────────────────────────────────────── | |
| "cybersecurity" : ["siber güvenlik", "information security", "bilgi güvenliği", | |
| "security engineering", "application security"], | |
| "penetration testing": ["pentest", "ethical hacking", "offensive security", | |
| "vulnerability assessment", "metasploit", "burp suite"], | |
| "soc" : ["security operations center", "siem", "soc analyst", | |
| "splunk", "qradar", "incident response"], | |
| "network security": ["firewall", "ids", "ips", "vpn", "zero trust", | |
| "ddos protection", "network monitoring"], | |
| "cryptography" : ["encryption", "ssl/tls", "ssl", "tls", "pki", | |
| "certificate management", "hsm"], | |
| "devsecops" : ["dev sec ops", "security in devops", "shift left security", | |
| "sast", "dast", "dependency scanning"], | |
| "identity management": ["iam", "active directory", "ldap", "sso", | |
| "azure ad", "okta", "privileged access management", "pam"], | |
| # ── 12. YAZILIM MÜHENDİSLİĞİ / MİMARİ ─────────────────────────────────── | |
| "software architecture": ["yazılım mimarisi", "solution architecture", | |
| "system design", "design patterns", "solid principles"], | |
| "microservices" : ["micro services", "distributed systems", "service mesh", | |
| "event driven architecture", "eda architecture", "cqrs", "ddd"], | |
| "unit testing" : ["test driven development", "tdd", "junit", "pytest", "nunit", | |
| "xunit", "mocha", "jasmine", "jest"], | |
| "code review" : ["peer review", "pull request", "code quality", "static analysis", | |
| "sonarqube"], | |
| "agile" : ["scrum", "kanban", "safe", "extreme programming", "xp", | |
| "sprint", "backlog", "çevik"], | |
| "uml" : ["unified modeling language", "class diagram", "sequence diagram"], | |
| "clean code" : ["refactoring", "solid", "dry principle", "kiss principle"], | |
| # ── 13. ERP / CRM / KURUMSAL YAZILIMLAR ────────────────────────────────── | |
| "sap" : ["sap erp", "sap s/4hana", "sap hana", "sap abap", "sap bw", | |
| "sap fi", "sap mm", "sap sd", "sap pp", "sap hr"], | |
| "oracle erp" : ["oracle ebs", "oracle e-business suite", "oracle fusion", | |
| "oracle hcm", "oracle scm"], | |
| "microsoft dynamics": ["dynamics 365", "dynamics crm", "dynamics ax", | |
| "dynamics nav", "business central"], | |
| "salesforce" : ["salesforce crm", "salesforce cloud", "apex salesforce", "soql"], | |
| "servicenow" : ["service now", "itsm servicenow"], | |
| "jira" : ["jira software", "jira service", "atlassian jira", "confluence"], | |
| # ── 14. TESTİNG / QA ───────────────────────────────────────────────────── | |
| "selenium" : ["selenium webdriver", "selenium grid", "selenium automation"], | |
| "cypress" : ["cypress testing", "cypress e2e"], | |
| "playwright" : ["playwright testing", "playwright e2e"], | |
| "appium" : ["appium mobile testing"], | |
| "postman" : ["postman api testing", "api testing"], | |
| "load testing" : ["jmeter", "locust", "k6", "gatling", "performance testing", | |
| "stress testing"], | |
| "qa" : ["quality assurance", "kalite güvencesi", "test automation", | |
| "manual testing", "test engineer"], | |
| # ── 15. AĞLAR / SİSTEM / ALTYAPI ───────────────────────────────────────── | |
| "networking" : ["tcp/ip", "dns", "dhcp", "routing", "switching", | |
| "cisco", "vlan", "bgp", "ospf", "network administration"], | |
| "windows server" : ["windows server 2019", "windows server 2022", "active directory", | |
| "group policy", "iis"], | |
| "virtualization" : ["vmware", "vsphere", "hyper-v", "virtualbox", "proxmox"], | |
| "storage" : ["san", "nas", "object storage", "backup solutions"], | |
| # ── 16. IOT / GÖMÜLÜ SİSTEMLER ─────────────────────────────────────────── | |
| "iot" : ["internet of things", "nesnelerin interneti", "mqtt", | |
| "iot platform", "azure iot", "aws iot"], | |
| "embedded systems": ["gömülü sistemler", "rtos", "freertos", "bare metal", | |
| "embedded linux", "microcontroller"], | |
| "arduino" : ["arduino uno", "arduino ide", "arduino programming"], | |
| "raspberry pi" : ["rpi", "raspberry pi os"], | |
| "fpga" : ["vhdl", "verilog", "xilinx", "intel fpga"], | |
| # ── 17. ROBOTİK / KONTROL SİSTEMLERİ ───────────────────────────────────── | |
| "ros" : ["robot operating system", "ros2", "roslaunch", "rosnode"], | |
| "control systems" : ["kontrol sistemleri", "pid controller", "pid", | |
| "control theory", "feedback control", "state space"], | |
| "robotics" : ["robot programlama", "robot kinematics", "motion planning"], | |
| "plc" : ["programmable logic controller", "siemens plc", "allen bradley", | |
| "ladder logic", "structured text", "scada"], | |
| "solidworks" : ["cad", "catia", "autocad", "fusion 360", "inventor"], | |
| "matlab" : ["matlab/simulink", "simulink", "mathworks"], | |
| # ── 18. LOJİSTİK / OPERASYON ────────────────────────────────────────────── | |
| "supply chain" : ["tedarik zinciri", "logistics", "lojistik", "supply chain management", "scm"], | |
| "vehicle routing" : ["vrp", "route optimization", "güzergah optimizasyonu"], | |
| "demand forecasting": ["talep tahmini", "demand planning", "inventory forecasting"], | |
| "warehouse management": ["wms", "depo yönetimi", "warehouse operations"], | |
| "operations research": ["yöneylem araştırması", "linear programming", "integer programming", | |
| "optimization", "or tools", "mathematical optimization"], | |
| # ── 19. PROJE YÖNETİMİ / SOFT SKILLS ───────────────────────────────────── | |
| "project management": ["proje yönetimi", "agile", "scrum", "kanban", | |
| "pmp", "prince2", "waterfall"], | |
| "team leadership" : ["takım liderliği", "people management", "tech lead", | |
| "team lead", "mentoring"], | |
| "communication" : ["presentation skills", "technical writing", "documentation", | |
| "iletişim becerileri"], | |
| "problem solving" : ["analytical thinking", "critical thinking", "problem çözme"], | |
| # ── 20. İŞ ANALİZİ & YÖNETİM ────────────────────────────────────── | |
| "business analysis" : ["iş analizi", "ba", "business analyst", "iş analisti", | |
| "requirements analysis", "gereksinim analizi"], | |
| "requirements engineering": ["gereksinim mühendisliği", "requirements gathering", | |
| "requirements management", "brd", "frd", "srs"], | |
| "process modeling" : ["süreç modelleme", "bpmn", "business process", | |
| "iş süreçleri", "workflow modeling", "process mapping"], | |
| "stakeholder management": ["paydaş yönetimi", "stakeholder analysis", | |
| "stakeholder engagement"], | |
| "user story" : ["kullanıcı hikayesi", "user stories", "acceptance criteria", | |
| "kabul kriterleri", "epic", "product backlog"], | |
| "use case" : ["kullanım senaryosu", "use case diagram", | |
| "use case modeling", "uml use case"], | |
| "business intelligence": ["bi", "iş zekası", "bi tools", "bi reporting", | |
| "business analytics"], | |
| "data visualization" : ["veri görselleştirme", "data viz", "dashboard design", | |
| "reporting", "raporlama"], | |
| "kpi" : ["key performance indicator", "performans göstergesi", | |
| "kpi tracking", "metrics", "okr"], | |
| "excel" : ["microsoft excel", "excel advanced", "pivot table", | |
| "vlookup", "excel macros", "spreadsheet"], | |
| "financial analysis" : ["finansal analiz", "financial modeling", "financial reporting", | |
| "mali analiz", "bütçe analizi"], | |
| "risk management" : ["risk yönetimi", "risk analysis", "risk assessment", | |
| "risk mitigation"], | |
| "change management" : ["değişiklik yönetimi", "organizational change", | |
| "change control"], | |
| "product management" : ["ürün yönetimi", "product owner", "po", "product strategy", | |
| "product roadmap", "ürün yol haritası"], | |
| "ux research" : ["kullanıcı araştırması", "user research", "usability testing", | |
| "user interview", "persona"], | |
| "erp consulting" : ["erp danışmanlığı", "erp implementation", | |
| "erp customization", "system integration"], | |
| "crm" : ["customer relationship management", "müşteri ilişkileri yönetimi", | |
| "crm tools", "hubspot", "zoho crm"], | |
| "digital marketing" : ["dijital pazarlama", "seo", "sem", "google analytics", | |
| "social media marketing", "content marketing"], | |
| "six sigma" : ["lean", "lean six sigma", "kaizen", "continuous improvement", | |
| "sürekli iyileştirme", "dmaic"], | |
| # ── 21. MEKANİK MÜHENDİSLİK ─────────────────────────────────────── | |
| "mechanical design" : ["mekanik tasarım", "machine design", "mechanical engineering", | |
| "mekanik mühendislik", "design engineer"], | |
| "cad" : ["autocad", "solidworks cad", "catia", "inventor", "creo", | |
| "bilgisayar destekli tasarım", "computer aided design"], | |
| "cam" : ["computer aided manufacturing", "bilgisayar destekli üretim", | |
| "cnc programming", "cam software"], | |
| "fea" : ["finite element analysis", "sonlu elemanlar analizi", | |
| "ansys", "abaqus", "nastran", "comsol", "fem"], | |
| "cfd" : ["computational fluid dynamics", "hesaplamalı akışkanlar", | |
| "fluent", "openfoam", "flow simulation"], | |
| "thermodynamics" : ["termodinamik", "heat transfer", "ısı transferi", | |
| "thermal analysis", "termal analiz"], | |
| "materials science" : ["malzeme bilimi", "material selection", "malzeme seçimi", | |
| "metallurgy", "metalürji", "composites", "kompozitler"], | |
| "manufacturing" : ["üretim", "production engineering", "üretim mühendisliği", | |
| "injection molding", "sheet metal", "casting", "döküm", | |
| "welding", "kaynak"], | |
| "quality control" : ["kalite kontrol", "qc", "inspection", "muayene", | |
| "quality assurance", "kalite güvence"], | |
| "tolerance analysis" : ["tolerans analizi", "gd&t", "geometric dimensioning", | |
| "tolerancing"], | |
| "cnc" : ["cnc machining", "cnc tezgah", "cnc torna", "cnc freze", | |
| "g-code", "lathe", "milling"], | |
| # ── 22. ELEKTRİK & ELEKTRONİK ───────────────────────────────────── | |
| "electrical engineering": ["elektrik mühendisliği", "electrical design", | |
| "elektrik tasarım", "power systems", "güç sistemleri"], | |
| "pcb design" : ["baskı devre tasarımı", "pcb layout", "altium", | |
| "eagle", "kicad", "orcad"], | |
| "power electronics" : ["güç elektroniği", "inverter", "converter", | |
| "motor drive", "motor sürücü"], | |
| "signal processing" : ["sinyal işleme", "dsp", "digital signal processing", | |
| "analog design", "analog tasarım"], | |
| "scada" : ["scada systems", "hmi", "industrial automation", | |
| "endüstriyel otomasyon", "dcs"], | |
| # ── 23. OTOMOTİV ────────────────────────────────────────────────── | |
| "automotive engineering": ["otomotiv mühendisliği", "vehicle engineering", | |
| "araç mühendisliği"], | |
| "adas" : ["advanced driver assistance", "ileri sürücü destek", | |
| "autonomous driving", "otonom sürüş", "lidar", "radar"], | |
| "vehicle dynamics" : ["araç dinamiği", "suspension", "süspansiyon", | |
| "braking systems", "fren sistemleri"], | |
| "powertrain" : ["güç aktarma", "engine design", "motor tasarım", | |
| "transmission", "şanzıman", "electric vehicle", "ev"], | |
| "iso 26262" : ["functional safety", "fonksiyonel güvenlik", | |
| "asil", "automotive safety"], | |
| # ── 24. ENDÜSTRİ MÜHENDİSLİĞİ ──────────────────────────────────── | |
| "industrial engineering": ["endüstri mühendisliği", "ie", "work study", | |
| "iş etüdü", "ergonomi", "ergonomics"], | |
| "production planning" : ["üretim planlama", "mrp", "mrp ii", "erp planning", | |
| "capacity planning", "kapasite planlama"], | |
| "inventory management" : ["stok yönetimi", "inventory control", "stok kontrol", | |
| "warehouse optimization", "depo optimizasyonu"], | |
| "process optimization" : ["süreç optimizasyonu", "operational excellence", | |
| "time study", "zaman etüdü", "line balancing", | |
| "hat dengeleme", "value stream mapping"], | |
| "iso 9001" : ["quality management system", "kalite yönetim sistemi", | |
| "qms", "iso 14001", "iso 45001", "iatf 16949"], | |
| # ── 25. ROBOTİK & OTOMASYON (genişletilmiş) ─────────────────────── | |
| "robot programming" : ["robot programlama", "teach pendant", "offline programming", | |
| "abb robot", "fanuc robot", "kuka robot", "ur robot"], | |
| "machine vision" : ["makine görüşü", "industrial vision", "endüstriyel görüş", | |
| "opencv industrial", "vision inspection"], | |
| "motion control" : ["hareket kontrol", "servo motor", "stepper motor", | |
| "motion planning", "trajectory planning"], | |
| "industrial robot" : ["endüstriyel robot", "cobot", "collaborative robot", | |
| "pick and place", "palletizing", "robot cell"], | |
| "automation design" : ["otomasyon tasarım", "factory automation", | |
| "fabrika otomasyonu", "industry 4.0", "endüstri 4.0"], | |
| # ── 26. İNŞAAT & YAPI ───────────────────────────────────────────── | |
| "structural engineering": ["yapı mühendisliği", "structural analysis", | |
| "yapı analizi", "steel structure", "çelik yapı", | |
| "reinforced concrete", "betonarme"], | |
| "bim" : ["building information modeling", "yapı bilgi modellemesi", | |
| "revit", "navisworks", "archicad"], | |
| "hvac" : ["heating ventilation", "iklimlendirme", | |
| "air conditioning", "soğutma", "ısıtma"], | |
| "geotechnical" : ["geoteknik", "soil mechanics", "zemin mekaniği", | |
| "foundation design", "temel tasarımı"], | |
| } | |
| # ── Ters index: eş anlamlı → canonical form ───────────────────────────────── | |
| SYNONYM_TO_CANONICAL: Dict[str, str] = {} | |
| for canonical, synonyms in SKILL_ONTOLOGY.items(): | |
| SYNONYM_TO_CANONICAL[canonical] = canonical | |
| for syn in synonyms: | |
| SYNONYM_TO_CANONICAL[syn.lower()] = canonical | |
| # ── Kategoriler: canonical skill → kategori ────────────────────────────────── | |
| SKILL_CATEGORIES: Dict[str, str] = { | |
| # Programlama Dilleri | |
| "python": "Prog. Dili", "java": "Prog. Dili", "c#": "Prog. Dili", | |
| "c++": "Prog. Dili", "c": "Prog. Dili", "javascript": "Prog. Dili", | |
| "typescript": "Prog. Dili", "kotlin": "Prog. Dili", "swift": "Prog. Dili", | |
| "go": "Prog. Dili", "rust": "Prog. Dili", "php": "Prog. Dili", | |
| "ruby": "Prog. Dili", "scala": "Prog. Dili", "r": "Prog. Dili", | |
| "matlab": "Prog. Dili", "dart": "Prog. Dili", "perl": "Prog. Dili", | |
| "bash": "Prog. Dili", "powershell": "Prog. Dili", "groovy": "Prog. Dili", | |
| "lua": "Prog. Dili", "haskell": "Prog. Dili", "elixir": "Prog. Dili", | |
| "clojure": "Prog. Dili", "assembly": "Prog. Dili", "vba": "Prog. Dili", | |
| "visual basic": "Prog. Dili", "cobol": "Prog. Dili", "fortran": "Prog. Dili", | |
| # Web Frontend | |
| "react": "Web Frontend", "angular": "Web Frontend", "vue": "Web Frontend", | |
| "svelte": "Web Frontend", "html": "Web Frontend", "css": "Web Frontend", | |
| "tailwind": "Web Frontend", "bootstrap": "Web Frontend", | |
| "jquery": "Web Frontend", "webpack": "Web Frontend", "graphql": "Web Frontend", | |
| # Web Backend | |
| "node.js": "Backend", "django": "Backend", "flask": "Backend", | |
| "fastapi": "Backend", "spring boot": "Backend", "asp.net": "Backend", | |
| "laravel": "Backend", "nestjs": "Backend", "rest api": "Backend", | |
| "microservices": "Backend", "grpc": "Backend", | |
| "websocket": "Backend", "oauth": "Backend", | |
| # Mobil | |
| "android": "Mobil", "ios": "Mobil", "flutter": "Mobil", | |
| "react native": "Mobil", "xamarin": "Mobil", "ionic": "Mobil", | |
| # Veritabanı | |
| "sql": "Veritabanı", "mysql": "Veritabanı", "postgresql": "Veritabanı", | |
| "mssql": "Veritabanı", "oracle": "Veritabanı", "sqlite": "Veritabanı", | |
| "mariadb": "Veritabanı", "db2": "Veritabanı", | |
| "mongodb": "Veritabanı", "redis": "Veritabanı", "cassandra": "Veritabanı", | |
| "elasticsearch": "Veritabanı", "dynamodb": "Veritabanı", | |
| "neo4j": "Veritabanı", "couchdb": "Veritabanı", | |
| "firebase": "Veritabanı", "influxdb": "Veritabanı", | |
| # Bulut | |
| "aws": "Bulut", "azure": "Bulut", "google cloud": "Bulut", | |
| "alibaba cloud": "Bulut", "oracle cloud": "Bulut", | |
| # DevOps | |
| "docker": "DevOps", "kubernetes": "DevOps", "git": "DevOps", | |
| "jenkins": "DevOps", "github actions": "DevOps", "gitlab ci": "DevOps", | |
| "terraform": "DevOps", "ansible": "DevOps", "prometheus": "DevOps", | |
| "grafana": "DevOps", "nginx": "DevOps", "apache": "DevOps", | |
| "linux": "DevOps", "vagrant": "DevOps", "puppet": "DevOps", "chef": "DevOps", | |
| # Veri Mühendisliği | |
| "apache spark": "Veri Mühendisliği", "hadoop": "Veri Mühendisliği", | |
| "kafka": "Veri Mühendisliği", "airflow": "Veri Mühendisliği", | |
| "dbt": "Veri Mühendisliği", "etl": "Veri Mühendisliği", | |
| "data warehouse": "Veri Mühendisliği", "data analysis": "Veri Mühendisliği", | |
| "pandas": "Veri Mühendisliği", "numpy": "Veri Mühendisliği", | |
| "tableau": "Veri Mühendisliği", "power bi": "Veri Mühendisliği", | |
| "qlik": "Veri Mühendisliği", "looker": "Veri Mühendisliği", | |
| # AI/ML | |
| "machine learning": "Yapay Zeka", "deep learning": "Yapay Zeka", | |
| "natural language processing": "Yapay Zeka", "computer vision": "Yapay Zeka", | |
| "reinforcement learning": "Yapay Zeka", "large language model": "Yapay Zeka", | |
| "tensorflow": "Yapay Zeka", "pytorch": "Yapay Zeka", | |
| "scikit-learn": "Yapay Zeka", "hugging face": "Yapay Zeka", | |
| "langchain": "Yapay Zeka", "mlflow": "Yapay Zeka", | |
| "feature engineering": "Yapay Zeka", "xgboost": "Yapay Zeka", | |
| "time series": "Yapay Zeka", | |
| # Siber Güvenlik | |
| "cybersecurity": "Siber Güvenlik", "penetration testing": "Siber Güvenlik", | |
| "soc": "Siber Güvenlik", "network security": "Siber Güvenlik", | |
| "cryptography": "Siber Güvenlik", "devsecops": "Siber Güvenlik", | |
| "identity management": "Siber Güvenlik", | |
| # Yazılım Mimarisi | |
| "software architecture": "Yazılım Mimarisi", "microservices": "Yazılım Mimarisi", | |
| "unit testing": "Yazılım Mimarisi", "code review": "Yazılım Mimarisi", | |
| "agile": "Yazılım Mimarisi", "uml": "Yazılım Mimarisi", "clean code": "Yazılım Mimarisi", | |
| # ERP/CRM | |
| "sap": "ERP/CRM", "oracle erp": "ERP/CRM", "microsoft dynamics": "ERP/CRM", | |
| "salesforce": "ERP/CRM", "servicenow": "ERP/CRM", "jira": "ERP/CRM", | |
| # Testing | |
| "selenium": "Testing/QA", "cypress": "Testing/QA", "playwright": "Testing/QA", | |
| "appium": "Testing/QA", "postman": "Testing/QA", | |
| "load testing": "Testing/QA", "qa": "Testing/QA", | |
| # Altyapı | |
| "networking": "Sistem/Altyapı", "windows server": "Sistem/Altyapı", | |
| "virtualization": "Sistem/Altyapı", "storage": "Sistem/Altyapı", | |
| # IoT / Gömülü | |
| "iot": "IoT/Gömülü", "embedded systems": "IoT/Gömülü", | |
| "arduino": "IoT/Gömülü", "raspberry pi": "IoT/Gömülü", "fpga": "IoT/Gömülü", | |
| # Robotik | |
| "ros": "Robotik", "control systems": "Robotik", "robotics": "Robotik", | |
| "plc": "Robotik", "solidworks": "Robotik", | |
| # Lojistik | |
| "supply chain": "Lojistik", "vehicle routing": "Lojistik", | |
| "demand forecasting": "Lojistik", "warehouse management": "Lojistik", | |
| "operations research": "Lojistik", | |
| # Soft Skills | |
| "project management": "Soft Skill", "team leadership": "Soft Skill", | |
| "communication": "Soft Skill", "problem solving": "Soft Skill", | |
| # İş Analizi & Yönetim | |
| "business analysis": "İş Analizi", "requirements engineering": "İş Analizi", | |
| "process modeling": "İş Analizi", "stakeholder management": "İş Analizi", | |
| "user story": "İş Analizi", "use case": "İş Analizi", | |
| "business intelligence": "İş Analizi", "data visualization": "İş Analizi", | |
| "kpi": "İş Analizi", "excel": "İş Analizi", | |
| "financial analysis": "İş Analizi", "risk management": "İş Analizi", | |
| "change management": "İş Analizi", "product management": "İş Analizi", | |
| "ux research": "İş Analizi", "erp consulting": "İş Analizi", | |
| "crm": "İş Analizi", "digital marketing": "İş Analizi", | |
| "six sigma": "İş Analizi", | |
| # Mekanik Mühendislik | |
| "mechanical design": "Mekanik Müh.", "cad": "Mekanik Müh.", "cam": "Mekanik Müh.", | |
| "fea": "Mekanik Müh.", "cfd": "Mekanik Müh.", "thermodynamics": "Mekanik Müh.", | |
| "materials science": "Mekanik Müh.", "manufacturing": "Mekanik Müh.", | |
| "quality control": "Mekanik Müh.", "tolerance analysis": "Mekanik Müh.", | |
| "cnc": "Mekanik Müh.", | |
| # Elektrik & Elektronik | |
| "electrical engineering": "Elektrik/Elektronik", "pcb design": "Elektrik/Elektronik", | |
| "power electronics": "Elektrik/Elektronik", "signal processing": "Elektrik/Elektronik", | |
| "scada": "Elektrik/Elektronik", | |
| # Otomotiv | |
| "automotive engineering": "Otomotiv", "adas": "Otomotiv", | |
| "vehicle dynamics": "Otomotiv", "powertrain": "Otomotiv", | |
| "iso 26262": "Otomotiv", | |
| # Endüstri Mühendisliği | |
| "industrial engineering": "Endüstri Müh.", "production planning": "Endüstri Müh.", | |
| "inventory management": "Endüstri Müh.", "process optimization": "Endüstri Müh.", | |
| "iso 9001": "Endüstri Müh.", | |
| # Robotik & Otomasyon (genişletilmiş) | |
| "robot programming": "Robotik", "machine vision": "Robotik", | |
| "motion control": "Robotik", "industrial robot": "Robotik", | |
| "automation design": "Robotik", | |
| # İnşaat & Yapı | |
| "structural engineering": "İnşaat/Yapı", "bim": "İnşaat/Yapı", | |
| "hvac": "İnşaat/Yapı", "geotechnical": "İnşaat/Yapı", | |
| } | |
| # ── Özet istatistik ────────────────────────────────────────────────────────── | |
| def extract_text_from_pdf(pdf_path: str, dual_column: bool = False) -> str: | |
| """ | |
| PDF'den metin çıkarır. | |
| dual_column=True → iki sütunlu CV formatlarını düzgün ayrıştırır | |
| """ | |
| doc = fitz.open(pdf_path) | |
| full_text = "" | |
| for page in doc: | |
| if dual_column: | |
| # Dual-list CV sorununun çözümü: clip ile sol/sağ sütun ayrı okunur | |
| width = page.rect.width | |
| left_rect = fitz.Rect(0, 0, width / 2, page.rect.height) | |
| right_rect = fitz.Rect(width / 2, 0, width, page.rect.height) | |
| left_text = page.get_text("text", clip=left_rect) | |
| right_text = page.get_text("text", clip=right_rect) | |
| full_text += left_text + "\n" + right_text | |
| else: | |
| full_text += page.get_text("text") | |
| doc.close() | |
| return full_text.strip() | |
| def clean_text(text: str) -> str: | |
| """Metni normalize eder: fazla boşlukları temizler, lowercase yapar""" | |
| text = re.sub(r'\s+', ' ', text) | |
| text = text.strip() | |
| return text | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # FAZ 1 — ENTITY EXTRACTION: Konum, Üniversite, Firma, Dil | |
| # | |
| # Literatür Boşlukları: | |
| # 1. "Shallow Parsing" → Mevcut sistemler sadece skill çıkarır, yapısal | |
| # bilgileri (konum, eğitim, firma) ihmal eder. | |
| # 2. "Geographic Bias" → Hard location filter aday havuzunu daraltır. | |
| # Bu sistem soft scoring kullanır, konum uyarır ama elemez. | |
| # 3. "Single-Entity NER" → spaCy NER'i GPE+ORG+NORP ile çoklu entity | |
| # çıkarma yaparak zengin aday profili oluşturur. | |
| # 4. "Title Mismatch" → İş tanımı bazlı eşleştirme, title uyumsuzluğunu | |
| # ortadan kaldırır (iş ilanı CV ile doğrudan karşılaştırılır). | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # ── Türkiye şehir listesi (konum çıkarma için) ───────────────────────────── | |
| TR_CITIES = { | |
| "istanbul","ankara","izmir","bursa","antalya","adana","konya","gaziantep", | |
| "mersin","diyarbakır","kayseri","eskişehir","trabzon","samsun","denizli", | |
| "malatya","erzurum","van","batman","şanlıurfa","sakarya","kocaeli", | |
| "tekirdağ","manisa","balıkesir","elazığ","muğla","bolu","düzce", | |
| "çanakkale","aydın","hatay","kahramanmaraş","afyon","aksaray","sivas", | |
| "tokat","yozgat","çorum","kastamonu","rize","artvin","giresun","ordu", | |
| } | |
| # ── Üniversite anahtar kelimeleri ───────────────────────────────────────── | |
| UNIVERSITY_KEYWORDS = [ | |
| "university", "üniversitesi", "üniversite", "institute of technology", | |
| "polytechnic", "college", "école", "hochschule", "universität", | |
| "fakülte", "faculty", "mühendislik fakültesi", "engineering faculty", | |
| "yüksek lisans", "master", "bachelor", "lisans", "doktora", "phd", | |
| "mba", "msc", "bsc", "bs", "ms", | |
| ] | |
| # ── Dil anahtar kelimeleri ───────────────────────────────────────────────── | |
| LANGUAGE_KEYWORDS = { | |
| "turkish": "Türkçe", "türkçe": "Türkçe", | |
| "english": "İngilizce", "ingilizce": "İngilizce", | |
| "german": "Almanca", "almanca": "Almanca", "deutsch": "Almanca", | |
| "french": "Fransızca", "fransızca": "Fransızca", "français": "Fransızca", | |
| "spanish": "İspanyolca", "ispanyolca": "İspanyolca", "español": "İspanyolca", | |
| "arabic": "Arapça", "arapça": "Arapça", | |
| "russian": "Rusça", "rusça": "Rusça", | |
| "chinese": "Çince", "çince": "Çince", "mandarin": "Çince", | |
| "japanese": "Japonca", "japonca": "Japonca", | |
| "korean": "Korece", "korece": "Korece", | |
| "italian": "İtalyanca", "italyanca": "İtalyanca", | |
| "portuguese": "Portekizce", "portekizce": "Portekizce", | |
| "dutch": "Hollandaca", "hollandaca": "Hollandaca", | |
| } | |
| def extract_location(text: str) -> Dict: | |
| """ | |
| CV veya iş ilanından konum bilgisi çıkarır. | |
| Yöntem: spaCy NER (GPE) + Türkiye şehir listesi + pattern matching. | |
| Literatür katkısı: Çoğu sistem binary konum filtresi kullanır. | |
| Bu fonksiyon soft bilgi çıkarır, eşleştirme aşamasında ağırlıklandırılır. | |
| Returns: {"city": str|None, "country": str|None, "remote": bool, "raw_locations": list} | |
| """ | |
| nlp_model = get_nlp() # spaCy modeli | |
| doc = nlp_model(text[:5000]) # İlk 5000 karakter yeterli | |
| raw_locations = [] | |
| for ent in doc.ents: | |
| if ent.label_ == "GPE": | |
| raw_locations.append(ent.text.strip()) | |
| # Türkiye şehir eşleştirmesi | |
| text_lower = text.lower() | |
| city = None | |
| country = None | |
| for c in TR_CITIES: | |
| if re.search(r'\b' + re.escape(c) + r'\b', text_lower): | |
| city = c.title() | |
| country = "Türkiye" | |
| break | |
| # NER'den çıkan konumları da değerlendir | |
| if not city and raw_locations: | |
| city = raw_locations[0] | |
| # Ülke tespiti (yaygın ülke adları) | |
| country_map = { | |
| "turkey": "Türkiye", "türkiye": "Türkiye", "germany": "Almanya", | |
| "united states": "ABD", "usa": "ABD", "uk": "İngiltere", | |
| "united kingdom": "İngiltere", "netherlands": "Hollanda", | |
| "france": "Fransa", "canada": "Kanada", "australia": "Avustralya", | |
| "uae": "BAE", "dubai": "BAE", "singapore": "Singapur", | |
| } | |
| if not country: | |
| for key, val in country_map.items(): | |
| if re.search(r'\b' + re.escape(key) + r'\b', text_lower): | |
| country = val | |
| break | |
| # Remote/uzaktan çalışma tespiti | |
| remote = bool(re.search( | |
| r'\b(remote|uzaktan|home.?office|hybrid|hibrit|work from home|evden çalışma)\b', | |
| text_lower | |
| )) | |
| return { | |
| "city": city, | |
| "country": country, | |
| "remote": remote, | |
| "raw_locations": list(set(raw_locations)), | |
| } | |
| # ── Türkiye Üniversiteleri (dropdown + matching için) ────────────────────── | |
| TR_UNIVERSITIES = [ | |
| "Abdullah Gül Üniversitesi", "Acıbadem Üniversitesi", "Adana Alparslan Türkeş Bilim ve Teknoloji Üniversitesi", | |
| "Adıyaman Üniversitesi", "Afyon Kocatepe Üniversitesi", "Afyonkarahisar Sağlık Bilimleri Üniversitesi", | |
| "Ağrı İbrahim Çeçen Üniversitesi", "Akdeniz Üniversitesi", "Aksaray Üniversitesi", "Alanya Alaaddin Keykubat Üniversitesi", | |
| "Alanya Üniversitesi", "Altınbaş Üniversitesi", "Amasya Üniversitesi", "Anadolu Üniversitesi", "Anka Teknoloji Üniversitesi", | |
| "Ankara Bilim Üniversitesi", "Ankara Hacı Bayram Veli Üniversitesi", "Ankara Medipol Üniversitesi", | |
| "Ankara Müzik ve Güzel Sanatlar Üniversitesi", "Ankara Sosyal Bilimler Üniversitesi", "Ankara Üniversitesi", | |
| "Antalya Belek Üniversitesi", "Antalya Bilim Üniversitesi", "Ardahan Üniversitesi", "Artvin Çoruh Üniversitesi", | |
| "Atatürk Üniversitesi", "Atılım Üniversitesi", "Avrasya Üniversitesi", "Aydın Adnan Menderes Üniversitesi", | |
| "Bahçeşehir Üniversitesi", "Balıkesir Üniversitesi", "Bandırma Onyedi Eylül Üniversitesi", "Bartın Üniversitesi", | |
| "Başkent Üniversitesi", "Batman Üniversitesi", "Bayburt Üniversitesi", "Beykent Üniversitesi", "Beykoz Üniversitesi", | |
| "Bezmiâlem Vakıf Üniversitesi", "Bilecik Şeyh Edebali Üniversitesi", "Bingöl Üniversitesi", "Biruni Üniversitesi", | |
| "Bitlis Eren Üniversitesi", "Bizim Tepe Üniversitesi", "Boğaziçi Üniversitesi", "Bolu Abant İzzet Baysal Üniversitesi", | |
| "Burdur Mehmet Akif Ersoy Üniversitesi", "Bursa Teknik Üniversitesi", "Bursa Uludağ Üniversitesi", "Çağ Üniversitesi", | |
| "Çanakkale Onsekiz Mart Üniversitesi", "Çankaya Üniversitesi", "Çankırı Karatekin Üniversitesi", "Çukurova Üniversitesi", | |
| "Demiroğlu Bilim Üniversitesi", "Dicle Üniversitesi", "Doğuş Üniversitesi", "Dokuz Eylül Üniversitesi", | |
| "Dumlupınar Üniversitesi", "Düzce Üniversitesi", "Ege Üniversitesi", "Erciyes Üniversitesi", "Erzincan Binali Yıldırım Üniversitesi", | |
| "Erzurum Teknik Üniversitesi", "Eskişehir Osmangazi Üniversitesi", "Eskişehir Teknik Üniversitesi", | |
| "Fatih Sultan Mehmet Vakıf Üniversitesi", "Fenerbahçe Üniversitesi", "Fırat Üniversitesi", "Galatasaray Üniversitesi", | |
| "Gazi Üniversitesi", "Gaziantep İslam Bilim ve Teknoloji Üniversitesi", "Gaziantep Üniversitesi", "Gebze Teknik Üniversitesi", | |
| "Giresun Üniversitesi", "Gümüşhane Üniversitesi", "Hacettepe Üniversitesi", "Hakkari Üniversitesi", "Haliç Üniversitesi", | |
| "Harran Üniversitesi", "Hasan Kalyoncu Üniversitesi", "Hitit Üniversitesi", "Iğdır Üniversitesi", "Işık Üniversitesi", | |
| "İbn Haldun Üniversitesi", "İhsan Doğramacı Bilkent Üniversitesi", "İnönü Üniversitesi", "İskenderun Teknik Üniversitesi", | |
| "İstanbul Atlas Üniversitesi", "İstanbul Arel Üniversitesi", "İstanbul Aydın Üniversitesi", "İstanbul Bilgi Üniversitesi", | |
| "İstanbul Esenyurt Üniversitesi", "İstanbul Galata Üniversitesi", "İstanbul Gedik Üniversitesi", "İstanbul Gelişim Üniversitesi", | |
| "İstanbul Kent Üniversitesi", "İstanbul Kültür Üniversitesi", "İstanbul Medeniyet Üniversitesi", "İstanbul Medipol Üniversitesi", | |
| "İstanbul Okan Üniversitesi", "İstanbul Rumeli Üniversitesi", "İstanbul Sabahattin Zaim Üniversitesi", | |
| "İstanbul Sağlık ve Teknoloji Üniversitesi", "İstanbul Teknik Üniversitesi", "İstanbul Ticaret Üniversitesi", | |
| "İstanbul Üniversitesi", "İstanbul Üniversitesi-Cerrahpaşa", "İstanbul Yeni Yüzyıl Üniversitesi", "İstanbul 29 Mayıs Üniversitesi", | |
| "İstinye Üniversitesi", "İzmir Bakırçay Üniversitesi", "İzmir Demokrasi Üniversitesi", "İzmir Ekonomi Üniversitesi", | |
| "İzmir Katip Çelebi Üniversitesi", "İzmir Tınaztepe Üniversitesi", "İzmir Yüksek Teknoloji Enstitüsü", "Kadir Has Üniversitesi", | |
| "Kafkas Üniversitesi", "Kahramanmaraş Sütçü İmam Üniversitesi", "Kahramanmaraş İstiklal Üniversitesi", "Karabük Üniversitesi", | |
| "Karadeniz Teknik Üniversitesi", "Karamanoğlu Mehmetbey Üniversitesi", "Kastamonu Üniversitesi", "Kayseri Üniversitesi", | |
| "Kırıkkale Üniversitesi", "Kırklareli Üniversitesi", "Kırşehir Ahi Evran Üniversitesi", "Kilis 7 Aralık Üniversitesi", | |
| "Kocaeli Sağlık ve Teknoloji Üniversitesi", "Kocaeli Üniversitesi", "Koç Üniversitesi", "Konya Gıda ve Tarım Üniversitesi", | |
| "Konya Teknik Üniversitesi", "KTO Karatay Üniversitesi", "Kütahya Dumlupınar Üniversitesi", "Kütahya Sağlık Bilimleri Üniversitesi", | |
| "Lokman Hekim Üniversitesi", "Malatya Turgut Özal Üniversitesi", "Maltepe Üniversitesi", "Manisa Celal Bayar Üniversitesi", | |
| "Mardin Artuklu Üniversitesi", "Marmara Üniversitesi", "MEF Üniversitesi", "Mersin Üniversitesi", "Mimar Sinan Güzel Sanatlar Üniversitesi", | |
| "Mudanya Üniversitesi", "Muğla Sıtkı Koçman Üniversitesi", "Munzur Üniversitesi", "Muş Alparslan Üniversitesi", | |
| "Necmettin Erbakan Üniversitesi", "Nevşehir Hacı Bektaş Veli Üniversitesi", "Niğde Ömer Halisdemir Üniversitesi", | |
| "Nişantaşı Üniversitesi", "Nuh Naci Yazgan Üniversitesi", "Ondokuz Mayıs Üniversitesi", "Ordu Üniversitesi", | |
| "Orta Doğu Teknik Üniversitesi", "Osmaniye Korkut Ata Üniversitesi", "Ostim Teknik Üniversitesi", "Özyeğin Üniversitesi", | |
| "Pamukkale Üniversitesi", "Piri Reis Üniversitesi", "Recep Tayyip Erdoğan Üniversitesi", "Sabancı Üniversitesi", | |
| "Sağlık Bilimleri Üniversitesi", "Sakarya Uygulamalı Bilimler Üniversitesi", "Sakarya Üniversitesi", "Samsun Üniversitesi", | |
| "Sanko Üniversitesi", "Selçuk Üniversitesi", "Siirt Üniversitesi", "Sinop Üniversitesi", "Sivas Bilim ve Teknoloji Üniversitesi", | |
| "Sivas Cumhuriyet Üniversitesi", "Süleyman Demirel Üniversitesi", "Şırnak Üniversitesi", "Tarsus Üniversitesi", "TED Üniversitesi", | |
| "Tekirdağ Namık Kemal Üniversitesi", "TOBB Ekonomi ve Teknoloji Üniversitesi", "Tokat Gaziosmanpaşa Üniversitesi", | |
| "Toros Üniversitesi", "Trabzon Üniversitesi", "Trakya Üniversitesi", "Türk-Alman Üniversitesi", "Türk Hava Kurumu Üniversitesi", | |
| "Türkiye Uluslararası İslam, Bilim ve Teknoloji Üniversitesi", "Ufuk Üniversitesi", "Uşak Üniversitesi", "Üsküdar Üniversitesi", | |
| "Yalova Üniversitesi", "Yaşar Üniversitesi", "Yeditepe Üniversitesi", "Yıldız Teknik Üniversitesi", "Yozgat Bozok Üniversitesi", | |
| "Yüksek İhtisas Üniversitesi", "Zonguldak Bülent Ecevit Üniversitesi" | |
| ] | |
| # İngilizce karşılıklar, kısaltmalar ve karakter normalizasyonu | |
| TR_UNI_EN_MAP = { | |
| # Popüler Kısaltmalar ve İngilizce Karşılıklar | |
| "metu": "Orta Doğu Teknik Üniversitesi", | |
| "odtü": "Orta Doğu Teknik Üniversitesi", | |
| "odtu": "Orta Doğu Teknik Üniversitesi", | |
| "middle east technical university": "Orta Doğu Teknik Üniversitesi", | |
| "itu": "İstanbul Teknik Üniversitesi", | |
| "itü": "İstanbul Teknik Üniversitesi", | |
| "istanbul technical university": "İstanbul Teknik Üniversitesi", | |
| "ytu": "Yıldız Teknik Üniversitesi", | |
| "ytü": "Yıldız Teknik Üniversitesi", | |
| "yildiz technical university": "Yıldız Teknik Üniversitesi", | |
| "boun": "Boğaziçi Üniversitesi", | |
| "bogazici university": "Boğaziçi Üniversitesi", | |
| "boğaziçi university": "Boğaziçi Üniversitesi", | |
| "hacettepe university": "Hacettepe Üniversitesi", | |
| "bilkent": "İhsan Doğramacı Bilkent Üniversitesi", | |
| "bilkent university": "İhsan Doğramacı Bilkent Üniversitesi", | |
| "sabanci university": "Sabancı Üniversitesi", | |
| "koc university": "Koç Üniversitesi", | |
| "ozyegin university": "Özyeğin Üniversitesi", | |
| "marmara university": "Marmara Üniversitesi", | |
| "istanbul university": "İstanbul Üniversitesi", | |
| "ankara university": "Ankara Üniversitesi", | |
| "ege university": "Ege Üniversitesi", | |
| "dokuz eylul university": "Dokuz Eylül Üniversitesi", | |
| "gazi university": "Gazi Üniversitesi", | |
| "gebze technical university": "Gebze Teknik Üniversitesi", | |
| "gtu": "Gebze Teknik Üniversitesi", | |
| "iyte": "İzmir Yüksek Teknoloji Enstitüsü", | |
| "izmir institute of technology": "İzmir Yüksek Teknoloji Enstitüsü", | |
| # Liste Geneli (İngilizce Karakter ve 'University' Varyasyonları) | |
| "abdullah gul university": "Abdullah Gül Üniversitesi", | |
| "agu": "Abdullah Gül Üniversitesi", | |
| "acibadem university": "Acıbadem Üniversitesi", | |
| "adana alparslan turkes science and technology university": "Adana Alparslan Türkeş Bilim ve Teknoloji Üniversitesi", | |
| "adiyaman university": "Adıyaman Üniversitesi", | |
| "afyon kocatepe university": "Afyon Kocatepe Üniversitesi", | |
| "agri ibrahim cecen university": "Ağrı İbrahim Çeçen Üniversitesi", | |
| "akdeniz university": "Akdeniz Üniversitesi", | |
| "aksaray university": "Aksaray Üniversitesi", | |
| "alanya alaaddin keykubat university": "Alanya Alaaddin Keykubat Üniversitesi", | |
| "altinbas university": "Altınbaş Üniversitesi", | |
| "amasya university": "Amasya Üniversitesi", | |
| "anadolu university": "Anadolu Üniversitesi", | |
| "atilim university": "Atılım Üniversitesi", | |
| "bahcesehir university": "Bahçeşehir Üniversitesi", | |
| "bau": "Bahçeşehir Üniversitesi", | |
| "balikesir university": "Balıkesir Üniversitesi", | |
| "baskent university": "Başkent Üniversitesi", | |
| "beykent university": "Beykent Üniversitesi", | |
| "bezmialem vakif university": "Bezmiâlem Vakıf Üniversitesi", | |
| "bilecik seyh edebali university": "Bilecik Şeyh Edebali Üniversitesi", | |
| "bingol university": "Bingöl Üniversitesi", | |
| "biruni university": "Biruni Üniversitesi", | |
| "bolu abant izzet baysal university": "Bolu Abant İzzet Baysal Üniversitesi", | |
| "bursa uludag university": "Bursa Uludağ Üniversitesi", | |
| "canakkale onsekiz mart university": "Çanakkale Onsekiz Mart Üniversitesi", | |
| "comu": "Çanakkale Onsekiz Mart Üniversitesi", | |
| "cankaya university": "Çankaya Üniversitesi", | |
| "cukurova university": "Çukurova Üniversitesi", | |
| "dicle university": "Dicle Üniversitesi", | |
| "dogus university": "Doğuş Üniversitesi", | |
| "dumlupinar university": "Kütahya Dumlupınar Üniversitesi", | |
| "duzce university": "Düzce Üniversitesi", | |
| "erciyes university": "Erciyes Üniversitesi", | |
| "erzincan binali yildirim university": "Erzincan Binali Yıldırım Üniversitesi", | |
| "eskisehir osmangazi university": "Eskişehir Osmangazi Üniversitesi", | |
| "ogü": "Eskişehir Osmangazi Üniversitesi", | |
| "eskisehir technical university": "Eskişehir Teknik Üniversitesi", | |
| "estü": "Eskişehir Teknik Üniversitesi", | |
| "fatih sultan mehmet vakif university": "Fatih Sultan Mehmet Vakıf Üniversitesi", | |
| "fırat university": "Fırat Üniversitesi", | |
| "galatasaray university": "Galatasaray Üniversitesi", | |
| "gaziantep university": "Gaziantep Üniversitesi", | |
| "haliç university": "Haliç Üniversitesi", | |
| "inonu university": "İnönü Üniversitesi", | |
| "iskenderun technical university": "İskenderun Teknik Üniversitesi", | |
| "iste": "İskenderun Teknik Üniversitesi", | |
| "istanbul aydın university": "İstanbul Aydın Üniversitesi", | |
| "istanbul bilgi university": "İstanbul Bilgi Üniversitesi", | |
| "istanbul gelisim university": "İstanbul Gelişim Üniversitesi", | |
| "istanbul medipol university": "İstanbul Medipol Üniversitesi", | |
| "istinye university": "İstinye Üniversitesi", | |
| "izmir economy university": "İzmir Ekonomi Üniversitesi", | |
| "kadir has university": "Kadir Has Üniversitesi", | |
| "karabuk university": "Karabük Üniversitesi", | |
| "karadeniz technical university": "Karadeniz Teknik Üniversitesi", | |
| "ktu": "Karadeniz Teknik Üniversitesi", | |
| "kastamonu university": "Kastamonu Üniversitesi", | |
| "kirikkale university": "Kırıkkale Üniversitesi", | |
| "kocaeli university": "Kocaeli Üniversitesi", | |
| "maltepe university": "Maltepe Üniversitesi", | |
| "manisa celal bayar university": "Manisa Celal Bayar Üniversitesi", | |
| "mef university": "MEF Üniversitesi", | |
| "mersin university": "Mersin Üniversitesi", | |
| "mugla sitki kocman university": "Muğla Sıtkı Koçman Üniversitesi", | |
| "namik kemal university": "Tekirdağ Namık Kemal Üniversitesi", | |
| "nisantasi university": "Nişantaşı Üniversitesi", | |
| "ondokuz mayis university": "Ondokuz Mayıs Üniversitesi", | |
| "omu": "Ondokuz Mayıs Üniversitesi", | |
| "ordu university": "Ordu Üniversitesi", | |
| "pamukkale university": "Pamukkale Üniversitesi", | |
| "paü": "Pamukkale Üniversitesi", | |
| "recep tayyip erdogan university": "Recep Tayyip Erdoğan Üniversitesi", | |
| "sakarya university": "Sakarya Üniversitesi", | |
| "sau": "Sakarya Üniversitesi", | |
| "selcuk university": "Selçuk Üniversitesi", | |
| "suleyman demirel university": "Süleyman Demirel Üniversitesi", | |
| "sdü": "Süleyman Demirel Üniversitesi", | |
| "tobb etu": "TOBB Ekonomi ve Teknoloji Üniversitesi", | |
| "tobb university of economics and technology": "TOBB Ekonomi ve Teknoloji Üniversitesi", | |
| "trakya university": "Trakya Üniversitesi", | |
| "turkish-german university": "Türk-Alman Üniversitesi", | |
| "tau": "Türk-Alman Üniversitesi", | |
| "uskudar university": "Üsküdar Üniversitesi", | |
| "yasar university": "Yaşar Üniversitesi", | |
| "yeditepe university": "Yeditepe Üniversitesi", | |
| "zonguldak bulent ecevit university": "Zonguldak Bülent Ecevit Üniversitesi" | |
| } | |
| def extract_universities(text: str) -> List[str]: | |
| """ | |
| CV'den üniversite adlarını çıkarır. | |
| 3 katmanlı yaklaşım: | |
| 1. TR üniversite listesinden doğrudan eşleştirme (EN + TR) | |
| 2. "University/Üniversite" keyword'ü içeren satırları yakala | |
| 3. Eğitim bölümü context'inde arama | |
| Returns: Bulunan üniversite adlarının listesi. | |
| """ | |
| text_lower = text.lower() | |
| found = [] | |
| # Katman 1: TR üniversite listesinden doğrudan eşleştirme | |
| for en_name, tr_name in TR_UNI_EN_MAP.items(): | |
| if en_name in text_lower: | |
| found.append(tr_name) | |
| # Katman 2: Genel "university/üniversite" pattern'i (uluslararası üniversiteler için) | |
| uni_patterns = [ | |
| r"(?:university of [\w\s]+|[\w\s]+ university)", | |
| r"(?:[\w\s]+ üniversitesi)", | |
| r"(?:institute of technology|polytechnic|école|hochschule)", | |
| ] | |
| for pattern in uni_patterns: | |
| matches = re.findall(pattern, text_lower) | |
| for m in matches: | |
| clean = m.strip().title() | |
| if len(clean) > 5 and clean not in found: | |
| # TR listesinde zaten varsa tekrar ekleme | |
| if not any(clean.lower() in f.lower() for f in found): | |
| found.append(clean) | |
| # Katman 3: Derece bilgisi ile satır bazlı arama (Bachelor, Master, PhD) | |
| degree_keywords = ["bachelor", "master", "phd", "lisans", "yüksek lisans", "doktora", "mba"] | |
| for line in text.split("\n"): | |
| line_clean = line.strip() | |
| line_lower = line_clean.lower() | |
| if any(dk in line_lower for dk in degree_keywords): | |
| # Bu satırda veya bir sonraki satırda üniversite adı olabilir | |
| for kw in ["university", "üniversite", "institute", "college"]: | |
| if kw in line_lower and len(line_clean) > 10: | |
| clean = re.sub(r"\s+", " ", line_clean) | |
| if len(clean) < 120 and not any(clean.lower()[:30] in f.lower() for f in found): | |
| found.append(clean[:80]) | |
| break | |
| # Tekrarları kaldır | |
| seen = set() | |
| unique = [] | |
| for f in found: | |
| key = f.lower()[:30] | |
| if key not in seen: | |
| seen.add(key) | |
| unique.append(f) | |
| return unique[:5] | |
| def extract_companies(text: str) -> List[str]: | |
| """ | |
| CV'den firma/şirket adlarını çıkarır. | |
| İş deneyimi bölümüne odaklanan context-aware yaklaşım. | |
| Yöntem: | |
| 1. İş deneyimi bölümünü tespit et (EXPERIENCE, WORK, İŞ DENEYİMİ vb.) | |
| 2. Firma-konum pattern'i ara: "FirmaAdı | Şehir" veya "FirmaAdı, Şehir" | |
| 3. spaCy NER ORG'u sadece bu bölümde çalıştır | |
| 4. Geniş skip listesi ile tool/skill/üniversite adlarını filtrele | |
| Returns: Bulunan firma adlarının listesi. | |
| """ | |
| companies = [] | |
| # Yöntem 1: "Firma | Şehir" veya "Firma, Şehir, Country" pattern'i (en güvenilir) | |
| # Örnek: "Horoz Lojistik | Istanbul, Turkey" | |
| # Örnek: "DataWorks Consultancy Austin, TX" | |
| company_patterns = [ | |
| r"^\s*([A-ZÇĞİÖŞÜa-zçğıöşü][\w\s&.,-]+?)\s*[|·•]\s*[A-ZÇĞİÖŞÜ][\w\s,]+$", | |
| r"^\s*([A-ZÇĞİÖŞÜ][\w\s&.]+?)\s+(?:Istanbul|Ankara|İstanbul|Izmir|İzmir|Bursa|San\s+\w+|New\s+\w+|Los\s+\w+|Dallas|Austin|Seattle|Chicago|London|Berlin|Munich|Dubai)", | |
| ] | |
| lines = text.split("\n") | |
| in_experience = False | |
| for line in lines: | |
| line_clean = line.strip() | |
| line_lower = line_clean.lower() | |
| # İş deneyimi bölümünü tespit et | |
| if any(kw in line_lower for kw in ["experience", "work experience", "iş deneyimi", "deneyim"]): | |
| in_experience = True | |
| continue | |
| # Eğitim bölümüne gelince çık | |
| if any(kw in line_lower for kw in ["education", "eğitim", "certif", "sertifika", "project"]): | |
| in_experience = False | |
| continue | |
| if in_experience: | |
| for pattern in company_patterns: | |
| m = re.search(pattern, line_clean, re.MULTILINE) | |
| if m: | |
| name = m.group(1).strip() | |
| name = re.sub(r"\s+", " ", name) | |
| if 3 < len(name) < 50: | |
| companies.append(name) | |
| # Yöntem 2: spaCy NER (yedek, sadece iş deneyimi bölümü bulunamazsa) | |
| if not companies: | |
| nlp_model = get_nlp() | |
| doc = nlp_model(text[:5000]) | |
| # Geniş skip listesi — tool, skill, dil, üniversite, kişi adı parçaları | |
| skip_terms = { | |
| "python", "java", "sql", "html", "css", "react", "angular", "vue", | |
| "docker", "kubernetes", "aws", "azure", "gcp", "linux", "git", | |
| "github", "gitlab", "jira", "confluence", "slack", "trello", | |
| "tensorflow", "pytorch", "scikit-learn", "pandas", "numpy", "scipy", | |
| "power bi", "tableau", "excel", "spss", "knime", "r", | |
| "flask", "django", "fastapi", "node.js", "spring", | |
| "postgresql", "mysql", "mongodb", "redis", "kafka", | |
| "bachelor", "master", "phd", "mba", "university", | |
| "üniversite", "enstitü", "fakülte", "academy", | |
| "english", "turkish", "german", "french", "spanish", | |
| "native", "intermediate", "advanced", "fluent", | |
| "data scientist", "senior", "junior", "intern", "engineer", | |
| "manager", "analyst", "developer", "designer", "consultant", | |
| "stored procedures", "query optimization", | |
| "tidyverse", "beautifulsoup", "selenium", "opencv", "tesseract", | |
| "datacamp", "coursera", "udemy", "qwiklabs", | |
| "california", "texas", "new york", | |
| } | |
| for ent in doc.ents: | |
| if ent.label_ == "ORG": | |
| name = ent.text.strip() | |
| name_lower = name.lower() | |
| # Filtreler | |
| if len(name) <= 2: | |
| continue | |
| if name_lower in skip_terms: | |
| continue | |
| if any(skip in name_lower for skip in [ | |
| "university", "üniversite", "fakülte", "college", | |
| "institute", "academy", "school", | |
| "certification", "certificate", | |
| ]): | |
| continue | |
| # Tek kelime ve çok kısa = muhtemelen tool adı | |
| if len(name.split()) == 1 and len(name) < 6: | |
| continue | |
| companies.append(name) | |
| # Tekrarları kaldır | |
| seen = set() | |
| unique = [] | |
| for c in companies: | |
| key = c.lower().strip() | |
| if key not in seen and len(key) > 2: | |
| seen.add(key) | |
| unique.append(c) | |
| return unique[:10] | |
| def extract_languages(text: str) -> List[str]: | |
| """ | |
| CV'den bilinen dilleri çıkarır. | |
| Returns: Normalize edilmiş dil adları listesi. | |
| """ | |
| text_lower = text.lower() | |
| found = set() | |
| for keyword, lang_name in LANGUAGE_KEYWORDS.items(): | |
| if re.search(r'\b' + re.escape(keyword) + r'\b', text_lower): | |
| found.add(lang_name) | |
| return sorted(found) | |
| def location_score(cv_loc: Dict, job_loc: Dict) -> float: | |
| """ | |
| Konum uyumu skoru (0-1). | |
| Skorlama mantığı (HR feedback'e göre): | |
| - Aynı şehir → 1.0 | |
| - Aynı ülke, farklı şehir → 0.7 | |
| - CV veya iş ilanı remote → 0.8 | |
| - Farklı ülke → 0.4 | |
| - Konum bilgisi bulunamadı → 0.5 (nötr, cezalandırma yok) | |
| Kritik tasarım kararı: | |
| HR: "illaki konum uzakta diye CV'si uyuyorsa elenmez" | |
| Bu yüzden minimum skor 0.4 — hiçbir zaman 0 olmaz. | |
| """ | |
| cv_city = (cv_loc.get("city") or "").lower() | |
| cv_country = (cv_loc.get("country") or "").lower() | |
| job_city = (job_loc.get("city") or "").lower() | |
| job_country= (job_loc.get("country") or "").lower() | |
| # Bilgi yoksa nötr skor | |
| if not cv_city and not cv_country: | |
| return 0.5 | |
| if not job_city and not job_country: | |
| return 0.5 | |
| # Remote = yüksek skor | |
| if cv_loc.get("remote") or job_loc.get("remote"): | |
| return 0.8 | |
| # Aynı şehir | |
| if cv_city and job_city and cv_city == job_city: | |
| return 1.0 | |
| # Aynı ülke | |
| if cv_country and job_country and cv_country == job_country: | |
| return 0.7 | |
| # Farklı ülke — düşük ama SIFIR DEĞİL | |
| return 0.4 | |
| def keyword_search(text: str, keywords: str) -> Dict[str, bool]: | |
| """ | |
| CV metninde belirli anahtar kelimeleri arar. | |
| HR: "bazı diller - kelimeler aranabilmeli" | |
| Args: | |
| text: CV metni | |
| keywords: Virgülle ayrılmış anahtar kelimeler | |
| Returns: {keyword: bulundu_mu} | |
| """ | |
| if not keywords.strip(): | |
| return {} | |
| text_lower = text.lower() | |
| results = {} | |
| for kw in keywords.split(","): | |
| kw = kw.strip() | |
| if kw: | |
| results[kw] = bool(re.search(r'\b' + re.escape(kw.lower()) + r'\b', text_lower)) | |
| return results | |
| def extract_skills(text: str) -> Dict[str, List[str]]: | |
| """ | |
| Ontoloji tabanlı skill extraction. | |
| Hem canonical hem eş anlamlıları tanır. | |
| Returns: | |
| { | |
| 'canonical_skills': [...], # normalize edilmiş beceriler | |
| 'raw_matches': [...], # ham eşleşmeler | |
| 'by_category': {...} # kategori → beceriler | |
| } | |
| """ | |
| text_lower = text.lower() | |
| canonical_found = set() | |
| raw_matches = [] | |
| for term, canonical in SYNONYM_TO_CANONICAL.items(): | |
| # Kelime sınırlarına dikkat ederek eşleştir | |
| pattern = r'\b' + re.escape(term) + r'\b' | |
| if re.search(pattern, text_lower): | |
| canonical_found.add(canonical) | |
| raw_matches.append(term) | |
| # Kategorilere göre grupla | |
| by_category: Dict[str, List[str]] = defaultdict(list) | |
| for skill in canonical_found: | |
| cat = SKILL_CATEGORIES.get(skill, "Diğer") | |
| by_category[cat].append(skill) | |
| return { | |
| "canonical_skills": sorted(canonical_found), | |
| "raw_matches" : sorted(set(raw_matches)), | |
| "by_category" : dict(by_category) | |
| } | |
| def skill_overlap_score(cv_skills: List[str], job_skills: List[str]) -> float: | |
| """ | |
| Jaccard Similarity ile skill örtüşme skoru (0-1) | |
| """ | |
| if not job_skills: | |
| return 0.0 | |
| cv_set = set(cv_skills) | |
| job_set = set(job_skills) | |
| intersection = cv_set & job_set | |
| union = cv_set | job_set | |
| return len(intersection) / len(union) if union else 0.0 | |
| def get_embedding(text: str) -> np.ndarray: | |
| """Metni vektöre dönüştürür""" | |
| return get_embed_model().encode(text, show_progress_bar=False) | |
| def compute_semantic_similarity(text1: str, text2: str) -> float: | |
| """İki metin arasındaki cosine similarity (0-1)""" | |
| emb1 = get_embedding(text1) | |
| emb2 = get_embedding(text2) | |
| score = cosine_similarity([emb1], [emb2])[0][0] | |
| return float(score) | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # HİBRİT SKORLAMA — 4 BİLEŞENLİ (Literatür Katkısı: Multi-Signal Scoring) | |
| # | |
| # Mevcut sistemler genellikle 1-2 sinyal (keyword match + TF-IDF) kullanır. | |
| # Bu sistem 4 bileşenli ağırlıklı hibrit skor ile daha robust eşleştirme sağlar. | |
| # | |
| # Önemli: İş tanımı bazlı eşleştirme yapılır, title değil. | |
| # HR: "kişinin CV'si iş tanımıyla uyumlu olabilir ama title uymayabilir" | |
| # Bu yüzden sistem iş tanımını (description) temel alır, title opsiyoneldir. | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| SCORE_WEIGHTS = { | |
| "semantic" : 0.50, # Semantik anlam benzerliği (Sentence-BERT) | |
| "skill_overlap": 0.30, # Skill örtüşmesi (Jaccard) | |
| "experience" : 0.10, # Deneyim yılı bonusu | |
| "location" : 0.10, # Konum uyumu (soft signal) | |
| } | |
| assert abs(sum(SCORE_WEIGHTS.values()) - 1.0) < 1e-6, "Ağırlıklar toplamı 1 olmalı!" | |
| def extract_experience_years(text: str) -> float: | |
| """ | |
| Metinden deneyim yılını çıkarır (basit regex). | |
| Örn: '5 years of experience' → 5.0 | |
| """ | |
| patterns = [ | |
| r'(\d+)\+?\s*(?:years?|yıl)\s*(?:of\s+)?(?:experience|deneyim)', | |
| r'(?:experience|deneyim)\s*:?\s*(\d+)\+?\s*(?:years?|yıl)', | |
| ] | |
| for pattern in patterns: | |
| match = re.search(pattern, text.lower()) | |
| if match: | |
| return float(match.group(1)) | |
| return 0.0 | |
| def experience_score(cv_years: float, min_years: float = 0.0, max_years: float = 15.0) -> float: | |
| """ | |
| Deneyim skoru (0-1) — aralık bazlı. | |
| min_years-max_years aralığında tam skor, dışında oransal düşüş. | |
| Örnekler (3-5 yıl aralığı): | |
| 0 yıl → 0.0 | |
| 2 yıl → 0.67 | |
| 3 yıl → 1.0 (aralık içi) | |
| 5 yıl → 1.0 (aralık içi) | |
| 7 yıl → 0.8 (fazla deneyim hafif düşüş — overqualified sinyali) | |
| """ | |
| if max_years <= 0 and min_years <= 0: | |
| return 1.0 # Deneyim filtresi kapalı | |
| # Aralık içinde → tam skor | |
| if min_years <= cv_years <= max_years: | |
| return 1.0 | |
| # Aralığın altında → oransal | |
| if cv_years < min_years: | |
| if min_years <= 0: | |
| return 1.0 | |
| return max(cv_years / min_years, 0.0) | |
| # Aralığın üstünde → hafif düşüş (overqualified ama cezalandırma yok) | |
| if cv_years > max_years and max_years > 0: | |
| excess = cv_years - max_years | |
| return max(1.0 - (excess * 0.05), 0.6) # Min 0.6, çok deneyimli aday elenmez | |
| return 0.5 | |
| def hybrid_score( | |
| semantic: float, | |
| skill_jac: float, | |
| exp_score: float, | |
| loc_score: float = 0.5, | |
| weights: Dict[str, float] = SCORE_WEIGHTS | |
| ) -> float: | |
| """ | |
| 4-bileşenli ağırlıklı hibrit skor. | |
| Literatür Katkısı: | |
| - Konum soft signal olarak dahil edilir, hard filter değil. | |
| - Ağırlıklar UI'dan ayarlanabilir (transparanlık). | |
| """ | |
| return ( | |
| weights.get("semantic", 0) * semantic + | |
| weights.get("skill_overlap", 0) * skill_jac + | |
| weights.get("experience", 0) * exp_score + | |
| weights.get("location", 0) * loc_score | |
| ) | |
| def explain_match( | |
| cv_skills : List[str], | |
| job_skills : List[str], | |
| semantic_score: float, | |
| skill_score : float, | |
| exp_score : float, | |
| loc_score : float, | |
| final_score : float, | |
| weights : Dict[str, float] = SCORE_WEIGHTS | |
| ) -> Dict: | |
| """ | |
| XAI: Skorun neden bu değer olduğunu açıklar. | |
| 4-bileşenli (semantic + skill + experience + location). | |
| """ | |
| matched_skills = sorted(set(cv_skills) & set(job_skills)) | |
| missing_skills = sorted(set(job_skills) - set(cv_skills)) | |
| extra_skills = sorted(set(cv_skills) - set(job_skills)) | |
| contributions = { | |
| "Semantik Benzerlik" : round(weights.get("semantic", 0) * semantic_score, 4), | |
| "Skill Örtüşmesi" : round(weights.get("skill_overlap", 0) * skill_score, 4), | |
| "Deneyim Bonusu" : round(weights.get("experience", 0) * exp_score, 4), | |
| "Konum Uyumu" : round(weights.get("location", 0) * loc_score, 4), | |
| } | |
| if final_score >= 0.75: | |
| verdict = "🟢 Güçlü Eşleşme — Mülakat önerilir" | |
| elif final_score >= 0.55: | |
| verdict = "🟡 Orta Eşleşme — Değerlendirmeye alınabilir" | |
| else: | |
| verdict = "🔴 Zayıf Eşleşme — Pozisyona uygun değil" | |
| return { | |
| "verdict" : verdict, | |
| "final_score" : round(final_score, 4), | |
| "contributions" : contributions, | |
| "matched_skills" : matched_skills, | |
| "missing_skills" : missing_skills, | |
| "extra_skills" : extra_skills, | |
| "skill_gap_count" : len(missing_skills), | |
| } | |
| def print_explanation(name: str, explanation: Dict): | |
| """Konsola güzel formatlı açıklama yazdırır""" | |
| print(f"\n{'='*55}") | |
| print(f" 📋 Aday: {name}") | |
| print(f" 🏆 Nihai Skor : {explanation['final_score']:.4f}") | |
| print(f" 📌 Değerlendirme: {explanation['verdict']}") | |
| print(f"\n 📊 Skor Bileşenleri:") | |
| for component, contrib in explanation['contributions'].items(): | |
| bar = '█' * int(contrib * 40) | |
| print(f" {component:<22} {contrib:.4f} {bar}") | |
| print(f"\n ✅ Eşleşen Beceriler ({len(explanation['matched_skills'])}): {', '.join(explanation['matched_skills']) or '-'}") | |
| print(f" ❌ Eksik Beceriler ({len(explanation['missing_skills'])}): {', '.join(explanation['missing_skills']) or '-'}") | |
| print(f" ➕ Ek Beceriler ({len(explanation['extra_skills'])}): {', '.join(explanation['extra_skills']) or '-'}") | |
| print(f"{'='*55}") | |
| def evaluate_candidate( | |
| cv_text : str, | |
| job_description : str, | |
| job_skills : List[str], | |
| job_location : Dict = None, | |
| min_years : float = 0.0, | |
| max_years : float = 15.0, | |
| name : str = "Bilinmeyen", | |
| weights : Dict[str, float] = SCORE_WEIGHTS, | |
| verbose : bool = True | |
| ) -> Dict: | |
| """ | |
| Tek bir CV için tam değerlendirme — zenginleştirilmiş profil. | |
| Literatür Katkısı: | |
| - Multi-entity extraction (skill + konum + üniversite + firma + dil) | |
| - İş tanımı bazlı eşleştirme (title değil, description) | |
| - Soft location scoring (eleme yok, uyarı var) | |
| """ | |
| # 1. Skill extraction | |
| cv_skill_result = extract_skills(cv_text) | |
| cv_skills = cv_skill_result["canonical_skills"] | |
| # 2. Semantic similarity (iş TANIMI bazlı — title değil) | |
| sem_score = compute_semantic_similarity(cv_text, job_description) | |
| # 3. Skill overlap (Jaccard) | |
| jac_score = skill_overlap_score(cv_skills, job_skills) | |
| # 4. Experience score | |
| cv_years = extract_experience_years(cv_text) | |
| exp_sc = experience_score(cv_years, min_years, max_years) | |
| # 5. Entity extraction — Faz 1 | |
| cv_location = extract_location(cv_text) | |
| cv_universities = extract_universities(cv_text) | |
| cv_companies = extract_companies(cv_text) | |
| cv_languages = extract_languages(cv_text) | |
| # 6. Location score (soft signal) | |
| if job_location is None: | |
| job_location = {} | |
| loc_sc = location_score(cv_location, job_location) | |
| # 7. Hybrid score (4 bileşenli) | |
| final = hybrid_score(sem_score, jac_score, exp_sc, loc_sc, weights) | |
| # 8. XAI explanation | |
| explanation = explain_match( | |
| cv_skills, job_skills, sem_score, jac_score, exp_sc, loc_sc, final, weights | |
| ) | |
| if verbose: | |
| print_explanation(name, explanation) | |
| return { | |
| "name" : name, | |
| "cv_skills" : cv_skills, | |
| "skill_by_category": cv_skill_result["by_category"], | |
| "semantic_score" : round(sem_score, 4), | |
| "skill_score" : round(jac_score, 4), | |
| "experience_years" : cv_years, | |
| "experience_score" : round(exp_sc, 4), | |
| "location_score" : round(loc_sc, 4), | |
| "final_score" : round(final, 4), | |
| "explanation" : explanation, | |
| # Yeni entity bilgileri | |
| "cv_location" : cv_location, | |
| "universities" : cv_universities, | |
| "companies" : cv_companies, | |
| "languages" : cv_languages, | |
| } | |
| import plotly.express as px | |
| from plotly.subplots import make_subplots | |
| import tempfile, shutil, os, json | |
| import pandas as pd | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # CUSTOM CSS — Precision Dark / Industrial Analytics Teması | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| CUSTOM_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Inter:wght@300;400;500&display=swap'); | |
| :root { | |
| --bg-primary: #060d1a; | |
| --bg-card: #0d1829; | |
| --bg-input: #111e33; | |
| --bg-hover: #162440; | |
| --border: #1e3050; | |
| --border-glow: #00d4aa40; | |
| --teal: #00d4aa; | |
| --teal-dim: #00a886; | |
| --amber: #f59e0b; | |
| --amber-dim: #d97706; | |
| --red: #ef4444; | |
| --green: #22c55e; | |
| --text-primary: #e2eaf6; | |
| --text-muted: #6b8ab0; | |
| --text-dim: #3d5878; | |
| --font-display: 'Rajdhani', sans-serif; | |
| --font-mono: 'JetBrains Mono', monospace; | |
| --font-body: 'Inter', sans-serif; | |
| --radius: 8px; | |
| --shadow: 0 4px 24px rgba(0,0,0,0.6); | |
| --glow: 0 0 20px rgba(0,212,170,0.15); | |
| } | |
| /* ── Global ── */ | |
| body, .gradio-container { background: var(--bg-primary) !important; color: var(--text-primary) !important; font-family: var(--font-body) !important; } | |
| .gradio-container { max-width: 100% !important; width: 100% !important; padding: 0 16px !important; margin: 0 auto !important; } | |
| /* ── Header Banner ── */ | |
| #hero-banner { | |
| background: linear-gradient(135deg, #0d1829 0%, #061224 50%, #0a1f10 100%); | |
| border: 1px solid var(--border); | |
| border-bottom: 2px solid var(--teal); | |
| border-radius: var(--radius); | |
| padding: 28px 36px; | |
| margin-bottom: 20px; | |
| position: relative; | |
| overflow: hidden; | |
| box-shadow: var(--shadow), var(--glow); | |
| } | |
| #hero-banner::before { | |
| content: ''; | |
| position: absolute; top: 0; left: 0; right: 0; bottom: 0; | |
| background: repeating-linear-gradient(90deg, transparent, transparent 40px, rgba(0,212,170,0.02) 40px, rgba(0,212,170,0.02) 41px), | |
| repeating-linear-gradient(0deg, transparent, transparent 40px, rgba(0,212,170,0.02) 40px, rgba(0,212,170,0.02) 41px); | |
| pointer-events: none; | |
| } | |
| #hero-banner h1 { font-family: var(--font-display) !important; font-size: 2.4rem !important; font-weight: 700 !important; color: var(--teal) !important; letter-spacing: 2px !important; margin: 0 0 6px 0 !important; text-shadow: 0 0 30px rgba(0,212,170,0.4); } | |
| #hero-banner p { font-family: var(--font-body) !important; color: var(--text-muted) !important; font-size: 0.9rem !important; margin: 0 !important; letter-spacing: 0.5px; } | |
| .hero-badge { display: inline-block; background: rgba(0,212,170,0.1); border: 1px solid var(--teal); color: var(--teal); font-family: var(--font-mono); font-size: 0.7rem; padding: 2px 10px; border-radius: 20px; margin-right: 8px; letter-spacing: 1px; } | |
| /* ── Tabs ── */ | |
| .tab-nav { background: var(--bg-card) !important; border: 1px solid var(--border) !important; border-radius: var(--radius) !important; padding: 4px !important; margin-bottom: 16px !important; } | |
| .tab-nav button { font-family: var(--font-display) !important; font-size: 0.95rem !important; font-weight: 600 !important; letter-spacing: 1px !important; color: var(--text-muted) !important; background: transparent !important; border: none !important; border-radius: 6px !important; padding: 10px 20px !important; transition: all 0.2s !important; } | |
| .tab-nav button:hover { color: var(--teal) !important; background: rgba(0,212,170,0.08) !important; } | |
| .tab-nav button.selected { color: var(--bg-primary) !important; background: var(--teal) !important; box-shadow: 0 0 15px rgba(0,212,170,0.3) !important; } | |
| /* ── Cards / Panels ── */ | |
| .panel-card { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| padding: 20px 24px; | |
| box-shadow: var(--shadow); | |
| } | |
| .panel-title { | |
| font-family: var(--font-display); | |
| font-size: 1.1rem; | |
| font-weight: 700; | |
| letter-spacing: 1.5px; | |
| color: var(--teal); | |
| text-transform: uppercase; | |
| border-bottom: 1px solid var(--border); | |
| padding-bottom: 10px; | |
| margin-bottom: 14px; | |
| } | |
| /* ── Inputs ── */ | |
| textarea, input[type='text'] { | |
| background: var(--bg-input) !important; | |
| border: 1px solid var(--border) !important; | |
| color: var(--text-primary) !important; | |
| font-family: var(--font-body) !important; | |
| border-radius: 6px !important; | |
| transition: border-color 0.2s, box-shadow 0.2s !important; | |
| } | |
| textarea:focus, input[type='text']:focus { | |
| border-color: var(--teal) !important; | |
| box-shadow: 0 0 0 2px rgba(0,212,170,0.15) !important; | |
| outline: none !important; | |
| } | |
| label, .label-wrap span { font-family: var(--font-display) !important; font-weight: 600 !important; letter-spacing: 0.8px !important; color: var(--text-muted) !important; font-size: 0.85rem !important; } | |
| /* ── Buttons ── */ | |
| button.primary-btn, #analyze-btn { | |
| background: linear-gradient(135deg, var(--teal) 0%, var(--teal-dim) 100%) !important; | |
| color: var(--bg-primary) !important; | |
| font-family: var(--font-display) !important; | |
| font-size: 1rem !important; | |
| font-weight: 700 !important; | |
| letter-spacing: 2px !important; | |
| border: none !important; | |
| border-radius: var(--radius) !important; | |
| padding: 14px 32px !important; | |
| cursor: pointer !important; | |
| transition: all 0.2s !important; | |
| box-shadow: 0 4px 20px rgba(0,212,170,0.3) !important; | |
| width: 100% !important; | |
| } | |
| button.primary-btn:hover, #analyze-btn:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 8px 30px rgba(0,212,170,0.45) !important; | |
| } | |
| .secondary-btn { | |
| background: transparent !important; | |
| color: var(--teal) !important; | |
| border: 1px solid var(--teal) !important; | |
| font-family: var(--font-display) !important; | |
| font-weight: 600 !important; | |
| letter-spacing: 1px !important; | |
| border-radius: 6px !important; | |
| padding: 8px 20px !important; | |
| transition: all 0.2s !important; | |
| } | |
| .secondary-btn:hover { background: rgba(0,212,170,0.1) !important; } | |
| /* ── File Upload ── */ | |
| .upload-zone { | |
| background: var(--bg-input) !important; | |
| border: 2px dashed var(--border) !important; | |
| border-radius: var(--radius) !important; | |
| transition: border-color 0.2s, background 0.2s !important; | |
| } | |
| .upload-zone:hover { border-color: var(--teal) !important; background: rgba(0,212,170,0.05) !important; } | |
| .upload-zone .icon { color: var(--teal) !important; } | |
| /* ── Slider ── */ | |
| input[type='range'] { accent-color: var(--teal) !important; } | |
| /* ── Score Cards (HTML içinde) ── */ | |
| .score-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin: 12px 0; } | |
| .score-card { | |
| background: var(--bg-input); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 14px 16px; | |
| text-align: center; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .score-card::after { | |
| content: ''; | |
| position: absolute; bottom: 0; left: 0; right: 0; height: 3px; | |
| background: linear-gradient(90deg, var(--teal), var(--amber)); | |
| } | |
| .score-card .val { font-family: var(--font-mono); font-size: 1.8rem; font-weight: 500; color: var(--teal); } | |
| .score-card .lbl { font-family: var(--font-display); font-size: 0.7rem; letter-spacing: 1px; color: var(--text-muted); text-transform: uppercase; margin-top: 4px; } | |
| .verdict-strong { background: rgba(34,197,94,0.12); border: 1px solid rgba(34,197,94,0.4); color: #22c55e; } | |
| .verdict-mid { background: rgba(245,158,11,0.12); border: 1px solid rgba(245,158,11,0.4); color: #f59e0b; } | |
| .verdict-weak { background: rgba(239,68,68,0.12); border: 1px solid rgba(239,68,68,0.4); color: #ef4444; } | |
| /* ── Skill Tags ── */ | |
| .skill-tag { | |
| display: inline-block; | |
| font-family: var(--font-mono); | |
| font-size: 0.72rem; | |
| padding: 3px 10px; | |
| border-radius: 20px; | |
| margin: 3px; | |
| } | |
| .skill-match { background: rgba(34,197,94,0.15); border: 1px solid rgba(34,197,94,0.5); color: #22c55e; } | |
| .skill-missing { background: rgba(239,68,68,0.12); border: 1px solid rgba(239,68,68,0.4); color: #ef4444; } | |
| .skill-extra { background: rgba(0,212,170,0.1); border: 1px solid rgba(0,212,170,0.35); color: #00d4aa; } | |
| /* ── Progress Bar ── */ | |
| .prog-bar-wrap { margin: 8px 0; } | |
| .prog-bar-label { font-family: var(--font-display); font-size: 0.78rem; letter-spacing: 0.5px; color: var(--text-muted); display: flex; justify-content: space-between; margin-bottom: 4px; } | |
| .prog-bar-track { background: var(--bg-input); border-radius: 4px; height: 8px; overflow: hidden; } | |
| .prog-bar-fill { height: 100%; border-radius: 4px; transition: width 0.6s ease; } | |
| /* ── Ranking Table ── */ | |
| .rank-table { width: 100%; border-collapse: collapse; font-family: var(--font-body); font-size: 0.88rem; } | |
| .rank-table thead tr { background: rgba(0,212,170,0.08); border-bottom: 2px solid var(--teal); } | |
| .rank-table th { font-family: var(--font-display); font-weight: 700; letter-spacing: 1px; color: var(--teal); padding: 12px 14px; text-align: left; font-size: 0.8rem; text-transform: uppercase; } | |
| .rank-table td { padding: 12px 14px; border-bottom: 1px solid var(--border); color: var(--text-primary); vertical-align: middle; } | |
| .rank-table tr:hover td { background: var(--bg-hover); } | |
| .rank-num { font-family: var(--font-mono); font-size: 1.1rem; color: var(--teal); font-weight: 500; } | |
| .score-pill { font-family: var(--font-mono); font-size: 0.85rem; font-weight: 500; padding: 4px 12px; border-radius: 20px; } | |
| .score-high { background: rgba(34,197,94,0.15); color: #22c55e; } | |
| .score-mid { background: rgba(245,158,11,0.15); color: #f59e0b; } | |
| .score-low { background: rgba(239,68,68,0.12); color: #ef4444; } | |
| /* ── Status / Log ── */ | |
| .log-box { background: var(--bg-input); border: 1px solid var(--border); border-radius: 6px; padding: 14px 18px; font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); line-height: 1.8; max-height: 120px; overflow-y: auto; } | |
| .log-ok { color: var(--green); } | |
| .log-warn { color: var(--amber); } | |
| .log-info { color: var(--teal); } | |
| /* ── Dividers ── */ | |
| .section-divider { border: none; border-top: 1px solid var(--border); margin: 18px 0; } | |
| /* ── Scrollbar ── */ | |
| ::-webkit-scrollbar { width: 6px; height: 6px; } | |
| ::-webkit-scrollbar-track { background: var(--bg-card); } | |
| ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } | |
| ::-webkit-scrollbar-thumb:hover { background: var(--teal-dim); } | |
| """ | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # YARDIMCI FONKSİYONLAR — Gradio callback'leri (Zenginleştirilmiş) | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| def make_score_pill(score: float) -> str: | |
| cls = 'score-high' if score >= 0.75 else 'score-mid' if score >= 0.55 else 'score-low' | |
| return f'<span class="score-pill {cls}">{score:.3f}</span>' | |
| def make_skill_tags(skills: list, kind: str) -> str: | |
| if not skills: | |
| return '<span style="color:#3d5878;font-size:0.8rem">—</span>' | |
| return ' '.join(f'<span class="skill-tag skill-{kind}">{s}</span>' for s in skills) | |
| def make_progress_bar(label: str, value: float, color: str = '#00d4aa') -> str: | |
| pct = int(value * 100) | |
| return f""" | |
| <div class="prog-bar-wrap"> | |
| <div class="prog-bar-label"><span>{label}</span><span style="font-family:var(--font-mono);color:{color}">{value:.4f}</span></div> | |
| <div class="prog-bar-track"> | |
| <div class="prog-bar-fill" style="width:{pct}%;background:{color};"></div> | |
| </div> | |
| </div>""" | |
| def make_location_badge(loc_data: dict) -> str: | |
| """Konum badge'i oluşturur (şehir + remote etiketi)""" | |
| city = loc_data.get("city", "") | |
| country = loc_data.get("country", "") | |
| remote = loc_data.get("remote", False) | |
| parts = [] | |
| if city: | |
| parts.append(city) | |
| elif country: | |
| parts.append(country) | |
| else: | |
| parts.append("—") | |
| loc_text = ", ".join(parts) | |
| remote_tag = ' <span style="background:#22c55e22;color:#22c55e;padding:1px 6px;border-radius:4px;font-size:0.7rem">REMOTE</span>' if remote else '' | |
| return f'{loc_text}{remote_tag}' | |
| def build_ranking_table(df: pd.DataFrame) -> str: | |
| """Zenginleştirilmiş sıralama tablosu — konum, üniversite, firma, dil""" | |
| rows = "" | |
| for _, r in df.iterrows(): | |
| verdict = r['Değerlendirme'] | |
| badge_cls = 'verdict-strong' if '🟢' in verdict else 'verdict-mid' if '🟡' in verdict else 'verdict-weak' | |
| badge_txt = 'Güçlü' if '🟢' in verdict else 'Orta' if '🟡' in verdict else 'Zayıf' | |
| # Konum badge | |
| raw_data = r.get('_raw', {}) | |
| loc_data = raw_data.get('cv_location', {}) | |
| loc_html = make_location_badge(loc_data) | |
| # Üniversite (kısa gösterim) | |
| uni = str(r.get('Üniversite', '—'))[:40] | |
| rows += f""" | |
| <tr> | |
| <td><span class="rank-num">#{r['Sıra']}</span></td> | |
| <td><strong style="color:var(--text-primary)">{r['Aday']}</strong></td> | |
| <td>{make_score_pill(r['Final Skor'])}</td> | |
| <td><span style="font-family:var(--font-mono);color:var(--text-muted)">{r['Semantic']:.3f}</span></td> | |
| <td><span style="font-family:var(--font-mono);color:var(--text-muted)">{r['Skill Overlap']:.3f}</span></td> | |
| <td><span style="font-family:var(--font-mono);color:var(--amber)">{int(r['Deneyim (Yıl)'])} yıl</span></td> | |
| <td><span style="font-size:0.8rem">{loc_html}</span></td> | |
| <td><span style="font-size:0.78rem;color:var(--text-muted)">{uni}</span></td> | |
| <td><span style="font-family:var(--font-mono);color:var(--green)">{int(r['Eşleşen Skill'])}</span> / <span style="font-family:var(--font-mono);color:var(--red)">{int(r['Eksik Skill'])}</span></td> | |
| <td><span class="score-pill {badge_cls}">{badge_txt}</span></td> | |
| </tr>""" | |
| return f""" | |
| <div style="max-height:520px;overflow-y:auto;border-radius:10px"> | |
| <table class="rank-table"> | |
| <thead> | |
| <tr> | |
| <th>Sıra</th><th>Aday</th><th>Final Skor</th> | |
| <th>Semantik</th><th>Skill</th><th>Deneyim</th> | |
| <th>Konum</th><th>Üniversite</th> | |
| <th>Eşleşen/Eksik</th><th>Durum</th> | |
| </tr> | |
| </thead> | |
| <tbody>{rows}</tbody> | |
| </table> | |
| </div>""" | |
| def build_xai_panel(result: dict) -> str: | |
| """Zenginleştirilmiş XAI paneli — konum, üniversite, firma, dil bilgileri""" | |
| exp = result['explanation'] | |
| verdict = exp['verdict'] | |
| badge_cls = 'verdict-strong' if '🟢' in verdict else 'verdict-mid' if '🟡' in verdict else 'verdict-weak' | |
| sem = result['semantic_score'] | |
| skill = result['skill_score'] | |
| expsc = result['experience_score'] | |
| locsc = result.get('location_score', 0.5) | |
| final = result['final_score'] | |
| w = SCORE_WEIGHTS | |
| bars = ( | |
| make_progress_bar(f'Semantik Benzerlik (×{w.get("semantic",0)})', sem * w.get('semantic',0), '#00d4aa') + | |
| make_progress_bar(f'Skill Örtüşmesi (×{w.get("skill_overlap",0)})', skill * w.get('skill_overlap',0), '#a78bfa') + | |
| make_progress_bar(f'Deneyim Bonusu (×{w.get("experience",0)})', expsc * w.get('experience',0), '#f59e0b') + | |
| make_progress_bar(f'Konum Uyumu (×{w.get("location",0)})', locsc * w.get('location',0), '#3b82f6') | |
| ) | |
| # Profil bilgileri (konum, üniversite, firma, dil) | |
| loc_data = result.get('cv_location', {}) | |
| loc_html = make_location_badge(loc_data) | |
| unis = result.get('universities', []) | |
| companies = result.get('companies', []) | |
| languages = result.get('languages', []) | |
| profile_html = f""" | |
| <div style="margin-top:16px"> | |
| <div class="panel-title" style="font-size:0.85rem">👤 Aday Profili</div> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;font-size:0.82rem"> | |
| <div><span style="color:var(--text-dim)">📍 Konum:</span> {loc_html}</div> | |
| <div><span style="color:var(--text-dim)">🌐 Diller:</span> {', '.join(languages) or '—'}</div> | |
| <div style="grid-column:1/3"><span style="color:var(--text-dim)">🎓 Eğitim:</span> {'; '.join(unis[:2]) or '—'}</div> | |
| <div style="grid-column:1/3"><span style="color:var(--text-dim)">🏢 Firmalar:</span> {', '.join(companies[:5]) or '—'}</div> | |
| </div> | |
| </div> | |
| """ | |
| return f""" | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px"> | |
| <!-- Sol: Skor Detay --> | |
| <div> | |
| <div class="panel-title">📊 Skor Analizi</div> | |
| <div class="score-grid"> | |
| <div class="score-card"><div class="val">{final:.3f}</div><div class="lbl">Final Skor</div></div> | |
| <div class="score-card"><div class="val" style="color:#a78bfa">{sem:.3f}</div><div class="lbl">Semantik</div></div> | |
| <div class="score-card"><div class="val" style="color:#f59e0b">{skill:.3f}</div><div class="lbl">Skill</div></div> | |
| <div class="score-card"><div class="val" style="color:#22c55e">{result['experience_years']:.0f} yıl</div><div class="lbl">Deneyim</div></div> | |
| <div class="score-card"><div class="val" style="color:#3b82f6">{locsc:.2f}</div><div class="lbl">Konum</div></div> | |
| </div> | |
| <div style="margin:16px 0 8px"><div class="panel-title" style="font-size:0.85rem">⚖️ Bileşen Katkıları</div></div> | |
| {bars} | |
| <div style="margin-top:16px;padding:12px 16px;border-radius:8px" class="{badge_cls}"> | |
| <div style="font-family:var(--font-display);font-weight:700;font-size:1rem;letter-spacing:1px">{verdict}</div> | |
| </div> | |
| {profile_html} | |
| </div> | |
| <!-- Sağ: Skill Analizi --> | |
| <div> | |
| <div class="panel-title">🔍 Skill Gap Analizi</div> | |
| <div style="margin-bottom:14px"> | |
| <div style="font-family:var(--font-display);font-size:0.78rem;letter-spacing:1px;color:var(--green);margin-bottom:6px">✅ EŞLEŞEN BECERİLER ({len(exp['matched_skills'])})</div> | |
| {make_skill_tags(exp['matched_skills'], 'match')} | |
| </div> | |
| <div style="margin-bottom:14px"> | |
| <div style="font-family:var(--font-display);font-size:0.78rem;letter-spacing:1px;color:var(--red);margin-bottom:6px">❌ EKSİK BECERİLER ({len(exp['missing_skills'])})</div> | |
| {make_skill_tags(exp['missing_skills'], 'missing')} | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # PLOTLy GRAFİK FONKSİYONLARI | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| PLOTLY_LAYOUT = dict( | |
| paper_bgcolor='rgba(13,24,41,0)', | |
| plot_bgcolor='rgba(13,24,41,0)', | |
| font=dict(family='Rajdhani, Inter, sans-serif', color='#6b8ab0'), | |
| title_font=dict(family='Rajdhani, sans-serif', color='#00d4aa', size=16), | |
| margin=dict(l=20, r=20, t=50, b=20), | |
| colorway=['#00d4aa', '#f59e0b', '#a78bfa', '#ef4444', '#22c55e'], | |
| ) | |
| def plotly_ranking_bar(df: pd.DataFrame) -> go.Figure: | |
| colors = ['#22c55e' if s >= 0.75 else '#f59e0b' if s >= 0.55 else '#ef4444' | |
| for s in df['Final Skor']] | |
| fig = go.Figure(go.Bar( | |
| x=df['Final Skor'], y=df['Aday'], orientation='h', | |
| marker_color=colors, | |
| text=[f'{s:.3f}' for s in df['Final Skor']], | |
| textposition='outside', | |
| textfont=dict(family='JetBrains Mono', color='#e2eaf6', size=12), | |
| )) | |
| fig.add_vline(x=0.75, line_dash='dot', line_color='rgba(34,197,94,0.5)', annotation_text='Güçlü', annotation_font_color='#22c55e') | |
| fig.add_vline(x=0.55, line_dash='dot', line_color='rgba(245,158,11,0.5)', annotation_text='Orta', annotation_font_color='#f59e0b') | |
| fig.update_layout(**PLOTLY_LAYOUT, title='🏆 Nihai Aday Sıralaması', | |
| xaxis=dict(range=[0, 1.05], gridcolor='rgba(30,48,80,0.8)', title='Skor'), | |
| yaxis=dict(autorange='reversed', gridcolor='rgba(30,48,80,0.8)'), | |
| height=max(280, len(df) * 62)) | |
| return fig | |
| def plotly_component_stacked(df: pd.DataFrame) -> go.Figure: | |
| w = SCORE_WEIGHTS | |
| names = df['Aday'].tolist() | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar(name='Semantik', x=names, y=(df['Semantic'] * w['semantic']).tolist(), | |
| marker_color='#00d4aa', opacity=0.9)) | |
| fig.add_trace(go.Bar(name='Skill Overlap', x=names, y=(df['Skill Overlap'] * w['skill_overlap']).tolist(), | |
| marker_color='#a78bfa', opacity=0.9)) | |
| fig.add_trace(go.Bar(name='Deneyim', x=names, y=(df['Deneyim (Yıl)'].apply(lambda x: min(x/3,1)) * w['experience']).tolist(), | |
| marker_color='#f59e0b', opacity=0.9)) | |
| fig.update_layout(**PLOTLY_LAYOUT, barmode='stack', | |
| title='📊 Skor Bileşeni Dağılımı', | |
| xaxis=dict(gridcolor='rgba(30,48,80,0.8)'), | |
| yaxis=dict(gridcolor='rgba(30,48,80,0.8)', title='Katkı'), | |
| legend=dict(bgcolor='rgba(13,24,41,0.8)', bordercolor='#1e3050', borderwidth=1), | |
| height=360) | |
| return fig | |
| def plotly_skill_gap(df: pd.DataFrame) -> go.Figure: | |
| names = df['Aday'].tolist() | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar(name='Eşleşen ✅', x=names, y=df['Eşleşen Skill'].tolist(), | |
| marker_color='rgba(34,197,94,0.8)')) | |
| fig.add_trace(go.Bar(name='Eksik ❌', x=names, y=df['Eksik Skill'].tolist(), | |
| marker_color='rgba(239,68,68,0.7)')) | |
| fig.update_layout(**PLOTLY_LAYOUT, barmode='group', | |
| title='🔍 Skill Gap Analizi', | |
| xaxis=dict(gridcolor='rgba(30,48,80,0.8)'), | |
| yaxis=dict(gridcolor='rgba(30,48,80,0.8)', title='Skill Sayısı'), | |
| legend=dict(bgcolor='rgba(13,24,41,0.8)', bordercolor='#1e3050', borderwidth=1), | |
| height=340) | |
| return fig | |
| def plotly_scatter(df: pd.DataFrame) -> go.Figure: | |
| fig = go.Figure(go.Scatter( | |
| x=df['Semantic'], y=df['Skill Overlap'], | |
| mode='markers+text', | |
| text=df['Aday'].apply(lambda n: n.split()[0]), | |
| textposition='top center', | |
| textfont=dict(family='Rajdhani', color='#e2eaf6', size=12), | |
| marker=dict( | |
| size=18, | |
| color=df['Final Skor'], | |
| colorscale=[[0,'#ef4444'],[0.5,'#f59e0b'],[1,'#00d4aa']], | |
| showscale=True, | |
| colorbar=dict(title='Final Skor', tickfont=dict(color='#6b8ab0')), | |
| line=dict(width=1, color='#1e3050'), | |
| ), | |
| hovertemplate='<b>%{text}</b><br>Semantic: %{x:.3f}<br>Skill: %{y:.3f}<extra></extra>' | |
| )) | |
| fig.update_layout(**PLOTLY_LAYOUT, | |
| title='🔵 Semantic vs Skill Dağılımı', | |
| xaxis=dict(title='Semantic Similarity', gridcolor='rgba(30,48,80,0.8)', range=[0,1]), | |
| yaxis=dict(title='Skill Overlap (Jaccard)', gridcolor='rgba(30,48,80,0.8)', range=[0,1]), | |
| height=360) | |
| return fig | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # ANA CALLBACK (Zenginleştirilmiş — 4 bileşenli + entity extraction) | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| _last_results: list = [] # Global cache — XAI tab için | |
| def run_analysis( | |
| pdf_files, | |
| job_desc: str, | |
| sem_weight: float, | |
| skill_weight: float, | |
| exp_weight: float, | |
| loc_weight: float, | |
| min_years: float, | |
| max_years: float, | |
| dual_col: bool, | |
| job_location_str: str, | |
| keyword_filter: str, | |
| uni_filter: list, | |
| ): | |
| global _last_results | |
| # ── Validasyon ────────────────────────────────────────────────────────── | |
| if not pdf_files: | |
| empty = '<div class="log-box"><span class="log-warn">⚠ Lütfen en az bir PDF CV yükleyin.</span></div>' | |
| return (empty, None, None, None, None, gr.update(choices=[], value=None), empty) | |
| if not job_desc.strip(): | |
| empty = '<div class="log-box"><span class="log-warn">⚠ İş ilanı açıklaması boş olamaz.</span></div>' | |
| return (empty, None, None, None, None, gr.update(choices=[], value=None), empty) | |
| # ── Ağırlık normalize (4 bileşen) ─────────────────────────────────── | |
| total = sem_weight + skill_weight + exp_weight + loc_weight | |
| if total == 0: total = 1 | |
| weights = { | |
| 'semantic' : round(sem_weight / total, 4), | |
| 'skill_overlap': round(skill_weight / total, 4), | |
| 'experience' : round(exp_weight / total, 4), | |
| 'location' : round(loc_weight / total, 4), | |
| } | |
| # ── İş ilanından konum bilgisi çıkar ──────────────────────────────── | |
| job_location = {"city": None, "country": None, "remote": False} | |
| if job_location_str.strip(): | |
| # Kullanıcı manuel konum girdiyse | |
| loc_parts = [p.strip() for p in job_location_str.split(",")] | |
| job_location["city"] = loc_parts[0] if loc_parts else None | |
| if len(loc_parts) > 1: | |
| job_location["country"] = loc_parts[1] | |
| if "remote" in job_location_str.lower() or "uzaktan" in job_location_str.lower(): | |
| job_location["remote"] = True | |
| else: | |
| # İş ilanı metninden otomatik çıkar | |
| job_location = extract_location(job_desc) | |
| # ── PDF okuma ─────────────────────────────────────────────────────────── | |
| candidates = [] | |
| log_lines = ['<span class="log-info">► Analiz başlatıldı...</span>'] | |
| for f in pdf_files: | |
| path = f.name if hasattr(f, 'name') else f | |
| fname = os.path.basename(path) | |
| try: | |
| text = extract_text_from_pdf(path, dual_column=dual_col) | |
| text = clean_text(text) | |
| candidates.append({'name': fname.replace('.pdf',''), 'text': text}) | |
| log_lines.append(f'<span class="log-ok">✔ Yüklendi: {fname}</span>') | |
| except Exception as e: | |
| log_lines.append(f'<span class="log-warn">✘ Hata ({fname}): {e}</span>') | |
| if not candidates: | |
| log_html = '<div class="log-box">' + '<br>'.join(log_lines) + '</div>' | |
| return log_html, None, None, None, None, gr.update(choices=[], value=None), log_html | |
| # ── Pipeline çalıştır ─────────────────────────────────────────────────── | |
| job_skills = extract_skills(job_desc)['canonical_skills'] | |
| log_lines.append(f'<span class="log-info">► İş ilanı skill sayısı: {len(job_skills)}</span>') | |
| if job_location.get("city"): | |
| log_lines.append(f'<span class="log-info">► İş konumu: {job_location["city"]}</span>') | |
| results = [] | |
| for cand in candidates: | |
| res = evaluate_candidate( | |
| cv_text=cand['text'], job_description=job_desc, | |
| job_skills=job_skills, job_location=job_location, | |
| min_years=min_years, max_years=max_years, | |
| name=cand['name'], weights=weights, verbose=False | |
| ) | |
| results.append(res) | |
| loc_info = res.get("cv_location", {}).get("city", "?") | |
| log_lines.append(f'<span class="log-ok">✔ {cand["name"]} → {res["final_score"]:.3f} (📍{loc_info})</span>') | |
| # ── Keyword filter (post-hoc) ─────────────────────────────────────── | |
| if keyword_filter.strip(): | |
| filtered_results = [] | |
| kw_list = [kw.strip().lower() for kw in keyword_filter.split(",") if kw.strip()] | |
| for res in results: | |
| cand_text = next((c['text'] for c in candidates if c['name'] == res['name']), "") | |
| text_lower = cand_text.lower() | |
| all_found = all(kw in text_lower for kw in kw_list) | |
| if all_found: | |
| filtered_results.append(res) | |
| else: | |
| missing_kws = [kw for kw in kw_list if kw not in text_lower] | |
| log_lines.append(f'<span class="log-warn">⊘ {res["name"]} filtrelendi (eksik: {", ".join(missing_kws)})</span>') | |
| results = filtered_results | |
| log_lines.append(f'<span class="log-info">► Keyword filtre sonrası: {len(results)} aday</span>') | |
| # ── Üniversite filtresi (post-hoc) ────────────────────────────────── | |
| if uni_filter and len(uni_filter) > 0: | |
| uni_set = set(u.lower() for u in uni_filter) | |
| filtered_results = [] | |
| for res in results: | |
| unis = res.get("universities", []) | |
| uni_text = " ".join(unis).lower() | |
| if any(u in uni_text for u in uni_set): | |
| filtered_results.append(res) | |
| else: | |
| log_lines.append(f'<span class="log-warn">⊘ {res["name"]} filtrelendi (üniversite eşleşmedi)</span>') | |
| results = filtered_results | |
| log_lines.append(f'<span class="log-info">► Üniversite filtre sonrası: {len(results)} aday</span>') | |
| _last_results = results | |
| if not results: | |
| log_lines.append('<span class="log-warn">⚠ Keyword filtresine uyan aday bulunamadı.</span>') | |
| log_html = '<div class="log-box">' + '<br>'.join(log_lines) + '</div>' | |
| return ( | |
| '<div class="log-box"><span class="log-warn">Filtrelere uyan aday yok.</span></div>', | |
| None, None, None, None, gr.update(choices=[], value=None), log_html | |
| ) | |
| # ── DataFrame (zenginleştirilmiş) ─────────────────────────────────── | |
| df = pd.DataFrame([{ | |
| 'Sıra': 0, 'Aday': r['name'], | |
| 'Final Skor': r['final_score'], 'Semantic': r['semantic_score'], | |
| 'Skill Overlap': r['skill_score'], | |
| 'Konum Skoru': r.get('location_score', 0.5), | |
| 'Deneyim (Yıl)': r['experience_years'], | |
| 'Konum': r.get('cv_location', {}).get('city') or '—', | |
| 'Üniversite': (r.get('universities') or ['—'])[0], | |
| 'Firmalar': ', '.join((r.get('companies') or [])[:3]) or '—', | |
| 'Diller': ', '.join(r.get('languages') or []) or '—', | |
| 'CV Skill Sayısı': len(r['cv_skills']), | |
| 'Eşleşen Skill': len(r['explanation']['matched_skills']), | |
| 'Eksik Skill': len(r['explanation']['missing_skills']), | |
| 'Değerlendirme': r['explanation']['verdict'], | |
| '_raw': r | |
| } for r in results]) | |
| df = df.sort_values('Final Skor', ascending=False).reset_index(drop=True) | |
| df['Sıra'] = df.index + 1 | |
| # ── Çıktılar ───────────────────────────────────────────────────────── | |
| log_lines.append(f'<span class="log-ok">✔ Analiz tamamlandı — {len(results)} aday değerlendirildi.</span>') | |
| log_html = '<div class="log-box">' + '<br>'.join(log_lines) + '</div>' | |
| table_html = build_ranking_table(df) | |
| fig_bar = plotly_ranking_bar(df) | |
| fig_stack = plotly_component_stacked(df) | |
| fig_gap = plotly_skill_gap(df) | |
| fig_scatter = plotly_scatter(df) | |
| names_list = [r['name'] for r in results] | |
| return ( | |
| table_html, | |
| fig_bar, fig_stack, fig_gap, fig_scatter, | |
| gr.update(choices=names_list, value=names_list[0] if names_list else None), | |
| log_html | |
| ) | |
| def show_xai(selected_name: str): | |
| if not selected_name or not _last_results: | |
| return '<div class="log-box"><span class="log-warn">Önce analiz çalıştırın.</span></div>' | |
| result = next((r for r in _last_results if r['name'] == selected_name), None) | |
| if not result: | |
| return '<div class="log-box"><span class="log-warn">Aday bulunamadı.</span></div>' | |
| return build_xai_panel(result) | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| # CV ASİSTANI — Kural Tabanlı (LLM bağımlılığı yok, hızlı + güvenilir) | |
| # ══════════════════════════════════════════════════════════════════════════════ | |
| def chat_with_cvs(user_message, history): | |
| """Kural tabanlı CV asistanı — yapısal veriden doğrudan yanıt üretir.""" | |
| if not _last_results: | |
| return "⚠️ Önce CV yükleyip analizi başlatın, sonra sorularınızı sorun." | |
| return answer_question(user_message) | |
| def answer_question(q): | |
| ql = q.lower().strip() | |
| R = _last_results | |
| best = max(R, key=lambda x: x['final_score']) | |
| # ── En iyi aday ── | |
| if any(w in ql for w in ['en iyi', 'best', 'birinci', 'en yüksek', 'hangisi daha iyi', | |
| 'öneri', 'tavsiye', 'kimi seçmeliyim', 'mülakat', 'interview', | |
| 'öner', 'recommend', 'top', 'winner']): | |
| exp = best.get('explanation', {}) | |
| matched = exp.get('matched_skills', []) | |
| missing = exp.get('missing_skills', []) | |
| loc = best.get('cv_location', {}) | |
| return f"""🏆 **En uygun aday: {best['name']}** | |
| 📊 Final Skor: **{best['final_score']:.3f}** | |
| 📅 Deneyim: {best.get('experience_years',0):.0f} yıl | |
| 📍 Konum: {loc.get('city','?')}{' (Remote)' if loc.get('remote') else ''} | |
| 🎓 Eğitim: {', '.join(best.get('universities',['—'])[:2])} | |
| 🏢 Firmalar: {', '.join(best.get('companies',[])[:4]) or '—'} | |
| ✅ Eşleşen beceriler ({len(matched)}): {', '.join(matched[:10]) or '—'} | |
| ❌ Eksik beceriler ({len(missing)}): {', '.join(missing[:10]) or '—'}""" | |
| # ── Tüm adayları sırala ── | |
| if any(w in ql for w in ['sırala', 'sıralama', 'ranking', 'rank', 'tüm adaylar', | |
| 'hepsini göster', 'liste', 'genel durum', 'özet']): | |
| sorted_r = sorted(R, key=lambda x: x['final_score'], reverse=True) | |
| lines = [] | |
| for i, r in enumerate(sorted_r, 1): | |
| medal = ['🥇','🥈','🥉'][i-1] if i <= 3 else f'#{i}' | |
| lines.append(f"{medal} **{r['name']}** — Skor: {r['final_score']:.3f} | {r.get('experience_years',0):.0f} yıl | {r.get('cv_location',{}).get('city','?')}") | |
| avg = sum(r['final_score'] for r in R) / len(R) | |
| return f"📊 **Aday Sıralaması** ({len(R)} aday, ort: {avg:.3f})\n\n" + '\n'.join(lines) | |
| # ── Skill arama ── | |
| skill_keywords = { | |
| 'python':'python', 'sql':'sql', 'java':'java', 'javascript':'javascript', | |
| 'react':'react', 'docker':'docker', 'kubernetes':'kubernetes', | |
| 'machine learning':'machine learning', 'ml':'machine learning', | |
| 'deep learning':'deep learning', 'nlp':'natural language processing', | |
| 'natural language':'natural language processing', | |
| 'pytorch':'pytorch', 'tensorflow':'tensorflow', | |
| 'power bi':'power bi', 'tableau':'data visualization', | |
| 'excel':'excel', 'aws':'aws', 'azure':'azure', 'gcp':'google cloud', | |
| 'git':'git', 'linux':'linux', 'flask':'flask', 'django':'django', | |
| 'solidworks':'cad', 'autocad':'cad', 'cad':'cad', | |
| 'plc':'plc', 'scada':'scada', 'ros':'ros', | |
| 'opencv':'computer vision', 'computer vision':'computer vision', | |
| 'pandas':'pandas', 'numpy':'numpy', 'scikit':'scikit-learn', | |
| 'r programlama':'r', 'r dili':'r', ' r ':'r', | |
| 'spss':'spss', 'matlab':'matlab', | |
| } | |
| for kw, skill in skill_keywords.items(): | |
| if kw in ql: | |
| matches = [] | |
| for r in R: | |
| cv_skills_lower = [s.lower() for s in r.get('cv_skills', [])] | |
| if skill.lower() in cv_skills_lower or kw in ' '.join(cv_skills_lower): | |
| matches.append(r) | |
| if matches: | |
| lines = [] | |
| for r in sorted(matches, key=lambda x: x['final_score'], reverse=True): | |
| lines.append(f" ✅ **{r['name']}** (Skor: {r['final_score']:.3f}, {r.get('experience_years',0):.0f} yıl)") | |
| return f"🔍 **{skill.title()}** bilen adaylar ({len(matches)}/{len(R)}):\n\n" + '\n'.join(lines) | |
| return f"❌ **{skill.title()}** becerisine sahip aday bulunamadı." | |
| # ── Konum ── | |
| if any(w in ql for w in ['konum', 'nerede', 'şehir', 'lokasyon', 'location', | |
| 'istanbul', 'ankara', 'izmir', 'remote', 'uzaktan']): | |
| # Spesifik şehir arama | |
| for city in ['istanbul', 'ankara', 'izmir', 'bursa', 'antalya']: | |
| if city in ql: | |
| matches = [r for r in R if city in (r.get('cv_location',{}).get('city','') or '').lower()] | |
| if matches: | |
| lines = [f" • **{r['name']}** (Skor: {r['final_score']:.3f})" for r in matches] | |
| return f"📍 **{city.title()}** konumundaki adaylar ({len(matches)}):\n\n" + '\n'.join(lines) | |
| return f"📍 {city.title()} konumunda aday bulunamadı." | |
| if 'remote' in ql or 'uzaktan' in ql: | |
| matches = [r for r in R if r.get('cv_location',{}).get('remote')] | |
| if matches: | |
| lines = [f" • **{r['name']}** ({r.get('cv_location',{}).get('city','?')})" for r in matches] | |
| return f"🏠 **Remote** çalışabilecek adaylar ({len(matches)}):\n\n" + '\n'.join(lines) | |
| return "🏠 Remote tercih belirten aday bulunamadı." | |
| lines = [] | |
| for r in R: | |
| loc = r.get('cv_location', {}) | |
| remote = ' 🟢 Remote' if loc.get('remote') else '' | |
| lines.append(f" 📍 **{r['name']}**: {loc.get('city','?')}{remote}") | |
| return "📍 **Tüm Konum Bilgileri**\n\n" + '\n'.join(lines) | |
| # ── Deneyim ── | |
| if any(w in ql for w in ['deneyim', 'tecrübe', 'experience', 'yıl', 'kıdemli', | |
| 'senior', 'junior', 'stajyer', 'yeni mezun', 'entry']): | |
| sorted_r = sorted(R, key=lambda x: x.get('experience_years',0), reverse=True) | |
| lines = [f" {'🟢' if r.get('experience_years',0)>=3 else '🟡' if r.get('experience_years',0)>=1 else '🔴'} **{r['name']}**: {r.get('experience_years',0):.0f} yıl" for r in sorted_r] | |
| avg = sum(r.get('experience_years',0) for r in R) / len(R) | |
| return f"📅 **Deneyim Sıralaması** (ort: {avg:.1f} yıl)\n\n" + '\n'.join(lines) | |
| # ── Üniversite ── | |
| if any(w in ql for w in ['üniversite', 'okul', 'eğitim', 'mezun', 'university', | |
| 'lisans', 'master', 'yüksek lisans', 'doktora', 'phd', | |
| 'marmara', 'boğaziçi', 'itü', 'odtü', 'bilkent']): | |
| # Spesifik üniversite arama | |
| for uni in ['marmara', 'boğaziçi', 'itü', 'istanbul teknik', 'odtü', 'bilkent', | |
| 'hacettepe', 'ege', 'ankara', 'sabancı', 'koç']: | |
| if uni in ql: | |
| matches = [r for r in R if uni in ' '.join(r.get('universities',[])).lower()] | |
| if matches: | |
| lines = [f" • **{r['name']}** (Skor: {r['final_score']:.3f})" for r in matches] | |
| return f"🎓 **{uni.title()}** mezunu adaylar:\n\n" + '\n'.join(lines) | |
| return f"🎓 {uni.title()} mezunu aday bulunamadı." | |
| lines = [f" 🎓 **{r['name']}**: {', '.join(r.get('universities',['—'])[:2])}" for r in R] | |
| return "🎓 **Eğitim Bilgileri**\n\n" + '\n'.join(lines) | |
| # ── Firma ── | |
| if any(w in ql for w in ['firma', 'şirket', 'company', 'çalıştığı', 'iş yeri', | |
| 'nereden gelmiş', 'geçmiş', 'work history']): | |
| lines = [f" 🏢 **{r['name']}**: {', '.join(r.get('companies',[])[:4]) or '—'}" for r in R] | |
| return "🏢 **Firma Bilgileri**\n\n" + '\n'.join(lines) | |
| # ── Dil ── | |
| if any(w in ql for w in ['dil', 'language', 'ingilizce', 'almanca', 'türkçe', | |
| 'fransızca', 'ispanyolca', 'bildiği diller']): | |
| for lang in ['ingilizce', 'almanca', 'fransızca', 'ispanyolca', 'arapça', 'çince']: | |
| if lang in ql: | |
| matches = [r for r in R if lang.title() in r.get('languages',[])] | |
| if matches: | |
| lines = [f" • **{r['name']}**" for r in matches] | |
| return f"🌐 **{lang.title()}** bilen adaylar:\n\n" + '\n'.join(lines) | |
| return f"🌐 {lang.title()} bilen aday bulunamadı." | |
| lines = [f" 🌐 **{r['name']}**: {', '.join(r.get('languages',[])) or '—'}" for r in R] | |
| return "🌐 **Dil Bilgileri**\n\n" + '\n'.join(lines) | |
| # ── Karşılaştırma ── | |
| if any(w in ql for w in ['karşılaştır', 'compare', 'fark', 'vs', 'arasında', | |
| 'hangisi daha', 'avantaj', 'dezavantaj']): | |
| # İsim geçiyorsa o ikisini karşılaştır | |
| mentioned = [r for r in R if r['name'].lower().split()[0] in ql or r['name'].lower() in ql] | |
| if len(mentioned) < 2: | |
| mentioned = sorted(R, key=lambda x: x['final_score'], reverse=True)[:2] | |
| if len(mentioned) >= 2: | |
| a, b = mentioned[0], mentioned[1] | |
| return f"""🔄 **Karşılaştırma** | |
| | | **{a['name']}** | **{b['name']}** | | |
| |---|---|---| | |
| | Final Skor | {a['final_score']:.3f} | {b['final_score']:.3f} | | |
| | Semantik | {a['semantic_score']:.3f} | {b['semantic_score']:.3f} | | |
| | Skill | {a['skill_score']:.3f} | {b['skill_score']:.3f} | | |
| | Deneyim | {a.get('experience_years',0):.0f} yıl | {b.get('experience_years',0):.0f} yıl | | |
| | Konum | {a.get('cv_location',{}).get('city','?')} | {b.get('cv_location',{}).get('city','?')} | | |
| | Eşleşen | {len(a.get('explanation',{}).get('matched_skills',[]))} skill | {len(b.get('explanation',{}).get('matched_skills',[]))} skill | | |
| | Eksik | {len(a.get('explanation',{}).get('missing_skills',[]))} skill | {len(b.get('explanation',{}).get('missing_skills',[]))} skill |""" | |
| # ── Eksik beceri ── | |
| if any(w in ql for w in ['eksik', 'missing', 'skill gap', 'ne eksik', 'hangi skill', | |
| 'geliştirmeli', 'öğrenmeli']): | |
| lines = [] | |
| for r in sorted(R, key=lambda x: x['final_score'], reverse=True): | |
| missing = r.get('explanation',{}).get('missing_skills',[]) | |
| if missing: | |
| lines.append(f" ❌ **{r['name']}** ({len(missing)} eksik): {', '.join(missing[:6])}") | |
| return "🔍 **Eksik Beceri Analizi**\n\n" + ('\n'.join(lines) if lines else "Tüm adaylarda eksik beceri bilgisi mevcut değil.") | |
| # ── Eşleşen beceri ── | |
| if any(w in ql for w in ['eşleşen', 'matched', 'ortak skill', 'uyumlu', 'hangi beceri']): | |
| lines = [] | |
| for r in sorted(R, key=lambda x: x['final_score'], reverse=True): | |
| matched = r.get('explanation',{}).get('matched_skills',[]) | |
| lines.append(f" ✅ **{r['name']}** ({len(matched)}): {', '.join(matched[:8]) or '—'}") | |
| return "✅ **Eşleşen Beceriler**\n\n" + '\n'.join(lines) | |
| # ── Spesifik aday hakkında ── | |
| for r in R: | |
| name_parts = r['name'].lower().replace('_',' ').replace('-',' ').split() | |
| if any(part in ql for part in name_parts if len(part) > 2): | |
| loc = r.get('cv_location', {}) | |
| exp = r.get('explanation', {}) | |
| return f"""👤 **{r['name']}** | |
| 📊 Final Skor: {r['final_score']:.3f} (Semantik: {r['semantic_score']:.3f}, Skill: {r['skill_score']:.3f}) | |
| 📅 Deneyim: {r.get('experience_years',0):.0f} yıl | |
| 📍 Konum: {loc.get('city','?')}{' (Remote)' if loc.get('remote') else ''} | |
| 🎓 Eğitim: {', '.join(r.get('universities',['—'])[:2])} | |
| 🏢 Firmalar: {', '.join(r.get('companies',[])[:5]) or '—'} | |
| 🌐 Diller: {', '.join(r.get('languages',[])) or '—'} | |
| ✅ Eşleşen: {', '.join(exp.get('matched_skills',[])[:8]) or '—'} | |
| ❌ Eksik: {', '.join(exp.get('missing_skills',[])[:8]) or '—'}""" | |
| # ── Genel / tanınmayan soru ── | |
| avg = sum(r['final_score'] for r in R) / len(R) | |
| return f"""📊 **Analiz Özeti** ({len(R)} aday) | |
| 🏆 En iyi: **{best['name']}** ({best['final_score']:.3f}) | |
| 📊 Ortalama skor: {avg:.3f} | |
| 📅 Deneyim aralığı: {min(r.get('experience_years',0) for r in R):.0f} - {max(r.get('experience_years',0) for r in R):.0f} yıl | |
| Aşağıdaki hazır sorulardan birini seçin veya şu formatlarda sorun: | |
| • Aday adı yazın → detay bilgi (ör: "Cihat") | |
| • Skill adı yazın → o skill'e sahip adaylar (ör: "Docker bilen") | |
| • Şehir yazın → o konumdakiler (ör: "İstanbul") | |
| • "Karşılaştır" → ilk 2 adayın detaylı karşılaştırması""" | |
| # ── İş Pozisyonu Şablonları ────────────────────────────────────────────────── | |
| JOB_TEMPLATES = { | |
| "-- Pozisyon Seçin (Opsiyonel) --": "", | |
| # ── DATA & AI ──────────────────────────────────────────────────────── | |
| "Senior Data Scientist": """We are looking for a Senior Data Scientist with 3+ years of experience in building and deploying machine learning models. | |
| Required: Python, machine learning, deep learning, NLP, SQL, data analysis, pandas, scikit-learn. | |
| Nice to have: PyTorch, Docker, Git, demand forecasting, time series, feature engineering, MLflow. | |
| The candidate will work on LLM fine-tuning, transformer-based models, and A/B testing for model evaluation. | |
| Strong statistical background and experience with large-scale datasets expected.""", | |
| "Machine Learning Engineer": """Seeking an ML Engineer to design, build, and productionize machine learning pipelines at scale. | |
| Required: Python, machine learning, deep learning, PyTorch or TensorFlow, Docker, REST API, Git. | |
| Nice to have: Kubernetes, MLflow, Apache Spark, feature engineering, cloud platforms (AWS/GCP), CI/CD. | |
| Experience with model serving (TorchServe, TFServing), monitoring, and A/B testing is essential. | |
| Must be comfortable working across research and engineering teams.""", | |
| "Data Engineer": """Hiring a Data Engineer to architect, build, and maintain robust data pipelines and infrastructure. | |
| Required: Python, SQL, ETL, Apache Spark, data warehouse, Git, data modeling. | |
| Nice to have: Kafka, Airflow, dbt, AWS or GCP, Docker, PostgreSQL, Snowflake, data quality frameworks. | |
| Experience with big data technologies, streaming data, and cloud-native architectures. | |
| The candidate will own the data platform end-to-end.""", | |
| "Data Analyst": """Looking for a Data Analyst to transform raw data into actionable business insights. | |
| Required: SQL, Excel, data analysis, data visualization, Power BI or Tableau, Python or R. | |
| Nice to have: pandas, statistics, A/B testing, Google Analytics, business intelligence, KPI tracking. | |
| Strong storytelling ability with data and experience creating executive dashboards. | |
| Must collaborate closely with product and business teams.""", | |
| "NLP Engineer": """Hiring an NLP Engineer to build and optimize natural language processing systems. | |
| Required: Python, natural language processing, deep learning, PyTorch or TensorFlow, large language model. | |
| Nice to have: Hugging Face, LangChain, RAG, fine-tuning, text classification, named entity recognition, Docker. | |
| Experience with multilingual models, prompt engineering, and deploying NLP services at scale. | |
| Research publication experience is a plus.""", | |
| "AI/ML Researcher": """Seeking an AI/ML Researcher to push the boundaries of applied machine learning. | |
| Required: Python, machine learning, deep learning, PyTorch, natural language processing or computer vision. | |
| Nice to have: reinforcement learning, large language model, scientific writing, feature engineering, Git. | |
| Must have strong mathematical foundations (linear algebra, probability, optimization). | |
| Publication track record in top venues (NeurIPS, ICML, ACL, CVPR) preferred.""", | |
| # ── BACKEND & FRONTEND ─────────────────────────────────────────────── | |
| "Backend Developer (Python)": """We are hiring a Backend Developer with strong Python and API design skills. | |
| Required: Python, Django or FastAPI, REST API, PostgreSQL, Git, unit testing. | |
| Nice to have: Docker, Kubernetes, Redis, microservices, CI/CD, message queues (RabbitMQ/Kafka). | |
| Experience with cloud platforms (AWS/GCP), database optimization, and API security. | |
| The candidate will design scalable, high-performance backend services.""", | |
| "Backend Developer (Java/Spring)": """Looking for a Java Backend Developer to build enterprise-grade applications. | |
| Required: Java, Spring Boot, REST API, SQL, Git, microservices, unit testing. | |
| Nice to have: Docker, Kubernetes, Kafka, Redis, PostgreSQL, CI/CD, cloud platforms. | |
| Experience with domain-driven design, event-driven architecture, and performance tuning. | |
| Must be comfortable with code reviews, TDD, and agile methodologies.""", | |
| "Backend Developer (C#/.NET)": """Hiring a C#/.NET Backend Developer for scalable enterprise solutions. | |
| Required: C#, ASP.NET Core, REST API, SQL Server, Git, Entity Framework, unit testing. | |
| Nice to have: Docker, Azure, microservices, RabbitMQ, Redis, CI/CD pipelines. | |
| Experience with clean architecture, CQRS pattern, and Azure DevOps. | |
| The candidate will maintain and evolve mission-critical backend systems.""", | |
| "Frontend Developer (React)": """Looking for a Frontend Developer experienced in modern web technologies. | |
| Required: JavaScript, TypeScript, React, HTML, CSS, Git, responsive design. | |
| Nice to have: Next.js, Tailwind, GraphQL, unit testing (Jest), Figma, Storybook. | |
| Strong understanding of web performance, accessibility (a11y), and state management. | |
| The candidate will collaborate with designers and backend engineers.""", | |
| "Frontend Developer (Angular)": """Seeking an Angular Frontend Developer for enterprise web applications. | |
| Required: JavaScript, TypeScript, Angular, HTML, CSS, Git, RxJS. | |
| Nice to have: NgRx, Tailwind or Bootstrap, unit testing (Jasmine/Karma), REST API integration. | |
| Experience with modular architecture, lazy loading, and enterprise design systems. | |
| Must be comfortable with agile workflows and cross-functional teams.""", | |
| "Full Stack Developer": """Seeking a Full Stack Developer with end-to-end development experience. | |
| Required: JavaScript, TypeScript, React, Node.js, SQL, REST API, Git. | |
| Nice to have: Docker, MongoDB, AWS, CI/CD pipelines, GraphQL, unit testing. | |
| The candidate will own features from database design to UI deployment. | |
| Must be comfortable context-switching between frontend and backend tasks.""", | |
| # ── MOBILE ─────────────────────────────────────────────────────────── | |
| "Mobile Developer (Flutter)": """Hiring a Mobile Developer for cross-platform app development. | |
| Required: Flutter, Dart, REST API, Git, state management, mobile UI/UX. | |
| Nice to have: Firebase, iOS, Android, unit testing, push notifications, app performance. | |
| Published apps on App Store or Google Play is a strong plus. | |
| Experience with CI/CD for mobile (Codemagic, Fastlane) preferred.""", | |
| "Mobile Developer (iOS)": """Looking for an iOS Developer to build high-quality native applications. | |
| Required: Swift, iOS, Xcode, UIKit or SwiftUI, REST API, Git. | |
| Nice to have: Core Data, Combine, unit testing (XCTest), CI/CD, push notifications, App Store deployment. | |
| Experience with MVVM/VIPER architecture and Apple Human Interface Guidelines. | |
| Must have published at least one app on the App Store.""", | |
| "Mobile Developer (Android)": """Seeking an Android Developer for native Android application development. | |
| Required: Kotlin, Android, Android Studio, REST API, Git, Jetpack Compose. | |
| Nice to have: Room, Coroutines, unit testing, Firebase, CI/CD, Google Play deployment. | |
| Experience with MVVM architecture, Material Design, and performance optimization. | |
| Must be familiar with Android lifecycle and background processing.""", | |
| # ── DEVOPS & CLOUD ─────────────────────────────────────────────────── | |
| "DevOps Engineer": """We need a DevOps Engineer to design and manage cloud infrastructure and CI/CD pipelines. | |
| Required: Linux, Docker, Kubernetes, Terraform, CI/CD, Git, Python or Bash. | |
| Nice to have: AWS or Azure, Prometheus, Grafana, Ansible, Helm, ArgoCD. | |
| Experience with Infrastructure as Code, monitoring/alerting, and incident response. | |
| The candidate will drive reliability, scalability, and deployment automation.""", | |
| "Cloud Architect (AWS)": """Hiring a Cloud Architect to design and govern AWS cloud infrastructure. | |
| Required: AWS, cloud architecture, networking, security, Terraform, Docker. | |
| Nice to have: Kubernetes, serverless (Lambda), cost optimization, multi-account strategy, CI/CD. | |
| AWS Solutions Architect certification strongly preferred. | |
| The candidate will define cloud standards and migration strategies.""", | |
| "Site Reliability Engineer (SRE)": """Looking for an SRE to ensure the reliability and performance of production systems. | |
| Required: Linux, Docker, Kubernetes, monitoring (Prometheus/Grafana), CI/CD, Python or Go. | |
| Nice to have: Terraform, incident management, SLO/SLI definition, chaos engineering, AWS or GCP. | |
| Experience with on-call rotations, postmortem culture, and capacity planning. | |
| Must balance reliability engineering with feature velocity.""", | |
| # ── SECURITY ───────────────────────────────────────────────────────── | |
| "Cybersecurity Analyst": """Looking for a Cybersecurity Analyst to protect our organization's infrastructure and data. | |
| Required: Cybersecurity, networking, Linux, SIEM, incident response, identity management. | |
| Nice to have: Penetration testing, SOC operations, Python scripting, cloud security, vulnerability scanning. | |
| Security certifications (CISSP, CEH, CompTIA Security+) are a plus. | |
| Experience with compliance frameworks (ISO 27001, SOC 2, GDPR) preferred.""", | |
| # ── İŞ ANALİZİ & YÖNETİM ──────────────────────────────────────────── | |
| "Business Analyst": """Looking for a Business Analyst to bridge business needs and technology solutions. | |
| Required: Business analysis, requirements engineering, process modeling, SQL, Excel, stakeholder management. | |
| Nice to have: Power BI or Tableau, BPMN, Jira, user story writing, data visualization, use case modeling. | |
| Strong communication, documentation, and presentation skills are essential. | |
| Experience with agile methodologies and cross-functional collaboration.""", | |
| "Senior Business Analyst": """Seeking a Senior Business Analyst to lead complex business transformation projects. | |
| Required: Business analysis, requirements engineering, process modeling, stakeholder management, SQL, Excel. | |
| Nice to have: Business intelligence, Power BI, data visualization, change management, risk management, ERP consulting. | |
| Must be able to facilitate workshops, manage conflicting requirements, and mentor junior analysts. | |
| CBAP or PMI-PBA certification is a plus.""", | |
| "Product Manager": """Seeking a Product Manager to own product strategy, roadmap, and execution. | |
| Required: Product management, stakeholder management, user story, KPI tracking, agile, data analysis. | |
| Nice to have: SQL, Jira, UX research, A/B testing, data visualization, business intelligence. | |
| Experience in SaaS, B2B, or platform products is preferred. | |
| Must be able to prioritize ruthlessly and communicate product vision clearly.""", | |
| "Product Owner": """Hiring a Product Owner to manage the product backlog and maximize value delivery. | |
| Required: Product management, user story, stakeholder management, agile, KPI tracking, Jira. | |
| Nice to have: SQL, data analysis, UX research, process modeling, business analysis. | |
| Must have strong prioritization skills and ability to translate business needs into technical requirements. | |
| Certified Scrum Product Owner (CSPO) is a plus.""", | |
| "Project Manager (IT)": """Looking for an IT Project Manager to deliver technology projects on time and within budget. | |
| Required: Project management, stakeholder management, risk management, agile, Excel, Jira. | |
| Nice to have: change management, process modeling, business analysis, KPI tracking, budgeting. | |
| PMP or PRINCE2 certification strongly preferred. | |
| Experience managing cross-functional teams of 5-20 members.""", | |
| "Scrum Master": """Seeking a Scrum Master to facilitate agile practices across development teams. | |
| Required: Agile, project management, stakeholder management, user story, communication. | |
| Nice to have: Jira, KPI tracking, change management, risk management, coaching skills. | |
| CSM or PSM certification required. Must understand Scrum, Kanban, and SAFe frameworks. | |
| Experience removing impediments and fostering continuous improvement culture.""", | |
| # ── ERP & CRM ──────────────────────────────────────────────────────── | |
| "ERP Consultant (SAP)": """Hiring an ERP Consultant specializing in SAP implementations and process optimization. | |
| Required: SAP, ERP consulting, process modeling, requirements engineering, SQL, stakeholder management. | |
| Nice to have: SAP S/4HANA, change management, Excel, data visualization, business analysis, ABAP basics. | |
| Functional expertise in FI/CO, MM/SD, or PP modules preferred. | |
| Experience with full-cycle SAP implementations (Blueprint to Go-Live).""", | |
| "CRM Specialist": """Looking for a CRM Specialist to optimize customer relationship management processes. | |
| Required: CRM, business analysis, data analysis, Excel, process modeling, stakeholder management. | |
| Nice to have: Salesforce, HubSpot, SQL, data visualization, KPI tracking, digital marketing. | |
| Experience with CRM configuration, workflow automation, and user training. | |
| Must understand sales/marketing funnels and customer journey mapping.""", | |
| # ── QA & TEST ──────────────────────────────────────────────────────── | |
| "QA / Test Automation Engineer": """Looking for a QA Engineer to design and implement automated test frameworks. | |
| Required: QA, Selenium or Cypress, Python or JavaScript, REST API testing, Git, unit testing. | |
| Nice to have: Playwright, CI/CD, Docker, Postman, load testing, agile, test planning. | |
| ISTQB certification is a plus. Experience with BDD (Cucumber) and performance testing (JMeter). | |
| Must advocate for quality throughout the software development lifecycle.""", | |
| # ── TASARIM ────────────────────────────────────────────────────────── | |
| "UI/UX Designer": """Seeking a UI/UX Designer to create intuitive and engaging digital experiences. | |
| Required: UX research, data visualization, process modeling, communication, Figma or Sketch. | |
| Nice to have: HTML, CSS, user story, stakeholder management, A/B testing, design systems. | |
| Portfolio demonstrating end-to-end design process (research → wireframe → prototype → test) required. | |
| Experience with accessibility standards and mobile-first design.""", | |
| # ── DİĞER ──────────────────────────────────────────────────────────── | |
| "Technical Writer": """Hiring a Technical Writer to create clear, accurate technical documentation. | |
| Required: Communication, process modeling, documentation tools (Confluence, GitBook), Git. | |
| Nice to have: HTML, REST API knowledge, UX research, user story, diagramming (draw.io, Mermaid). | |
| Must be able to translate complex technical concepts into user-friendly content. | |
| Experience with API documentation (Swagger/OpenAPI) is a plus.""", | |
| "Solutions Architect": """Seeking a Solutions Architect to design end-to-end technical solutions for enterprise clients. | |
| Required: Software architecture, cloud platforms (AWS/Azure/GCP), microservices, REST API, SQL, stakeholder management. | |
| Nice to have: Docker, Kubernetes, Terraform, process modeling, requirements engineering, security. | |
| Must bridge business requirements and technical implementation. | |
| Experience with pre-sales, RFP responses, and client-facing presentations.""", | |
| "Database Administrator (DBA)": """Looking for a DBA to manage, optimize, and secure database infrastructure. | |
| Required: SQL, PostgreSQL or MySQL or Oracle, database performance tuning, backup/recovery, Linux. | |
| Nice to have: MongoDB, Redis, cloud databases (RDS, Cloud SQL), automation scripting (Python/Bash). | |
| Experience with replication, sharding, and high-availability configurations. | |
| Must ensure data integrity, security, and compliance.""", | |
| # ── MEKANİK & ENDÜSTRİ MÜHENDİSLİĞİ ──────────────────────────────── | |
| "Mechanical Design Engineer": """Seeking a Mechanical Design Engineer for product development and detailed design. | |
| Required: Mechanical design, CAD (SolidWorks or CATIA), tolerance analysis, GD&T, materials science. | |
| Nice to have: FEA (ANSYS), CFD, manufacturing, 3D printing, PDM/PLM systems. | |
| Experience with design for manufacturability (DFM) and design reviews. | |
| Must be proficient in creating production-ready technical drawings.""", | |
| "Manufacturing Engineer": """Hiring a Manufacturing Engineer to optimize production processes and quality. | |
| Required: Manufacturing, CNC, quality control, CAD, process optimization, ISO 9001. | |
| Nice to have: Lean, Six Sigma, CAM, injection molding, sheet metal, welding, ERP planning. | |
| Experience with production planning, line balancing, and continuous improvement.""", | |
| "Robotics Engineer": """Looking for a Robotics Engineer to design and program industrial automation systems. | |
| Required: Robotics, ROS, PLC, robot programming, control systems, Python or C++. | |
| Nice to have: Machine vision, motion control, industrial robot, Arduino, embedded systems. | |
| Experience with ABB/FANUC/KUKA robot programming and sensor integration.""", | |
| "Automation Engineer": """Seeking an Automation Engineer for factory automation and Industry 4.0 solutions. | |
| Required: PLC, SCADA, automation design, industrial robot, electrical engineering, HMI. | |
| Nice to have: Robot programming, machine vision, IoT, Python, data analysis, OPC UA. | |
| Experience with Siemens/Allen-Bradley PLC programming and commissioning.""", | |
| "Automotive Engineer (R&D)": """Hiring an Automotive R&D Engineer for vehicle development projects. | |
| Required: Automotive engineering, CAD, FEA, vehicle dynamics, materials science. | |
| Nice to have: ADAS, powertrain, ISO 26262, CFD, MATLAB, Simulink. | |
| Experience with APQP/PPAP processes and automotive safety standards.""", | |
| "Electrical & Electronics Engineer": """Looking for an E&E Engineer for product development and system design. | |
| Required: Electrical engineering, PCB design, signal processing, circuit design, CAD. | |
| Nice to have: Power electronics, embedded systems, SCADA, MATLAB, testing/debugging. | |
| Experience with EMC testing, safety standards (CE/UL), and schematic review.""", | |
| "Industrial Engineer": """Seeking an Industrial Engineer to improve operational efficiency and productivity. | |
| Required: Industrial engineering, process optimization, production planning, Excel, data analysis. | |
| Nice to have: Lean, Six Sigma, inventory management, ERP, time study, simulation. | |
| Experience with capacity planning, ergonomics, and work measurement studies.""", | |
| "Quality Engineer": """Hiring a Quality Engineer to ensure product quality and compliance. | |
| Required: Quality control, ISO 9001, quality assurance, statistical analysis, root cause analysis. | |
| Nice to have: Six Sigma, FMEA, 8D, SPC, IATF 16949, measurement systems analysis. | |
| ASQ CQE certification is a plus.""", | |
| "Structural Engineer": """Looking for a Structural Engineer to design and analyze building structures. | |
| Required: Structural engineering, FEA, CAD, BIM (Revit), reinforced concrete, steel structure. | |
| Nice to have: Geotechnical, SAP2000, ETABS, seismic design, construction supervision. | |
| Must be licensed (Professional Engineer) or working towards licensure.""", | |
| "Mechatronics Engineer": """Hiring a Mechatronics Engineer to integrate mechanical, electronic, and software systems. | |
| Required: Mechanical design, electrical engineering, embedded systems, PLC, Python or C++. | |
| Nice to have: ROS, control systems, IoT, PCB design, CAD, machine vision. | |
| Experience with system integration, prototyping, and cross-disciplinary problem solving.""", | |
| } | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| # GRADIO ARAYÜZÜ | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| HERO_HTML = """ | |
| <div id="hero-banner"> | |
| <h1>⚡ CV MATCHING ENGINE</h1> | |
| <p>PDF CV'leri yükle · İş ilanını gir · Semantik analiz & XAI ile sırala</p> | |
| <div style="margin-top:14px"> | |
| <span class="hero-badge">SEMANTIC NLP</span> | |
| <span class="hero-badge">SKILL ONTOLOGY</span> | |
| <span class="hero-badge">HYBRID SCORING</span> | |
| <span class="hero-badge">EXPLAINABLE AI</span> | |
| </div> | |
| </div> | |
| """ | |
| with gr.Blocks(css=CUSTOM_CSS, title="CV Matching Engine") as demo: | |
| # ── Hero Banner ──────────────────────────────────────────────────────── | |
| gr.HTML(HERO_HTML) | |
| # ── Ana Layout ──────────────────────────────────────────────────────── | |
| with gr.Row(): | |
| # ── SOL: Kontrol Paneli ────────────────────────────────────────── | |
| with gr.Column(scale=1, min_width=310): | |
| gr.HTML('<div class="panel-title">📂 CV YÜKLEME</div>') | |
| pdf_input = gr.File( | |
| label="PDF CV Dosyaları (Çoklu seçim desteklenir)", | |
| file_types=[".pdf"], | |
| file_count="multiple", | |
| elem_classes=["upload-zone"] | |
| ) | |
| gr.HTML('<div style="margin-top:20px" class="panel-title">📋 İŞ İLANI</div>') | |
| job_title_dd = gr.Dropdown( | |
| choices=list(JOB_TEMPLATES.keys()), | |
| value="-- Pozisyon Seçin (Opsiyonel) --", | |
| label="Pozisyon Şablonu", | |
| interactive=True, | |
| elem_classes=["job-title-dropdown"] | |
| ) | |
| job_input = gr.Textbox( | |
| label="İş Tanımı", | |
| placeholder="Yukarıdan şablon seçin veya kendi iş ilanınızı yazın...", | |
| lines=7, | |
| value="""We are looking for a Senior Data Scientist with 3+ years of experience. | |
| Required: Python, machine learning, deep learning, NLP, SQL. | |
| Nice to have: PyTorch, Docker, Git, demand forecasting, data analysis. | |
| The candidate will work on LLM fine-tuning and transformer-based models.""" | |
| ) | |
| gr.HTML('<div style="margin-top:20px" class="panel-title">⚙️ SKOR AĞIRLIKLARI</div>') | |
| gr.HTML('<div style="font-family:var(--font-mono);font-size:0.72rem;color:var(--text-dim);margin-bottom:12px">Ağırlıklar otomatik normalize edilir (toplam = 1.0)</div>') | |
| sem_w = gr.Slider(0.0, 1.0, value=0.55, step=0.05, label="Semantik Benzerlik") | |
| skill_w = gr.Slider(0.0, 1.0, value=0.35, step=0.05, label="Skill Örtüşmesi") | |
| exp_w = gr.Slider(0.0, 1.0, value=0.10, step=0.05, label="Deneyim Bonusu") | |
| loc_w = gr.Slider(0.0, 1.0, value=0.10, step=0.05, label="Konum Uyumu") | |
| gr.HTML('<div style="margin-top:16px" class="panel-title">📍 KONUM & FİLTRE</div>') | |
| job_location_input = gr.Textbox( | |
| label="İş Konumu (opsiyonel)", | |
| placeholder="Örn: İstanbul, Türkiye veya Remote", | |
| lines=1, | |
| value="" | |
| ) | |
| keyword_input = gr.Textbox( | |
| label="Anahtar Kelime Filtresi (opsiyonel)", | |
| placeholder="Virgülle ayırın: SAP, Almanca, PMP", | |
| lines=1, | |
| value="" | |
| ) | |
| uni_dropdown = gr.Dropdown( | |
| choices=TR_UNIVERSITIES, | |
| value=[], | |
| label="Üniversite Filtresi (opsiyonel, çoklu seçim)", | |
| multiselect=True, | |
| interactive=True, | |
| ) | |
| gr.HTML('<div style="margin-top:16px" class="panel-title">🔧 SEÇENEKLER</div>') | |
| gr.HTML('<div style="font-family:var(--font-mono);font-size:0.72rem;color:var(--text-dim);margin-bottom:6px">Deneyim aralığı (yıl)</div>') | |
| with gr.Row(): | |
| min_years = gr.Slider(0, 15, value=3, step=0.5, label="Min Deneyim") | |
| max_years = gr.Slider(0, 15, value=5, step=0.5, label="Max Deneyim") | |
| dual_col = gr.Checkbox(label="İki sütunlu CV formatı (dual-column)", value=False) | |
| analyze_btn = gr.Button("▶ ANALİZİ BAŞLAT", elem_id="analyze-btn", variant="primary") | |
| gr.HTML('<div style="margin-top:16px" class="panel-title">📟 DURUM</div>') | |
| status_out = gr.HTML('<div class="log-box"><span style="color:var(--text-dim)">Hazır. PDF yükleyip analizi başlatın.</span></div>') | |
| # ── SAĞ: Sonuç Paneli ──────────────────────────────────────────── | |
| with gr.Column(scale=3): | |
| with gr.Tabs(): | |
| # ── Tab 1: Sıralama Tablosu ─────────────────────────── | |
| with gr.Tab("🏆 Sıralama"): | |
| gr.HTML('<div style="margin-bottom:12px" class="panel-title">ADAY SIRALAMA TABLOSU</div>') | |
| table_out = gr.HTML( | |
| value='<div class="log-box" style="text-align:center;padding:40px">' | |
| '<span style="color:var(--text-dim);font-family:var(--font-display);letter-spacing:2px">' | |
| 'PDF CV yükleyip analizi başlatın</span></div>' | |
| ) | |
| gr.HTML('<div style="margin-top:24px" class="panel-title">💬 CV ASİSTANI</div>') | |
| chatbot = gr.Chatbot(height=300, show_label=False, type="tuples", elem_id="inline-chat") | |
| with gr.Row(): | |
| chat_input = gr.Textbox(placeholder="Adaylar hakkında sorun...", show_label=False, scale=6, lines=1) | |
| chat_send = gr.Button("Gönder", variant="primary", scale=1) | |
| gr.HTML('<div style="margin-top:10px;font-size:0.72rem;color:var(--text-dim);letter-spacing:0.5px">HAZIR SORULAR</div>') | |
| quick_questions = [ | |
| "🏆 En iyi aday kim?", | |
| "📊 Tüm adayları sırala", | |
| "📅 Deneyim sıralaması", | |
| "📍 Konum bilgileri", | |
| "🎓 Üniversite bilgileri", | |
| "🏢 Firma geçmişleri", | |
| "🌐 Dil bilgileri", | |
| "🔄 İlk 2 adayı karşılaştır", | |
| "❌ Eksik beceri analizi", | |
| "✅ Eşleşen beceriler", | |
| "🐍 Python bilen adaylar", | |
| "🗄️ SQL bilen adaylar", | |
| "🤖 ML bilen adaylar", | |
| "🐳 Docker bilen adaylar", | |
| ] | |
| with gr.Row(elem_id="quick-q-row"): | |
| for qq in quick_questions[:7]: | |
| gr.Button(qq, size="sm", elem_classes=["quick-q-btn"]).click( | |
| fn=lambda q=qq: ([(q, chat_with_cvs(q, []))], ""), | |
| outputs=[chatbot, chat_input], api_name=False) | |
| with gr.Row(elem_id="quick-q-row2"): | |
| for qq in quick_questions[7:]: | |
| gr.Button(qq, size="sm", elem_classes=["quick-q-btn"]).click( | |
| fn=lambda q=qq: ([(q, chat_with_cvs(q, []))], ""), | |
| outputs=[chatbot, chat_input], api_name=False) | |
| # ── Tab 2: Görsel Dashboard ─────────────────────────── | |
| with gr.Tab("📊 Dashboard"): | |
| gr.HTML('<div class="panel-title">📊 SKOR GRAFİĞİ</div>') | |
| bar_plot = gr.Plot(label="") | |
| with gr.Row(): | |
| stack_plot = gr.Plot(label="") | |
| scatter_plot = gr.Plot(label="") | |
| with gr.Row(): | |
| gap_plot = gr.Plot(label="") | |
| # ── Tab 3: XAI Detay ────────────────────────────────── | |
| with gr.Tab("🔍 XAI Detay"): | |
| gr.HTML('<div style="margin-bottom:14px" class="panel-title">AÇIKLANABİLİR AI — ADAY DETAY ANALİZİ</div>') | |
| xai_selector = gr.Dropdown( | |
| choices=[], label="Aday Seçin", interactive=True | |
| ) | |
| xai_out = gr.HTML( | |
| value='<div class="log-box" style="text-align:center;padding:40px">' | |
| '<span style="color:var(--text-dim);font-family:var(--font-display);letter-spacing:2px">' | |
| 'Analiz tamamlandıktan sonra aday seçin</span></div>' | |
| ) | |
| # ── Event Handlers ─────────────────────────────────────────────────── | |
| def on_title_select(title): | |
| template = JOB_TEMPLATES.get(title, "") | |
| return gr.update(value=template) if template else gr.update() | |
| job_title_dd.change( | |
| fn=on_title_select, | |
| inputs=[job_title_dd], | |
| outputs=[job_input], | |
| api_name=False, # ← ekle | |
| ) | |
| analyze_btn.click( | |
| fn=run_analysis, | |
| inputs=[pdf_input, job_input, sem_w, skill_w, exp_w, loc_w, min_years, max_years, dual_col, job_location_input, keyword_input, uni_dropdown], | |
| outputs=[table_out, bar_plot, stack_plot, gap_plot, scatter_plot, xai_selector, status_out], | |
| api_name=False, # ← ekle | |
| ) | |
| xai_selector.change( | |
| fn=show_xai, | |
| inputs=[xai_selector], | |
| outputs=[xai_out], | |
| api_name=False, # ← ekle | |
| ) | |
| def chat_respond(msg, hist): | |
| if not msg.strip(): return hist, "" | |
| return hist + [(msg, chat_with_cvs(msg, hist))], "" | |
| chat_send.click(fn=chat_respond, inputs=[chat_input, chatbot], outputs=[chatbot, chat_input], api_name=False) | |
| chat_input.submit(fn=chat_respond, inputs=[chat_input, chatbot], outputs=[chatbot, chat_input], api_name=False) | |
| gr.close_all() # This shuts down any other apps running in the background | |
| # ── Uygulamayı başlat ──────────────────────────────────────────────────── | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True, | |
| show_api=False, # ← bunu ekle | |
| height=900 | |
| ) | |