CapProj commited on
Commit
5dec17e
·
verified ·
1 Parent(s): 444431c

Upload 25 files

Browse files
README.md CHANGED
@@ -1,12 +1,28 @@
1
- ---
2
- title: Ats Optimizer
3
- emoji: 🏢
4
- colorFrom: blue
5
- colorTo: red
6
- sdk: streamlit
7
- sdk_version: 1.44.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ATS Optimizer Pro
2
+
3
+ Complete solution to optimize resumes for applicant tracking systems.
4
+
5
+ ## Features
6
+
7
+ 1. **Format-Preserving Analysis**
8
+ - Parses PDF/Word resumes while keeping original formatting
9
+ - Advanced section detection
10
+
11
+ 2. **5-Factor ATS Scoring**
12
+ - Keyword matching (TF-IDF + exact match)
13
+ - Section completeness
14
+ - Semantic similarity (Qdrant vectors)
15
+ - Experience matching
16
+ - Education verification
17
+
18
+ 3. **AI-Powered Optimization**
19
+ - DeepSeek API integration
20
+ - 3-step iterative refinement
21
+ - Format-aware rewriting
22
+
23
+ ## Setup
24
+
25
+ 1. Install requirements:
26
+ ```bash
27
+ pip install -r requirements.txt
28
+ python -m spacy download en_core_web_md
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from ats_optimizer.core import ResumeAnalyzer, ResumeOptimizer
3
+ from ats_optimizer.data_models import Resume, JobDescription
4
+ from ats_optimizer.utils.file_handlers import FileHandler # Updated import
5
+ # from ats_optimizer.utils import FileHandler, Config
6
+ from ats_optimizer.utils.config_manager import Config
7
+ from pathlib import Path
8
+ import tempfile
9
+ import sys
10
+
11
+ # Fix module imports for Hugging Face
12
+ sys.path.append(str(Path(__file__).parent))
13
+
14
+ # def main():
15
+ # # Initialize components
16
+ # config = Config('config.yaml')
17
+ # analyzer = ResumeAnalyzer()
18
+ # optimizer = ResumeOptimizer(config.deepseek_api_key)
19
+
20
+ # # Streamlit UI
21
+ # st.title("🚀 ATS Optimizer Pro")
22
+ # st.markdown("Upload your resume and job description to analyze and optimize for ATS compatibility")
23
+
24
+ # # File upload section
25
+ # with st.expander("Upload Files", expanded=True):
26
+ # col1, col2 = st.columns(2)
27
+ # with col1:
28
+ # resume_file = st.file_uploader("Resume", type=["pdf", "docx"], key="resume_upload")
29
+ # with col2:
30
+ # jd_file = st.file_uploader("Job Description", type=["pdf", "docx", "txt"], key="jd_upload")
31
+
32
+ # if st.button("Analyze", type="primary"):
33
+ # if resume_file and jd_file:
34
+ # with st.spinner("Processing files..."):
35
+ # try:
36
+ # # Save uploaded files
37
+ # with tempfile.TemporaryDirectory() as temp_dir:
38
+ # resume_path = FileHandler.save_uploaded_file(resume_file, temp_dir)
39
+ # jd_path = FileHandler.save_uploaded_file(jd_file, temp_dir)
40
+
41
+ # if not resume_path or not jd_path:
42
+ # st.error("Failed to process uploaded files")
43
+ # return
44
+
45
+ # # Analyze documents
46
+ # resume = analyzer.parse_resume(resume_path)
47
+ # jd = analyzer.parse_jd(jd_path)
48
+
49
+ # # Calculate score
50
+ # score = analyzer.calculate_ats_score(resume, jd)
51
+
52
+ # # Display results
53
+ # st.subheader("📊 Analysis Results")
54
+ # st.metric("Overall ATS Score", f"{score['overall_score']:.1f}%")
55
+
56
+ # with st.expander("Detailed Scores"):
57
+ # st.write(f"Keyword Match: {score['keyword_score']:.1f}%")
58
+ # st.write(f"Section Completeness: {score['section_score']:.1f}%")
59
+ # st.write(f"Experience Match: {score['experience_score']:.1f}%")
60
+
61
+ # # Optimization section
62
+ # st.subheader("🛠 Optimization")
63
+ # if st.button("Optimize Resume", key="optimize_btn"):
64
+ # with st.spinner("Rewriting resume..."):
65
+ # optimized = optimizer.rewrite_resume(resume, jd)
66
+ # temp_output = Path(temp_dir) / "optimized_resume.docx"
67
+ # FileHandler.save_resume(optimized, str(temp_output))
68
+
69
+ # st.success("Optimization complete!")
70
+ # with open(temp_output, "rb") as f:
71
+ # st.download_button(
72
+ # "Download Optimized Resume",
73
+ # data=f,
74
+ # file_name="optimized_resume.docx",
75
+ # mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
76
+ # )
77
+
78
+ # except Exception as e:
79
+ # st.error(f"An error occurred: {str(e)}")
80
+ # st.stop()
81
+ # else:
82
+ # st.warning("Please upload both resume and job description files")
83
+
84
+ # if __name__ == "__main__":
85
+ # main()
86
+
87
+ def main():
88
+ # Initialize components
89
+ config = Config('config.yaml')
90
+ analyzer = ResumeAnalyzer()
91
+ optimizer = ResumeOptimizer(config.deepseek_api_key)
92
+
93
+ # Streamlit UI
94
+ st.title("ATS Optimizer Pro")
95
+
96
+ # File upload
97
+ resume_file = st.file_uploader("Upload Resume", type=["pdf", "docx"])
98
+ jd_file = st.file_uploader("Upload Job Description", type=["pdf", "docx", "txt"])
99
+
100
+ if st.button("Analyze"):
101
+ if resume_file and jd_file:
102
+ with st.spinner("Processing..."):
103
+ try:
104
+ # Create temp directory
105
+ with tempfile.TemporaryDirectory() as temp_dir:
106
+ # Save uploaded files
107
+ resume_path = FileHandler.save_uploaded_file(resume_file, temp_dir)
108
+ jd_path = FileHandler.save_uploaded_file(jd_file, temp_dir)
109
+
110
+ if not resume_path or not jd_path:
111
+ st.error("Failed to process uploaded files")
112
+ return
113
+
114
+ # Analyze documents
115
+ resume = analyzer.parse_resume(resume_path)
116
+ jd = analyzer.parse_jd(jd_path)
117
+
118
+ # Calculate score
119
+ score = analyzer.calculate_ats_score(resume, jd)
120
+
121
+ # Display results
122
+ st.subheader("Analysis Results")
123
+ st.json(score)
124
+
125
+ # Optimization
126
+ if st.button("Optimize Resume"):
127
+ optimized = optimizer.rewrite_resume(resume, jd)
128
+ output_path = os.path.join(temp_dir, "optimized_resume.docx")
129
+ if FileHandler.save_resume(optimized, output_path):
130
+ with open(output_path, "rb") as f:
131
+ st.download_button(
132
+ "Download Optimized Resume",
133
+ data=f,
134
+ file_name="optimized_resume.docx"
135
+ )
136
+ except Exception as e:
137
+ st.error(f"An error occurred: {str(e)}")
138
+ else:
139
+ st.warning("Please upload both resume and job description files")
config.yaml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ deepseek:
2
+ api_key: "sk-cf0b89ca56e44cb9980113909ebe687e"
3
+ model: "deepseek-chat"
4
+ temperature: 0.7
5
+
6
+ google_sheets:
7
+ enabled: true # Set to true if using Google Sheets or else false
8
+ credentials: "credentials.json"
9
+ # sheet_id: "your-sheet-id"
10
+ sheet_name: "Job Applications"
11
+
12
+ scoring:
13
+ weights:
14
+ keyword: 0.3
15
+ section: 0.2
16
+ vector: 0.25
17
+ experience: 0.15
18
+ education: 0.1
core/__init__.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # from .analyzer import ResumeAnalyzer
2
+ # from .optimizer import ResumeOptimizer
3
+ # from .tracker import ApplicationTracker
4
+
5
+ from ats_optimizer.core.analyzer import ResumeAnalyzer
6
+ from ats_optimizer.core.optimizer import ResumeOptimizer
7
+ from ats_optimizer.core.tracker import ApplicationTracker
8
+
9
+ __all__ = ['ResumeAnalyzer', 'ResumeOptimizer', 'ApplicationTracker']
10
+ # __all__ = ['ResumeAnalyzer', 'ResumeOptimizer'] #if doesnt want to use tracker & comment above import too
core/analyzer.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from qdrant_client import QdrantClient
2
+ from ats_optimizer.data_models.resume import Resume
3
+ from ats_optimizer.data_models.job_description import JobDescription
4
+ # from utils.logger import logger
5
+ # from ..utils.logger import logger
6
+ from ats_optimizer.utils.logger import logger
7
+
8
+ class ResumeAnalyzer:
9
+ def __init__(self):
10
+ self.client = QdrantClient(":memory:")
11
+ self.client.set_model("BAAI/bge-base-en")
12
+
13
+ def parse_resume(self, file_path: str) -> Resume:
14
+ """Step 1: Parse resume with formatting preservation"""
15
+ # Uses python-docx for Word docs, pdfminer for PDFs
16
+ raw_text, formatting = FileHandler.extract_with_formatting(file_path)
17
+ return Resume(raw_text, formatting)
18
+
19
+ def parse_jd(self, file_path: str) -> JobDescription:
20
+ """Step 2: Analyze job description"""
21
+ raw_text = FileHandler.extract_text(file_path)
22
+ return JobDescription(raw_text)
23
+
24
+ def calculate_ats_score(self, resume: Resume, jd: JobDescription) -> dict:
25
+ """Step 3: Comprehensive ATS scoring"""
26
+ # Enhanced scoring with 5 factors
27
+ scores = {
28
+ 'keyword': self._keyword_match_score(resume, jd),
29
+ 'section': self._section_completeness(resume, jd),
30
+ 'vector': self._vector_similarity(resume, jd),
31
+ 'experience': self._experience_match(resume, jd),
32
+ 'education': self._education_match(resume, jd)
33
+ }
34
+ scores['overall'] = sum(w * s for w, s in [
35
+ (0.3, scores['keyword']),
36
+ (0.2, scores['section']),
37
+ (0.25, scores['vector']),
38
+ (0.15, scores['experience']),
39
+ (0.1, scores['education'])
40
+ ])
41
+ return scores
core/form_filler/background.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2
+ if (request.action === "fillForm") {
3
+ chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
4
+ chrome.tabs.sendMessage(tabs[0].id, {
5
+ action: "fillFields",
6
+ resumeData: request.data
7
+ });
8
+ });
9
+ }
10
+ });
core/form_filler/content.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
2
+ if (msg.action === "fillFields") {
3
+ const fields = {
4
+ 'input[name*="name"]': msg.resumeData.name,
5
+ 'input[name*="email"]': msg.resumeData.email,
6
+ 'textarea[name*="experience"]': msg.resumeData.experience
7
+ };
8
+
9
+ Object.entries(fields).forEach(([selector, value]) => {
10
+ const el = document.querySelector(selector);
11
+ if (el) el.value = value;
12
+ });
13
+ }
14
+ });
core/form_filler/manifest.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "ATS AutoFill",
4
+ "version": "1.0",
5
+ "permissions": ["activeTab", "storage"],
6
+ "action": {
7
+ "default_popup": "popup.html",
8
+ "default_icon": "icon.png"
9
+ },
10
+ "background": {
11
+ "service_worker": "background.js"
12
+ },
13
+ "content_scripts": [{
14
+ "matches": ["*://*/*"],
15
+ "js": ["content.js"]
16
+ }]
17
+ }
core/form_filler/popup.html ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>ATS AutoFill</title>
5
+ <script src="popup.js"></script>
6
+ </head>
7
+ <body>
8
+ <button id="fillButton">Fill Form</button>
9
+ </body>
10
+ </html>
core/optimizer.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from docx import Document
3
+ from data_models.resume import Resume
4
+
5
+ from ats_optimizer.data_models.job_description import JobDescription
6
+ from ats_optimizer.data_models.resume import Resume
7
+
8
+ class ResumeOptimizer:
9
+ def __init__(self, api_key: str):
10
+ self.api_key = api_key
11
+ # self.template = "templates/professional.docx"
12
+ self.template = "templates/Ramen_DXC.docx"
13
+
14
+ def rewrite_resume(self, resume: Resume, jd: JobDescription) -> Resume:
15
+ """Step 4: AI rewriting with formatting preservation"""
16
+ prompt = self._build_optimization_prompt(resume, jd)
17
+
18
+ response = requests.post(
19
+ "https://api.deepseek.com/v1/chat/completions",
20
+ headers={"Authorization": f"Bearer {self.api_key}"},
21
+ json={
22
+ "model": "deepseek-chat",
23
+ "messages": [{
24
+ "role": "user",
25
+ "content": prompt
26
+ }],
27
+ "temperature": 0.7
28
+ }
29
+ )
30
+
31
+ # Apply optimized content to original format
32
+ optimized_content = response.json()["choices"][0]["message"]["content"]
33
+ return self._apply_formatting(resume, optimized_content)
34
+
35
+ def _apply_formatting(self, original: Resume, new_content: str) -> Resume:
36
+ """Preserve original formatting with new content"""
37
+ doc = Document(original.file_path)
38
+ # Advanced formatting preservation logic
39
+ return Resume.from_docx(doc, new_content)
core/tracker.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gspread
2
+ from oauth2client.service_account import ServiceAccountCredentials
3
+ from pathlib import Path
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ '''
9
+ class ApplicationTracker:
10
+ def __init__(self, creds_path: str):
11
+ self.scope = ["https://www.googleapis.com/auth/spreadsheets"]
12
+ self.creds = ServiceAccountCredentials.from_json_keyfile_name(creds_path, self.scope)
13
+
14
+ def track(self, application_data: dict):
15
+ """Track application in Google Sheets with enhanced fields"""
16
+ client = gspread.authorize(self.creds)
17
+ sheet = client.open("Job Applications").sheet1
18
+
19
+ sheet.append_row([
20
+ application_data['company'],
21
+ application_data['position'],
22
+ application_data['date'],
23
+ application_data['status'],
24
+ application_data['score'],
25
+ application_data['url'],
26
+ application_data['resume_version'],
27
+ application_data['notes']
28
+ ])
29
+ '''
30
+ class ApplicationTracker:
31
+ def __init__(self, creds_path: str):
32
+ if not Path(creds_path).exists():
33
+ raise FileNotFoundError(f"Credentials file not found at {creds_path}")
34
+
35
+ try:
36
+ self.scope = ["https://www.googleapis.com/auth/spreadsheets"]
37
+ self.creds = ServiceAccountCredentials.from_json_keyfile_name(creds_path, self.scope)
38
+ self.client = gspread.authorize(self.creds)
39
+ except Exception as e:
40
+ logger.error(f"Google Sheets auth failed: {e}")
41
+ raise
42
+
43
+ def track(self, application_data: dict, sheet_name="Job Applications"):
44
+ try:
45
+ sheet = self.client.open(sheet_name).sheet1
46
+ sheet.append_row([
47
+ application_data.get('company', ''),
48
+ application_data.get('position', ''),
49
+ application_data.get('date_applied', ''),
50
+ application_data.get('status', 'Applied'),
51
+ application_data.get('score', ''),
52
+ application_data.get('url', '')
53
+ ])
54
+ return True
55
+ except Exception as e:
56
+ logger.error(f"Tracking failed: {e}")
57
+ return False
credentials.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "service_account",
3
+ "project_id": "formal-theater-458022-d2",
4
+ "private_key_id": "e7494ef90b6d43b0dc15ce9f9db0922225a48694",
5
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDGjEU29ETZSyx\nqr8zakizg+q3IqWq3pQdMVHH72GLq/vugkZiqao11YdFK3igyKIRApacpLAACjmm\nEoUwcKEh1R7WKExbAvyFTLIm65j9wZ2Z2LeAuRRZFjU2uQ+iCXOTQA7YgIPU3sMg\nLI+cMAITr0C4hLRnjgUcPeNQRG8M/Io3Oq4KeTDvEKS99aqVvA7zgGH98awdBd2c\ncciYGHaTUq5sJxK72AKFnPxrLHgbq9hiuc5fw7YdCVzmuVY/LXkwSkosbIsIrxUx\nPHwlnYdoWHZ0aI76x3qluw+MUqZoIEp3ctkIfHaZbLf4yuqTcbUuQ7RtkPFLifge\ndFsM7BnhAgMBAAECggEASLQr8h/wC5A6VYLReXFz4iGYh+JLZh9HhpFobl8QNKJE\nYZ7+Z6neGe2WWPpYG2JosnoKchkU1Q76aJ6iL2jpQthOg3PE8G1ueKYaBVLqUjWi\na0BNMZTGtmQGNHxGDRYEkazfW2KYvey9PfIdGhDx1TALqDcbmzNbSCjv2muGDoou\nzmEcAzdil5M0UNVctqxqAOk4PK3l59dSUdRgJLP6LsceT1xqNDw9Ps9x8vOUY3iq\n0kkOeXu+eTVIkgTxIsJGnBQ0wr+Ao5r12zJ8Y/iyXDVN/RTOl+aakegYiIYjeVZu\nEPyZ2fMNnQCtfSGzoLrcHyFxkhh1Fl2E4ZhW4hWh+QKBgQDgjtlnhBdhNuBGHOp0\nknRKWlhtRXzuIDklCmIS/vK0ZXpCF2wacXo7UKHQ7oVPsA5+0MqeV9teu8hqQm/Y\nZRipHU911zlsR81ZejR4WQ8xDci4Ewpt5JVkx+DfPlNXuPapgGTtg/KBDfKHWnnv\nNr/BSeRSNb5iSPkS/j4ENnrhEwKBgQDea4U0cy9J2YvBhOmb/Bk9fOIBcnlz/B2v\nWt/+ddIZnW1IOYgSd/W8W3GyQ803OzzuHa+y0vLNnGBwapOMPJ8foagy36XYbRxr\nSu8He+oTTJTgwidEnUz1DS8B3IIgi+FJK8YLUL8hVupuBqqI/r9G/wtULTFutzvD\n5/N/rCaruwKBgADOIlNvstHDa5x0wBZ46/fUSRrjM+Z6sRnD5sQgq+gfsQeJo/aY\nT5Lk4B+qq0m03OhxgTh+Iig9ziMrZ9FD04nPtBg9FFSiEUdv275Ou3I2lXCriM8K\nEcsRuGm0hIH9BM1oy3PalEUIMsVvep5z+M4NoMb2sF8T2ejKhphnRZuHAoGAL+rM\nFMOn8WoLwNJIndFPAr8v1Y36+nDbWFbkoOZzMA+JZqD2Xrw3VbABq50NzhNWChqd\nKpJlusQwxqc/SFwbD+581RD3osvG7pqDKoKYqDW8cTuCyDZ3SOfhM6503lwkWeYz\nUWbA9obKFJAdF0yCmuIBZ84gszCIkKkc/WlyH1cCgYEAtkmceBmt/YCPnaVDhk/L\n4hWqcVHqD2RU4DPPhjIXppjahDrcOzjD7Lt9swKK3QZEE840DcIAKVU140V2Neie\nOXqzgrLn/dIe0j15FK4lmY9GhCVU3kTx08JwxEiBIDoi0Z6F926C9dvjGxarpsi7\n77pNNpxzZ9i6mx9dK9ECuMI=\n-----END PRIVATE KEY-----\n",
6
+ "client_email": "[email protected]",
7
+ "client_id": "102212927624577642135",
8
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9
+ "token_uri": "https://oauth2.googleapis.com/token",
10
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ats-optimizer%40formal-theater-458022-d2.iam.gserviceaccount.com",
12
+ "universe_domain": "googleapis.com"
13
+ }
data_models/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # from .resume import Resume
2
+ from .resume import Resume
3
+ from .job_description import JobDescription
4
+
5
+ __all__ = ['Resume', 'JobDescription']
data_models/job_description.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from typing import Dict, List
3
+
4
+ @dataclass
5
+ class JobDescription:
6
+ # raw_text: str
7
+ # extracted_keywords: List[str]
8
+ # keyterms: List[tuple]
9
+ # entities: List[str]
10
+ raw_text: str
11
+ extracted_keywords: list
12
+ keyterms: list
13
+ entities: list
14
+
15
+ @property
16
+ def required_skills(self) -> List[str]:
17
+ return [kw for kw in self.extracted_keywords if 'skill' in kw.lower()]
18
+
19
+ @property
20
+ def experience_requirements(self) -> Dict:
21
+ return {
22
+ 'years': self._extract_years(),
23
+ 'technologies': self._extract_tech()
24
+ }
25
+
26
+ def _extract_years(self) -> int:
27
+ # Extract years requirement using regex
28
+ pass
29
+
30
+ def _extract_tech(self) -> List[str]:
31
+ # Extract required technologies
32
+ pass
data_models/resume.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from typing import Dict, List
3
+
4
+ @dataclass
5
+ class Resume:
6
+ # raw_text: str
7
+ # formatting: Dict
8
+ # metadata: Dict
9
+ # scores: Dict = None
10
+ raw_text: str
11
+ extracted_keywords: list
12
+ keyterms: list
13
+ entities: list
14
+
15
+ @classmethod
16
+ def from_docx(cls, file_path: str):
17
+ """Parse while preserving formatting"""
18
+ text, formatting = parse_docx_with_formatting(file_path)
19
+ return cls(text, formatting, extract_metadata(text))
20
+
21
+ def to_docx(self, output_path: str):
22
+ """Export with original formatting"""
23
+ apply_formatting(self.raw_text, self.formatting, output_path)
requirement.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core
2
+ python-docx==0.8.11
3
+ pdfminer.six==20221105
4
+ #python-docx-template==0.16.0
5
+ docxtpl==0.16.0
6
+ pdf2docx==0.5.6
7
+ qdrant-client==1.6.4
8
+ sentence-transformers==2.2.2
9
+
10
+ # API & Services
11
+ google-api-python-client==2.104.0
12
+ gspread==5.11.3
13
+ requests==2.31.0
14
+ oauth2client==4.1.3
15
+
16
+ # UI
17
+ streamlit==1.29.0
18
+ plotly==5.18.0
19
+
20
+ # NLP
21
+ spacy==3.7.2
22
+ en-core-web-md @ https://github.com/explosion/spacy-models/releases/download/en_core_web_md-3.7.0/en_core_web_md-3.7.0-py3-none-any.whl
setup.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # setup.py
2
+ from setuptools import setup, find_packages
3
+
4
+ setup(
5
+ name="ats_optimizer",
6
+ version="0.1",
7
+ packages=find_packages(),
8
+ install_requires=[
9
+ 'python-docx>=0.8.11',
10
+ 'pdfminer.six>=20221105',
11
+ 'pyyaml>=6.0',
12
+ 'streamlit>=1.28.0',
13
+ 'spacy>=3.7.2',
14
+ ],
15
+ python_requires='>=3.9',
16
+ )
templates/Ramen_DXC.docx ADDED
Binary file (39.9 kB). View file
 
templates/professional.docx ADDED
File without changes
utils/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from .file_handlers import FileHandler
2
+ from .logger import setup_logger
3
+ from .config_manager import Config
4
+
5
+ __all__ = ['FileHandler', 'setup_logger', 'Config']
utils/config_manager.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import yaml
2
+
3
+ class Config:
4
+ def __init__(self, config_path: str):
5
+ with open(config_path, 'r') as f:
6
+ self.config = yaml.safe_load(f)
7
+
8
+ def __getattr__(self, name):
9
+ return self.config.get(name)
utils/file_handlers.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from docx import Document
3
+ from pdfminer.high_level import extract_text
4
+ from pathlib import Path
5
+ from tempfile import NamedTemporaryFile
6
+ import uuid
7
+
8
+ class FileHandler:
9
+ @staticmethod
10
+ def read_file(file_path: str) -> str:
11
+ if file_path.endswith('.docx'):
12
+ return FileHandler._read_docx(file_path)
13
+ elif file_path.endswith('.pdf'):
14
+ return extract_text(file_path)
15
+ else:
16
+ with open(file_path, 'r') as f:
17
+ return f.read()
18
+ #--
19
+
20
+ @staticmethod
21
+ def save_uploaded_file(uploaded_file, directory="temp_uploads"):
22
+ """Save Streamlit uploaded file to a temporary directory"""
23
+ try:
24
+ # Create directory if it doesn't exist
25
+ Path(directory).mkdir(exist_ok=True)
26
+
27
+ # Generate unique filename
28
+ file_ext = Path(uploaded_file.name).suffix
29
+ unique_id = uuid.uuid4().hex
30
+ temp_file = Path(directory) / f"{unique_id}{file_ext}"
31
+
32
+ # Save file
33
+ with open(temp_file, "wb") as f:
34
+ f.write(uploaded_file.getbuffer())
35
+
36
+ return str(temp_file)
37
+ except Exception as e:
38
+ print(f"Error saving file: {e}")
39
+ return None
40
+
41
+ @staticmethod
42
+ def cleanup_temp_files(directory="temp_uploads"):
43
+ """Remove temporary files"""
44
+ try:
45
+ for file in Path(directory).glob("*"):
46
+ file.unlink()
47
+ except Exception as e:
48
+ print(f"Error cleaning files: {e}")
49
+ #--
50
+
51
+ @staticmethod
52
+ def _read_docx(file_path: str) -> str:
53
+ doc = Document(file_path)
54
+ return '\n'.join([para.text for para in doc.paragraphs])
55
+
56
+ @staticmethod
57
+ def save_resume(resume_data: dict, output_path: str):
58
+ if output_path.endswith('.docx'):
59
+ FileHandler._save_as_docx(resume_data, output_path)
60
+ else:
61
+ with open(output_path, 'w') as f:
62
+ f.write(resume_data['content'])
63
+
64
+ @staticmethod
65
+ def _save_as_docx(resume_data: dict, output_path: str):
66
+ doc = Document()
67
+ # Add formatting preservation logic here
68
+ doc.save(output_path)
69
+
utils/file_handlers1.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ import uuid
4
+ from docx import Document
5
+ from pdfminer.high_level import extract_text
6
+
7
+ class FileHandler:
8
+ @staticmethod
9
+ def save_uploaded_file(uploaded_file, directory="temp_uploads"):
10
+ """Save Streamlit uploaded file to temporary directory"""
11
+ try:
12
+ # Create directory if needed
13
+ Path(directory).mkdir(exist_ok=True)
14
+
15
+ # Generate unique filename
16
+ file_ext = Path(uploaded_file.name).suffix
17
+ unique_id = uuid.uuid4().hex
18
+ temp_file = Path(directory) / f"{unique_id}{file_ext}"
19
+
20
+ # Save file
21
+ with open(temp_file, "wb") as f:
22
+ f.write(uploaded_file.getbuffer())
23
+
24
+ return str(temp_file)
25
+ except Exception as e:
26
+ print(f"Error saving file: {e}")
27
+ return None
28
+
29
+ @staticmethod
30
+ def save_resume(resume_data, output_path):
31
+ """Save resume content to file"""
32
+ try:
33
+ if output_path.endswith('.docx'):
34
+ doc = Document()
35
+ for paragraph in resume_data.split('\n'):
36
+ doc.add_paragraph(paragraph)
37
+ doc.save(output_path)
38
+ else:
39
+ with open(output_path, 'w') as f:
40
+ f.write(resume_data)
41
+ return True
42
+ except Exception as e:
43
+ print(f"Error saving resume: {e}")
44
+ return False
45
+
46
+ @staticmethod
47
+ def cleanup_temp_files(directory="temp_uploads"):
48
+ """Remove temporary files"""
49
+ try:
50
+ for file in Path(directory).glob("*"):
51
+ file.unlink()
52
+ return True
53
+ except Exception as e:
54
+ print(f"Error cleaning files: {e}")
55
+ return False
utils/logger.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+
3
+ def setup_logger(name: str = 'ats_optimizer'):
4
+ logger = logging.getLogger(name)
5
+ logger.setLevel(logging.INFO)
6
+
7
+ handler = logging.StreamHandler()
8
+ formatter = logging.Formatter(
9
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
10
+ )
11
+ handler.setFormatter(formatter)
12
+ logger.addHandler(handler)
13
+
14
+ return logger
15
+
16
+
17
+ # Create a module-level logger instance
18
+ logger = setup_logger()