Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,19 +3,47 @@
|
|
| 3 |
|
| 4 |
import streamlit as st
|
| 5 |
import os
|
| 6 |
-
|
| 7 |
-
import
|
| 8 |
-
|
| 9 |
import hashlib
|
| 10 |
import json
|
|
|
|
|
|
|
| 11 |
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
# Loading the .env keys
|
| 14 |
load_dotenv()
|
| 15 |
|
| 16 |
-
#
|
| 17 |
-
|
|
|
|
| 18 |
|
|
|
|
| 19 |
UNIVERSAL_MASTER_PROMPT = """
|
| 20 |
You are the ULTIMATE ATS OPTIMIZATION ENGINE 3.0 - A state-of-the-art AI system designed to provide CONSISTENT, PRECISE, and GLOBALLY APPLICABLE resume analysis across ALL industries, roles, and experience levels.
|
| 21 |
|
|
@@ -112,37 +140,17 @@ Always provide results in this EXACT format for consistency:
|
|
| 112 |
**π FINAL VERDICT:**
|
| 113 |
[EXCEPTIONAL 90-100 | STRONG 75-89 | GOOD 60-74 | DEVELOPING 45-59 | NEEDS WORK <45]
|
| 114 |
|
| 115 |
-
**GLOBAL INDUSTRY ADAPTATION MATRIX:**
|
| 116 |
-
Automatically adapt analysis based on detected industry context:
|
| 117 |
-
- Technology: Focus on technical skills, certifications, project impact
|
| 118 |
-
- Healthcare: Emphasize compliance, patient outcomes, clinical expertise
|
| 119 |
-
- Finance: Highlight risk management, regulatory knowledge, quantitative skills
|
| 120 |
-
- Manufacturing: Assess process improvement, safety, operational efficiency
|
| 121 |
-
- Marketing: Evaluate campaign results, digital proficiency, creative impact
|
| 122 |
-
- Education: Focus on learning outcomes, curriculum development, mentoring
|
| 123 |
-
- Legal: Emphasize case outcomes, regulatory expertise, research capabilities
|
| 124 |
-
- Consulting: Highlight client impact, analytical skills, strategic thinking
|
| 125 |
-
|
| 126 |
**CONSISTENCY GUARANTEES:**
|
| 127 |
- Same resume + same job description = identical analysis (Β±2 points variation max)
|
| 128 |
- Standardized language and terminology across all evaluations
|
| 129 |
- Reproducible scoring methodology regardless of domain
|
| 130 |
- Time-consistent results (same analysis today and tomorrow)
|
| 131 |
-
|
| 132 |
-
**QUALITY ASSURANCE CHECKS:**
|
| 133 |
-
- Bias detection and mitigation protocols
|
| 134 |
-
- Cultural sensitivity and inclusive language
|
| 135 |
-
- Legal compliance verification
|
| 136 |
-
- Ethical evaluation standards
|
| 137 |
-
|
| 138 |
-
Proceed with analysis using this framework while maintaining absolute consistency and global applicability.
|
| 139 |
"""
|
| 140 |
|
| 141 |
# Specialized prompts that extend the master prompt for specific use cases
|
| 142 |
SPECIALIZED_PROMPTS = {
|
| 143 |
"evaluate_resume": f"""
|
| 144 |
{UNIVERSAL_MASTER_PROMPT}
|
| 145 |
-
|
| 146 |
**SPECIFIC TASK: COMPREHENSIVE RESUME EVALUATION**
|
| 147 |
Apply the Universal Evaluation Framework above to provide a complete assessment.
|
| 148 |
Focus on overall candidacy evaluation with balanced perspective on strengths and development areas.
|
|
@@ -151,108 +159,87 @@ Maintain professional tone suitable for HR professionals and hiring managers.
|
|
| 151 |
|
| 152 |
"improve_skills": f"""
|
| 153 |
{UNIVERSAL_MASTER_PROMPT}
|
| 154 |
-
|
| 155 |
**SPECIFIC TASK: SKILL ENHANCEMENT STRATEGY**
|
| 156 |
After completing the standard evaluation, provide additional guidance:
|
| 157 |
-
|
| 158 |
**π SKILL DEVELOPMENT ROADMAP:**
|
| 159 |
- **Immediate Actions (0-3 months):** Quick wins and foundational improvements
|
| 160 |
- **Short-term Goals (3-12 months):** Structured learning and certification paths
|
| 161 |
- **Long-term Vision (1-3 years):** Strategic career advancement opportunities
|
| 162 |
-
|
| 163 |
**π LEARNING RESOURCES:**
|
| 164 |
- Recommended courses, certifications, and training programs
|
| 165 |
- Industry conferences and networking opportunities
|
| 166 |
- Practical projects and portfolio development suggestions
|
| 167 |
-
|
| 168 |
Focus on actionable, measurable improvement strategies with clear timelines.
|
| 169 |
""",
|
| 170 |
|
| 171 |
"missing_keywords": f"""
|
| 172 |
{UNIVERSAL_MASTER_PROMPT}
|
| 173 |
-
|
| 174 |
**SPECIFIC TASK: ATS KEYWORD OPTIMIZATION**
|
| 175 |
After completing the standard evaluation, provide enhanced keyword analysis:
|
| 176 |
-
|
| 177 |
**π ADVANCED KEYWORD ANALYSIS:**
|
| 178 |
- **CRITICAL MISSING (High Impact):** Essential terms significantly affecting ATS ranking
|
| 179 |
- **IMPORTANT ADDITIONS (Medium Impact):** Valuable terms improving visibility
|
| 180 |
- **OPTIMIZATION OPPORTUNITIES (Low Impact):** Supplementary terms for comprehensive coverage
|
| 181 |
-
|
| 182 |
**π INTEGRATION STRATEGY:**
|
| 183 |
- Specific resume sections for keyword placement
|
| 184 |
- Natural integration techniques avoiding keyword stuffing
|
| 185 |
- Industry-appropriate phrasing and terminology
|
| 186 |
-
|
| 187 |
**π€ ATS COMPATIBILITY SCORE:** [Detailed breakdown of parsing efficiency]
|
| 188 |
""",
|
| 189 |
|
| 190 |
"percentage_match": f"""
|
| 191 |
{UNIVERSAL_MASTER_PROMPT}
|
| 192 |
-
|
| 193 |
**SPECIFIC TASK: PRECISE MATCHING ANALYSIS**
|
| 194 |
Provide the standard evaluation with enhanced quantitative focus:
|
| 195 |
-
|
| 196 |
**π DETAILED SCORING BREAKDOWN:**
|
| 197 |
Present exact point allocation for each category with clear justification.
|
| 198 |
Include competitive benchmarking and market positioning analysis.
|
| 199 |
Provide specific improvement strategies for 10-15% score increase.
|
| 200 |
-
|
| 201 |
**π― MATCH PERCENTAGE: [XX%]**
|
| 202 |
Tier Classification with detailed rationale and next steps.
|
| 203 |
""",
|
| 204 |
|
| 205 |
"answer_query": f"""
|
| 206 |
{UNIVERSAL_MASTER_PROMPT}
|
| 207 |
-
|
| 208 |
**SPECIFIC TASK: EXPERT CONSULTATION**
|
| 209 |
Apply domain expertise to answer the specific query while considering:
|
| 210 |
- Resume content and job description context
|
| 211 |
- Industry best practices and current market trends
|
| 212 |
- Practical, actionable guidance
|
| 213 |
- Evidence-based recommendations
|
| 214 |
-
|
| 215 |
Provide thorough, well-researched responses with specific examples and multiple solution approaches when applicable.
|
| 216 |
""",
|
| 217 |
|
| 218 |
"executive_assessment": f"""
|
| 219 |
{UNIVERSAL_MASTER_PROMPT}
|
| 220 |
-
|
| 221 |
**SPECIFIC TASK: EXECUTIVE-LEVEL EVALUATION**
|
| 222 |
Apply enhanced criteria for senior leadership positions:
|
| 223 |
-
|
| 224 |
**π EXECUTIVE COMPETENCY FRAMEWORK:**
|
| 225 |
- Strategic thinking and vision development
|
| 226 |
- Change management and transformation leadership
|
| 227 |
- Financial acumen and business impact
|
| 228 |
- Board readiness and governance experience
|
| 229 |
-
|
| 230 |
**π LEADERSHIP IMPACT ANALYSIS:**
|
| 231 |
- Quantifiable business results and achievements
|
| 232 |
- Market expansion and competitive positioning
|
| 233 |
- Organizational culture and talent development
|
| 234 |
- Crisis leadership and resilience
|
| 235 |
-
|
| 236 |
Provide insights suitable for C-suite and board-level discussions.
|
| 237 |
""",
|
| 238 |
|
| 239 |
"career_transition": f"""
|
| 240 |
{UNIVERSAL_MASTER_PROMPT}
|
| 241 |
-
|
| 242 |
**SPECIFIC TASK: CAREER PIVOT ANALYSIS**
|
| 243 |
Evaluate career change feasibility with:
|
| 244 |
-
|
| 245 |
**π TRANSITION ASSESSMENT:**
|
| 246 |
- Transferable skills mapping across industries
|
| 247 |
- Market positioning strategy for career change
|
| 248 |
- Risk mitigation and success probability analysis
|
| 249 |
- Timeline and milestone planning
|
| 250 |
-
|
| 251 |
**π― TRANSITION ROADMAP:**
|
| 252 |
- Phase-wise transition strategy
|
| 253 |
- Skill development priorities
|
| 254 |
- Network building and industry immersion plan
|
| 255 |
-
|
| 256 |
Provide strategic guidance maximizing transition success while minimizing career risks.
|
| 257 |
"""
|
| 258 |
}
|
|
@@ -267,20 +254,218 @@ GENERATION_CONFIG = {
|
|
| 267 |
}
|
| 268 |
|
| 269 |
# Model options optimized for consistency and performance
|
| 270 |
-
|
| 271 |
-
"gemini-2.
|
| 272 |
-
"gemini-
|
|
|
|
|
|
|
| 273 |
]
|
| 274 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
def create_consistency_hash(resume_text, job_description, prompt_type):
|
| 276 |
"""Create a hash for identical inputs to ensure consistent outputs"""
|
| 277 |
content = f"{resume_text[:1000]}{job_description[:1000]}{prompt_type}"
|
| 278 |
return hashlib.md5(content.encode()).hexdigest()
|
| 279 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
def get_consistent_gemini_response(model_id, prompt, pdf_content, input_text, consistency_hash):
|
| 281 |
-
"""Enhanced response generation with
|
| 282 |
try:
|
| 283 |
-
model = genai.GenerativeModel(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
# Add consistency instruction to prompt
|
| 286 |
enhanced_prompt = f"""
|
|
@@ -289,43 +474,256 @@ def get_consistent_gemini_response(model_id, prompt, pdf_content, input_text, co
|
|
| 289 |
**CONSISTENCY PROTOCOL ACTIVE:**
|
| 290 |
Session ID: {consistency_hash}
|
| 291 |
Evaluation Date: {datetime.now().strftime('%Y-%m-%d')}
|
| 292 |
-
|
| 293 |
Apply identical methodology and scoring for consistent results.
|
| 294 |
Use deterministic analysis patterns and standardized language.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
"""
|
| 296 |
|
| 297 |
response = model.generate_content(
|
| 298 |
-
|
| 299 |
generation_config=genai.types.GenerationConfig(**GENERATION_CONFIG)
|
| 300 |
)
|
| 301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
except Exception as e:
|
| 304 |
st.error(f"β οΈ Analysis Error: {str(e)}")
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
|
| 307 |
-
def
|
| 308 |
-
"""Enhanced PDF
|
| 309 |
text = ""
|
| 310 |
-
|
| 311 |
-
|
|
|
|
| 312 |
if doc.name.endswith(".pdf"):
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
|
|
|
| 317 |
try:
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
|
|
|
|
|
|
|
| 327 |
return text
|
| 328 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
# Streamlit App Configuration
|
| 330 |
st.set_page_config(
|
| 331 |
page_title="Ultimate Smart ATS System 2025",
|
|
@@ -371,6 +769,10 @@ st.markdown("""
|
|
| 371 |
display: inline-block;
|
| 372 |
margin: 0.5rem 0;
|
| 373 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
</style>
|
| 375 |
""", unsafe_allow_html=True)
|
| 376 |
|
|
@@ -383,18 +785,32 @@ st.markdown("""
|
|
| 383 |
</div>
|
| 384 |
""", unsafe_allow_html=True)
|
| 385 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
# Sidebar Configuration
|
| 387 |
with st.sidebar:
|
| 388 |
st.markdown("### π Configuration")
|
| 389 |
st.markdown("[Get your Google API Key](https://aistudio.google.com/app/apikey)")
|
| 390 |
|
| 391 |
api_key = st.text_input("π Google API Key", type="password", help="Your Gemini API key for AI analysis")
|
|
|
|
| 392 |
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
|
|
|
|
|
|
| 398 |
|
| 399 |
st.markdown("### π Document Upload")
|
| 400 |
uploaded_files = st.file_uploader(
|
|
@@ -413,12 +829,9 @@ with st.sidebar:
|
|
| 413 |
πΉ **Global Domain Support**: Works across all industries
|
| 414 |
πΉ **Advanced ATS Optimization**: 85% better callback rates
|
| 415 |
πΉ **Real-time Market Insights**: June 2025 standards
|
|
|
|
| 416 |
""")
|
| 417 |
|
| 418 |
-
# Set API key
|
| 419 |
-
if api_key:
|
| 420 |
-
genai.configure(api_key=api_key)
|
| 421 |
-
|
| 422 |
# Main Interface
|
| 423 |
st.markdown("### π Job Description Input")
|
| 424 |
input_text = st.text_area(
|
|
@@ -468,47 +881,72 @@ if analysis_triggered:
|
|
| 468 |
else:
|
| 469 |
# Process analysis
|
| 470 |
with st.spinner("π Analyzing with advanced AI algorithms..."):
|
| 471 |
-
pdf_content =
|
| 472 |
|
| 473 |
-
#
|
| 474 |
-
|
| 475 |
-
|
| 476 |
|
| 477 |
-
#
|
| 478 |
if evaluate_btn:
|
| 479 |
-
|
| 480 |
elif improve_btn:
|
| 481 |
-
|
| 482 |
elif keywords_btn:
|
| 483 |
-
|
| 484 |
elif match_btn:
|
| 485 |
-
|
| 486 |
elif executive_btn:
|
| 487 |
-
|
| 488 |
elif transition_btn:
|
| 489 |
-
|
| 490 |
elif query_btn:
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
# Generate response
|
| 494 |
-
response = get_consistent_gemini_response(
|
| 495 |
-
selected_model, prompt, pdf_content, input_text, consistency_hash
|
| 496 |
-
)
|
| 497 |
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
|
| 513 |
# Footer
|
| 514 |
st.markdown("---")
|
|
@@ -516,5 +954,10 @@ st.markdown("""
|
|
| 516 |
<div style="text-align: center; color: #666;">
|
| 517 |
<p>π Ultimate Smart ATS System 2025 | Powered by Advanced AI | Consistent β’ Reliable β’ Universal</p>
|
| 518 |
<p>Built with cutting-edge strategies for maximum ATS compatibility and career success</p>
|
|
|
|
| 519 |
</div>
|
| 520 |
-
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
import streamlit as st
|
| 5 |
import os
|
| 6 |
+
import re
|
| 7 |
+
import tempfile
|
| 8 |
+
import sqlite3
|
| 9 |
import hashlib
|
| 10 |
import json
|
| 11 |
+
import time
|
| 12 |
+
import pickle
|
| 13 |
from datetime import datetime
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
from functools import wraps
|
| 16 |
+
from PyPDF2 import PdfReader
|
| 17 |
+
import google.generativeai as genai
|
| 18 |
+
from dotenv import load_dotenv
|
| 19 |
+
|
| 20 |
+
# Optional imports with fallbacks
|
| 21 |
+
try:
|
| 22 |
+
import pdfplumber
|
| 23 |
+
HAS_PDFPLUMBER = True
|
| 24 |
+
except ImportError:
|
| 25 |
+
HAS_PDFPLUMBER = False
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
import docx
|
| 29 |
+
HAS_DOCX = True
|
| 30 |
+
except ImportError:
|
| 31 |
+
HAS_DOCX = False
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
import tiktoken
|
| 35 |
+
HAS_TIKTOKEN = True
|
| 36 |
+
except ImportError:
|
| 37 |
+
HAS_TIKTOKEN = False
|
| 38 |
|
| 39 |
# Loading the .env keys
|
| 40 |
load_dotenv()
|
| 41 |
|
| 42 |
+
# Create cache directory
|
| 43 |
+
CACHE_DIR = Path("ats_cache")
|
| 44 |
+
CACHE_DIR.mkdir(exist_ok=True)
|
| 45 |
|
| 46 |
+
# MASTER UNIVERSAL SYSTEM PROMPT - Designed for Maximum Consistency & Global Applicability
|
| 47 |
UNIVERSAL_MASTER_PROMPT = """
|
| 48 |
You are the ULTIMATE ATS OPTIMIZATION ENGINE 3.0 - A state-of-the-art AI system designed to provide CONSISTENT, PRECISE, and GLOBALLY APPLICABLE resume analysis across ALL industries, roles, and experience levels.
|
| 49 |
|
|
|
|
| 140 |
**π FINAL VERDICT:**
|
| 141 |
[EXCEPTIONAL 90-100 | STRONG 75-89 | GOOD 60-74 | DEVELOPING 45-59 | NEEDS WORK <45]
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
**CONSISTENCY GUARANTEES:**
|
| 144 |
- Same resume + same job description = identical analysis (Β±2 points variation max)
|
| 145 |
- Standardized language and terminology across all evaluations
|
| 146 |
- Reproducible scoring methodology regardless of domain
|
| 147 |
- Time-consistent results (same analysis today and tomorrow)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
"""
|
| 149 |
|
| 150 |
# Specialized prompts that extend the master prompt for specific use cases
|
| 151 |
SPECIALIZED_PROMPTS = {
|
| 152 |
"evaluate_resume": f"""
|
| 153 |
{UNIVERSAL_MASTER_PROMPT}
|
|
|
|
| 154 |
**SPECIFIC TASK: COMPREHENSIVE RESUME EVALUATION**
|
| 155 |
Apply the Universal Evaluation Framework above to provide a complete assessment.
|
| 156 |
Focus on overall candidacy evaluation with balanced perspective on strengths and development areas.
|
|
|
|
| 159 |
|
| 160 |
"improve_skills": f"""
|
| 161 |
{UNIVERSAL_MASTER_PROMPT}
|
|
|
|
| 162 |
**SPECIFIC TASK: SKILL ENHANCEMENT STRATEGY**
|
| 163 |
After completing the standard evaluation, provide additional guidance:
|
|
|
|
| 164 |
**π SKILL DEVELOPMENT ROADMAP:**
|
| 165 |
- **Immediate Actions (0-3 months):** Quick wins and foundational improvements
|
| 166 |
- **Short-term Goals (3-12 months):** Structured learning and certification paths
|
| 167 |
- **Long-term Vision (1-3 years):** Strategic career advancement opportunities
|
|
|
|
| 168 |
**π LEARNING RESOURCES:**
|
| 169 |
- Recommended courses, certifications, and training programs
|
| 170 |
- Industry conferences and networking opportunities
|
| 171 |
- Practical projects and portfolio development suggestions
|
|
|
|
| 172 |
Focus on actionable, measurable improvement strategies with clear timelines.
|
| 173 |
""",
|
| 174 |
|
| 175 |
"missing_keywords": f"""
|
| 176 |
{UNIVERSAL_MASTER_PROMPT}
|
|
|
|
| 177 |
**SPECIFIC TASK: ATS KEYWORD OPTIMIZATION**
|
| 178 |
After completing the standard evaluation, provide enhanced keyword analysis:
|
|
|
|
| 179 |
**π ADVANCED KEYWORD ANALYSIS:**
|
| 180 |
- **CRITICAL MISSING (High Impact):** Essential terms significantly affecting ATS ranking
|
| 181 |
- **IMPORTANT ADDITIONS (Medium Impact):** Valuable terms improving visibility
|
| 182 |
- **OPTIMIZATION OPPORTUNITIES (Low Impact):** Supplementary terms for comprehensive coverage
|
|
|
|
| 183 |
**π INTEGRATION STRATEGY:**
|
| 184 |
- Specific resume sections for keyword placement
|
| 185 |
- Natural integration techniques avoiding keyword stuffing
|
| 186 |
- Industry-appropriate phrasing and terminology
|
|
|
|
| 187 |
**π€ ATS COMPATIBILITY SCORE:** [Detailed breakdown of parsing efficiency]
|
| 188 |
""",
|
| 189 |
|
| 190 |
"percentage_match": f"""
|
| 191 |
{UNIVERSAL_MASTER_PROMPT}
|
|
|
|
| 192 |
**SPECIFIC TASK: PRECISE MATCHING ANALYSIS**
|
| 193 |
Provide the standard evaluation with enhanced quantitative focus:
|
|
|
|
| 194 |
**π DETAILED SCORING BREAKDOWN:**
|
| 195 |
Present exact point allocation for each category with clear justification.
|
| 196 |
Include competitive benchmarking and market positioning analysis.
|
| 197 |
Provide specific improvement strategies for 10-15% score increase.
|
|
|
|
| 198 |
**π― MATCH PERCENTAGE: [XX%]**
|
| 199 |
Tier Classification with detailed rationale and next steps.
|
| 200 |
""",
|
| 201 |
|
| 202 |
"answer_query": f"""
|
| 203 |
{UNIVERSAL_MASTER_PROMPT}
|
|
|
|
| 204 |
**SPECIFIC TASK: EXPERT CONSULTATION**
|
| 205 |
Apply domain expertise to answer the specific query while considering:
|
| 206 |
- Resume content and job description context
|
| 207 |
- Industry best practices and current market trends
|
| 208 |
- Practical, actionable guidance
|
| 209 |
- Evidence-based recommendations
|
|
|
|
| 210 |
Provide thorough, well-researched responses with specific examples and multiple solution approaches when applicable.
|
| 211 |
""",
|
| 212 |
|
| 213 |
"executive_assessment": f"""
|
| 214 |
{UNIVERSAL_MASTER_PROMPT}
|
|
|
|
| 215 |
**SPECIFIC TASK: EXECUTIVE-LEVEL EVALUATION**
|
| 216 |
Apply enhanced criteria for senior leadership positions:
|
|
|
|
| 217 |
**π EXECUTIVE COMPETENCY FRAMEWORK:**
|
| 218 |
- Strategic thinking and vision development
|
| 219 |
- Change management and transformation leadership
|
| 220 |
- Financial acumen and business impact
|
| 221 |
- Board readiness and governance experience
|
|
|
|
| 222 |
**π LEADERSHIP IMPACT ANALYSIS:**
|
| 223 |
- Quantifiable business results and achievements
|
| 224 |
- Market expansion and competitive positioning
|
| 225 |
- Organizational culture and talent development
|
| 226 |
- Crisis leadership and resilience
|
|
|
|
| 227 |
Provide insights suitable for C-suite and board-level discussions.
|
| 228 |
""",
|
| 229 |
|
| 230 |
"career_transition": f"""
|
| 231 |
{UNIVERSAL_MASTER_PROMPT}
|
|
|
|
| 232 |
**SPECIFIC TASK: CAREER PIVOT ANALYSIS**
|
| 233 |
Evaluate career change feasibility with:
|
|
|
|
| 234 |
**π TRANSITION ASSESSMENT:**
|
| 235 |
- Transferable skills mapping across industries
|
| 236 |
- Market positioning strategy for career change
|
| 237 |
- Risk mitigation and success probability analysis
|
| 238 |
- Timeline and milestone planning
|
|
|
|
| 239 |
**π― TRANSITION ROADMAP:**
|
| 240 |
- Phase-wise transition strategy
|
| 241 |
- Skill development priorities
|
| 242 |
- Network building and industry immersion plan
|
|
|
|
| 243 |
Provide strategic guidance maximizing transition success while minimizing career risks.
|
| 244 |
"""
|
| 245 |
}
|
|
|
|
| 254 |
}
|
| 255 |
|
| 256 |
# Model options optimized for consistency and performance
|
| 257 |
+
MODEL_FALLBACK_CHAIN = [
|
| 258 |
+
"gemini-2.0-flash-exp",
|
| 259 |
+
"gemini-1.5-pro",
|
| 260 |
+
"gemini-1.5-flash",
|
| 261 |
+
"gemini-pro"
|
| 262 |
]
|
| 263 |
|
| 264 |
+
# Rate limiting decorator
|
| 265 |
+
def rate_limit(min_interval=2):
|
| 266 |
+
def decorator(func):
|
| 267 |
+
last_called = [0]
|
| 268 |
+
@wraps(func)
|
| 269 |
+
def wrapper(*args, **kwargs):
|
| 270 |
+
elapsed = time.time() - last_called[0]
|
| 271 |
+
left_to_wait = min_interval - elapsed
|
| 272 |
+
if left_to_wait > 0:
|
| 273 |
+
time.sleep(left_to_wait)
|
| 274 |
+
result = func(*args, **kwargs)
|
| 275 |
+
last_called[0] = time.time()
|
| 276 |
+
return result
|
| 277 |
+
return wrapper
|
| 278 |
+
return decorator
|
| 279 |
+
|
| 280 |
+
# Cache management functions
|
| 281 |
+
def init_cache():
|
| 282 |
+
"""Initialize SQLite cache for consistency"""
|
| 283 |
+
conn = sqlite3.connect(CACHE_DIR / "ats_cache.db")
|
| 284 |
+
cursor = conn.cursor()
|
| 285 |
+
cursor.execute("""
|
| 286 |
+
CREATE TABLE IF NOT EXISTS analysis_cache (
|
| 287 |
+
hash_key TEXT PRIMARY KEY,
|
| 288 |
+
response TEXT,
|
| 289 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 290 |
+
model_used TEXT
|
| 291 |
+
)
|
| 292 |
+
""")
|
| 293 |
+
conn.commit()
|
| 294 |
+
conn.close()
|
| 295 |
+
|
| 296 |
+
def get_cached_response(consistency_hash, model_id):
|
| 297 |
+
"""Get cached response if available"""
|
| 298 |
+
try:
|
| 299 |
+
conn = sqlite3.connect(CACHE_DIR / "ats_cache.db")
|
| 300 |
+
cursor = conn.cursor()
|
| 301 |
+
cursor.execute(
|
| 302 |
+
"SELECT response FROM analysis_cache WHERE hash_key = ? AND model_used = ?",
|
| 303 |
+
(consistency_hash, model_id)
|
| 304 |
+
)
|
| 305 |
+
result = cursor.fetchone()
|
| 306 |
+
conn.close()
|
| 307 |
+
return result[0] if result else None
|
| 308 |
+
except:
|
| 309 |
+
return None
|
| 310 |
+
|
| 311 |
+
def cache_response(consistency_hash, response, model_id):
|
| 312 |
+
"""Cache the response for future use"""
|
| 313 |
+
try:
|
| 314 |
+
conn = sqlite3.connect(CACHE_DIR / "ats_cache.db")
|
| 315 |
+
cursor = conn.cursor()
|
| 316 |
+
cursor.execute(
|
| 317 |
+
"INSERT OR REPLACE INTO analysis_cache (hash_key, response, model_used) VALUES (?, ?, ?)",
|
| 318 |
+
(consistency_hash, response, model_id)
|
| 319 |
+
)
|
| 320 |
+
conn.commit()
|
| 321 |
+
conn.close()
|
| 322 |
+
except Exception as e:
|
| 323 |
+
st.warning(f"Cache save failed: {e}")
|
| 324 |
+
|
| 325 |
+
# Token estimation and content optimization
|
| 326 |
+
def estimate_tokens(text, model="gpt-3.5-turbo"):
|
| 327 |
+
"""Estimate token count for text"""
|
| 328 |
+
if HAS_TIKTOKEN:
|
| 329 |
+
try:
|
| 330 |
+
encoding = tiktoken.encoding_for_model(model)
|
| 331 |
+
return len(encoding.encode(text))
|
| 332 |
+
except:
|
| 333 |
+
pass
|
| 334 |
+
# Fallback estimation: roughly 4 characters per token
|
| 335 |
+
return len(text) // 4
|
| 336 |
+
|
| 337 |
+
def optimize_content_length(resume_text, job_description, max_resume_tokens=2000, max_job_tokens=1500):
|
| 338 |
+
"""Optimize content length to stay within token limits"""
|
| 339 |
+
|
| 340 |
+
# Prioritize key sections in resume
|
| 341 |
+
resume_sections = {
|
| 342 |
+
'experience': [],
|
| 343 |
+
'skills': [],
|
| 344 |
+
'education': [],
|
| 345 |
+
'summary': []
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
# Simple section detection
|
| 349 |
+
lines = resume_text.split('\n')
|
| 350 |
+
current_section = 'summary'
|
| 351 |
+
|
| 352 |
+
for line in lines:
|
| 353 |
+
line_lower = line.lower().strip()
|
| 354 |
+
if any(keyword in line_lower for keyword in ['experience', 'work', 'employment']):
|
| 355 |
+
current_section = 'experience'
|
| 356 |
+
elif any(keyword in line_lower for keyword in ['skills', 'technical', 'competencies']):
|
| 357 |
+
current_section = 'skills'
|
| 358 |
+
elif any(keyword in line_lower for keyword in ['education', 'academic', 'degree']):
|
| 359 |
+
current_section = 'education'
|
| 360 |
+
|
| 361 |
+
if line.strip():
|
| 362 |
+
resume_sections[current_section].append(line)
|
| 363 |
+
|
| 364 |
+
# Build optimized resume content
|
| 365 |
+
optimized_resume = []
|
| 366 |
+
|
| 367 |
+
# Add summary (first 300 chars)
|
| 368 |
+
if resume_sections['summary']:
|
| 369 |
+
summary_text = '\n'.join(resume_sections['summary'][:5])
|
| 370 |
+
optimized_resume.append(f"PROFESSIONAL SUMMARY:\n{summary_text[:300]}")
|
| 371 |
+
|
| 372 |
+
# Add experience (prioritize recent)
|
| 373 |
+
if resume_sections['experience']:
|
| 374 |
+
exp_text = '\n'.join(resume_sections['experience'][:15])
|
| 375 |
+
optimized_resume.append(f"WORK EXPERIENCE:\n{exp_text[:800]}")
|
| 376 |
+
|
| 377 |
+
# Add skills
|
| 378 |
+
if resume_sections['skills']:
|
| 379 |
+
skills_text = '\n'.join(resume_sections['skills'][:8])
|
| 380 |
+
optimized_resume.append(f"SKILLS:\n{skills_text[:400]}")
|
| 381 |
+
|
| 382 |
+
# Add education
|
| 383 |
+
if resume_sections['education']:
|
| 384 |
+
edu_text = '\n'.join(resume_sections['education'][:5])
|
| 385 |
+
optimized_resume.append(f"EDUCATION:\n{edu_text[:200]}")
|
| 386 |
+
|
| 387 |
+
optimized_resume_text = '\n\n'.join(optimized_resume)
|
| 388 |
+
|
| 389 |
+
# Ensure we're within token limits
|
| 390 |
+
resume_tokens = estimate_tokens(optimized_resume_text)
|
| 391 |
+
if resume_tokens > max_resume_tokens:
|
| 392 |
+
# Truncate if still too long
|
| 393 |
+
chars_per_token = len(optimized_resume_text) / resume_tokens
|
| 394 |
+
max_chars = int(max_resume_tokens * chars_per_token)
|
| 395 |
+
optimized_resume_text = optimized_resume_text[:max_chars] + "... [truncated]"
|
| 396 |
+
|
| 397 |
+
# Optimize job description
|
| 398 |
+
job_lines = job_description.split('\n')
|
| 399 |
+
important_lines = []
|
| 400 |
+
|
| 401 |
+
for line in job_lines:
|
| 402 |
+
line_lower = line.lower()
|
| 403 |
+
# Prioritize lines with key information
|
| 404 |
+
if any(keyword in line_lower for keyword in [
|
| 405 |
+
'require', 'must', 'essential', 'experience', 'skill',
|
| 406 |
+
'qualification', 'bachelor', 'master', 'year', 'certification'
|
| 407 |
+
]):
|
| 408 |
+
important_lines.append(line)
|
| 409 |
+
elif line.strip() and len(important_lines) < 20:
|
| 410 |
+
important_lines.append(line)
|
| 411 |
+
|
| 412 |
+
optimized_job = '\n'.join(important_lines)
|
| 413 |
+
|
| 414 |
+
# Ensure job description is within limits
|
| 415 |
+
job_tokens = estimate_tokens(optimized_job)
|
| 416 |
+
if job_tokens > max_job_tokens:
|
| 417 |
+
chars_per_token = len(optimized_job) / job_tokens
|
| 418 |
+
max_chars = int(max_job_tokens * chars_per_token)
|
| 419 |
+
optimized_job = optimized_job[:max_chars] + "... [truncated]"
|
| 420 |
+
|
| 421 |
+
return optimized_resume_text, optimized_job
|
| 422 |
+
|
| 423 |
def create_consistency_hash(resume_text, job_description, prompt_type):
|
| 424 |
"""Create a hash for identical inputs to ensure consistent outputs"""
|
| 425 |
content = f"{resume_text[:1000]}{job_description[:1000]}{prompt_type}"
|
| 426 |
return hashlib.md5(content.encode()).hexdigest()
|
| 427 |
|
| 428 |
+
def get_available_model():
|
| 429 |
+
"""Get the first available model from the fallback chain"""
|
| 430 |
+
for model in MODEL_FALLBACK_CHAIN:
|
| 431 |
+
try:
|
| 432 |
+
test_model = genai.GenerativeModel(
|
| 433 |
+
model,
|
| 434 |
+
safety_settings={
|
| 435 |
+
genai.types.HarmCategory.HARM_CATEGORY_HARASSMENT: genai.types.HarmBlockThreshold.BLOCK_NONE,
|
| 436 |
+
genai.types.HarmCategory.HARM_CATEGORY_HATE_SPEECH: genai.types.HarmBlockThreshold.BLOCK_NONE,
|
| 437 |
+
genai.types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: genai.types.HarmBlockThreshold.BLOCK_NONE,
|
| 438 |
+
genai.types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: genai.types.HarmBlockThreshold.BLOCK_NONE,
|
| 439 |
+
}
|
| 440 |
+
)
|
| 441 |
+
# Test with a simple prompt
|
| 442 |
+
test_response = test_model.generate_content(
|
| 443 |
+
"Say 'OK'",
|
| 444 |
+
generation_config=genai.types.GenerationConfig(
|
| 445 |
+
temperature=0.1,
|
| 446 |
+
max_output_tokens=10
|
| 447 |
+
)
|
| 448 |
+
)
|
| 449 |
+
if test_response.text:
|
| 450 |
+
return model
|
| 451 |
+
except Exception:
|
| 452 |
+
continue
|
| 453 |
+
|
| 454 |
+
raise Exception("No available Gemini models found")
|
| 455 |
+
|
| 456 |
+
@rate_limit(min_interval=2)
|
| 457 |
def get_consistent_gemini_response(model_id, prompt, pdf_content, input_text, consistency_hash):
|
| 458 |
+
"""Enhanced response generation with robust error handling"""
|
| 459 |
try:
|
| 460 |
+
model = genai.GenerativeModel(
|
| 461 |
+
model_id,
|
| 462 |
+
safety_settings={
|
| 463 |
+
genai.types.HarmCategory.HARM_CATEGORY_HARASSMENT: genai.types.HarmBlockThreshold.BLOCK_NONE,
|
| 464 |
+
genai.types.HarmCategory.HARM_CATEGORY_HATE_SPEECH: genai.types.HarmBlockThreshold.BLOCK_NONE,
|
| 465 |
+
genai.types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: genai.types.HarmBlockThreshold.BLOCK_NONE,
|
| 466 |
+
genai.types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: genai.types.HarmBlockThreshold.BLOCK_NONE,
|
| 467 |
+
}
|
| 468 |
+
)
|
| 469 |
|
| 470 |
# Add consistency instruction to prompt
|
| 471 |
enhanced_prompt = f"""
|
|
|
|
| 474 |
**CONSISTENCY PROTOCOL ACTIVE:**
|
| 475 |
Session ID: {consistency_hash}
|
| 476 |
Evaluation Date: {datetime.now().strftime('%Y-%m-%d')}
|
|
|
|
| 477 |
Apply identical methodology and scoring for consistent results.
|
| 478 |
Use deterministic analysis patterns and standardized language.
|
| 479 |
+
|
| 480 |
+
**RESUME CONTENT:**
|
| 481 |
+
{pdf_content[:3000]}
|
| 482 |
+
|
| 483 |
+
**JOB DESCRIPTION:**
|
| 484 |
+
{input_text[:2000]}
|
| 485 |
"""
|
| 486 |
|
| 487 |
response = model.generate_content(
|
| 488 |
+
enhanced_prompt,
|
| 489 |
generation_config=genai.types.GenerationConfig(**GENERATION_CONFIG)
|
| 490 |
)
|
| 491 |
+
|
| 492 |
+
# Enhanced error checking
|
| 493 |
+
if hasattr(response, 'candidates') and response.candidates:
|
| 494 |
+
candidate = response.candidates[0]
|
| 495 |
+
|
| 496 |
+
# Check finish reason
|
| 497 |
+
if hasattr(candidate, 'finish_reason'):
|
| 498 |
+
if candidate.finish_reason == 1: # STOP - Normal completion
|
| 499 |
+
return response.text if hasattr(response, 'text') and response.text else "Analysis completed but no content returned."
|
| 500 |
+
elif candidate.finish_reason == 2: # MAX_TOKENS
|
| 501 |
+
return "β οΈ Analysis truncated due to length. Please try with a shorter resume or job description."
|
| 502 |
+
elif candidate.finish_reason == 3: # SAFETY
|
| 503 |
+
return "β οΈ Content filtered for safety. Please review your input for any potentially problematic content."
|
| 504 |
+
elif candidate.finish_reason == 4: # RECITATION
|
| 505 |
+
return "β οΈ Content blocked due to recitation concerns. Please try rephrasing your input."
|
| 506 |
+
else:
|
| 507 |
+
return f"β οΈ Generation stopped with reason: {candidate.finish_reason}"
|
| 508 |
+
|
| 509 |
+
# Try to get text anyway
|
| 510 |
+
try:
|
| 511 |
+
return response.text if response.text else "No analysis content generated."
|
| 512 |
+
except:
|
| 513 |
+
return "Analysis completed but content could not be retrieved."
|
| 514 |
+
|
| 515 |
+
return "No response candidates generated. Please try again."
|
| 516 |
|
| 517 |
except Exception as e:
|
| 518 |
st.error(f"β οΈ Analysis Error: {str(e)}")
|
| 519 |
+
|
| 520 |
+
# Fallback with simpler model configuration
|
| 521 |
+
try:
|
| 522 |
+
simple_model = genai.GenerativeModel("gemini-pro")
|
| 523 |
+
simple_prompt = f"Analyze this resume against the job description:\n\nResume: {pdf_content[:1000]}\n\nJob: {input_text[:1000]}"
|
| 524 |
+
|
| 525 |
+
fallback_response = simple_model.generate_content(simple_prompt)
|
| 526 |
+
return f"β οΈ Using fallback analysis:\n\n{fallback_response.text}"
|
| 527 |
+
except:
|
| 528 |
+
return "Unable to complete analysis. Please check your API key, reduce content length, and try again."
|
| 529 |
+
|
| 530 |
+
def clean_extracted_text(text):
|
| 531 |
+
"""Clean and format extracted text"""
|
| 532 |
+
# Remove excessive whitespace
|
| 533 |
+
text = re.sub(r'\n\s*\n\s*\n', '\n\n', text)
|
| 534 |
+
text = re.sub(r'[ \t]+', ' ', text)
|
| 535 |
+
|
| 536 |
+
# Fix common extraction issues
|
| 537 |
+
text = re.sub(r'([a-z])([A-Z])', r'\1 \2', text) # Add space before capitals
|
| 538 |
+
text = re.sub(r'(\w)([β’Β·βͺβ«])', r'\1 \2', text) # Space before bullets
|
| 539 |
+
text = re.sub(r'([β’Β·βͺβ«])(\w)', r'\1 \2', text) # Space after bullets
|
| 540 |
+
|
| 541 |
+
# Remove page markers
|
| 542 |
+
text = re.sub(r'--- Page \d+ ---', '', text)
|
| 543 |
+
|
| 544 |
+
# Normalize line endings
|
| 545 |
+
text = text.replace('\r\n', '\n').replace('\r', '\n')
|
| 546 |
+
|
| 547 |
+
# Remove empty lines at start and end
|
| 548 |
+
text = text.strip()
|
| 549 |
+
|
| 550 |
+
return text
|
| 551 |
|
| 552 |
+
def enhanced_pdf_processing(pdf_docs):
|
| 553 |
+
"""Enhanced PDF processing with better text extraction and formatting"""
|
| 554 |
text = ""
|
| 555 |
+
|
| 556 |
+
for doc in pdf_docs:
|
| 557 |
+
try:
|
| 558 |
if doc.name.endswith(".pdf"):
|
| 559 |
+
# Save uploaded file temporarily
|
| 560 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
|
| 561 |
+
tmp_file.write(doc.getvalue())
|
| 562 |
+
tmp_path = tmp_file.name
|
| 563 |
+
|
| 564 |
try:
|
| 565 |
+
# Try multiple extraction methods
|
| 566 |
+
pdf_reader = PdfReader(tmp_path)
|
| 567 |
+
extracted_text = ""
|
| 568 |
+
|
| 569 |
+
for page_num, page in enumerate(pdf_reader.pages):
|
| 570 |
+
page_text = page.extract_text()
|
| 571 |
+
|
| 572 |
+
# Clean up common PDF extraction issues
|
| 573 |
+
page_text = re.sub(r'\s+', ' ', page_text) # Normalize whitespace
|
| 574 |
+
page_text = re.sub(r'([a-z])([A-Z])', r'\1 \2', page_text) # Add spaces between words
|
| 575 |
+
|
| 576 |
+
extracted_text += f"\n--- Page {page_num + 1} ---\n{page_text}\n"
|
| 577 |
+
|
| 578 |
+
# If extraction is poor, try alternative method
|
| 579 |
+
if len(extracted_text.strip()) < 100 and HAS_PDFPLUMBER:
|
| 580 |
+
try:
|
| 581 |
+
with pdfplumber.open(tmp_path) as pdf:
|
| 582 |
+
for page in pdf.pages:
|
| 583 |
+
page_text = page.extract_text()
|
| 584 |
+
if page_text:
|
| 585 |
+
extracted_text += page_text + "\n"
|
| 586 |
+
except Exception:
|
| 587 |
+
pass
|
| 588 |
+
|
| 589 |
+
text += extracted_text
|
| 590 |
+
|
| 591 |
+
finally:
|
| 592 |
+
# Clean up temporary file
|
| 593 |
+
os.unlink(tmp_path)
|
| 594 |
+
|
| 595 |
+
elif doc.name.endswith(".docx") and HAS_DOCX:
|
| 596 |
+
try:
|
| 597 |
+
# Save uploaded file temporarily
|
| 598 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp_file:
|
| 599 |
+
tmp_file.write(doc.getvalue())
|
| 600 |
+
tmp_path = tmp_file.name
|
| 601 |
+
|
| 602 |
+
try:
|
| 603 |
+
doc_reader = docx.Document(tmp_path)
|
| 604 |
+
|
| 605 |
+
# Extract paragraphs
|
| 606 |
+
for para in doc_reader.paragraphs:
|
| 607 |
+
if para.text.strip():
|
| 608 |
+
text += para.text + "\n"
|
| 609 |
+
|
| 610 |
+
# Extract tables
|
| 611 |
+
for table in doc_reader.tables:
|
| 612 |
+
for row in table.rows:
|
| 613 |
+
row_text = " | ".join([cell.text.strip() for cell in row.cells])
|
| 614 |
+
if row_text.strip():
|
| 615 |
+
text += row_text + "\n"
|
| 616 |
+
|
| 617 |
+
finally:
|
| 618 |
+
os.unlink(tmp_path)
|
| 619 |
+
|
| 620 |
+
except Exception as e:
|
| 621 |
+
st.error(f"π Error processing DOCX {doc.name}: {str(e)}")
|
| 622 |
+
|
| 623 |
+
except Exception as e:
|
| 624 |
+
st.error(f"π Error processing {doc.name}: {str(e)}")
|
| 625 |
+
continue
|
| 626 |
|
| 627 |
+
# Clean and format the extracted text
|
| 628 |
+
text = clean_extracted_text(text)
|
| 629 |
return text
|
| 630 |
|
| 631 |
+
def validate_resume_content(text):
|
| 632 |
+
"""Validate that the extracted text looks like a resume"""
|
| 633 |
+
text_lower = text.lower()
|
| 634 |
+
|
| 635 |
+
# Check for common resume indicators
|
| 636 |
+
resume_indicators = [
|
| 637 |
+
'experience', 'education', 'skills', 'work', 'employment',
|
| 638 |
+
'university', 'college', 'degree', 'certification', 'project',
|
| 639 |
+
'email', 'phone', 'address', 'linkedin'
|
| 640 |
+
]
|
| 641 |
+
|
| 642 |
+
found_indicators = sum(1 for indicator in resume_indicators if indicator in text_lower)
|
| 643 |
+
|
| 644 |
+
if found_indicators < 3:
|
| 645 |
+
st.warning("β οΈ The uploaded file may not be a resume. Please verify the content.")
|
| 646 |
+
return False
|
| 647 |
+
|
| 648 |
+
if len(text.strip()) < 200:
|
| 649 |
+
st.warning("β οΈ The extracted text seems too short. Please check your file.")
|
| 650 |
+
return False
|
| 651 |
+
|
| 652 |
+
return True
|
| 653 |
+
|
| 654 |
+
def validate_configuration():
|
| 655 |
+
"""Validate system configuration"""
|
| 656 |
+
issues = []
|
| 657 |
+
|
| 658 |
+
# Check API key
|
| 659 |
+
if not os.getenv("GOOGLE_API_KEY") and not st.session_state.get("api_key"):
|
| 660 |
+
issues.append("β Google API Key not configured")
|
| 661 |
+
|
| 662 |
+
# Check optional packages
|
| 663 |
+
if not HAS_DOCX:
|
| 664 |
+
issues.append("β οΈ Optional: Install python-docx for better DOCX support (pip install python-docx)")
|
| 665 |
+
|
| 666 |
+
if not HAS_PDFPLUMBER:
|
| 667 |
+
issues.append("β οΈ Optional: Install pdfplumber for better PDF extraction (pip install pdfplumber)")
|
| 668 |
+
|
| 669 |
+
if not HAS_TIKTOKEN:
|
| 670 |
+
issues.append("β οΈ Optional: Install tiktoken for better token estimation (pip install tiktoken)")
|
| 671 |
+
|
| 672 |
+
return issues
|
| 673 |
+
|
| 674 |
+
@st.cache_data
|
| 675 |
+
def load_system_status():
|
| 676 |
+
"""Load and cache system status"""
|
| 677 |
+
issues = validate_configuration()
|
| 678 |
+
return issues
|
| 679 |
+
|
| 680 |
+
def perform_enhanced_analysis(resume_text, job_description, analysis_type, custom_query=None):
|
| 681 |
+
"""Main analysis function with all improvements"""
|
| 682 |
+
|
| 683 |
+
# Initialize cache
|
| 684 |
+
init_cache()
|
| 685 |
+
|
| 686 |
+
# Optimize content length
|
| 687 |
+
optimized_resume, optimized_job = optimize_content_length(resume_text, job_description)
|
| 688 |
+
|
| 689 |
+
# Create consistency hash
|
| 690 |
+
consistency_hash = create_consistency_hash(optimized_resume, optimized_job, analysis_type)
|
| 691 |
+
|
| 692 |
+
# Try to get from cache first
|
| 693 |
+
model_id = get_available_model()
|
| 694 |
+
cached_response = get_cached_response(consistency_hash, model_id)
|
| 695 |
+
|
| 696 |
+
if cached_response:
|
| 697 |
+
st.success("β‘ Retrieved from cache for consistency")
|
| 698 |
+
return cached_response, consistency_hash
|
| 699 |
+
|
| 700 |
+
# Select prompt
|
| 701 |
+
prompt_map = {
|
| 702 |
+
"evaluate": "evaluate_resume",
|
| 703 |
+
"improve": "improve_skills",
|
| 704 |
+
"keywords": "missing_keywords",
|
| 705 |
+
"match": "percentage_match",
|
| 706 |
+
"executive": "executive_assessment",
|
| 707 |
+
"transition": "career_transition",
|
| 708 |
+
"custom": "answer_query"
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
base_prompt = SPECIALIZED_PROMPTS[prompt_map.get(analysis_type, "evaluate_resume")]
|
| 712 |
+
|
| 713 |
+
if analysis_type == "custom" and custom_query:
|
| 714 |
+
base_prompt = f"{base_prompt}\n\nSPECIFIC QUERY: {custom_query}"
|
| 715 |
+
|
| 716 |
+
# Generate response
|
| 717 |
+
response = get_consistent_gemini_response(
|
| 718 |
+
model_id, base_prompt, optimized_resume, optimized_job, consistency_hash
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
# Cache the response
|
| 722 |
+
if response and not response.startswith("β οΈ"):
|
| 723 |
+
cache_response(consistency_hash, response, model_id)
|
| 724 |
+
|
| 725 |
+
return response, consistency_hash
|
| 726 |
+
|
| 727 |
# Streamlit App Configuration
|
| 728 |
st.set_page_config(
|
| 729 |
page_title="Ultimate Smart ATS System 2025",
|
|
|
|
| 769 |
display: inline-block;
|
| 770 |
margin: 0.5rem 0;
|
| 771 |
}
|
| 772 |
+
.stButton > button {
|
| 773 |
+
height: 3rem;
|
| 774 |
+
font-weight: 600;
|
| 775 |
+
}
|
| 776 |
</style>
|
| 777 |
""", unsafe_allow_html=True)
|
| 778 |
|
|
|
|
| 785 |
</div>
|
| 786 |
""", unsafe_allow_html=True)
|
| 787 |
|
| 788 |
+
# Check system status
|
| 789 |
+
config_issues = load_system_status()
|
| 790 |
+
|
| 791 |
+
if config_issues:
|
| 792 |
+
with st.expander("β οΈ System Status", expanded=any("β" in issue for issue in config_issues)):
|
| 793 |
+
for issue in config_issues:
|
| 794 |
+
if "β" in issue:
|
| 795 |
+
st.error(issue)
|
| 796 |
+
else:
|
| 797 |
+
st.info(issue)
|
| 798 |
+
|
| 799 |
# Sidebar Configuration
|
| 800 |
with st.sidebar:
|
| 801 |
st.markdown("### π Configuration")
|
| 802 |
st.markdown("[Get your Google API Key](https://aistudio.google.com/app/apikey)")
|
| 803 |
|
| 804 |
api_key = st.text_input("π Google API Key", type="password", help="Your Gemini API key for AI analysis")
|
| 805 |
+
st.session_state["api_key"] = api_key
|
| 806 |
|
| 807 |
+
if api_key:
|
| 808 |
+
try:
|
| 809 |
+
genai.configure(api_key=api_key)
|
| 810 |
+
model_id = get_available_model()
|
| 811 |
+
st.success(f"β
Connected to {model_id}")
|
| 812 |
+
except Exception as e:
|
| 813 |
+
st.error(f"β API Key Error: {str(e)}")
|
| 814 |
|
| 815 |
st.markdown("### π Document Upload")
|
| 816 |
uploaded_files = st.file_uploader(
|
|
|
|
| 829 |
πΉ **Global Domain Support**: Works across all industries
|
| 830 |
πΉ **Advanced ATS Optimization**: 85% better callback rates
|
| 831 |
πΉ **Real-time Market Insights**: June 2025 standards
|
| 832 |
+
πΉ **Smart Caching**: Instant results for repeated analyses
|
| 833 |
""")
|
| 834 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 835 |
# Main Interface
|
| 836 |
st.markdown("### π Job Description Input")
|
| 837 |
input_text = st.text_area(
|
|
|
|
| 881 |
else:
|
| 882 |
# Process analysis
|
| 883 |
with st.spinner("π Analyzing with advanced AI algorithms..."):
|
| 884 |
+
pdf_content = enhanced_pdf_processing(uploaded_files)
|
| 885 |
|
| 886 |
+
# Validate content
|
| 887 |
+
if not validate_resume_content(pdf_content):
|
| 888 |
+
st.warning("β οΈ Please verify that your uploaded file is a valid resume.")
|
| 889 |
|
| 890 |
+
# Determine analysis type
|
| 891 |
if evaluate_btn:
|
| 892 |
+
analysis_type = "evaluate"
|
| 893 |
elif improve_btn:
|
| 894 |
+
analysis_type = "improve"
|
| 895 |
elif keywords_btn:
|
| 896 |
+
analysis_type = "keywords"
|
| 897 |
elif match_btn:
|
| 898 |
+
analysis_type = "match"
|
| 899 |
elif executive_btn:
|
| 900 |
+
analysis_type = "executive"
|
| 901 |
elif transition_btn:
|
| 902 |
+
analysis_type = "transition"
|
| 903 |
elif query_btn:
|
| 904 |
+
analysis_type = "custom"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 905 |
|
| 906 |
+
try:
|
| 907 |
+
# Perform analysis
|
| 908 |
+
response, consistency_hash = perform_enhanced_analysis(
|
| 909 |
+
pdf_content, input_text, analysis_type, custom_query
|
| 910 |
+
)
|
| 911 |
+
|
| 912 |
+
# Display results
|
| 913 |
+
st.markdown("## π Analysis Results")
|
| 914 |
+
|
| 915 |
+
# Show metadata
|
| 916 |
+
col1, col2 = st.columns(2)
|
| 917 |
+
with col1:
|
| 918 |
+
st.markdown(f"**Consistency ID:** `{consistency_hash[:8]}`")
|
| 919 |
+
with col2:
|
| 920 |
+
st.markdown(f"**Analysis Type:** {analysis_type.title()}")
|
| 921 |
+
|
| 922 |
+
st.markdown("---")
|
| 923 |
+
st.markdown(response)
|
| 924 |
+
|
| 925 |
+
# Additional insights
|
| 926 |
+
st.markdown("### π‘ Pro Tips")
|
| 927 |
+
st.info("""
|
| 928 |
+
πΉ **Consistency**: Running the same analysis will yield identical results
|
| 929 |
+
πΉ **Optimization**: Use keyword suggestions to improve ATS compatibility
|
| 930 |
+
πΉ **Multi-Domain**: This system works across all industries and roles
|
| 931 |
+
πΉ **Latest Standards**: Analysis based on June 2025 best practices
|
| 932 |
+
πΉ **Caching**: Repeated analyses are retrieved instantly from cache
|
| 933 |
+
""")
|
| 934 |
+
|
| 935 |
+
# Show content optimization info
|
| 936 |
+
if st.checkbox("π Show Content Optimization Details"):
|
| 937 |
+
optimized_resume, optimized_job = optimize_content_length(pdf_content, input_text)
|
| 938 |
+
|
| 939 |
+
col1, col2 = st.columns(2)
|
| 940 |
+
with col1:
|
| 941 |
+
st.metric("Resume Tokens", estimate_tokens(optimized_resume))
|
| 942 |
+
st.metric("Original Resume Length", len(pdf_content))
|
| 943 |
+
with col2:
|
| 944 |
+
st.metric("Job Description Tokens", estimate_tokens(optimized_job))
|
| 945 |
+
st.metric("Original Job Length", len(input_text))
|
| 946 |
+
|
| 947 |
+
except Exception as e:
|
| 948 |
+
st.error(f"Analysis failed: {str(e)}")
|
| 949 |
+
st.info("Please try again with a shorter document or check your API key.")
|
| 950 |
|
| 951 |
# Footer
|
| 952 |
st.markdown("---")
|
|
|
|
| 954 |
<div style="text-align: center; color: #666;">
|
| 955 |
<p>π Ultimate Smart ATS System 2025 | Powered by Advanced AI | Consistent β’ Reliable β’ Universal</p>
|
| 956 |
<p>Built with cutting-edge strategies for maximum ATS compatibility and career success</p>
|
| 957 |
+
<small>Version 3.0 - Enhanced with Error Handling, Caching, and Content Optimization</small>
|
| 958 |
</div>
|
| 959 |
+
""", unsafe_allow_html=True)
|
| 960 |
+
|
| 961 |
+
# Initialize cache on startup
|
| 962 |
+
if __name__ == "__main__":
|
| 963 |
+
init_cache()
|