""" processor_utils.py ================== Shared taxonomy data and pure helper functions. THIS IS THE SINGLE SOURCE OF TRUTH for skill aliases, category mappings, seniority patterns, and normalisation utilities. The scrapper uses this directly. The processor fetches it via GET /taxonomy on the scrapper API. Design notes ------------ * Aliases are deliberately global — not specialised for any single industry. Tech, healthcare, finance, legal, education, construction, hospitality, etc. all work with the same extractor; only the alias set differs per domain. * OR/slash compound expressions ("Spring Boot/Django", "React or Angular") are already handled by _normalize() which converts "/" and the word "or" into spaces, letting each token match independently. * Over-broad single-word aliases (bare "compliance", bare "prototype") have been replaced with more specific multi-word variants to prevent false positives across domains. """ from __future__ import annotations import re from datetime import datetime as _dt from typing import Any, Dict, List, Optional, Set, Tuple # --------------------------------------------------------------------------- # Universal Skill Taxonomy # --------------------------------------------------------------------------- DEFAULT_SKILL_ALIASES: Dict[str, List[str]] = { # ── Office / Admin ─────────────────────────────────────────────────────── "microsoft word": ["microsoft word", "ms word", "word processor"], "microsoft excel": [ "microsoft excel", "ms excel", "excel", "spreadsheets", "google sheets", ], "microsoft powerpoint": [ "microsoft powerpoint", "ms powerpoint", "powerpoint", "power point", "google slides", "presentations", ], "microsoft outlook": ["microsoft outlook", "ms outlook", "outlook"], "microsoft office": [ "microsoft office", "ms office", "office suite", "office 365", "microsoft 365", ], "google workspace": [ "google workspace", "google docs", "google drive", "gsuite", "g suite", ], "data entry": [ "data entry", "typing", "data input", "data capture", "data processing", ], "filing": [ "filing", "document management", "records management", "file management", "document control", "record keeping", ], "scheduling": [ "scheduling", "calendar management", "appointment setting", "diary management", "timetabling", ], "communication": [ "communication", "interpersonal", "verbal communication", "written communication", "correspondence", ], "teamwork": [ "teamwork", "team work", "team player", "collaboration", "cross-functional", ], "customer service": [ "customer service", "client service", "customer support", "client relations", "client care", ], "problem solving": [ "problem solving", "problem-solving", "critical thinking", "analytical thinking", "troubleshooting", ], "time management": [ "time management", "prioritization", "prioritisation", "multitasking", "deadline management", ], "reporting": [ "reporting", "report writing", "report generation", "management reporting", "progress reports", ], "inventory management": [ "inventory management", "stock control", "stock management", "stock checking", "inventory tracking", ], "warehouse": [ "warehouse", "stockroom", "store room", "stores", "warehouse operations", ], "leadership": [ "leadership", "team leadership", "people management", "people leader", ], "attention to detail": [ "attention to detail", "accuracy", "meticulous", "detail oriented", "detail-oriented", ], "adaptability": ["adaptability", "adaptable", "flexible", "versatile"], "stakeholder management": [ "stakeholder management", "stakeholder engagement", "stakeholder communication", "stakeholder relations", ], "public speaking": [ "public speaking", "presentation skills", "presenting", "facilitation", "keynote", ], "conflict resolution": [ "conflict resolution", "dispute management", "mediation skills", "grievance resolution", ], "decision making": [ "decision making", "decision-making", "strategic thinking", "judgement", ], "emotional intelligence": [ "emotional intelligence", "eq", "empathy", "self-awareness", ], "technical writing": [ "technical writing", "technical documentation", "user manuals", "sop writing", "standard operating procedures", ], "erp": [ "erp", "enterprise resource planning", "sap erp", "oracle erp", "dynamics 365", "netsuite", "odoo", ], "ms project": [ "ms project", "microsoft project", "project planning software", "primavera", ], "minute taking": ["minute taking", "meeting minutes", "notetaking", "secretarial"], "virtual assistant": [ "virtual assistant", "executive assistant", "pa", "personal assistant", "administrative assistant", ], # ── Finance & Accounting ───────────────────────────────────────────────── "accounting": ["accounting", "accountancy", "bookkeeping", "accounts"], "financial analysis": [ "financial analysis", "financial modelling", "financial modeling", "financial reporting", "financial planning", "fp&a", ], "auditing": [ "auditing", "internal audit", "external audit", "audit", "statutory audit", "audit trail", ], "taxation": [ "taxation", "tax", "tax compliance", "tax preparation", "tax returns", "vat", "corporation tax", "withholding tax", ], "payroll": [ "payroll", "payroll processing", "payroll management", "payroll administration", ], "budgeting": [ "budgeting", "budget management", "budget planning", "forecasting", "variance analysis", "capex", "opex", ], "accounts payable": ["accounts payable", "vendor payments", "creditors", "ap"], "accounts receivable": [ "accounts receivable", "invoicing", "debtors", "collections", "ar", ], "quickbooks": ["quickbooks", "quick books"], "sage": ["sage", "sage accounting", "sage 50", "sage 200", "sage intacct"], "sap": ["sap", "sap finance", "sap s/4hana", "sap fico", "sap hana"], "xero": ["xero"], "ifrs": ["ifrs", "international financial reporting standards"], "gaap": ["gaap", "generally accepted accounting principles", "us gaap", "uk gaap"], "reconciliation": [ "reconciliation", "bank reconciliation", "account reconciliation", ], "cash flow": ["cash flow", "cash management", "treasury", "liquidity management"], "risk management": [ "risk management", "risk assessment", "enterprise risk management", "erm", "risk framework", "risk mitigation", ], "kyc": [ "kyc", "know your customer", "customer due diligence", "cdd", "customer onboarding compliance", ], "aml": [ "aml", "anti-money laundering", "anti money laundering", "financial crime", "fraud detection", ], "core banking": [ "core banking", "banking system", "flexcube", "temenos", "finacle", "murex", "t24", ], "trade finance": [ "trade finance", "letter of credit", "documentary credit", "trade operations", ], "wealth management": [ "wealth management", "asset management", "portfolio management", "investment management", "fund management", ], "bloomberg": ["bloomberg", "bloomberg terminal", "bloomberg api"], "financial compliance": [ "financial compliance", "financial regulation", "regulatory reporting", "cbk", "sec compliance", ], "actuarial": ["actuarial", "actuary", "actuarial science", "actuarial analysis"], "cfa": ["cfa", "chartered financial analyst"], "acca": ["acca", "chartered accountant", "cpa", "cima", "icpak", "aca"], "derivatives": [ "derivatives", "futures", "options", "swaps", "structured products", ], "credit analysis": [ "credit analysis", "credit risk", "loan appraisal", "credit scoring", "underwriting", ], # ── Human Resources ────────────────────────────────────────────────────── "recruitment": [ "recruitment", "recruiting", "talent acquisition", "hiring", "staffing", "talent sourcing", "headhunting", ], "onboarding": [ "staff onboarding", "new hire orientation", "employee induction", "new employee onboarding", ], "performance management": [ "performance management", "performance review", "appraisal", "kpi management", "okr", ], "employee relations": [ "employee relations", "labour relations", "labor relations", "grievance handling", "disciplinary", ], "training and development": [ "learning and development", "l&d", "employee training", "talent development", "workforce training", "capability building", ], "hris": [ "hris", "hr information system", "workday", "bamboohr", "zoho hr", "oracle hcm", "successfactors", "peoplesoft", ], "compensation": [ "compensation", "benefits", "compensation and benefits", "remuneration", "total rewards", "salary benchmarking", ], "hr compliance": [ "hr compliance", "employment law", "labour law", "labor law", "hr policy", "hr regulation", "employment relations", ], "job analysis": [ "job analysis", "job evaluation", "job grading", "job description", ], "succession planning": [ "succession planning", "talent pipeline", "leadership development", ], "workforce planning": [ "workforce planning", "headcount planning", "org design", "organisational design", ], "dei": [ "diversity equity inclusion", "dei", "diversity and inclusion", "equal opportunity", "inclusive workplace", ], "talent management": [ "talent management", "talent strategy", "high potential", "talent review", ], "culture": [ "culture", "employee engagement", "employer branding", "employee experience", ], # ── Sales & Business Development ───────────────────────────────────────── "sales": [ "sales", "selling", "business development", "b2b sales", "b2c sales", "direct sales", "inside sales", "field sales", ], "crm": [ "crm", "salesforce", "hubspot", "zoho crm", "customer relationship management", "pipedrive", "dynamics crm", ], "negotiation": [ "negotiation", "contract negotiation", "deal closing", "deal making", ], "lead generation": [ "lead generation", "prospecting", "cold calling", "cold outreach", "demand generation", ], "account management": [ "account management", "key account management", "client management", "portfolio management", ], "retail": [ "retail", "retail sales", "merchandising", "point of sale", "pos", "fmcg", ], "business analysis": [ "business analysis", "business analyst", "requirements gathering", "process improvement", "business case", ], "partnership": [ "partnership", "channel sales", "partnership management", "alliances", "channel management", "reseller", ], "customer success": [ "customer success", "customer retention", "account growth", "upselling", "cross-selling", ], "revenue operations": [ "revenue operations", "revops", "sales operations", "go-to-market", "gtm", ], # ── Customer Support ───────────────────────────────────────────────────── "help desk": [ "help desk", "helpdesk", "it support", "technical support", "service desk", "l1 support", "l2 support", ], "ticketing systems": [ "ticketing", "zendesk", "freshdesk", "jira service desk", "servicenow", "remedy", "ivanti", ], "call centre": [ "call centre", "call center", "contact centre", "contact center", "inbound calls", "outbound calls", ], "live chat": ["live chat", "chat support", "intercom", "drift", "livechat"], "sla management": [ "sla", "sla management", "service level agreement", "service level", "response time", ], "customer satisfaction": [ "customer satisfaction", "csat", "net promoter score", "nps", "customer feedback", ], # ── Healthcare ─────────────────────────────────────────────────────────── "patient care": [ "patient care", "patient management", "bedside manner", "patient assessment", "patient monitoring", ], "nursing": [ "nursing", "registered nurse", "rn", "clinical nursing", "enrolled nurse", "bscn", ], "clinical skills": [ "clinical skills", "clinical assessment", "clinical procedures", "clinical competencies", ], "pharmacy": [ "pharmacy", "dispensing", "pharmaceutical", "pharmacology", "medication management", ], "first aid": [ "first aid", "cpr", "bls", "basic life support", "acls", "emergency response", "resuscitation", ], "medical records": [ "medical records", "electronic health records", "medical documentation", "health records", ], "infection control": [ "infection control", "infection prevention", "ips", "sterilization", "ppe", ], "midwifery": [ "midwifery", "obstetrics", "maternity", "antenatal", "postnatal", "labour ward", ], "physiotherapy": [ "physiotherapy", "physical therapy", "rehabilitation", "musculoskeletal", ], "laboratory": [ "laboratory", "lab technician", "medical laboratory", "specimen processing", "pathology", ], "ehr systems": [ "ehr", "emr", "electronic health record", "epic", "cerner", "meditech", "allscripts", "hospital information system", ], "hl7": [ "hl7", "hl7 fhir", "fhir", "healthcare interoperability", "health data exchange", ], "clinical trials": [ "clinical trials", "good clinical practice", "gcp", "clinical research", "research protocol", ], "icd coding": [ "icd", "icd-10", "icd-11", "medical coding", "cpt coding", "clinical coding", ], "telemedicine": [ "telemedicine", "telehealth", "remote patient monitoring", "virtual clinic", ], "surgical": [ "surgical", "theatre", "perioperative", "scrub nurse", "surgical technician", ], "radiology": [ "radiology", "imaging", "mri", "ct scan", "x-ray", "radiography", "ultrasound", ], "nutrition": [ "nutrition", "dietetics", "dietitian", "nutritionist", "food science", ], "mental health": [ "mental health", "psychiatry", "psychology", "counselling", "counseling", "psychotherapy", "cbt", ], # ── Legal ──────────────────────────────────────────────────────────────── "legal research": [ "legal research", "case research", "statute interpretation", "case law", ], "contract drafting": [ "contract drafting", "contract review", "drafting agreements", "legal drafting", "contract management", ], "litigation": [ "litigation", "court proceedings", "legal proceedings", "advocacy", "trial", ], "legal compliance": [ "legal compliance", "regulatory affairs", "compliance management", "regulatory framework", ], "conveyancing": [ "conveyancing", "property law", "land transactions", "title search", ], "corporate law": [ "corporate law", "company law", "corporate governance", "company secretarial", ], "legal writing": [ "legal writing", "pleadings", "legal opinions", "memoranda", "legal briefs", ], "due diligence": [ "due diligence", "legal due diligence", "commercial due diligence", ], "mergers acquisitions": [ "mergers and acquisitions", "m&a", "corporate restructuring", "merger", "acquisition", "takeover", ], "intellectual property": [ "intellectual property", "ip law", "trademark", "patent", "copyright", "ip rights", "ip management", ], "arbitration": [ "arbitration", "mediation", "dispute resolution", "adr", "alternative dispute resolution", ], "legal tech": [ "legal tech", "legaltech", "contract automation", "clm", "e-discovery", ], "employment law": [ "employment law", "labour law", "labor law", "employment tribunal", ], # ── Education & Training ───────────────────────────────────────────────── "teaching": [ "teaching", "instruction", "lecturing", "tutoring", "classroom teaching", "pedagogy", ], "curriculum development": [ "curriculum development", "curriculum design", "lesson planning", "scheme of work", "curriculum mapping", ], "classroom management": [ "classroom management", "student management", "behaviour management", "student discipline", ], "e-learning": [ "e-learning", "elearning", "lms", "moodle", "instructional design", "online course development", ], "lms": [ "learning management system", "blackboard", "canvas lms", "google classroom", "schoology", "d2l", "brightspace", ], "assessment": [ "assessment", "grading", "marking", "examination", "evaluation", "formative assessment", ], "mentoring": [ "mentoring", "mentorship", "coaching", "academic guidance", "career coaching", ], "distance learning": [ "distance learning", "online teaching", "remote teaching", "blended learning", "hybrid learning", ], "stem": ["stem", "science technology engineering mathematics", "stem education"], "research": [ "research", "academic research", "literature review", "research methodology", "data collection", ], "student counseling": [ "student counseling", "student counselling", "academic advising", "student welfare", "pastoral care", ], "special education": [ "special education", "special needs", "sen", "inclusive education", "learning disabilities", ], # ── Operations & Logistics ─────────────────────────────────────────────── "supply chain": [ "supply chain", "supply chain management", "scm", "end-to-end supply chain", ], "procurement": [ "procurement", "purchasing", "vendor management", "sourcing", "tendering", "rfq", "rfp", ], "logistics": [ "logistics", "fleet management", "transportation management", "freight", "delivery", "last mile delivery", ], "quality control": [ "quality control", "qc", "quality assurance", "qa", "iso", "quality management", "iso 9001", "total quality management", ], "project management": [ "project management", "pmp", "prince2", "agile project management", "project planning", "waterfall", "project delivery", ], "lean": [ "lean", "lean manufacturing", "lean six sigma", "continuous improvement", "kaizen", "5s", "value stream mapping", ], "six sigma": ["six sigma", "6 sigma", "dmaic", "black belt", "green belt"], "warehouse management": [ "warehouse management system", "wms", "inventory system", "warehouse operations", ], "demand planning": [ "demand planning", "demand forecasting", "s&op", "sales and operations planning", ], "customs": [ "customs", "customs clearance", "import export", "trade compliance", "incoterms", ], "3pl": [ "3pl", "third party logistics", "outsourced logistics", "logistics provider", ], "health and safety": [ "health and safety", "hse", "ohse", "occupational health", "workplace safety", "risk assessment", ], # ── Software Engineering ───────────────────────────────────────────────── "html": ["html", "html5", "semantic html"], "css": ["css", "css3", "sass", "scss", "less", "stylesheets"], "javascript": ["javascript", "js", "es6", "es2015", "ecmascript", "vanilla js"], "typescript": ["typescript", "ts"], "react": ["react", "react.js", "reactjs", "react hooks", "react native"], "next.js": ["next.js", "nextjs", "next js"], "vue": ["vue", "vue.js", "vuejs", "vue 3", "nuxt", "nuxt.js"], "angular": ["angular", "angularjs", "angular.js", "angular 2+"], "svelte": ["svelte", "sveltekit"], "tailwind css": ["tailwind", "tailwind css"], "node.js": ["node", "node.js", "nodejs", "express", "express.js"], "nestjs": ["nestjs", "nest.js", "nestjs framework"], "python": ["python"], "django": ["django", "django rest framework", "drf"], "flask": ["flask", "flask api"], "fastapi": ["fastapi", "fast api"], "java": ["java"], "spring boot": [ "spring boot", "spring", "spring framework", "spring mvc", "spring security", "spring cloud", ], "kotlin": ["kotlin", "kotlin multiplatform"], "android": ["android", "android studio", "android sdk", "android development"], "php": ["php"], "laravel": ["laravel"], "symfony": ["symfony"], "rails": ["rails", "ruby on rails", "ror"], "dotnet": [ ".net", ".net core", "dotnet", "asp.net", "asp.net core", "net core", ".net framework", ".net maui", "blazor", "wpf", "xamarin", ], "mysql": ["mysql"], "postgresql": ["postgresql", "postgres", "supabase"], "mongodb": ["mongodb", "mongo"], "sqlite": ["sqlite"], "mariadb": ["mariadb", "maria db"], "oracle db": ["oracle database", "oracle db", "oracle sql", "pl/sql"], "cassandra": ["cassandra", "apache cassandra"], "dynamodb": ["dynamodb", "dynamo db", "amazon dynamodb"], "firestore": [ "firestore", "firebase", "cloud firestore", "firebase realtime database", ], "neo4j": ["neo4j", "graph database", "graph db"], "influxdb": ["influxdb", "influx db", "time series database", "timescaledb"], "clickhouse": ["clickhouse"], "redis": ["redis", "redis cache", "redis cluster"], "elasticsearch": ["elasticsearch", "elastic search", "opensearch", "elk"], "rest api": [ "rest api", "restful api", "restful apis", "restful", "api development", "api design", "web services", "api integration", ], "graphql": ["graphql", "graph ql", "apollo", "apollo server", "apollo client"], "grpc": ["grpc", "protocol buffers", "protobuf"], "soap": ["soap", "soap api", "soap web service", "wsdl", "web service"], "openapi": [ "openapi", "swagger", "openapi spec", "swagger ui", "api spec", "api documentation", ], "git": [ "git", "github", "gitlab", "bitbucket", "version control", "source control", ], "docker": [ "docker", "docker compose", "dockerfile", "containerization", "containerisation", ], "kubernetes": ["kubernetes", "k8s", "helm", "openshift", "container orchestration"], "spring boot": [ "spring boot", "spring", "spring framework", "spring mvc", "spring security", "spring cloud", ], "ci/cd": [ "ci/cd", "ci cd", "github actions", "jenkins", "gitlab ci", "bitbucket pipelines", "circle ci", "travis ci", "argocd", "tekton", ], "aws": [ "aws", "amazon web services", "ec2", "s3", "lambda", "ecs", "eks", "rds", "cloudformation", "cdk", ], "azure": [ "azure", "microsoft azure", "azure devops", "azure functions", "azure pipelines", "arm templates", "bicep", ], "gcp": [ "gcp", "google cloud", "google cloud platform", "cloud run", "gke", "bigquery", "cloud functions", ], "terraform": [ "terraform", "infrastructure as code", "iac", "hashicorp terraform", "pulumi", ], "ansible": ["ansible", "ansible playbook", "configuration management"], "flutter": ["flutter"], "dart": ["dart"], "swift": ["swift", "swiftui", "swift ui"], "ios": ["ios", "xcode", "ios development", "iphone development"], "jetpack compose": ["jetpack compose", "android compose", "compose multiplatform"], "ionic": ["ionic", "capacitor", "cordova", "hybrid app"], "c#": ["c#", "csharp", "c sharp"], "c++": ["c++", "cpp", "c plus plus"], "c": ["c language", "c programming", "embedded c"], "rust": ["rust", "rust lang", "systems programming"], "go": ["golang", "go lang", "go programming"], "scala": ["scala"], "ruby": ["ruby"], "perl": ["perl"], "bash": [ "bash", "shell scripting", "bash scripting", "shell script", "unix scripting", ], "powershell": ["powershell", "power shell", "ps script", "windows scripting"], "lua": ["lua"], "elixir": ["elixir", "phoenix framework", "phoenix"], "groovy": ["groovy"], "haskell": ["haskell"], "f#": ["f#", "fsharp", "f sharp"], "r": ["r programming", "r language", "rstudio"], "sql": ["sql", "pl/sql", "t-sql", "structured query language"], "data analysis": [ "data analysis", "analytics", "data analytics", "business intelligence", ], "machine learning": [ "machine learning", "ml", "deep learning", "neural networks", "artificial intelligence", "ai", ], "tensorflow": ["tensorflow", "tf", "keras"], "pytorch": ["pytorch", "torch"], "scikit-learn": ["scikit-learn", "sklearn", "scikit learn"], "nlp": [ "nlp", "natural language processing", "text mining", "sentiment analysis", "text analytics", ], "computer vision": [ "computer vision", "image recognition", "object detection", "opencv", "image processing", ], "generative ai": [ "generative ai", "gen ai", "llm", "large language model", "gpt", "langchain", "hugging face", "transformers", "openai", ], "microservices": [ "microservices", "micro-services", "microservice architecture", "service-oriented", "soa", "event-driven", ], "kafka": ["kafka", "apache kafka", "kafka streams", "confluent", "event streaming"], "rabbitmq": ["rabbitmq", "rabbit mq", "amqp", "message queue"], "mqtt": ["mqtt", "hivemq", "mosquitto", "emqx", "iot messaging"], "message broker": [ "message broker", "activemq", "apache activemq", "nats", "zeromq", "pub/sub", "pubsub", "azure service bus", "aws sqs", "google pub/sub", ], "hibernate": ["hibernate", "jpa", "java persistence api", "orm"], "sequelize": ["sequelize"], "prisma": ["prisma", "prisma orm"], "typeorm": ["typeorm", "type orm"], "sqlalchemy": ["sqlalchemy", "sql alchemy"], "entity framework": ["entity framework", "ef core", "entity framework core"], "redux": ["redux", "redux toolkit", "react redux", "rtk query"], "zustand": ["zustand"], "mobx": ["mobx"], "pinia": ["pinia"], "ngrx": ["ngrx", "ngxs"], "vuex": ["vuex"], "context api": ["context api", "react context"], "jwt": ["jwt", "json web token", "json web tokens"], "oauth": ["oauth", "oauth2", "oauth 2.0", "openid connect", "oidc", "pkce"], "saml": [ "saml", "saml 2.0", "sso", "single sign-on", "single sign on", "federated identity", ], "jest": ["jest", "jest.js"], "junit": ["junit", "junit5", "junit 4", "junit jupiter"], "pytest": ["pytest", "py.test"], "mocha": ["mocha", "mocha.js"], "cypress": ["cypress", "cypress.io"], "selenium": ["selenium", "selenium webdriver", "selenium grid"], "jasmine": ["jasmine"], "testng": ["testng", "test ng"], "nunit": ["nunit"], "xunit": ["xunit", "x unit"], "robot framework": ["robot framework", "robotframework"], "cucumber": [ "cucumber", "gherkin", "bdd", "behavior driven development", "behaviour driven development", ], "tdd": [ "tdd", "test driven development", "test-driven development", "unit testing", "integration testing", "end-to-end testing", ], "jmeter": ["jmeter", "apache jmeter", "load testing", "performance testing"], "postman": ["postman", "api testing"], "agile": [ "agile", "agile methodology", "agile development", "agile framework", "scrum", "scrum master", "safe", "scaled agile", "sprint ceremonies", ], "kanban": ["kanban", "kanban board"], "jira": ["jira", "atlassian jira", "jira software"], "confluence": ["confluence", "atlassian confluence", "wiki"], # ── Infrastructure & Cloud Ops ─────────────────────────────────────────── "linux": [ "linux", "ubuntu", "centos", "rhel", "red hat", "debian", "fedora", "unix", "linux administration", ], "windows server": [ "windows server", "iis", "windows administration", "active directory", ], "active directory": [ "active directory", "ldap", "azure ad", "microsoft entra", "entra id", "domain services", ], "vmware": ["vmware", "vsphere", "vcenter", "esxi", "vsan", "vmware workstation"], "hyper-v": ["hyper-v", "hyperv", "hyper v"], "virtualization": [ "virtualization", "virtualisation", "virtual machine", "vm management", "kvm", "proxmox", ], "itil": ["itil", "it service management", "itsm", "itil v4", "service lifecycle"], "sre": [ "sre", "site reliability engineering", "site reliability", "reliability engineering", ], "high availability": [ "high availability", "ha cluster", "failover", "load balancing", "disaster recovery", "rto", "rpo", "bcp", "business continuity", "fault tolerance", ], "networking": [ "networking", "network administration", "network engineering", "tcp/ip", "dns", "dhcp", "vpn", "lan", "wan", "sd-wan", "sdwan", "firewall", "routing", "switching", "vlan", "mpls", ], "cisco": ["cisco", "ccna", "ccnp", "ccie", "cisco ios", "cisco networking"], "puppet": ["puppet", "puppet enterprise"], "chef": ["chef", "chef infra"], "vagrant": ["vagrant"], "prometheus": ["prometheus", "prometheus monitoring", "alertmanager"], "grafana": ["grafana", "grafana dashboard"], "datadog": ["datadog", "data dog"], "elk stack": ["elk stack", "logstash", "kibana", "elastic stack", "fluentd"], "splunk": ["splunk", "splunk siem"], "cloudwatch": ["cloudwatch", "aws cloudwatch"], "new relic": ["new relic", "newrelic"], "dynatrace": ["dynatrace"], "opentelemetry": [ "opentelemetry", "open telemetry", "distributed tracing", "jaeger", "zipkin", "observability", ], "storage": [ "storage", "nas", "san", "object storage", "block storage", "aws s3", "azure blob", "gcs", ], "backup recovery": [ "backup", "backup and recovery", "backup strategy", "data backup", "veeam", "commvault", ], # ── Cybersecurity ──────────────────────────────────────────────────────── "penetration testing": [ "penetration testing", "pen testing", "pentesting", "ethical hacking", "red team", "blue team", "purple team", ], "vulnerability management": [ "vulnerability management", "vulnerability assessment", "vulnerability scanning", "nessus", "qualys", ], "siem": [ "siem", "security information and event management", "soc analyst", "security operations center", "soc", ], "iso 27001": ["iso 27001", "iso27001", "information security management", "isms"], "soc 2": ["soc 2", "soc2", "aicpa soc"], "gdpr": ["gdpr", "data protection regulation", "ccpa", "data privacy law"], "owasp": ["owasp", "owasp top 10", "web application security", "appsec"], "pci dss": ["pci dss", "pci", "payment card industry"], "nist": ["nist", "nist framework", "nist cybersecurity"], "zero trust": ["zero trust", "zero trust network", "ztna"], "ssl tls": [ "ssl", "tls", "ssl/tls", "certificates", "pki", "public key infrastructure", ], "devsecops": [ "devsecops", "security as code", "shift left security", "sast", "dast", ], "threat intelligence": [ "threat intelligence", "threat hunting", "cyber threat", "ioc", "indicators of compromise", ], "cloud security": ["cloud security", "aws security", "azure security", "cspm"], # ── Data & Analytics ───────────────────────────────────────────────────── "power bi": ["power bi", "powerbi", "power bi desktop", "dax"], "tableau": ["tableau", "tableau desktop"], "looker": ["looker", "looker studio", "google data studio"], "qlik": ["qlik", "qlikview", "qliksense"], "data science": ["data science", "data scientist"], "statistics": ["statistics", "statistical analysis", "spss", "stata", "minitab"], "data visualization": [ "data visualization", "data visualisation", "data viz", "dashboarding", ], "excel analytics": [ "pivot tables", "advanced excel", "excel analytics", "vlookup", "xlookup", ], # ── Data Engineering ───────────────────────────────────────────────────── "apache spark": ["apache spark", "pyspark", "spark streaming", "spark sql"], "apache hadoop": ["apache hadoop", "hadoop", "hdfs", "hive", "hbase"], "apache airflow": ["apache airflow", "airflow", "workflow orchestration", "dag"], "dbt": ["dbt", "data build tool", "analytics engineering"], "snowflake": ["snowflake", "snowflake data warehouse"], "databricks": ["databricks", "lakehouse"], "data pipeline": [ "data pipeline", "etl", "elt", "data integration", "data ingestion", "data warehouse", "data lake", "data lakehouse", ], "data modeling": [ "data modeling", "data modelling", "star schema", "dimensional modeling", "entity relationship", ], "data governance": [ "data governance", "data quality", "data catalog", "metadata management", "data stewardship", ], "streaming": [ "real-time streaming", "stream processing", "event-driven architecture", "cdc", "change data capture", ], # ── Design & Creative ──────────────────────────────────────────────────── "ui design": ["ui design", "user interface design", "interface design"], "ux design": [ "ux design", "user experience design", "ux research", "usability", "ux writing", ], "figma": ["figma"], "wireframing": ["wireframe", "wireframing", "low-fidelity"], "prototyping": [ "prototyping", "rapid prototyping", "interactive prototype", "high-fidelity prototype", ], "adobe photoshop": ["photoshop", "adobe photoshop"], "adobe illustrator": ["illustrator", "adobe illustrator", "vector graphics"], "adobe xd": ["adobe xd", "xd"], "adobe premiere": ["adobe premiere", "premiere pro", "video editing"], "adobe after effects": ["after effects", "adobe after effects", "motion graphics"], "graphic design": ["graphic design", "graphics", "visual design", "print design"], "video editing": [ "video editing", "video production", "final cut pro", "davinci resolve", ], "photography": ["photography", "photo editing", "lightroom", "photo retouching"], "canva": ["canva"], "sketch": ["sketch", "sketch app"], "invision": ["invision", "invision studio"], "miro": ["miro", "miroboard", "whiteboarding", "collaborative design"], "3d design": [ "3d design", "blender", "cinema 4d", "3d modeling", "3d rendering", "maya", ], "brand design": ["brand design", "branding", "visual identity", "brand guidelines"], # ── Marketing ──────────────────────────────────────────────────────────── "seo": [ "seo", "search engine optimization", "search engine optimisation", "on-page seo", "off-page seo", "technical seo", ], "content writing": [ "content writing", "copywriting", "content creation", "blog writing", "content strategy", "editorial", ], "digital marketing": [ "digital marketing", "online marketing", "marketing campaigns", "marketing strategy", "performance marketing", ], "social media": [ "social media", "social media management", "instagram", "facebook marketing", "tiktok", "linkedin marketing", "twitter", ], "email marketing": [ "email marketing", "mailchimp", "email campaigns", "newsletter", "klaviyo", "sendgrid", ], "google analytics": [ "google analytics", "ga4", "web analytics", "google tag manager", "gtm", ], "google ads": [ "google ads", "google adwords", "ppc", "pay per click", "sem", "paid search", ], "facebook ads": [ "facebook ads", "meta ads", "social media advertising", "paid social", ], "brand management": ["brand management", "brand strategy"], "marketing automation": [ "marketing automation", "hubspot marketing", "marketo", "pardot", "salesforce marketing cloud", ], "affiliate marketing": [ "affiliate marketing", "performance marketing", "influencer marketing", "partnership marketing", ], "cro": [ "cro", "conversion rate optimization", "conversion optimisation", "a/b testing", "landing page optimization", ], "pr": [ "pr", "public relations", "media relations", "press release", "communications", ], # ── Construction & Engineering ─────────────────────────────────────────── "autocad": ["autocad", "auto cad", "cad", "computer aided design", "drafting"], "solidworks": ["solidworks", "solid works", "parametric design"], "revit": [ "revit", "bim", "building information modeling", "autodesk revit", "building information modelling", ], "civil engineering": [ "civil engineering", "structural engineering", "geotechnical", "quantity surveying", "qs", ], "primavera": [ "primavera", "oracle primavera", "p6", "primavera p6", "project scheduling", ], "civil 3d": ["civil 3d", "autocad civil 3d", "road design", "drainage design"], "surveying": [ "surveying", "land surveying", "total station", "gps surveying", "topographic", ], "electrical engineering": [ "electrical engineering", "power systems", "plc", "scada", "instrumentation", "hv", "lv", ], "mechanical engineering": [ "mechanical engineering", "hvac", "fluid mechanics", "thermodynamics", "piping", ], # ── Hospitality & Property ─────────────────────────────────────────────── "hospitality management": [ "hospitality management", "hotel management", "f&b", "food and beverage", "front office", ], "property management": [ "property management", "pms", "property management system", "opera pms", "real estate management", ], "revenue management": [ "revenue management", "yield management", "revenue optimization", "pricing strategy", ], "food safety": [ "food safety", "haccp", "food hygiene", "food handling", "food service", ], "housekeeping": [ "housekeeping", "rooms division", "facilities management", "janitorial", ], "real estate": [ "real estate", "property valuation", "mortgage", "property development", "estate agency", "letting", ], "event management": [ "event management", "events coordination", "venue management", "conference management", ], # ── Cross-domain Agile & Process ───────────────────────────────────────── "agile": [ "agile", "agile methodology", "agile development", "agile framework", "scrum", "scrum master", "safe", "scaled agile", ], "kanban": ["kanban", "kanban board"], "jira": ["jira", "atlassian jira"], "confluence": ["confluence", "atlassian confluence"], "business process": [ "business process", "bpm", "business process management", "process mapping", "process documentation", "sop", ], "change management": [ "change management", "organisational change", "change leadership", "transformation", ], "program management": [ "program management", "programme management", "portfolio management", "pmo", "project portfolio", ], } DEFAULT_CATEGORY_SKILLS: Dict[str, Set[str]] = { "Admin & Office": { "microsoft word", "microsoft excel", "microsoft powerpoint", "microsoft office", "google workspace", "data entry", "filing", "scheduling", "communication", "teamwork", "customer service", "reporting", "inventory management", "warehouse", "time management", "attention to detail", "erp", "minute taking", "virtual assistant", "adaptability", }, "Software Engineering": { "html", "css", "javascript", "typescript", "react", "next.js", "vue", "angular", "svelte", "tailwind css", "node.js", "nestjs", "python", "django", "flask", "fastapi", "java", "spring boot", "kotlin", "android", "php", "laravel", "symfony", "rails", "dotnet", "mysql", "postgresql", "mongodb", "sqlite", "mariadb", "oracle db", "cassandra", "dynamodb", "firestore", "neo4j", "redis", "elasticsearch", "rest api", "graphql", "grpc", "soap", "openapi", "git", "docker", "kubernetes", "ci/cd", "aws", "azure", "gcp", "terraform", "ansible", "flutter", "dart", "swift", "ios", "jetpack compose", "ionic", "c#", "c++", "c", "rust", "go", "scala", "ruby", "bash", "powershell", "elixir", "groovy", "haskell", "f#", "sql", "machine learning", "tensorflow", "pytorch", "scikit-learn", "nlp", "computer vision", "generative ai", "microservices", "kafka", "rabbitmq", "mqtt", "message broker", "hibernate", "sequelize", "prisma", "typeorm", "sqlalchemy", "entity framework", "redux", "zustand", "mobx", "pinia", "ngrx", "vuex", "context api", "jwt", "oauth", "saml", "jest", "junit", "pytest", "mocha", "cypress", "selenium", "jasmine", "testng", "nunit", "xunit", "robot framework", "cucumber", "tdd", "jmeter", "postman", "agile", "kanban", "jira", "confluence", "influxdb", "clickhouse", }, "Infrastructure & Cloud": { "linux", "windows server", "active directory", "vmware", "hyper-v", "virtualization", "itil", "sre", "high availability", "networking", "cisco", "puppet", "chef", "vagrant", "terraform", "ansible", "docker", "kubernetes", "aws", "azure", "gcp", "prometheus", "grafana", "datadog", "elk stack", "splunk", "cloudwatch", "new relic", "dynatrace", "opentelemetry", "storage", "backup recovery", "ci/cd", "git", "bash", "powershell", }, "Cybersecurity": { "penetration testing", "vulnerability management", "siem", "iso 27001", "soc 2", "gdpr", "owasp", "pci dss", "nist", "zero trust", "ssl tls", "devsecops", "threat intelligence", "cloud security", "networking", "linux", "python", "bash", }, "Design & Creative": { "ui design", "ux design", "figma", "wireframing", "prototyping", "adobe photoshop", "adobe illustrator", "adobe xd", "graphic design", "video editing", "photography", "canva", "sketch", "invision", "miro", "3d design", "brand design", "adobe premiere", "adobe after effects", }, "Marketing & Growth": { "seo", "content writing", "digital marketing", "social media", "email marketing", "google analytics", "google ads", "facebook ads", "brand management", "marketing automation", "affiliate marketing", "cro", "pr", "crm", }, "Data & Analytics": { "sql", "python", "machine learning", "data analysis", "microsoft excel", "power bi", "tableau", "looker", "qlik", "data science", "statistics", "data visualization", "r", "excel analytics", "elasticsearch", "tensorflow", "pytorch", "scikit-learn", "nlp", "computer vision", "generative ai", }, "Data Engineering": { "apache spark", "apache hadoop", "apache airflow", "dbt", "snowflake", "databricks", "data pipeline", "data modeling", "data governance", "streaming", "kafka", "python", "sql", "aws", "azure", "gcp", "postgresql", "mongodb", "elasticsearch", "redis", }, "Finance & Accounting": { "accounting", "financial analysis", "auditing", "taxation", "payroll", "budgeting", "accounts payable", "accounts receivable", "quickbooks", "sage", "sap", "xero", "ifrs", "gaap", "reconciliation", "cash flow", "microsoft excel", "risk management", "kyc", "aml", "core banking", "trade finance", "wealth management", "bloomberg", "financial compliance", "actuarial", "cfa", "acca", "derivatives", "credit analysis", }, "Human Resources": { "recruitment", "onboarding", "performance management", "employee relations", "training and development", "hris", "compensation", "hr compliance", "job analysis", "communication", "microsoft excel", "succession planning", "workforce planning", "dei", "talent management", "culture", }, "Sales & Business Dev": { "sales", "crm", "negotiation", "lead generation", "account management", "retail", "communication", "customer service", "partnership", "customer success", "revenue operations", "business analysis", }, "Healthcare": { "patient care", "nursing", "clinical skills", "pharmacy", "first aid", "medical records", "infection control", "midwifery", "physiotherapy", "laboratory", "ehr systems", "hl7", "clinical trials", "icd coding", "telemedicine", "surgical", "radiology", "nutrition", "mental health", }, "Legal": { "legal research", "contract drafting", "litigation", "legal compliance", "conveyancing", "corporate law", "legal writing", "due diligence", "mergers acquisitions", "intellectual property", "arbitration", "legal tech", "employment law", }, "Education & Training": { "teaching", "curriculum development", "classroom management", "e-learning", "assessment", "mentoring", "communication", "lms", "distance learning", "stem", "research", "student counseling", "special education", }, "Operations & Logistics": { "supply chain", "procurement", "logistics", "quality control", "project management", "lean", "six sigma", "inventory management", "reporting", "warehouse management", "demand planning", "customs", "3pl", "health and safety", "erp", }, "Customer Support": { "customer service", "help desk", "ticketing systems", "call centre", "communication", "crm", "reporting", "live chat", "sla management", "customer satisfaction", }, "Construction & Engineering": { "autocad", "solidworks", "revit", "civil engineering", "primavera", "civil 3d", "surveying", "electrical engineering", "mechanical engineering", "ms project", "health and safety", }, "Hospitality & Property": { "hospitality management", "property management", "revenue management", "food safety", "housekeeping", "real estate", "event management", "customer service", "communication", }, } SENIORITY_PATTERNS: Dict[str, List[str]] = { "intern": [ "intern", "internship", "trainee", "industrial attachment", "attachment student", "graduate trainee", "pupil", "cadet", ], "junior": [ "junior", "entry level", "entry-level", "assistant", "fresh graduate", "graduate", "associate", "junior officer", "junior analyst", "junior developer", "junior engineer", ], "mid": [ "mid", "middle", "officer", "specialist", "developer", "designer", "engineer", "analyst", "coordinator", "technician", "executive", "consultant", "representative", ], "senior": [ "senior", "lead", "manager", "head", "principal", "architect", "director", "vp", "vice president", "chief", "cto", "ceo", "coo", "cfo", "superintendent", "supervisor", "managing", "staff engineer", "distinguished", "fellow", ], } SENIORITY_ORDER: Dict[str, int] = {"intern": 0, "junior": 1, "mid": 2, "senior": 3} # Words that look like "City, Country" but are actually tech terms — kept in sync # with the skill list above so location parsing doesn't mistake skill names for places. _TECH_LOC_BLACKLIST: Set[str] = { "java", "spring", "boot", "sql", "python", "node", "react", "angular", "vue", "docker", "linux", "windows", "android", "swift", "kotlin", "scala", "ruby", "rails", "django", "flask", "express", "mongo", "postgres", "redis", "kafka", "hadoop", "spark", "airflow", "jenkins", "gitlab", "github", "bitbucket", "jira", "confluence", "slack", "figma", "oracle", "mysql", "azure", "aws", "gcp", "rust", "golang", "flutter", "dart", "ionic", "svelte", "nextjs", "nestjs", "laravel", "symfony", "grafana", "kibana", "splunk", "terraform", "ansible", "puppet", "kubernetes", "helm", "istio", "nginx", "apache", } # --------------------------------------------------------------------------- # Soft-skill keys (used by chunker to split canonical list) # --------------------------------------------------------------------------- SOFT_SKILL_KEYS: Set[str] = { "communication", "teamwork", "problem solving", "time management", "customer service", "mentoring", "negotiation", "leadership", "reporting", "scheduling", "filing", "adaptability", "attention to detail", "onboarding", "training and development", "stakeholder management", "public speaking", "conflict resolution", "decision making", "emotional intelligence", "customer satisfaction", } # --------------------------------------------------------------------------- # Title role words & phrase regex (universal) # --------------------------------------------------------------------------- TITLE_ROLE_WORDS: List[str] = [ # Tech "developer", "engineer", "programmer", "architect", "devops", # General "officer", "designer", "assistant", "accountant", "manager", "analyst", "specialist", "coordinator", "director", "executive", "lead", "scientist", "strategist", "advisor", "trainer", "consultant", "researcher", "technician", "supervisor", "associate", "administrator", # Healthcare "nurse", "doctor", "physician", "pharmacist", "therapist", "clinician", "midwife", "radiographer", "physiotherapist", # Finance "auditor", "bookkeeper", "controller", "treasurer", "actuary", # HR "recruiter", # Sales "salesperson", "representative", "agent", "broker", # Education "teacher", "instructor", "lecturer", "professor", "tutor", # Other "admin", "intern", "student", "cashier", "marketing", "procurement", "buyer", "dispatcher", "operator", "superintendent", ] TITLE_PHRASE_RE = re.compile( r"\b((?:(?:full[\s\-]?stack|front[\s\-]?end|back[\s\-]?end|senior|junior|lead|" r"mid[\s\-]?level|entry[\s\-]?level|chief|head\s+of|staff|principal)?\s+)?(?:[\w\-]+\s){0,3}" r"(?:developer|engineer|designer|analyst|manager|specialist|consultant|architect|" r"programmer|researcher|officer|director|executive|scientist|trainer|accountant|" r"nurse|doctor|pharmacist|therapist|teacher|instructor|lecturer|recruiter|" r"coordinator|supervisor|administrator|technician|advisor|auditor|bookkeeper|" r"physiotherapist|midwife|clinician|representative|salesperson|buyer|dispatcher|" r"actuary|superintendent|operator))\b", re.IGNORECASE, ) # --------------------------------------------------------------------------- # Normalisation helpers # --------------------------------------------------------------------------- def _normalize(text: str) -> str: text = (text or "").lower() # Expand slash-separated compound skills: "Spring Boot/Django/.NET" → tokens text = text.replace("/", " ") text = text.replace("_", " ") text = re.sub(r"[^a-z0-9+.#\s-]", " ", text) return re.sub(r"\s+", " ", text).strip() _ALIAS_REGEX_CACHE = {} def _has_alias(text_norm: str, alias: str) -> bool: alias_norm = _normalize(alias) if not alias_norm: return False pattern = _ALIAS_REGEX_CACHE.get(alias_norm) if pattern is None: pattern = re.compile(rf"(? Set[str]: text_norm = _normalize(text) if aliases is None or aliases is DEFAULT_SKILL_ALIASES: pattern, alias_map = _get_skill_pattern() return {alias_map[match.group(1)] for match in pattern.finditer(text_norm)} return { canon for canon, ali_list in aliases.items() if any(_has_alias(text_norm, a) for a in ali_list) } # --------------------------------------------------------------------------- # Category inference (universal) # --------------------------------------------------------------------------- _CATEGORY_TITLE_KEYWORDS: Dict[str, List[str]] = { "Admin & Office": [ "admin", "office", "stock", "inventory", "warehouse", "cashier", "receptionist", "clerk", "secretary", ], "Software Engineering": [ "developer", "engineer", "programmer", "frontend", "backend", "full stack", "mobile", "fullstack", "software", "devops", ], "Infrastructure & Cloud": [ "infrastructure", "cloud", "sre", "network", "system administrator", "sysadmin", "it administrator", "cloud engineer", "platform engineer", ], "Cybersecurity": [ "security", "cybersecurity", "cyber", "infosec", "soc analyst", "penetration tester", "ethical hacker", "information security", ], "Design & Creative": [ "designer", "ui", "ux", "creative", "graphic", "visual", "illustrator", "photographer", "art director", ], "Marketing & Growth": [ "marketing", "seo", "content", "social media", "growth", "digital", "brand", "communications", ], "Data & Analytics": [ "data analyst", "analytics", "machine learning", "ai", "scientist", "bi analyst", "business intelligence", ], "Data Engineering": [ "data engineer", "etl", "data pipeline", "data architect", "analytics engineer", "data platform", ], "Finance & Accounting": [ "accountant", "finance", "accounting", "auditor", "bookkeeper", "treasurer", "payroll", "tax", "financial", ], "Human Resources": [ "hr", "human resource", "recruitment", "recruiter", "talent", "people", "hris", ], "Sales & Business Dev": [ "sales", "business development", "account manager", "sales executive", "representative", "business dev", ], "Healthcare": [ "nurse", "doctor", "clinical", "medical", "pharmacy", "patient", "health", "therapist", "midwife", "radiographer", ], "Legal": [ "legal", "lawyer", "attorney", "advocate", "counsel", "paralegal", "solicitor", ], "Education & Training": [ "teacher", "lecturer", "tutor", "instructor", "educator", "academic", "trainer", "professor", ], "Operations & Logistics": [ "logistics", "supply chain", "procurement", "operations", "fleet", "warehouse manager", "quality", "planner", ], "Customer Support": [ "customer support", "help desk", "call centre", "contact centre", "customer care", "service desk", ], "Construction & Engineering": [ "civil", "structural", "mechanical", "electrical", "quantity surveyor", "site engineer", "construction", "autocad", ], "Hospitality & Property": [ "hotel", "hospitality", "property", "real estate", "housekeeping", "f&b", "restaurant", "events", ], } def _infer_category( text: str, skills: Set[str], category_skills: Optional[Dict[str, Set[str]]] = None, ) -> str: if category_skills is None: category_skills = DEFAULT_CATEGORY_SKILLS text_norm = _normalize(text) best, best_score = "Other", 0.0 for cat, cat_skills in category_skills.items(): skill_overlap = len(skills & cat_skills) kw_hits = sum( 1 for kw in _CATEGORY_TITLE_KEYWORDS.get(cat, []) if kw in text_norm ) score = skill_overlap * 2.0 + kw_hits * 1.25 if score > best_score: best_score, best = score, cat return best # --------------------------------------------------------------------------- # Years-of-experience extraction (text mention) # --------------------------------------------------------------------------- def _extract_years(text: str) -> int: text_norm = _normalize(text) patterns = [ r"(\d+)\+?\s*(?:years|year|yrs|yr)\s*(?:of)?\s*(?:experience|exp)", r"experience\s*(?:of)?\s*(\d+)\+?\s*(?:years|year|yrs|yr)", r"(\d+)\+?\s*(?:years|year|yrs|yr)\s+(?:in\s+)?(?:the\s+)?(?:industry|field|software|development|practice|profession)", ] years = [int(m.group(1)) for p in patterns for m in re.finditer(p, text_norm)] return max(years) if years else 0 # --------------------------------------------------------------------------- # Years computed from parsed experience date ranges (more accurate) # --------------------------------------------------------------------------- _MONTH_SHORT: Dict[str, int] = { "jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6, "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12, } def _parse_date_token(token: str) -> Optional[_dt]: token = token.strip().lower() if re.match(r"present|current|now|ongoing|till\s*date|to\s*date", token): return _dt.now() m = re.match(r"([a-z]+)\s+(\d{4})", token) if m: month = _MONTH_SHORT.get(m.group(1)[:3], 1) try: return _dt(int(m.group(2)), month, 1) except ValueError: return None m = re.match(r"(\d{4})", token) if m: return _dt(int(m.group(1)), 6, 1) # mid-year when month unknown return None def compute_years_from_experience(entries: List[Dict[str, Any]]) -> int: """Sum date-range durations across all experience entries → total years.""" total_months = 0 for entry in entries: period = (entry.get("period") or "").strip() if not period: continue parts = re.split(r"\s*[-–—]\s*", period, maxsplit=1) if len(parts) != 2: continue start = _parse_date_token(parts[0]) end = _parse_date_token(parts[1]) if start and end and end > start: months = (end.year - start.year) * 12 + (end.month - start.month) total_months += max(0, months) return total_months // 12 # --------------------------------------------------------------------------- # Seniority detection # --------------------------------------------------------------------------- def _detect_seniority(text: str, *, is_cv: bool = False) -> str: """Detect seniority level from title-zone first, then body text. For jobs: checks the first 300 chars (title/header area) with higher priority before scanning the full body. This prevents stray mentions like 'for senior roles, consider...' in advice sections from inflating a mid-level job to senior. """ title_zone = text[:300] title_norm = _normalize(title_zone) text_norm = _normalize(text) order = ( ["senior", "junior", "intern", "mid"] if is_cv else ["senior", "intern", "junior", "mid"] ) # 1. Title zone has highest priority for level in order: if any(_has_alias(title_norm, term) for term in SENIORITY_PATTERNS[level]): return level # 2. Full body — but skip "senior" re-check for jobs to avoid advice-section pollution for level in order: if level == "senior" and not is_cv: continue if any(_has_alias(text_norm, term) for term in SENIORITY_PATTERNS[level]): return level years = _extract_years(text) if years >= 7: return "senior" if years >= 3: return "mid" return "junior" if is_cv else "mid" def refine_seniority_with_years(detected: str, years: int) -> str: """ Override a weakly-detected seniority using computed years. 'mid' is the weakest keyword signal (matches many generic job titles). 'intern' can appear from a past role and should not dominate once the candidate has accumulated meaningful professional experience. """ if detected == "mid": if years >= 7: return "senior" if years >= 3: return "mid" if 0 < years < 2: return "junior" return detected if detected == "intern" and years >= 2: if years >= 7: return "senior" if years >= 3: return "mid" return "junior" return detected # --------------------------------------------------------------------------- # CV title extractor (universal, scans more lines) # --------------------------------------------------------------------------- def _extract_cv_title(cv_text: str, clean_line_fn=None) -> str: """ Extract the candidate's professional title from the top of the CV. Parameters ---------- cv_text : str Raw markdown string. clean_line_fn : callable, optional A function(raw_line) -> str that strips markdown / HTML. Falls back to a simple regex strip if not provided. """ def _simple_clean(raw: str) -> str: line = re.sub(r"^#+\s*", "", raw) line = re.sub(r"\*{1,3}|\_{1,2}|`", "", line) return re.sub(r"\s+", " ", line).strip() clean = clean_line_fn or _simple_clean clean_lines = [clean(x) for x in cv_text.splitlines() if x.strip()] _role_wb_re = re.compile( r"\b(" + "|".join(re.escape(w) for w in TITLE_ROLE_WORDS) + r")\b", re.IGNORECASE, ) for line in clean_lines[1:30]: stripped = line.strip("| \t") if ( len(stripped) <= 80 and bool(_role_wb_re.search(stripped)) and "|" not in stripped and not re.search(r"[@http]", stripped) and not re.search( r"\d{4}\s*[-–]\s*(?:\d{4}|present)", stripped, re.IGNORECASE ) ): return stripped.title() _SECTION_HDR_RE = re.compile( r"^(career\s*summary|professional\s*summary|executive\s*summary|" r"personal\s*statement|career\s*objective|professional\s*profile|" r"personal\s*profile|about\s*me|work\s*experience|" r"professional\s*experience|technical\s*skills?|core\s*skills?|" r"key\s*skills?|educational?\s*background|academic\s*background|" r"employment\s*history|career\s*history|skills?\s*&?\s*tools?|" r"skills?|summary|profile|experience|education|projects?|awards?|" r"achievements?|contact|links?|certifications?)$", re.IGNORECASE, ) non_header = [ ln for ln in clean_lines[:40] if not _SECTION_HDR_RE.match(re.sub(r"[^a-zA-Z\s&]", " ", ln).strip()) ] head = ". ".join(non_header)[:1000] m = TITLE_PHRASE_RE.search(head) if m: return m.group(1).strip().title() return "Unknown"