chore: untrack and ignore venv directory

This commit is contained in:
root
2026-05-14 09:46:14 -04:00
commit f66548f388
9 changed files with 1231 additions and 0 deletions
+245
View File
@@ -0,0 +1,245 @@
"""
resume_parser.py
----------------
Parses a resume (text or PDF) into a structured JSON profile
used by the matching engine. Run once, outputs profile.json.
"""
import json
import re
import sys
import os
import logging
logger = logging.getLogger(__name__)
# ---- Known skill taxonomy ----
SKILL_TAXONOMY = {
"bgp": ["bgp", "border gateway protocol", "bgp peering", "route maps"],
"ospf": ["ospf", "open shortest path first"],
"eigrp": ["eigrp"],
"mpls": ["mpls"],
"vxlan": ["vxlan"],
"vrrp": ["vrrp"],
"vlans": ["vlan", "vlans"],
"stp": ["stp", "spanning tree"],
"tcp/ip": ["tcp/ip", "tcp", "ip", "tcpip"],
"dns": ["dns", "domain name"],
"dhcp": ["dhcp"],
"qos": ["qos", "quality of service"],
"ipv6": ["ipv6"],
"multicast": ["multicast", "pim", "igmp"],
"nat": ["nat", "network address translation"],
"acls": ["acl", "acls", "access control list"],
"load balancing": ["load balanc"],
"cisco": ["cisco", "catalyst", "nexus"],
"arista": ["arista"],
"juniper": ["juniper", "junos", "srx"],
"mikrotik": ["mikrotik"],
"brocade": ["brocade"],
"fortinet": ["fortinet", "fortigate"],
"pfsense": ["pfsense"],
"barracuda": ["barracuda"],
"vyos": ["vyos"],
"firewall": ["firewall", "ngfw"],
"vpn": ["vpn", "ipsec", "site-to-site"],
"802.1x": ["802.1x", "nac", "network access control"],
"2fa": ["2fa", "mfa", "two-factor", "multi-factor"],
"network segmentation": ["network segmentation", "microsegmentation"],
"aws": ["aws", "amazon web services", "vpc"],
"azure": ["azure"],
"gcp": ["gcp", "google cloud"],
"ibm cloud": ["ibm cloud"],
"vmware": ["vmware", "esxi", "vsphere"],
"proxmox": ["proxmox"],
"lxc": ["lxc", "linux container"],
"docker": ["docker", "container"],
"kubernetes": ["kubernetes", "k8s"],
"terraform": ["terraform"],
"hybrid cloud": ["hybrid cloud"],
"python": ["python"],
"bash": ["bash", "shell script"],
"ansible": ["ansible"],
"git": ["git", "github", "gitlab"],
"iac": ["infrastructure as code", "infrastructure-as-code", "iac"],
"ci/cd": ["ci/cd", "cicd", "jenkins", "pipeline"],
"zabbix": ["zabbix"],
"librenms": ["librenms"],
"nagios": ["nagios"],
"snmp": ["snmp"],
"syslog": ["syslog"],
"datadog": ["datadog"],
"grafana": ["grafana"],
"prometheus": ["prometheus"],
"splunk": ["splunk"],
"solarwinds": ["solarwinds"],
"active directory": ["active directory", "ad"],
"linux": ["linux", "unix", "rhel", "ubuntu", "centos", "debian"],
"windows server": ["windows server", "windows ad"],
"voip": ["voip", "sip"],
"sql": ["sql", "mysql", "postgres", "mssql"],
"certificate management": ["certificate", "ssl", "tls", "pki"],
}
CERT_PATTERNS = [
(r"ccnp", "CCNP"),
(r"ccna", "CCNA"),
(r"ccie", "CCIE"),
(r"aws\s+certified\s+cloud\s+practitioner", "AWS Cloud Practitioner"),
(r"aws\s+certified\s+solutions?\s+architect", "AWS Solutions Architect"),
(r"aws\s+certified", "AWS Certified"),
(r"comptia\s+security\+", "CompTIA Security+"),
(r"comptia\s+network\+", "CompTIA Network+"),
(r"jncia", "JNCIA"),
(r"jncis", "JNCIS"),
(r"fortinet\s+nse", "Fortinet NSE"),
(r"palo\s+alto\s+pcnse", "PCNSE"),
]
TITLE_VARIANTS = {
"network engineer": ["network engineer", "network eng", "noc engineer"],
"infrastructure engineer": ["infrastructure engineer", "infra engineer"],
"network administrator": ["network administrator", "network admin"],
"system administrator": ["system administrator", "sysadmin", "systems administrator"],
"cloud engineer": ["cloud engineer", "cloud network engineer"],
"devops engineer": ["devops", "site reliability", "sre"],
"security engineer": ["security engineer", "network security"],
}
def extract_skills(text):
text_lower = text.lower()
found = {}
for skill_name, patterns in SKILL_TAXONOMY.items():
for pattern in patterns:
if pattern.lower() in text_lower:
count = text_lower.count(pattern.lower())
found[skill_name] = min(count, 5)
break
return found
def extract_titles(text):
titles = []
title_patterns = [
r"(?:^|\n)\s*([\w\s&/]+(?:Engineer|Administrator|Analyst|Manager|Architect|Specialist|Technician|Director))\s*(?:\(|–|-|\d|$)",
]
for pattern in title_patterns:
matches = re.findall(pattern, text, re.IGNORECASE | re.MULTILINE)
for m in matches:
clean = m.strip()
if 5 < len(clean) < 60:
titles.append(clean)
return list(set(titles))
def extract_experience_years(text):
explicit = re.search(r"(\d+)\+?\s*years?\s*(?:of\s+)?experience", text, re.IGNORECASE)
if explicit:
return int(explicit.group(1))
date_ranges = re.findall(r"(\d{2}/\d{4})\s*[–-]\s*(\d{2}/\d{4}|present|current)", text, re.IGNORECASE)
if date_ranges:
from datetime import datetime
total_months = 0
for start_str, end_str in date_ranges:
try:
start = datetime.strptime(start_str, "%m/%Y")
if end_str.lower() in ("present", "current"):
end = datetime.now()
else:
end = datetime.strptime(end_str, "%m/%Y")
months = (end.year - start.year) * 12 + (end.month - start.month)
total_months += max(months, 0)
except ValueError:
continue
if total_months > 0:
return total_months // 12
return 0
def extract_certifications(text):
text_lower = text.lower()
found = []
for pattern, cert_name in CERT_PATTERNS:
if re.search(pattern, text_lower):
found.append(cert_name)
return list(set(found))
def extract_education(text):
education = []
edu_patterns = [
r"(bachelor|master|diploma|degree|b\.?s\.?|m\.?s\.?|ph\.?d|associate)[\w\s,]*(?:in\s+)?([\w\s&]+?)(?:\d{4}|\n|$)",
]
for pattern in edu_patterns:
matches = re.findall(pattern, text, re.IGNORECASE)
for level, field in matches:
education.append({"level": level.strip(), "field": field.strip()[:50]})
return education
def parse_resume_text(text):
skills = extract_skills(text)
titles = extract_titles(text)
years = extract_experience_years(text)
certs = extract_certifications(text)
education = extract_education(text)
normalized_titles = set()
for title in titles:
title_lower = title.lower()
for norm, variants in TITLE_VARIANTS.items():
if any(v in title_lower for v in variants):
normalized_titles.add(norm)
break
else:
normalized_titles.add(title_lower)
return {
"skills": skills,
"titles": list(normalized_titles),
"raw_titles": titles,
"years_experience": years,
"certifications": certs,
"education": education,
"full_text": text,
}
def parse_resume_file(filepath):
ext = os.path.splitext(filepath)[1].lower()
if ext == ".txt":
with open(filepath, "r", encoding="utf-8") as f:
text = f.read()
elif ext == ".pdf":
try:
import pdfplumber
with pdfplumber.open(filepath) as pdf:
text = "\n".join(page.extract_text() or "" for page in pdf.pages)
except ImportError:
logger.error("Install pdfplumber: pip install pdfplumber")
sys.exit(1)
else:
raise ValueError(f"Unsupported file type: {ext}")
return parse_resume_text(text)
def save_profile(profile, output_path="profile.json"):
save_data = {k: v for k, v in profile.items() if k != "full_text"}
with open(output_path, "w") as f:
json.dump(save_data, f, indent=2)
logger.info(f"Profile saved to {output_path}")
return save_data
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
if len(sys.argv) < 2:
print("Usage: python resume_parser.py <resume.txt|resume.pdf>")
sys.exit(1)
filepath = sys.argv[1]
profile = parse_resume_file(filepath)
saved = save_profile(profile)
print(json.dumps(saved, indent=2))