dkshah commited on
Commit
eb6064a
·
verified ·
1 Parent(s): 840f8bd

Upload 4 files

Browse files
Files changed (4) hide show
  1. __init__.py +0 -0
  2. ai_summarizer.py +82 -0
  3. hf_api.py +27 -0
  4. resume_parser.py +78 -0
__init__.py ADDED
File without changes
ai_summarizer.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List, Optional
2
+ from .hf_api import hf_summarize
3
+
4
+ def summarize_resume(resume_data: Dict, *, hf_model: str, hf_token: Optional[str]) -> str:
5
+ """
6
+ Generate a summary of the resume using a Hugging Face text summarization model
7
+ via the Inference API.
8
+
9
+ Args:
10
+ resume_data: Parsed resume dictionary containing 'raw_text' and optional fields.
11
+ hf_model: Model repo id, e.g. 'facebook/bart-large-cnn' or 'sshleifer/distilbart-cnn-12-6'.
12
+ hf_token: Optional HF API token. Required for rate limits or private models.
13
+ Returns:
14
+ str: Generated summary text.
15
+ """
16
+ name = resume_data.get('name', 'The candidate')
17
+ skills: List[str] = resume_data.get('skills', []) or []
18
+ raw_text: str = resume_data.get('raw_text', '')
19
+
20
+ if not raw_text or len(raw_text.strip()) == 0:
21
+ return generate_template_summary(resume_data)
22
+
23
+ # The Inference API has input length limits; truncate long text conservatively.
24
+ # We'll keep about ~2000 characters to stay safe across most summarization models.
25
+ truncated = raw_text.strip()
26
+ if len(truncated) > 2000:
27
+ truncated = truncated[:2000]
28
+
29
+ prompt = truncated
30
+ try:
31
+ summary = hf_summarize(prompt, model=hf_model, api_token=hf_token)
32
+ except Exception:
33
+ # Fallback to template summary on any API failure
34
+ return generate_template_summary(resume_data)
35
+
36
+ # Optionally append skills
37
+ if skills:
38
+ summary = summary.strip() + f"\n\nKey Skills: {', '.join(skills[:10])}"
39
+ return summary
40
+
41
+ def generate_extractive_summary(*args, **kwargs) -> str:
42
+ """Deprecated placeholder. We now use the Hugging Face Inference API."""
43
+ raise NotImplementedError("Use summarize_resume with hf_model and hf_token instead.")
44
+
45
+ def generate_template_summary(resume_data: Dict) -> str:
46
+ """Generate a template-based summary when there's not enough text for extractive summarization."""
47
+ name = resume_data.get('name', 'The candidate')
48
+ skills = resume_data.get('skills', [])
49
+
50
+ summary = f"{name} "
51
+
52
+ if skills:
53
+ summary += f"is proficient in {', '.join(skills[:5])}."
54
+ else:
55
+ summary += "has submitted a resume for review."
56
+
57
+ return summary
58
+
59
+ # For testing
60
+ if __name__ == "__main__":
61
+ test_resume = {
62
+ 'name': 'John Doe',
63
+ 'email': 'john.doe@example.com',
64
+ 'phone': '(123) 456-7890',
65
+ 'skills': ['Python', 'Machine Learning', 'Data Analysis'],
66
+ 'raw_text': """John Doe
67
+ Senior Data Scientist
68
+
69
+ Experienced data scientist with 5+ years of experience in machine learning and data analysis.
70
+ Proficient in Python, R, and SQL. Strong background in statistical modeling and data visualization.
71
+
72
+ Experience:
73
+ - Data Scientist at Tech Corp (2020-Present)
74
+ - Junior Data Analyst at Data Systems (2018-2020)
75
+
76
+ Education:
77
+ - MS in Computer Science, University of Technology
78
+ - BS in Mathematics, State University
79
+ """
80
+ }
81
+
82
+ print(summarize_resume(test_resume))
hf_api.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from huggingface_hub import InferenceClient
3
+
4
+
5
+ def hf_summarize(text: str, *, model: str, api_token: Optional[str] = None, max_new_tokens: int = 200) -> str:
6
+ """
7
+ Call the Hugging Face Inference API for summarization.
8
+
9
+ Args:
10
+ text: Input text to summarize.
11
+ model: Hugging Face model repo id (e.g., 'facebook/bart-large-cnn').
12
+ api_token: Optional HF API token for higher rate limits / private models.
13
+ max_new_tokens: Maximum tokens to generate in the summary.
14
+
15
+ Returns:
16
+ The summarized text.
17
+ """
18
+ client = InferenceClient(model=model, token=api_token)
19
+ # The library returns either a string or a dict depending on the task helper used.
20
+ # Using the summarization task helper yields a string.
21
+ result = client.summarization(text, max_new_tokens=max_new_tokens)
22
+ if isinstance(result, str):
23
+ return result.strip()
24
+ # Fallback if a different payload format is returned
25
+ if isinstance(result, dict) and 'summary_text' in result:
26
+ return str(result['summary_text']).strip()
27
+ return str(result).strip()
resume_parser.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import PyPDF2
4
+ from docx import Document
5
+ from typing import Dict, List
6
+
7
+ def extract_text_from_pdf(file_path: str) -> str:
8
+ """Extract text from PDF file."""
9
+ with open(file_path, 'rb') as file:
10
+ reader = PyPDF2.PdfReader(file)
11
+ text = ""
12
+ for page in reader.pages:
13
+ text += page.extract_text() + "\n"
14
+ return text
15
+
16
+ def extract_text_from_docx(file_path: str) -> str:
17
+ """Extract text from DOCX file."""
18
+ doc = Document(file_path)
19
+ return "\n".join([paragraph.text for paragraph in doc.paragraphs])
20
+
21
+ def extract_contact_info(text: str) -> Dict[str, str]:
22
+ """Extract contact information from text."""
23
+ # Email pattern
24
+ email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
25
+ # Phone pattern (supports various formats)
26
+ phone_pattern = r'(\+\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}'
27
+
28
+ email = re.search(email_pattern, text)
29
+ phone = re.search(phone_pattern, text)
30
+
31
+ return {
32
+ 'email': email.group(0) if email else '',
33
+ 'phone': phone.group(0) if phone else ''
34
+ }
35
+
36
+ def extract_skills(text: str) -> List[str]:
37
+ """Extract skills by keyword scan (lightweight, no external NLP)."""
38
+ skills_list = [
39
+ 'python', 'java', 'javascript', 'c++', 'c#', 'sql', 'html', 'css',
40
+ 'machine learning', 'deep learning', 'data science', 'data analysis',
41
+ 'project management', 'team leadership', 'agile', 'scrum', 'devops',
42
+ 'cloud', 'aws', 'azure', 'gcp', 'docker', 'kubernetes', 'git', 'rest api',
43
+ 'graphql', 'mongodb', 'postgresql', 'mysql', 'tensorflow', 'pytorch',
44
+ 'pandas', 'numpy', 'spark', 'hadoop', 'nlp'
45
+ ]
46
+ text_lower = text.lower()
47
+ found = set()
48
+ for skill in skills_list:
49
+ if skill in text_lower:
50
+ found.add(skill)
51
+ return sorted(found)
52
+
53
+ def parse_resume(file_path: str) -> Dict:
54
+ """Main function to parse resume and extract information."""
55
+ # Determine file type and extract text
56
+ file_ext = os.path.splitext(file_path)[1].lower()
57
+
58
+ if file_ext == '.pdf':
59
+ text = extract_text_from_pdf(file_path)
60
+ elif file_ext == '.docx':
61
+ text = extract_text_from_docx(file_path)
62
+ else:
63
+ raise ValueError("Unsupported file format. Please upload a PDF or DOCX file.")
64
+
65
+ # Extract information
66
+ contact_info = extract_contact_info(text)
67
+ skills = extract_skills(text)
68
+
69
+ # Extract name (simple approach - first line is often the name)
70
+ name = text.split('\n')[0].strip()
71
+
72
+ return {
73
+ 'name': name,
74
+ 'email': contact_info['email'],
75
+ 'phone': contact_info['phone'],
76
+ 'skills': skills,
77
+ 'raw_text': text
78
+ }