Spaces:
Running
Running
Delete ats_optimizer
Browse files- ats_optimizer/README.md +0 -28
- ats_optimizer/__init__.py +0 -0
- ats_optimizer/app.py +0 -139
- ats_optimizer/config.yaml +0 -18
- ats_optimizer/core/__init__.py +0 -10
- ats_optimizer/core/analyzer.py +0 -41
- ats_optimizer/core/form_filler/background.js +0 -10
- ats_optimizer/core/form_filler/content.js +0 -14
- ats_optimizer/core/form_filler/manifest.json +0 -17
- ats_optimizer/core/form_filler/popup.html +0 -10
- ats_optimizer/core/optimizer.py +0 -39
- ats_optimizer/core/tracker.py +0 -57
- ats_optimizer/credentials.json +0 -13
- ats_optimizer/data_models/__init__.py +0 -5
- ats_optimizer/data_models/job_description.py +0 -32
- ats_optimizer/data_models/resume.py +0 -23
- ats_optimizer/requirement.txt +0 -22
- ats_optimizer/setup.py +0 -16
- ats_optimizer/templates/Ramen_DXC.docx +0 -0
- ats_optimizer/templates/professional.docx +0 -0
- ats_optimizer/utils/__init__.py +0 -5
- ats_optimizer/utils/config_manager.py +0 -9
- ats_optimizer/utils/file_handlers.py +0 -69
- ats_optimizer/utils/file_handlers1.py +0 -55
- ats_optimizer/utils/logger.py +0 -18
ats_optimizer/README.md
DELETED
@@ -1,28 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/__init__.py
DELETED
File without changes
|
ats_optimizer/app.py
DELETED
@@ -1,139 +0,0 @@
|
|
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")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/config.yaml
DELETED
@@ -1,18 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/core/__init__.py
DELETED
@@ -1,10 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/core/analyzer.py
DELETED
@@ -1,41 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/core/form_filler/background.js
DELETED
@@ -1,10 +0,0 @@
|
|
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 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/core/form_filler/content.js
DELETED
@@ -1,14 +0,0 @@
|
|
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 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/core/form_filler/manifest.json
DELETED
@@ -1,17 +0,0 @@
|
|
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 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/core/form_filler/popup.html
DELETED
@@ -1,10 +0,0 @@
|
|
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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/core/optimizer.py
DELETED
@@ -1,39 +0,0 @@
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/core/tracker.py
DELETED
@@ -1,57 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/credentials.json
DELETED
@@ -1,13 +0,0 @@
|
|
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 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/data_models/__init__.py
DELETED
@@ -1,5 +0,0 @@
|
|
1 |
-
# from .resume import Resume
|
2 |
-
from .resume import Resume
|
3 |
-
from .job_description import JobDescription
|
4 |
-
|
5 |
-
__all__ = ['Resume', 'JobDescription']
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/data_models/job_description.py
DELETED
@@ -1,32 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/data_models/resume.py
DELETED
@@ -1,23 +0,0 @@
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/requirement.txt
DELETED
@@ -1,22 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/setup.py
DELETED
@@ -1,16 +0,0 @@
|
|
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 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/templates/Ramen_DXC.docx
DELETED
Binary file (39.9 kB)
|
|
ats_optimizer/templates/professional.docx
DELETED
File without changes
|
ats_optimizer/utils/__init__.py
DELETED
@@ -1,5 +0,0 @@
|
|
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']
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/utils/config_manager.py
DELETED
@@ -1,9 +0,0 @@
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/utils/file_handlers.py
DELETED
@@ -1,69 +0,0 @@
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/utils/file_handlers1.py
DELETED
@@ -1,55 +0,0 @@
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ats_optimizer/utils/logger.py
DELETED
@@ -1,18 +0,0 @@
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|