parthib07 commited on
Commit
d7d3dff
·
verified ·
1 Parent(s): 3b4d5ba

Upload 531 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +28 -0
  2. LICENSE +21 -0
  3. app.py +0 -0
  4. assets/124852522.jpeg +0 -0
  5. assets/logo.jpg +0 -0
  6. config/__pycache__/courses.cpython-311.pyc +0 -0
  7. config/__pycache__/database.cpython-311.pyc +0 -0
  8. config/__pycache__/job_roles.cpython-311.pyc +0 -0
  9. config/courses.py +181 -0
  10. config/database.py +532 -0
  11. config/job_roles.py +167 -0
  12. dashboard/__init__.py +1 -0
  13. dashboard/__pycache__/__init__.cpython-311.pyc +0 -0
  14. dashboard/__pycache__/dashboard.cpython-311.pyc +0 -0
  15. dashboard/components.py +175 -0
  16. dashboard/dashboard.py +1155 -0
  17. feedback/__pycache__/feedback.cpython-311.pyc +0 -0
  18. feedback/feedback.db +0 -0
  19. feedback/feedback.py +295 -0
  20. feedback/schema.sql +10 -0
  21. jobs/__pycache__/companies.cpython-311.pyc +0 -0
  22. jobs/__pycache__/job_portals.cpython-311.pyc +0 -0
  23. jobs/__pycache__/job_search.cpython-311.pyc +0 -0
  24. jobs/__pycache__/linkedin_scraper.cpython-311.pyc +0 -0
  25. jobs/__pycache__/suggestions.cpython-311.pyc +0 -0
  26. jobs/__pycache__/webdriver_utils.cpython-311.pyc +0 -0
  27. jobs/companies.py +187 -0
  28. jobs/job_portals.py +288 -0
  29. jobs/job_search.py +510 -0
  30. jobs/linkedin_scraper.py +662 -0
  31. jobs/suggestions.py +225 -0
  32. jobs/webdriver_utils.py +219 -0
  33. packages.txt +9 -0
  34. poppler/poppler-24.08.0/Library/bin/Lerc.dll +3 -0
  35. poppler/poppler-24.08.0/Library/bin/cairo.dll +3 -0
  36. poppler/poppler-24.08.0/Library/bin/charset.dll +0 -0
  37. poppler/poppler-24.08.0/Library/bin/deflate.dll +3 -0
  38. poppler/poppler-24.08.0/Library/bin/expat.dll +3 -0
  39. poppler/poppler-24.08.0/Library/bin/fontconfig-1.dll +3 -0
  40. poppler/poppler-24.08.0/Library/bin/freetype.dll +3 -0
  41. poppler/poppler-24.08.0/Library/bin/iconv.dll +3 -0
  42. poppler/poppler-24.08.0/Library/bin/jpeg8.dll +3 -0
  43. poppler/poppler-24.08.0/Library/bin/lcms2.dll +3 -0
  44. poppler/poppler-24.08.0/Library/bin/libcrypto-3-x64.dll +3 -0
  45. poppler/poppler-24.08.0/Library/bin/libcurl.dll +3 -0
  46. poppler/poppler-24.08.0/Library/bin/libexpat.dll +3 -0
  47. poppler/poppler-24.08.0/Library/bin/liblzma.dll +3 -0
  48. poppler/poppler-24.08.0/Library/bin/libpng16.dll +3 -0
  49. poppler/poppler-24.08.0/Library/bin/libssh2.dll +3 -0
  50. poppler/poppler-24.08.0/Library/bin/libtiff.dll +3 -0
.gitattributes CHANGED
@@ -33,3 +33,31 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ poppler/poppler-24.08.0/Library/bin/cairo.dll filter=lfs diff=lfs merge=lfs -text
37
+ poppler/poppler-24.08.0/Library/bin/deflate.dll filter=lfs diff=lfs merge=lfs -text
38
+ poppler/poppler-24.08.0/Library/bin/expat.dll filter=lfs diff=lfs merge=lfs -text
39
+ poppler/poppler-24.08.0/Library/bin/fontconfig-1.dll filter=lfs diff=lfs merge=lfs -text
40
+ poppler/poppler-24.08.0/Library/bin/freetype.dll filter=lfs diff=lfs merge=lfs -text
41
+ poppler/poppler-24.08.0/Library/bin/iconv.dll filter=lfs diff=lfs merge=lfs -text
42
+ poppler/poppler-24.08.0/Library/bin/jpeg8.dll filter=lfs diff=lfs merge=lfs -text
43
+ poppler/poppler-24.08.0/Library/bin/lcms2.dll filter=lfs diff=lfs merge=lfs -text
44
+ poppler/poppler-24.08.0/Library/bin/Lerc.dll filter=lfs diff=lfs merge=lfs -text
45
+ poppler/poppler-24.08.0/Library/bin/libcrypto-3-x64.dll filter=lfs diff=lfs merge=lfs -text
46
+ poppler/poppler-24.08.0/Library/bin/libcurl.dll filter=lfs diff=lfs merge=lfs -text
47
+ poppler/poppler-24.08.0/Library/bin/libexpat.dll filter=lfs diff=lfs merge=lfs -text
48
+ poppler/poppler-24.08.0/Library/bin/liblzma.dll filter=lfs diff=lfs merge=lfs -text
49
+ poppler/poppler-24.08.0/Library/bin/libpng16.dll filter=lfs diff=lfs merge=lfs -text
50
+ poppler/poppler-24.08.0/Library/bin/libssh2.dll filter=lfs diff=lfs merge=lfs -text
51
+ poppler/poppler-24.08.0/Library/bin/libtiff.dll filter=lfs diff=lfs merge=lfs -text
52
+ poppler/poppler-24.08.0/Library/bin/libzstd.dll filter=lfs diff=lfs merge=lfs -text
53
+ poppler/poppler-24.08.0/Library/bin/openjp2.dll filter=lfs diff=lfs merge=lfs -text
54
+ poppler/poppler-24.08.0/Library/bin/pdftocairo.exe filter=lfs diff=lfs merge=lfs -text
55
+ poppler/poppler-24.08.0/Library/bin/pdftohtml.exe filter=lfs diff=lfs merge=lfs -text
56
+ poppler/poppler-24.08.0/Library/bin/pixman-1-0.dll filter=lfs diff=lfs merge=lfs -text
57
+ poppler/poppler-24.08.0/Library/bin/poppler-cpp.dll filter=lfs diff=lfs merge=lfs -text
58
+ poppler/poppler-24.08.0/Library/bin/poppler-glib.dll filter=lfs diff=lfs merge=lfs -text
59
+ poppler/poppler-24.08.0/Library/bin/poppler.dll filter=lfs diff=lfs merge=lfs -text
60
+ poppler/poppler-24.08.0/Library/bin/tiff.dll filter=lfs diff=lfs merge=lfs -text
61
+ poppler/poppler-24.08.0/Library/bin/zstd.dll filter=lfs diff=lfs merge=lfs -text
62
+ poppler/poppler-24.08.0/Library/bin/zstd.exe filter=lfs diff=lfs merge=lfs -text
63
+ resume_data.db filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Parthib karak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
app.py ADDED
The diff for this file is too large to render. See raw diff
 
assets/124852522.jpeg ADDED
assets/logo.jpg ADDED
config/__pycache__/courses.cpython-311.pyc ADDED
Binary file (12.1 kB). View file
 
config/__pycache__/database.cpython-311.pyc ADDED
Binary file (24.1 kB). View file
 
config/__pycache__/job_roles.cpython-311.pyc ADDED
Binary file (6.23 kB). View file
 
config/courses.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Course recommendations organized by job categories
2
+ COURSES_BY_CATEGORY = {
3
+ "Software Development and Engineering": {
4
+ "Frontend Developer": [
5
+ ["Frontend Web Development Bootcamp [Free]", "https://youtu.be/zJSY8tbf_ys"],
6
+ ["React Complete Course 2024 [Free]", "https://youtu.be/bMknfKXIFA8"],
7
+ ["The Web Developer Bootcamp", "https://www.udemy.com/course/the-web-developer-bootcamp/"],
8
+ ["Frontend Masters Complete Path", "https://frontendmasters.com/learn/beginner/"],
9
+ ["Advanced CSS and Sass", "https://www.udemy.com/course/advanced-css-and-sass/"]
10
+ ],
11
+ "Backend Developer": [
12
+ ["Node.js Tutorial for Beginners [Free]", "https://youtu.be/TlB_eWDSMt4"],
13
+ ["Python Django Full Course [Free]", "https://youtu.be/o0XbHvKxw7Y"],
14
+ ["Complete Python Developer in 2024", "https://www.udemy.com/course/complete-python-developer-zero-to-mastery/"],
15
+ ["Java Spring Boot Complete Course", "https://www.udemy.com/course/spring-hibernate-tutorial/"],
16
+ ["The Complete Node.js Developer Course", "https://www.udemy.com/course/the-complete-nodejs-developer-course-2/"]
17
+ ],
18
+ "Full Stack Developer": [
19
+ ["Full Stack Development Course [Free]", "https://youtu.be/nu_pCVPKzTk"],
20
+ ["The Complete 2024 Web Development Bootcamp", "https://www.udemy.com/course/the-complete-web-development-bootcamp/"],
21
+ ["Full Stack Engineer Career Path", "https://www.codecademy.com/learn/paths/full-stack-engineer-career-path"],
22
+ ["MERN Stack Front To Back", "https://www.udemy.com/course/mern-stack-front-to-back/"],
23
+ ["Full Stack Development with React & Node.js", "https://www.udemy.com/course/full-stack-react-node/"]
24
+ ],
25
+ "Mobile App Developer": [
26
+ ["Flutter & Dart Complete Course [Free]", "https://youtu.be/VPvVD8t02U8"],
27
+ ["iOS & Swift Complete iOS App Development", "https://www.udemy.com/course/ios-13-app-development-bootcamp/"],
28
+ ["Android Development with Kotlin", "https://www.udacity.com/course/android-kotlin-developer-nanodegree--nd940"],
29
+ ["React Native - The Practical Guide", "https://www.udemy.com/course/react-native-the-practical-guide/"],
30
+ ["Flutter & Firebase: Build a Complete App", "https://www.udemy.com/course/flutter-firebase-tutorial-build-5-social-media-apps/"]
31
+ ],
32
+ "Game Developer": [
33
+ ["Unity Game Development [Free]", "https://youtu.be/gB1F9G0JXOo"],
34
+ ["Unreal Engine 5 C++ Developer", "https://www.udemy.com/course/unrealcourse/"],
35
+ ["Complete C# Unity Game Developer 2D", "https://www.udemy.com/course/unitycourse/"],
36
+ ["Unity Certified Programmer Exam Preparation", "https://www.udemy.com/course/unity-advance-essentials-n/"],
37
+ ["Game Design and Development Specialization", "https://www.coursera.org/specializations/game-design-and-development"]
38
+ ]
39
+ },
40
+ "Data Science and Analytics": {
41
+ "Data Scientist": [
42
+ ["Data Science Full Course [Free]", "https://youtu.be/_V8eKsto3Ug"],
43
+ ["IBM Data Science Professional Certificate", "https://www.coursera.org/professional-certificates/ibm-data-science"],
44
+ ["Data Science Career Path", "https://www.codecademy.com/learn/paths/data-science"],
45
+ ["Applied Data Science with Python", "https://www.coursera.org/specializations/data-science-python"],
46
+ ["Complete Data Science Bootcamp", "https://www.udemy.com/course/the-data-science-course-complete-data-science-bootcamp/"]
47
+ ],
48
+ "Data Analyst": [
49
+ ["Data Analytics Full Course [Free]", "https://youtu.be/ua-CiDNNj30"],
50
+ ["Google Data Analytics Professional Certificate", "https://www.coursera.org/professional-certificates/google-data-analytics"],
51
+ ["Data Analyst with Python", "https://www.datacamp.com/tracks/data-analyst-with-python"],
52
+ ["Business Analytics Specialization", "https://www.coursera.org/specializations/business-analytics"],
53
+ ["Data Analysis with Pandas and Python", "https://www.udemy.com/course/data-analysis-with-pandas/"]
54
+ ],
55
+ "Machine Learning Engineer": [
56
+ ["Machine Learning Full Course [Free]", "https://youtu.be/jGwO_UgTS7I"],
57
+ ["Deep Learning Specialization", "https://www.coursera.org/specializations/deep-learning"],
58
+ ["Machine Learning Engineer Nanodegree", "https://www.udacity.com/course/machine-learning-engineer-nanodegree--nd009t"],
59
+ ["TensorFlow Developer Certificate", "https://www.tensorflow.org/certificate"],
60
+ ["Complete Machine Learning & Data Science Bootcamp", "https://www.udemy.com/course/complete-machine-learning-and-data-science-zero-to-mastery/"]
61
+ ]
62
+ },
63
+ "Cloud Computing and DevOps": {
64
+ "Cloud Architect": [
65
+ ["AWS Cloud Practitioner [Free]", "https://youtu.be/3hLmDS179YE"],
66
+ ["AWS Solutions Architect Professional", "https://www.udemy.com/course/aws-solutions-architect-professional/"],
67
+ ["Google Cloud Architect Professional Certificate", "https://www.coursera.org/professional-certificates/gcp-cloud-architect"],
68
+ ["Microsoft Azure Architect Technologies", "https://learn.microsoft.com/en-us/certifications/azure-solutions-architect/"],
69
+ ["Cloud Architecture with Google Cloud", "https://www.coursera.org/professional-certificates/gcp-cloud-architect"]
70
+ ],
71
+ "DevOps Engineer": [
72
+ ["DevOps Engineering Course [Free]", "https://youtu.be/j5Zsa_eOXeY"],
73
+ ["DevOps Engineer Masters Program", "https://www.simplilearn.com/cloud-computing/devops-engineer-masters-program-training"],
74
+ ["Docker and Kubernetes: The Complete Guide", "https://www.udemy.com/course/docker-and-kubernetes-the-complete-guide/"],
75
+ ["GitLab CI: The Complete Guide", "https://www.udemy.com/course/gitlab-ci-pipelines-ci-cd-and-devops-for-beginners/"],
76
+ ["Jenkins: The Complete Guide", "https://www.udemy.com/course/jenkins-from-zero-to-hero/"]
77
+ ],
78
+ "Site Reliability Engineer": [
79
+ ["SRE Course [Free]", "https://youtu.be/uTEL8Ff1Zvk"],
80
+ ["Site Reliability Engineering: Measuring and Managing Reliability", "https://www.coursera.org/learn/site-reliability-engineering-slos"],
81
+ ["Linux System Administration", "https://www.udemy.com/course/linux-administration-bootcamp/"],
82
+ ["Monitoring and Alerting with Prometheus", "https://www.udemy.com/course/monitoring-and-alerting-with-prometheus/"],
83
+ ["Advanced System Administration", "https://www.linkedin.com/learning/paths/advance-your-skills-as-a-linux-system-administrator"]
84
+ ]
85
+ },
86
+ "Cybersecurity": {
87
+ "Security Analyst": [
88
+ ["Cyber Security Full Course [Free]", "https://youtu.be/nzZkKoREEGo"],
89
+ ["CompTIA Security+ Certification", "https://www.comptia.org/certifications/security"],
90
+ ["Certified Information Systems Security Professional (CISSP)", "https://www.isc2.org/Certifications/CISSP"],
91
+ ["IBM Cybersecurity Analyst Professional Certificate", "https://www.coursera.org/professional-certificates/ibm-cybersecurity-analyst"],
92
+ ["The Complete Cyber Security Course", "https://www.udemy.com/course/the-complete-internet-security-privacy-course-volume-1/"]
93
+ ],
94
+ "Penetration Tester": [
95
+ ["Ethical Hacking Course [Free]", "https://youtu.be/3Kq1MIfTWCE"],
96
+ ["Certified Ethical Hacker (CEH)", "https://www.eccouncil.org/programs/certified-ethical-hacker-ceh/"],
97
+ ["Complete Ethical Hacking Bootcamp", "https://www.udemy.com/course/complete-ethical-hacking-bootcamp-zero-to-mastery/"],
98
+ ["Web Security & Bug Bounty", "https://www.udemy.com/course/web-security-bug-bounty-learn-penetration-testing/"],
99
+ ["Advanced Penetration Testing", "https://www.offensive-security.com/pwk-oscp/"]
100
+ ]
101
+ },
102
+ "UI/UX Design": {
103
+ "UI Designer": [
104
+ ["UI Design Course [Free]", "https://youtu.be/c9Wg6Cb_YlU"],
105
+ ["Google UX Design Professional Certificate", "https://www.coursera.org/professional-certificates/google-ux-design"],
106
+ ["UI Design Bootcamp", "https://www.udemy.com/course/ui-design-bootcamp/"],
107
+ ["Advanced UI Design Course", "https://www.udacity.com/course/ui-design--ud511"],
108
+ ["Design System Course", "https://www.designsystems.com/"]
109
+ ],
110
+ "UX Designer": [
111
+ ["UX Design Course [Free]", "https://youtu.be/uL2aArZGqzk"],
112
+ ["UX Design Professional Certificate", "https://www.coursera.org/professional-certificates/google-ux-design"],
113
+ ["User Experience Design Bootcamp", "https://www.udemy.com/course/user-experience-design-fundamentals/"],
114
+ ["UX Research & Strategy", "https://www.interaction-design.org/courses"],
115
+ ["Advanced UX Methods", "https://www.nngroup.com/courses/"]
116
+ ]
117
+ },
118
+ "Project Management": {
119
+ "Project Manager": [
120
+ ["Project Management Basics [Free]", "https://youtu.be/H0_yKBitO8M"],
121
+ ["PMP Certification Prep", "https://www.udemy.com/course/pmp-pmbok6-35-pdus/"],
122
+ ["Google Project Management Certificate", "https://www.coursera.org/professional-certificates/google-project-management"],
123
+ ["Agile with Atlassian Jira", "https://www.coursera.org/learn/agile-atlassian-jira"],
124
+ ["Scrum Master Certification", "https://www.scrum.org/professional-scrum-certifications"]
125
+ ],
126
+ "Product Manager": [
127
+ ["Product Management Course [Free]", "https://youtu.be/lYZYB9VWaeI"],
128
+ ["Product Management Certification", "https://www.udemy.com/course/become-a-product-manager-learn-the-skills-get-a-job/"],
129
+ ["Digital Product Management", "https://www.coursera.org/specializations/uva-darden-digital-product-management"],
130
+ ["Product Analytics", "https://www.udacity.com/course/product-manager-nanodegree--nd036"],
131
+ ["Agile Product Management", "https://www.scrum.org/professional-scrum-product-owner-certifications"]
132
+ ]
133
+ }
134
+ }
135
+
136
+ # Helper videos for resume and interview preparation
137
+ RESUME_VIDEOS = {
138
+ "Resume Writing": [
139
+ ["Resume Writing Masterclass [Free]", "https://youtu.be/Tt08KmFfIYQ"],
140
+ ["How to Write a Professional Resume in 2024", "https://youtu.be/y8YH0Qbu5h4"],
141
+ ["Resume Tips from a Hiring Manager", "https://youtu.be/u75hUSShvnc"],
142
+ ["ATS-Friendly Resume Guide", "https://youtu.be/BYUy1yvjHxE"]
143
+ ],
144
+ "Resume Design": [
145
+ ["Create a Modern Resume in Word", "https://youtu.be/3agP4x8LYFM"],
146
+ ["Professional Resume Design Tips", "https://youtu.be/KFaugkGVeNQ"],
147
+ ["Resume Templates and Formatting", "https://youtu.be/GyjzOKdaioU"]
148
+ ]
149
+ }
150
+
151
+ INTERVIEW_VIDEOS = {
152
+ "Technical Interviews": [
153
+ ["Coding Interview Preparation [Free]", "https://youtu.be/HG68Ymazo18"],
154
+ ["System Design Interview Guide", "https://youtu.be/BOvAAoxM4vg"],
155
+ ["Data Structures & Algorithms Interview", "https://youtu.be/KukmClH1KoA"]
156
+ ],
157
+ "Behavioral Interviews": [
158
+ ["STAR Method Explained", "https://youtu.be/7_aAicmPB3A"],
159
+ ["Common Behavioral Questions", "https://youtu.be/1mHjMNZZvFo"],
160
+ ["Interview Body Language Tips", "https://youtu.be/WfdtKbAJOmE"]
161
+ ],
162
+ "Interview Tips": [
163
+ ["Salary Negotiation Tips", "https://youtu.be/IBjM-F56qS0"],
164
+ ["Questions to Ask Interviewers", "https://youtu.be/4tYoVx0QoN0"],
165
+ ["Remote Interview Best Practices", "https://youtu.be/Ge0Udbws1kc"]
166
+ ]
167
+ }
168
+
169
+ def get_courses_for_role(role_name):
170
+ """Helper function to get courses for a specific role"""
171
+ for category, roles in COURSES_BY_CATEGORY.items():
172
+ if role_name in roles:
173
+ return roles[role_name]
174
+ return None
175
+
176
+ def get_category_for_role(role_name):
177
+ """Helper function to get the category for a specific role"""
178
+ for category, roles in COURSES_BY_CATEGORY.items():
179
+ if role_name in roles:
180
+ return category
181
+ return None
config/database.py ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ from datetime import datetime
3
+
4
+ def get_database_connection():
5
+ """Create and return a database connection"""
6
+ conn = sqlite3.connect('resume_data.db')
7
+ return conn
8
+
9
+ def init_database():
10
+ """Initialize database tables"""
11
+ conn = get_database_connection()
12
+ cursor = conn.cursor()
13
+
14
+ # Create resume_data table
15
+ cursor.execute('''
16
+ CREATE TABLE IF NOT EXISTS resume_data (
17
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
18
+ name TEXT NOT NULL,
19
+ email TEXT NOT NULL,
20
+ phone TEXT NOT NULL,
21
+ linkedin TEXT,
22
+ github TEXT,
23
+ portfolio TEXT,
24
+ summary TEXT,
25
+ target_role TEXT,
26
+ target_category TEXT,
27
+ education TEXT,
28
+ experience TEXT,
29
+ projects TEXT,
30
+ skills TEXT,
31
+ template TEXT,
32
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
33
+ )
34
+ ''')
35
+
36
+ # Create resume_skills table
37
+ cursor.execute('''
38
+ CREATE TABLE IF NOT EXISTS resume_skills (
39
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ resume_id INTEGER,
41
+ skill_name TEXT NOT NULL,
42
+ skill_category TEXT NOT NULL,
43
+ proficiency_score REAL,
44
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
45
+ FOREIGN KEY (resume_id) REFERENCES resume_data (id)
46
+ )
47
+ ''')
48
+
49
+ # Create resume_analysis table
50
+ cursor.execute('''
51
+ CREATE TABLE IF NOT EXISTS resume_analysis (
52
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
53
+ resume_id INTEGER,
54
+ ats_score REAL,
55
+ keyword_match_score REAL,
56
+ format_score REAL,
57
+ section_score REAL,
58
+ missing_skills TEXT,
59
+ recommendations TEXT,
60
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
61
+ FOREIGN KEY (resume_id) REFERENCES resume_data (id)
62
+ )
63
+ ''')
64
+
65
+ # Create admin_logs table
66
+ cursor.execute('''
67
+ CREATE TABLE IF NOT EXISTS admin_logs (
68
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
69
+ admin_email TEXT NOT NULL,
70
+ action TEXT NOT NULL,
71
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
72
+ )
73
+ ''')
74
+
75
+ # Create admin table
76
+ cursor.execute('''
77
+ CREATE TABLE IF NOT EXISTS admin (
78
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
79
+ email TEXT NOT NULL UNIQUE,
80
+ password TEXT NOT NULL,
81
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
82
+ )
83
+ ''')
84
+
85
+ conn.commit()
86
+ conn.close()
87
+
88
+ def save_resume_data(data):
89
+ """Save resume data to database"""
90
+ conn = get_database_connection()
91
+ cursor = conn.cursor()
92
+
93
+ try:
94
+ personal_info = data.get('personal_info', {})
95
+
96
+ cursor.execute('''
97
+ INSERT INTO resume_data (
98
+ name, email, phone, linkedin, github, portfolio,
99
+ summary, target_role, target_category, education,
100
+ experience, projects, skills, template
101
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
102
+ ''', (
103
+ personal_info.get('full_name', ''),
104
+ personal_info.get('email', ''),
105
+ personal_info.get('phone', ''),
106
+ personal_info.get('linkedin', ''),
107
+ personal_info.get('github', ''),
108
+ personal_info.get('portfolio', ''),
109
+ data.get('summary', ''),
110
+ data.get('target_role', ''),
111
+ data.get('target_category', ''),
112
+ str(data.get('education', [])),
113
+ str(data.get('experience', [])),
114
+ str(data.get('projects', [])),
115
+ str(data.get('skills', [])),
116
+ data.get('template', '')
117
+ ))
118
+
119
+ conn.commit()
120
+ return cursor.lastrowid
121
+ except Exception as e:
122
+ print(f"Error saving resume data: {str(e)}")
123
+ conn.rollback()
124
+ return None
125
+ finally:
126
+ conn.close()
127
+
128
+ def save_analysis_data(resume_id, analysis):
129
+ """Save resume analysis data"""
130
+ conn = get_database_connection()
131
+ cursor = conn.cursor()
132
+
133
+ try:
134
+ cursor.execute('''
135
+ INSERT INTO resume_analysis (
136
+ resume_id, ats_score, keyword_match_score,
137
+ format_score, section_score, missing_skills,
138
+ recommendations
139
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
140
+ ''', (
141
+ resume_id,
142
+ float(analysis.get('ats_score', 0)),
143
+ float(analysis.get('keyword_match_score', 0)),
144
+ float(analysis.get('format_score', 0)),
145
+ float(analysis.get('section_score', 0)),
146
+ analysis.get('missing_skills', ''),
147
+ analysis.get('recommendations', '')
148
+ ))
149
+
150
+ conn.commit()
151
+ except Exception as e:
152
+ print(f"Error saving analysis data: {str(e)}")
153
+ conn.rollback()
154
+ finally:
155
+ conn.close()
156
+
157
+ def get_resume_stats():
158
+ """Get statistics about resumes"""
159
+ conn = get_database_connection()
160
+ cursor = conn.cursor()
161
+
162
+ try:
163
+ # Get total resumes
164
+ cursor.execute('SELECT COUNT(*) FROM resume_data')
165
+ total_resumes = cursor.fetchone()[0]
166
+
167
+ # Get average ATS score
168
+ cursor.execute('SELECT AVG(ats_score) FROM resume_analysis')
169
+ avg_ats_score = cursor.fetchone()[0] or 0
170
+
171
+ # Get recent activity
172
+ cursor.execute('''
173
+ SELECT name, target_role, created_at
174
+ FROM resume_data
175
+ ORDER BY created_at DESC
176
+ LIMIT 5
177
+ ''')
178
+ recent_activity = cursor.fetchall()
179
+
180
+ return {
181
+ 'total_resumes': total_resumes,
182
+ 'avg_ats_score': round(avg_ats_score, 2),
183
+ 'recent_activity': recent_activity
184
+ }
185
+ except Exception as e:
186
+ print(f"Error getting resume stats: {str(e)}")
187
+ return None
188
+ finally:
189
+ conn.close()
190
+
191
+ def log_admin_action(admin_email, action):
192
+ """Log admin login/logout actions"""
193
+ conn = get_database_connection()
194
+ cursor = conn.cursor()
195
+
196
+ try:
197
+ cursor.execute('''
198
+ INSERT INTO admin_logs (admin_email, action)
199
+ VALUES (?, ?)
200
+ ''', (admin_email, action))
201
+ conn.commit()
202
+ except Exception as e:
203
+ print(f"Error logging admin action: {str(e)}")
204
+ finally:
205
+ conn.close()
206
+
207
+ def get_admin_logs():
208
+ """Get all admin login/logout logs"""
209
+ conn = get_database_connection()
210
+ cursor = conn.cursor()
211
+
212
+ try:
213
+ cursor.execute('''
214
+ SELECT admin_email, action, timestamp
215
+ FROM admin_logs
216
+ ORDER BY timestamp DESC
217
+ ''')
218
+ return cursor.fetchall()
219
+ except Exception as e:
220
+ print(f"Error getting admin logs: {str(e)}")
221
+ return []
222
+ finally:
223
+ conn.close()
224
+
225
+ def get_all_resume_data():
226
+ """Get all resume data for admin dashboard"""
227
+ conn = get_database_connection()
228
+ cursor = conn.cursor()
229
+
230
+ try:
231
+ # Get resume data joined with analysis data
232
+ cursor.execute('''
233
+ SELECT
234
+ r.id,
235
+ r.name,
236
+ r.email,
237
+ r.phone,
238
+ r.linkedin,
239
+ r.github,
240
+ r.portfolio,
241
+ r.target_role,
242
+ r.target_category,
243
+ r.created_at,
244
+ a.ats_score,
245
+ a.keyword_match_score,
246
+ a.format_score,
247
+ a.section_score
248
+ FROM resume_data r
249
+ LEFT JOIN resume_analysis a ON r.id = a.resume_id
250
+ ORDER BY r.created_at DESC
251
+ ''')
252
+ return cursor.fetchall()
253
+ except Exception as e:
254
+ print(f"Error getting resume data: {str(e)}")
255
+ return []
256
+ finally:
257
+ conn.close()
258
+
259
+ def verify_admin(email, password):
260
+ """Verify admin credentials"""
261
+ conn = get_database_connection()
262
+ cursor = conn.cursor()
263
+
264
+ try:
265
+ cursor.execute('SELECT * FROM admin WHERE email = ? AND password = ?', (email, password))
266
+ result = cursor.fetchone()
267
+ return bool(result)
268
+ except Exception as e:
269
+ print(f"Error verifying admin: {str(e)}")
270
+ return False
271
+ finally:
272
+ conn.close()
273
+
274
+ def add_admin(email, password):
275
+ """Add a new admin"""
276
+ conn = get_database_connection()
277
+ cursor = conn.cursor()
278
+
279
+ try:
280
+ cursor.execute('INSERT INTO admin (email, password) VALUES (?, ?)', (email, password))
281
+ conn.commit()
282
+ return True
283
+ except Exception as e:
284
+ print(f"Error adding admin: {str(e)}")
285
+ return False
286
+ finally:
287
+ conn.close()
288
+
289
+ def save_ai_analysis_data(resume_id, analysis_data):
290
+ """Save AI analysis data to the database"""
291
+ conn = get_database_connection()
292
+ cursor = conn.cursor()
293
+
294
+ try:
295
+ # Check if the ai_analysis table exists
296
+ cursor.execute("""
297
+ CREATE TABLE IF NOT EXISTS ai_analysis (
298
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
299
+ resume_id INTEGER,
300
+ model_used TEXT,
301
+ resume_score INTEGER,
302
+ job_role TEXT,
303
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
304
+ FOREIGN KEY (resume_id) REFERENCES resume_data (id)
305
+ )
306
+ """)
307
+
308
+ # Insert the analysis data
309
+ cursor.execute("""
310
+ INSERT INTO ai_analysis (
311
+ resume_id, model_used, resume_score, job_role
312
+ ) VALUES (?, ?, ?, ?)
313
+ """, (
314
+ resume_id,
315
+ analysis_data.get('model_used', ''),
316
+ analysis_data.get('resume_score', 0),
317
+ analysis_data.get('job_role', '')
318
+ ))
319
+
320
+ conn.commit()
321
+ return cursor.lastrowid
322
+ except Exception as e:
323
+ print(f"Error saving AI analysis data: {e}")
324
+ conn.rollback()
325
+ raise
326
+ finally:
327
+ conn.close()
328
+
329
+ def get_ai_analysis_stats():
330
+ """Get statistics about AI analyzer usage"""
331
+ conn = get_database_connection()
332
+ cursor = conn.cursor()
333
+
334
+ try:
335
+ # Check if the ai_analysis table exists
336
+ cursor.execute("""
337
+ SELECT name FROM sqlite_master WHERE type='table' AND name='ai_analysis'
338
+ """)
339
+
340
+ if not cursor.fetchone():
341
+ return {
342
+ "total_analyses": 0,
343
+ "model_usage": [],
344
+ "average_score": 0,
345
+ "top_job_roles": []
346
+ }
347
+
348
+ # Get total number of analyses
349
+ cursor.execute("SELECT COUNT(*) FROM ai_analysis")
350
+ total_analyses = cursor.fetchone()[0]
351
+
352
+ # Get model usage statistics
353
+ cursor.execute("""
354
+ SELECT model_used, COUNT(*) as count
355
+ FROM ai_analysis
356
+ GROUP BY model_used
357
+ ORDER BY count DESC
358
+ """)
359
+ model_usage = [{"model": row[0], "count": row[1]} for row in cursor.fetchall()]
360
+
361
+ # Get average resume score
362
+ cursor.execute("SELECT AVG(resume_score) FROM ai_analysis")
363
+ average_score = cursor.fetchone()[0] or 0
364
+
365
+ # Get top job roles
366
+ cursor.execute("""
367
+ SELECT job_role, COUNT(*) as count
368
+ FROM ai_analysis
369
+ GROUP BY job_role
370
+ ORDER BY count DESC
371
+ LIMIT 5
372
+ """)
373
+ top_job_roles = [{"role": row[0], "count": row[1]} for row in cursor.fetchall()]
374
+
375
+ return {
376
+ "total_analyses": total_analyses,
377
+ "model_usage": model_usage,
378
+ "average_score": round(average_score, 1),
379
+ "top_job_roles": top_job_roles
380
+ }
381
+ except Exception as e:
382
+ print(f"Error getting AI analysis stats: {e}")
383
+ return {
384
+ "total_analyses": 0,
385
+ "model_usage": [],
386
+ "average_score": 0,
387
+ "top_job_roles": []
388
+ }
389
+ finally:
390
+ conn.close()
391
+
392
+ def get_detailed_ai_analysis_stats():
393
+ """Get detailed statistics about AI analyzer usage including daily trends"""
394
+ conn = get_database_connection()
395
+ cursor = conn.cursor()
396
+
397
+ try:
398
+ # Check if the ai_analysis table exists
399
+ cursor.execute("""
400
+ SELECT name FROM sqlite_master WHERE type='table' AND name='ai_analysis'
401
+ """)
402
+
403
+ if not cursor.fetchone():
404
+ return {
405
+ "total_analyses": 0,
406
+ "model_usage": [],
407
+ "average_score": 0,
408
+ "top_job_roles": [],
409
+ "daily_trend": [],
410
+ "score_distribution": [],
411
+ "recent_analyses": []
412
+ }
413
+
414
+ # Get total number of analyses
415
+ cursor.execute("SELECT COUNT(*) FROM ai_analysis")
416
+ total_analyses = cursor.fetchone()[0]
417
+
418
+ # Get model usage statistics
419
+ cursor.execute("""
420
+ SELECT model_used, COUNT(*) as count
421
+ FROM ai_analysis
422
+ GROUP BY model_used
423
+ ORDER BY count DESC
424
+ """)
425
+ model_usage = [{"model": row[0], "count": row[1]} for row in cursor.fetchall()]
426
+
427
+ # Get average resume score
428
+ cursor.execute("SELECT AVG(resume_score) FROM ai_analysis")
429
+ average_score = cursor.fetchone()[0] or 0
430
+
431
+ # Get top job roles
432
+ cursor.execute("""
433
+ SELECT job_role, COUNT(*) as count
434
+ FROM ai_analysis
435
+ GROUP BY job_role
436
+ ORDER BY count DESC
437
+ LIMIT 5
438
+ """)
439
+ top_job_roles = [{"role": row[0], "count": row[1]} for row in cursor.fetchall()]
440
+
441
+ # Get daily trend for the last 7 days
442
+ cursor.execute("""
443
+ SELECT DATE(created_at) as date, COUNT(*) as count
444
+ FROM ai_analysis
445
+ WHERE created_at >= date('now', '-7 days')
446
+ GROUP BY DATE(created_at)
447
+ ORDER BY date
448
+ """)
449
+ daily_trend = [{"date": row[0], "count": row[1]} for row in cursor.fetchall()]
450
+
451
+ # Get score distribution
452
+ score_ranges = [
453
+ {"min": 0, "max": 20, "range": "0-20"},
454
+ {"min": 21, "max": 40, "range": "21-40"},
455
+ {"min": 41, "max": 60, "range": "41-60"},
456
+ {"min": 61, "max": 80, "range": "61-80"},
457
+ {"min": 81, "max": 100, "range": "81-100"}
458
+ ]
459
+
460
+ score_distribution = []
461
+ for range_info in score_ranges:
462
+ cursor.execute("""
463
+ SELECT COUNT(*) FROM ai_analysis
464
+ WHERE resume_score >= ? AND resume_score <= ?
465
+ """, (range_info["min"], range_info["max"]))
466
+ count = cursor.fetchone()[0]
467
+ score_distribution.append({"range": range_info["range"], "count": count})
468
+
469
+ # Get recent analyses
470
+ cursor.execute("""
471
+ SELECT model_used, resume_score, job_role, datetime(created_at) as date
472
+ FROM ai_analysis
473
+ ORDER BY created_at DESC
474
+ LIMIT 5
475
+ """)
476
+ recent_analyses = [
477
+ {
478
+ "model": row[0],
479
+ "score": row[1],
480
+ "job_role": row[2],
481
+ "date": row[3]
482
+ } for row in cursor.fetchall()
483
+ ]
484
+
485
+ return {
486
+ "total_analyses": total_analyses,
487
+ "model_usage": model_usage,
488
+ "average_score": round(average_score, 1),
489
+ "top_job_roles": top_job_roles,
490
+ "daily_trend": daily_trend,
491
+ "score_distribution": score_distribution,
492
+ "recent_analyses": recent_analyses
493
+ }
494
+ except Exception as e:
495
+ print(f"Error getting detailed AI analysis stats: {e}")
496
+ return {
497
+ "total_analyses": 0,
498
+ "model_usage": [],
499
+ "average_score": 0,
500
+ "top_job_roles": [],
501
+ "daily_trend": [],
502
+ "score_distribution": [],
503
+ "recent_analyses": []
504
+ }
505
+ finally:
506
+ conn.close()
507
+
508
+ def reset_ai_analysis_stats():
509
+ """Reset AI analysis statistics by truncating the ai_analysis table"""
510
+ conn = get_database_connection()
511
+ cursor = conn.cursor()
512
+
513
+ try:
514
+ # Check if the ai_analysis table exists
515
+ cursor.execute("""
516
+ SELECT name FROM sqlite_master WHERE type='table' AND name='ai_analysis'
517
+ """)
518
+
519
+ if not cursor.fetchone():
520
+ return {"success": False, "message": "AI analysis table does not exist"}
521
+
522
+ # Delete all records from the ai_analysis table
523
+ cursor.execute("DELETE FROM ai_analysis")
524
+ conn.commit()
525
+
526
+ return {"success": True, "message": "AI analysis statistics have been reset successfully"}
527
+ except Exception as e:
528
+ conn.rollback()
529
+ print(f"Error resetting AI analysis stats: {e}")
530
+ return {"success": False, "message": f"Error resetting AI analysis statistics: {str(e)}"}
531
+ finally:
532
+ conn.close()
config/job_roles.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ JOB_ROLES = {
2
+ "Software Development and Engineering": {
3
+ "Frontend Developer": {
4
+ "required_skills": ["HTML", "CSS", "JavaScript", "React", "Angular", "Vue.js", "UI/UX", "Responsive Design"],
5
+ "description": "Create user interfaces and implement visual elements",
6
+ "sections": ["Technical Skills", "Projects", "Work Experience", "Education"],
7
+ "recommended_skills": {
8
+ "technical": ["HTML5", "CSS3", "JavaScript", "React/Angular/Vue", "TypeScript", "Git"],
9
+ "soft": ["Communication", "Problem-solving", "Attention to detail", "Creativity"]
10
+ }
11
+ },
12
+ "Backend Developer": {
13
+ "required_skills": ["Python", "Java", "Node.js", "SQL", "APIs", "Django", "Flask", "Database Design"],
14
+ "description": "Build server-side logic and databases",
15
+ "sections": ["Technical Skills", "System Architecture", "Work Experience", "Education"],
16
+ "recommended_skills": {
17
+ "technical": ["Python/Java/Node.js", "SQL", "RESTful APIs", "Microservices", "Docker"],
18
+ "soft": ["Analytical thinking", "Problem-solving", "Team collaboration"]
19
+ }
20
+ },
21
+ "Full Stack Developer": {
22
+ "required_skills": ["Frontend Tech", "Backend Tech", "Databases", "DevOps", "System Design", "APIs"],
23
+ "description": "Handle both client and server-side development",
24
+ "sections": ["Technical Skills", "Full Stack Projects", "Work Experience", "Education"],
25
+ "recommended_skills": {
26
+ "technical": ["Frontend & Backend technologies", "Database design", "API development", "DevOps"],
27
+ "soft": ["Versatility", "Project management", "Communication"]
28
+ }
29
+ },
30
+ "Mobile App Developer": {
31
+ "required_skills": ["Swift", "Kotlin", "React Native", "Flutter", "Mobile UI/UX", "App Store Deployment"],
32
+ "description": "Develop mobile applications for iOS and Android platforms",
33
+ "sections": ["Technical Skills", "Mobile Projects", "Work Experience", "Education"],
34
+ "recommended_skills": {
35
+ "technical": ["iOS/Android Development", "Cross-platform frameworks", "Mobile UI/UX", "App Performance"],
36
+ "soft": ["User-centric thinking", "Problem-solving", "Attention to detail"]
37
+ }
38
+ },
39
+ "Game Developer": {
40
+ "required_skills": ["Unity", "Unreal Engine", "C++", "C#", "3D Graphics", "Game Physics"],
41
+ "description": "Create engaging and interactive games",
42
+ "sections": ["Technical Skills", "Game Projects", "Work Experience", "Education"],
43
+ "recommended_skills": {
44
+ "technical": ["Game Engines", "Graphics Programming", "Physics Simulation", "Multiplayer"],
45
+ "soft": ["Creativity", "Problem-solving", "Team collaboration"]
46
+ }
47
+ }
48
+ },
49
+ "Data Science and Analytics": {
50
+ "Data Scientist": {
51
+ "required_skills": ["Python", "R", "Machine Learning", "Statistics", "SQL", "Deep Learning"],
52
+ "description": "Analyze complex data sets to find patterns",
53
+ "sections": ["Technical Skills", "Projects", "Research", "Education"],
54
+ "recommended_skills": {
55
+ "technical": ["Python", "R", "Machine Learning", "Statistical Analysis", "Big Data"],
56
+ "soft": ["Analytical thinking", "Research", "Problem-solving"]
57
+ }
58
+ },
59
+ "Data Analyst": {
60
+ "required_skills": ["SQL", "Excel", "Python", "Data Visualization", "Statistics"],
61
+ "description": "Transform data into insights",
62
+ "sections": ["Technical Skills", "Analysis Projects", "Work Experience", "Education"],
63
+ "recommended_skills": {
64
+ "technical": ["SQL", "Excel", "Python", "Tableau/Power BI", "Statistical Analysis"],
65
+ "soft": ["Data interpretation", "Communication", "Attention to detail"]
66
+ }
67
+ },
68
+ "Machine Learning Engineer": {
69
+ "required_skills": ["Python", "TensorFlow", "PyTorch", "MLOps", "Deep Learning"],
70
+ "description": "Build and deploy machine learning models",
71
+ "sections": ["Technical Skills", "ML Projects", "Work Experience", "Education"],
72
+ "recommended_skills": {
73
+ "technical": ["Machine Learning", "Deep Learning", "MLOps", "Model Deployment"],
74
+ "soft": ["Research", "Problem-solving", "Critical thinking"]
75
+ }
76
+ }
77
+ },
78
+ "Cloud Computing and DevOps": {
79
+ "Cloud Architect": {
80
+ "required_skills": ["AWS", "Azure", "GCP", "Infrastructure as Code", "Security"],
81
+ "description": "Design and manage cloud infrastructure",
82
+ "sections": ["Technical Skills", "Cloud Projects", "Work Experience", "Certifications"],
83
+ "recommended_skills": {
84
+ "technical": ["Cloud Platforms", "Security", "Networking", "Cost Optimization"],
85
+ "soft": ["Strategic thinking", "Problem-solving", "Communication"]
86
+ }
87
+ },
88
+ "DevOps Engineer": {
89
+ "required_skills": ["Docker", "Kubernetes", "CI/CD", "Automation", "Monitoring"],
90
+ "description": "Implement DevOps practices and tools",
91
+ "sections": ["Technical Skills", "DevOps Projects", "Work Experience", "Education"],
92
+ "recommended_skills": {
93
+ "technical": ["Containerization", "Orchestration", "CI/CD", "Infrastructure as Code"],
94
+ "soft": ["Automation mindset", "Problem-solving", "Team collaboration"]
95
+ }
96
+ },
97
+ "Site Reliability Engineer": {
98
+ "required_skills": ["Linux", "Monitoring", "Automation", "Performance Tuning", "Incident Response"],
99
+ "description": "Ensure system reliability and performance",
100
+ "sections": ["Technical Skills", "SRE Projects", "Work Experience", "Education"],
101
+ "recommended_skills": {
102
+ "technical": ["System Administration", "Monitoring", "Automation", "Incident Management"],
103
+ "soft": ["Problem-solving", "Communication", "Critical thinking"]
104
+ }
105
+ }
106
+ },
107
+ "Cybersecurity": {
108
+ "Security Analyst": {
109
+ "required_skills": ["Network Security", "Threat Detection", "Security Tools", "Incident Response"],
110
+ "description": "Monitor and protect against security threats",
111
+ "sections": ["Technical Skills", "Security Projects", "Work Experience", "Certifications"],
112
+ "recommended_skills": {
113
+ "technical": ["Security Tools", "Threat Analysis", "Incident Response", "Compliance"],
114
+ "soft": ["Analytical thinking", "Attention to detail", "Communication"]
115
+ }
116
+ },
117
+ "Penetration Tester": {
118
+ "required_skills": ["Ethical Hacking", "Security Tools", "Network Security", "Web Security"],
119
+ "description": "Test systems for security vulnerabilities",
120
+ "sections": ["Technical Skills", "Security Projects", "Work Experience", "Certifications"],
121
+ "recommended_skills": {
122
+ "technical": ["Penetration Testing", "Security Tools", "Vulnerability Assessment"],
123
+ "soft": ["Ethical mindset", "Problem-solving", "Report writing"]
124
+ }
125
+ }
126
+ },
127
+ "UI/UX Design": {
128
+ "UI Designer": {
129
+ "required_skills": ["Figma", "Adobe XD", "Visual Design", "Typography", "Color Theory"],
130
+ "description": "Create beautiful user interfaces",
131
+ "sections": ["Design Skills", "Portfolio", "Work Experience", "Education"],
132
+ "recommended_skills": {
133
+ "technical": ["Design Tools", "Visual Design", "Prototyping", "Design Systems"],
134
+ "soft": ["Creativity", "Attention to detail", "User empathy"]
135
+ }
136
+ },
137
+ "UX Designer": {
138
+ "required_skills": ["User Research", "Wireframing", "Prototyping", "Usability Testing"],
139
+ "description": "Design user experiences and flows",
140
+ "sections": ["Design Skills", "Case Studies", "Work Experience", "Education"],
141
+ "recommended_skills": {
142
+ "technical": ["Research Methods", "Information Architecture", "User Testing"],
143
+ "soft": ["Empathy", "Communication", "Problem-solving"]
144
+ }
145
+ }
146
+ },
147
+ "Project Management": {
148
+ "Project Manager": {
149
+ "required_skills": ["Project Planning", "Agile", "Scrum", "Risk Management", "Stakeholder Management"],
150
+ "description": "Lead and manage project delivery",
151
+ "sections": ["Management Skills", "Project History", "Work Experience", "Certifications"],
152
+ "recommended_skills": {
153
+ "technical": ["Project Management Tools", "Agile Methodologies", "Budgeting"],
154
+ "soft": ["Leadership", "Communication", "Problem-solving"]
155
+ }
156
+ },
157
+ "Product Manager": {
158
+ "required_skills": ["Product Strategy", "Market Research", "User Stories", "Roadmapping"],
159
+ "description": "Define and drive product vision",
160
+ "sections": ["Product Skills", "Product Launches", "Work Experience", "Education"],
161
+ "recommended_skills": {
162
+ "technical": ["Product Management Tools", "Analytics", "Market Research"],
163
+ "soft": ["Strategic thinking", "Communication", "Leadership"]
164
+ }
165
+ }
166
+ }
167
+ }
dashboard/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .dashboard import DashboardManager
dashboard/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (245 Bytes). View file
 
dashboard/__pycache__/dashboard.cpython-311.pyc ADDED
Binary file (52.2 kB). View file
 
dashboard/components.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import plotly.graph_objects as go
3
+ from plotly.subplots import make_subplots
4
+ import pandas as pd
5
+ from datetime import datetime, timedelta
6
+
7
+ class DashboardComponents:
8
+ def __init__(self, colors):
9
+ self.colors = colors
10
+
11
+ def render_metric_card(self, title, value, subtitle=None, trend=None, trend_value=None):
12
+ """Render a metric card with optional trend indicator"""
13
+ trend_html = ""
14
+ if trend and trend_value:
15
+ trend_color = self.colors['success'] if trend == 'up' else self.colors['danger']
16
+ trend_arrow = '↑' if trend == 'up' else '↓'
17
+ trend_html = f"""
18
+ <div style="color: {trend_color}; font-size: 0.9rem; margin-top: 5px;">
19
+ {trend_arrow} {trend_value}%
20
+ </div>
21
+ """
22
+
23
+ st.markdown(f"""
24
+ <div class="metric-card">
25
+ <div style="color: {self.colors['subtext']}; font-size: 0.9rem;">{title}</div>
26
+ <div style="color: {self.colors['text']}; font-size: 2rem; font-weight: bold; margin: 10px 0;">
27
+ {value}
28
+ </div>
29
+ {f'<div style="color: {self.colors["subtext"]}; font-size: 0.8rem;">{subtitle}</div>' if subtitle else ''}
30
+ {trend_html}
31
+ </div>
32
+ """, unsafe_allow_html=True)
33
+
34
+ def create_gauge_chart(self, value, title):
35
+ """Create a gauge chart for metrics like ATS score"""
36
+ fig = go.Figure(go.Indicator(
37
+ mode="gauge+number",
38
+ value=value,
39
+ title={'text': title, 'font': {'size': 24, 'color': self.colors['text']}},
40
+ gauge={
41
+ 'axis': {'range': [0, 100], 'tickwidth': 1, 'tickcolor': self.colors['text']},
42
+ 'bar': {'color': self.colors['primary']},
43
+ 'bgcolor': "white",
44
+ 'borderwidth': 2,
45
+ 'bordercolor': "gray",
46
+ 'steps': [
47
+ {'range': [0, 40], 'color': self.colors['danger']},
48
+ {'range': [40, 70], 'color': self.colors['warning']},
49
+ {'range': [70, 100], 'color': self.colors['success']}
50
+ ],
51
+ }
52
+ ))
53
+
54
+ fig.update_layout(
55
+ paper_bgcolor=self.colors['card'],
56
+ plot_bgcolor=self.colors['card'],
57
+ font={'color': self.colors['text']},
58
+ height=300,
59
+ margin=dict(l=20, r=20, t=50, b=20)
60
+ )
61
+
62
+ return fig
63
+
64
+ def create_trend_chart(self, dates, values, title):
65
+ """Create a trend line chart"""
66
+ fig = go.Figure()
67
+ fig.add_trace(go.Scatter(
68
+ x=dates,
69
+ y=values,
70
+ mode='lines+markers',
71
+ line=dict(color=self.colors['info'], width=3),
72
+ marker=dict(size=8, color=self.colors['info'])
73
+ ))
74
+
75
+ fig.update_layout(
76
+ title=title,
77
+ paper_bgcolor=self.colors['card'],
78
+ plot_bgcolor=self.colors['card'],
79
+ font={'color': self.colors['text']},
80
+ height=300,
81
+ margin=dict(l=20, r=20, t=50, b=20),
82
+ xaxis=dict(
83
+ showgrid=True,
84
+ gridwidth=1,
85
+ gridcolor=self.colors['background']
86
+ ),
87
+ yaxis=dict(
88
+ showgrid=True,
89
+ gridwidth=1,
90
+ gridcolor=self.colors['background']
91
+ )
92
+ )
93
+
94
+ return fig
95
+
96
+ def create_bar_chart(self, categories, values, title):
97
+ """Create a bar chart"""
98
+ fig = go.Figure(go.Bar(
99
+ x=categories,
100
+ y=values,
101
+ marker_color=self.colors['primary'],
102
+ text=values,
103
+ textposition='auto',
104
+ ))
105
+
106
+ fig.update_layout(
107
+ title=title,
108
+ paper_bgcolor=self.colors['card'],
109
+ plot_bgcolor=self.colors['card'],
110
+ font={'color': self.colors['text']},
111
+ height=300,
112
+ margin=dict(l=20, r=20, t=50, b=20),
113
+ xaxis=dict(
114
+ showgrid=False,
115
+ title_text="Categories",
116
+ color=self.colors['text']
117
+ ),
118
+ yaxis=dict(
119
+ showgrid=True,
120
+ gridwidth=1,
121
+ gridcolor=self.colors['background'],
122
+ title_text="Values",
123
+ color=self.colors['text']
124
+ )
125
+ )
126
+
127
+ return fig
128
+
129
+ def create_dual_axis_chart(self, categories, values1, values2, title):
130
+ """Create a chart with dual y-axes"""
131
+ fig = make_subplots(specs=[[{"secondary_y": True}]])
132
+
133
+ fig.add_trace(
134
+ go.Bar(
135
+ x=categories,
136
+ y=values1,
137
+ name="Count",
138
+ marker_color=self.colors['secondary']
139
+ ),
140
+ secondary_y=False
141
+ )
142
+
143
+ fig.add_trace(
144
+ go.Scatter(
145
+ x=categories,
146
+ y=values2,
147
+ name="Score",
148
+ line=dict(color=self.colors['warning'], width=3),
149
+ mode='lines+markers'
150
+ ),
151
+ secondary_y=True
152
+ )
153
+
154
+ fig.update_layout(
155
+ title=title,
156
+ paper_bgcolor=self.colors['card'],
157
+ plot_bgcolor=self.colors['card'],
158
+ font={'color': self.colors['text']},
159
+ height=300,
160
+ margin=dict(l=20, r=20, t=50, b=20),
161
+ showlegend=True,
162
+ legend=dict(
163
+ orientation="h",
164
+ yanchor="bottom",
165
+ y=1.02,
166
+ xanchor="right",
167
+ x=1
168
+ )
169
+ )
170
+
171
+ fig.update_xaxes(title_text="Categories", color=self.colors['text'])
172
+ fig.update_yaxes(title_text="Count", color=self.colors['text'], secondary_y=False)
173
+ fig.update_yaxes(title_text="Score", color=self.colors['text'], secondary_y=True)
174
+
175
+ return fig
dashboard/dashboard.py ADDED
@@ -0,0 +1,1155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ from datetime import datetime, timedelta
6
+ from config.database import get_database_connection
7
+ import io
8
+ import uuid
9
+ from plotly.subplots import make_subplots
10
+ from io import BytesIO
11
+
12
+ class DashboardManager:
13
+ def __init__(self):
14
+ self.conn = get_database_connection()
15
+ self.colors = {
16
+ 'primary': '#4CAF50',
17
+ 'secondary': '#2196F3',
18
+ 'warning': '#FFA726',
19
+ 'danger': '#F44336',
20
+ 'info': '#00BCD4',
21
+ 'success': '#66BB6A',
22
+ 'purple': '#9C27B0',
23
+ 'background': '#1E1E1E',
24
+ 'card': '#2D2D2D',
25
+ 'text': '#FFFFFF',
26
+ 'subtext': '#B0B0B0'
27
+ }
28
+
29
+ def apply_dashboard_style(self):
30
+ """Apply custom styling for dashboard"""
31
+ st.markdown("""
32
+ <style>
33
+ .dashboard-title {
34
+ font-size: 2.5rem;
35
+ font-weight: bold;
36
+ margin-bottom: 2rem;
37
+ color: white;
38
+ text-align: center;
39
+ }
40
+
41
+ .metric-card {
42
+ background-color: #2D2D2D;
43
+ border-radius: 15px;
44
+ padding: 1.5rem;
45
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
46
+ transition: transform 0.3s ease;
47
+ height: 100%;
48
+ }
49
+
50
+ .metric-card:hover {
51
+ transform: translateY(-5px);
52
+ }
53
+
54
+ .metric-value {
55
+ font-size: 2.5rem;
56
+ font-weight: bold;
57
+ color: #4CAF50;
58
+ margin: 0.5rem 0;
59
+ }
60
+
61
+ .metric-label {
62
+ font-size: 1rem;
63
+ color: #B0B0B0;
64
+ }
65
+
66
+ .trend-up {
67
+ color: #4CAF50;
68
+ font-size: 1.2rem;
69
+ }
70
+
71
+ .trend-down {
72
+ color: #F44336;
73
+ font-size: 1.2rem;
74
+ }
75
+
76
+ .chart-container {
77
+ background-color: #2D2D2D;
78
+ border-radius: 15px;
79
+ padding: 1.5rem;
80
+ margin: 1rem 0;
81
+ }
82
+
83
+ .section-title {
84
+ font-size: 1.5rem;
85
+ color: white;
86
+ margin: 2rem 0 1rem 0;
87
+ }
88
+
89
+ .stPlotlyChart {
90
+ background-color: #2D2D2D;
91
+ border-radius: 15px;
92
+ padding: 1rem;
93
+ }
94
+
95
+ div[data-testid="stHorizontalBlock"] > div {
96
+ background-color: #2D2D2D;
97
+ border-radius: 15px;
98
+ padding: 1rem;
99
+ margin: 0.5rem;
100
+ }
101
+
102
+ [data-testid="stMetricValue"] {
103
+ font-size: 2rem !important;
104
+ }
105
+
106
+ [data-testid="stMetricLabel"] {
107
+ font-size: 1rem !important;
108
+ }
109
+ </style>
110
+ """, unsafe_allow_html=True)
111
+
112
+ def get_resume_metrics(self):
113
+ """Get resume-related metrics from database"""
114
+ cursor = self.conn.cursor()
115
+
116
+ # Get current date
117
+ now = datetime.now()
118
+ start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
119
+ start_of_week = now - timedelta(days=now.weekday())
120
+ start_of_month = now.replace(day=1)
121
+
122
+ # Fetch metrics for different time periods
123
+ metrics = {}
124
+ for period, start_date in [
125
+ ('Today', start_of_day),
126
+ ('This Week', start_of_week),
127
+ ('This Month', start_of_month),
128
+ ('All Time', datetime(2000, 1, 1))
129
+ ]:
130
+ cursor.execute("""
131
+ SELECT
132
+ COUNT(DISTINCT rd.id) as total_resumes,
133
+ ROUND(AVG(ra.ats_score), 1) as avg_ats_score,
134
+ ROUND(AVG(ra.keyword_match_score), 1) as avg_keyword_score,
135
+ COUNT(DISTINCT CASE WHEN ra.ats_score >= 70 THEN rd.id END) as high_scoring
136
+ FROM resume_data rd
137
+ LEFT JOIN resume_analysis ra ON rd.id = ra.resume_id
138
+ WHERE rd.created_at >= ?
139
+ """, (start_date.strftime('%Y-%m-%d %H:%M:%S'),))
140
+
141
+ row = cursor.fetchone()
142
+ if row:
143
+ metrics[period] = {
144
+ 'total': row[0] or 0,
145
+ 'ats_score': row[1] or 0,
146
+ 'keyword_score': row[2] or 0,
147
+ 'high_scoring': row[3] or 0
148
+ }
149
+ else:
150
+ metrics[period] = {
151
+ 'total': 0,
152
+ 'ats_score': 0,
153
+ 'keyword_score': 0,
154
+ 'high_scoring': 0
155
+ }
156
+
157
+ return metrics
158
+
159
+ def get_skill_distribution(self):
160
+ """Get skill distribution data"""
161
+ cursor = self.conn.cursor()
162
+ cursor.execute("""
163
+ WITH RECURSIVE split(skill, rest) AS (
164
+ SELECT '', skills || ','
165
+ FROM resume_data
166
+ UNION ALL
167
+ SELECT
168
+ substr(rest, 0, instr(rest, ',')),
169
+ substr(rest, instr(rest, ',') + 1)
170
+ FROM split
171
+ WHERE rest <> ''
172
+ ),
173
+ SkillCategories AS (
174
+ SELECT
175
+ CASE
176
+ WHEN LOWER(TRIM(skill, '[]" ')) LIKE '%python%' OR LOWER(TRIM(skill, '[]" ')) LIKE '%java%' OR
177
+ LOWER(TRIM(skill, '[]" ')) LIKE '%javascript%' OR LOWER(TRIM(skill, '[]" ')) LIKE '%c++%' OR
178
+ LOWER(TRIM(skill, '[]" ')) LIKE '%programming%' THEN 'Programming'
179
+ WHEN LOWER(TRIM(skill, '[]" ')) LIKE '%sql%' OR LOWER(TRIM(skill, '[]" ')) LIKE '%database%' OR
180
+ LOWER(TRIM(skill, '[]" ')) LIKE '%mongodb%' THEN 'Database'
181
+ WHEN LOWER(TRIM(skill, '[]" ')) LIKE '%aws%' OR LOWER(TRIM(skill, '[]" ')) LIKE '%cloud%' OR
182
+ LOWER(TRIM(skill, '[]" ')) LIKE '%azure%' THEN 'Cloud'
183
+ WHEN LOWER(TRIM(skill, '[]" ')) LIKE '%agile%' OR LOWER(TRIM(skill, '[]" ')) LIKE '%scrum%' OR
184
+ LOWER(TRIM(skill, '[]" ')) LIKE '%management%' THEN 'Management'
185
+ ELSE 'Other'
186
+ END as category,
187
+ COUNT(*) as count
188
+ FROM split
189
+ WHERE skill <> ''
190
+ GROUP BY category
191
+ )
192
+ SELECT category, count
193
+ FROM SkillCategories
194
+ ORDER BY count DESC
195
+ """)
196
+
197
+ categories, counts = [], []
198
+ for row in cursor.fetchall():
199
+ categories.append(row[0])
200
+ counts.append(row[1])
201
+
202
+ return categories, counts
203
+
204
+ def get_weekly_trends(self):
205
+ """Get weekly submission trends"""
206
+ cursor = self.conn.cursor()
207
+ now = datetime.now()
208
+ dates = [(now - timedelta(days=x)).strftime('%Y-%m-%d') for x in range(6, -1, -1)]
209
+
210
+ submissions = []
211
+ for date in dates:
212
+ cursor.execute("""
213
+ SELECT COUNT(*)
214
+ FROM resume_data
215
+ WHERE DATE(created_at) = DATE(?)
216
+ """, (date,))
217
+ submissions.append(cursor.fetchone()[0])
218
+
219
+ return [d[-3:] for d in dates], submissions # Return shortened date format (e.g., 'Mon', 'Tue')
220
+
221
+ def get_job_category_stats(self):
222
+ """Get statistics by job category"""
223
+ cursor = self.conn.cursor()
224
+ cursor.execute("""
225
+ SELECT
226
+ COALESCE(target_category, 'Other') as category,
227
+ COUNT(*) as count,
228
+ ROUND(AVG(CASE WHEN ra.ats_score >= 70 THEN 1 ELSE 0 END) * 100, 1) as success_rate
229
+ FROM resume_data rd
230
+ LEFT JOIN resume_analysis ra ON rd.id = ra.resume_id
231
+ GROUP BY category
232
+ ORDER BY count DESC
233
+ LIMIT 5
234
+ """)
235
+
236
+ categories, success_rates = [], []
237
+ for row in cursor.fetchall():
238
+ categories.append(row[0])
239
+ success_rates.append(row[2] or 0)
240
+
241
+ return categories, success_rates
242
+
243
+ def render_admin_panel(self):
244
+ """Render admin panel with data management tools"""
245
+ st.sidebar.markdown("### 👋 Welcome Admin!")
246
+ st.sidebar.markdown("---")
247
+
248
+ if st.sidebar.button("🚪 Logout"):
249
+ st.session_state.is_admin = False
250
+ st.rerun()
251
+
252
+ st.sidebar.markdown("### 🛠️ Admin Tools")
253
+
254
+ # Data Export Options
255
+ export_format = st.sidebar.selectbox(
256
+ "Export Format",
257
+ ["Excel", "CSV", "JSON"],
258
+ key="export_format"
259
+ )
260
+
261
+ if st.sidebar.button("📥 Export Data"):
262
+ if export_format == "Excel":
263
+ excel_data = self.export_to_excel()
264
+ if excel_data:
265
+ st.sidebar.download_button(
266
+ "⬇️ Download Excel",
267
+ data=excel_data,
268
+ file_name=f"resume_data_{datetime.now().strftime('%Y%m%d_%H%M')}.xlsx",
269
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
270
+ )
271
+ elif export_format == "CSV":
272
+ csv_data = self.export_to_csv()
273
+ if csv_data:
274
+ st.sidebar.download_button(
275
+ "⬇️ Download CSV",
276
+ data=csv_data,
277
+ file_name=f"resume_data_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
278
+ mime="text/csv"
279
+ )
280
+ else:
281
+ json_data = self.export_to_json()
282
+ if json_data:
283
+ st.sidebar.download_button(
284
+ "⬇️ Download JSON",
285
+ data=json_data,
286
+ file_name=f"resume_data_{datetime.now().strftime('%Y%m%d_%H%M')}.json",
287
+ mime="application/json"
288
+ )
289
+
290
+ # Database Stats
291
+ st.sidebar.markdown("### 📊 Database Stats")
292
+ stats = self.get_database_stats()
293
+ st.sidebar.markdown(f"""
294
+ - Total Resumes: {stats['total_resumes']}
295
+ - Today's Submissions: {stats['today_submissions']}
296
+ - Storage Used: {stats['storage_size']}
297
+ """)
298
+
299
+ def get_resume_data(self):
300
+ """Get all resume data"""
301
+ cursor = self.conn.cursor()
302
+ try:
303
+ cursor.execute('''
304
+ SELECT
305
+ r.id,
306
+ r.name,
307
+ r.email,
308
+ r.phone,
309
+ r.linkedin,
310
+ r.github,
311
+ r.portfolio,
312
+ r.target_role,
313
+ r.target_category,
314
+ r.created_at,
315
+ a.ats_score,
316
+ a.keyword_match_score,
317
+ a.format_score,
318
+ a.section_score
319
+ FROM resume_data r
320
+ LEFT JOIN resume_analysis a ON r.id = a.resume_id
321
+ ORDER BY r.created_at DESC
322
+ ''')
323
+ return cursor.fetchall()
324
+ except Exception as e:
325
+ print(f"Error fetching resume data: {str(e)}")
326
+ return []
327
+
328
+ def render_resume_data_section(self):
329
+ """Render resume data section with Excel download"""
330
+ st.markdown("<h2 class='section-title'>Resume Submissions</h2>", unsafe_allow_html=True)
331
+
332
+ # Get resume data
333
+ resume_data = self.get_resume_data()
334
+
335
+ if resume_data:
336
+ # Convert to DataFrame
337
+ columns = [
338
+ 'ID', 'Name', 'Email', 'Phone', 'LinkedIn', 'GitHub',
339
+ 'Portfolio', 'Target Role', 'Target Category', 'Submission Date',
340
+ 'ATS Score', 'Keyword Match', 'Format Score', 'Section Score'
341
+ ]
342
+ df = pd.DataFrame(resume_data, columns=columns)
343
+
344
+ # Format scores as percentages
345
+ score_columns = ['ATS Score', 'Keyword Match', 'Format Score', 'Section Score']
346
+ for col in score_columns:
347
+ df[col] = df[col].apply(lambda x: f"{x*100:.1f}%" if pd.notnull(x) else "N/A")
348
+
349
+ # Style the dataframe
350
+ st.markdown("""
351
+ <style>
352
+ .resume-data {
353
+ background-color: #2D2D2D;
354
+ border-radius: 10px;
355
+ padding: 1rem;
356
+ margin-bottom: 1rem;
357
+ }
358
+ </style>
359
+ """, unsafe_allow_html=True)
360
+
361
+ with st.container():
362
+ st.markdown('<div class="resume-data">', unsafe_allow_html=True)
363
+
364
+ # Add filters
365
+ col1, col2 = st.columns(2)
366
+ with col1:
367
+ target_role = st.selectbox(
368
+ "Filter by Target Role",
369
+ options=["All"] + list(df['Target Role'].unique()),
370
+ key="role_filter"
371
+ )
372
+ with col2:
373
+ target_category = st.selectbox(
374
+ "Filter by Category",
375
+ options=["All"] + list(df['Target Category'].unique()),
376
+ key="category_filter"
377
+ )
378
+
379
+ # Apply filters
380
+ filtered_df = df.copy()
381
+ if target_role != "All":
382
+ filtered_df = filtered_df[filtered_df['Target Role'] == target_role]
383
+ if target_category != "All":
384
+ filtered_df = filtered_df[filtered_df['Target Category'] == target_category]
385
+
386
+ # Display filtered data
387
+ st.dataframe(
388
+ filtered_df,
389
+ use_container_width=True,
390
+ hide_index=True
391
+ )
392
+
393
+ # Add download buttons
394
+ col1, col2 = st.columns(2)
395
+ with col1:
396
+ # Download filtered data
397
+ excel_buffer = BytesIO()
398
+ filtered_df.to_excel(excel_buffer, index=False, engine='openpyxl')
399
+ excel_buffer.seek(0)
400
+
401
+ st.download_button(
402
+ label="📥 Download Filtered Data",
403
+ data=excel_buffer,
404
+ file_name=f"resume_data_filtered_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx",
405
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
406
+ key="download_filtered_data"
407
+ )
408
+
409
+ with col2:
410
+ # Download all data
411
+ excel_buffer_all = BytesIO()
412
+ df.to_excel(excel_buffer_all, index=False, engine='openpyxl')
413
+ excel_buffer_all.seek(0)
414
+
415
+ st.download_button(
416
+ label="📥 Download All Data",
417
+ data=excel_buffer_all,
418
+ file_name=f"resume_data_all_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx",
419
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
420
+ key="download_all_data"
421
+ )
422
+
423
+ st.markdown('</div>', unsafe_allow_html=True)
424
+ else:
425
+ st.info("No resume submissions available")
426
+
427
+ def render_admin_section(self):
428
+ """Render admin section with logs and Excel download"""
429
+ # Render resume data section
430
+ self.render_resume_data_section()
431
+
432
+ # Render admin logs section
433
+ st.markdown("<h2 class='section-title'>Admin Activity Logs</h2>", unsafe_allow_html=True)
434
+
435
+ # Get admin logs
436
+ admin_logs = self.get_admin_logs()
437
+
438
+ if admin_logs:
439
+ # Convert to DataFrame
440
+ df = pd.DataFrame(admin_logs, columns=['Admin Email', 'Action', 'Timestamp'])
441
+
442
+ # Style the dataframe
443
+ st.markdown("""
444
+ <style>
445
+ .admin-logs {
446
+ background-color: #2D2D2D;
447
+ border-radius: 10px;
448
+ padding: 1rem;
449
+ }
450
+ </style>
451
+ """, unsafe_allow_html=True)
452
+
453
+ with st.container():
454
+ st.markdown('<div class="admin-logs">', unsafe_allow_html=True)
455
+ st.dataframe(
456
+ df,
457
+ use_container_width=True,
458
+ hide_index=True
459
+ )
460
+
461
+ # Add download button
462
+ excel_buffer = BytesIO()
463
+ df.to_excel(excel_buffer, index=False, engine='openpyxl')
464
+ excel_buffer.seek(0)
465
+
466
+ st.download_button(
467
+ label="📥 Download Admin Logs as Excel",
468
+ data=excel_buffer,
469
+ file_name=f"admin_logs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx",
470
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
471
+ key="download_admin_logs"
472
+ )
473
+ st.markdown('</div>', unsafe_allow_html=True)
474
+ else:
475
+ st.info("No admin activity logs available")
476
+
477
+ def export_to_excel(self):
478
+ """Export data to Excel format"""
479
+ query = """
480
+ SELECT
481
+ rd.name, rd.email, rd.phone, rd.linkedin, rd.github, rd.portfolio,
482
+ rd.summary, rd.target_role, rd.target_category,
483
+ rd.education, rd.experience, rd.projects, rd.skills,
484
+ ra.ats_score, ra.keyword_match_score, ra.format_score, ra.section_score,
485
+ ra.missing_skills, ra.recommendations,
486
+ rd.created_at
487
+ FROM resume_data rd
488
+ LEFT JOIN resume_analysis ra ON rd.id = ra.resume_id
489
+ """
490
+ try:
491
+ df = pd.read_sql_query(query, self.conn)
492
+
493
+ # Create Excel writer object
494
+ output = BytesIO()
495
+ with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
496
+ # Write main data
497
+ df.to_excel(writer, sheet_name='Resume Data', index=False)
498
+
499
+ # Get the workbook and the worksheet
500
+ workbook = writer.book
501
+ worksheet = writer.sheets['Resume Data']
502
+
503
+ # Add formatting
504
+ header_format = workbook.add_format({
505
+ 'bold': True,
506
+ 'text_wrap': True,
507
+ 'valign': 'top',
508
+ 'fg_color': '#D7E4BC',
509
+ 'border': 1
510
+ })
511
+
512
+ # Write headers with formatting
513
+ for col_num, value in enumerate(df.columns.values):
514
+ worksheet.write(0, col_num, value, header_format)
515
+
516
+ # Auto-adjust columns' width
517
+ for i, col in enumerate(df.columns):
518
+ max_length = max(
519
+ df[col].astype(str).apply(len).max(),
520
+ len(str(col))
521
+ ) + 2
522
+ worksheet.set_column(i, i, min(max_length, 50))
523
+
524
+ # Return the Excel file
525
+ output.seek(0)
526
+ return output.getvalue()
527
+
528
+ except Exception as e:
529
+ st.error(f"Error exporting to Excel: {str(e)}")
530
+ return None
531
+
532
+ def export_to_csv(self):
533
+ """Export data to CSV format"""
534
+ query = """
535
+ SELECT
536
+ rd.name, rd.email, rd.phone, rd.linkedin, rd.github, rd.portfolio,
537
+ rd.summary, rd.target_role, rd.target_category,
538
+ rd.education, rd.experience, rd.projects, rd.skills,
539
+ ra.ats_score, ra.keyword_match_score, ra.format_score, ra.section_score,
540
+ ra.missing_skills, ra.recommendations,
541
+ rd.created_at
542
+ FROM resume_data rd
543
+ LEFT JOIN resume_analysis ra ON rd.id = ra.resume_id
544
+ """
545
+ try:
546
+ df = pd.read_sql_query(query, self.conn)
547
+ return df.to_csv(index=False).encode('utf-8')
548
+ except Exception as e:
549
+ st.error(f"Error exporting to CSV: {str(e)}")
550
+ return None
551
+
552
+ def export_to_json(self):
553
+ """Export data to JSON format"""
554
+ query = """
555
+ SELECT
556
+ rd.*, ra.*
557
+ FROM resume_data rd
558
+ LEFT JOIN resume_analysis ra ON rd.id = ra.resume_id
559
+ """
560
+ try:
561
+ df = pd.read_sql_query(query, self.conn)
562
+ return df.to_json(orient='records', date_format='iso')
563
+ except Exception as e:
564
+ st.error(f"Error exporting to JSON: {str(e)}")
565
+ return None
566
+
567
+ def get_database_stats(self):
568
+ """Get database statistics"""
569
+ cursor = self.conn.cursor()
570
+ stats = {}
571
+
572
+ # Total resumes
573
+ cursor.execute("SELECT COUNT(*) FROM resume_data")
574
+ stats['total_resumes'] = cursor.fetchone()[0]
575
+
576
+ # Today's submissions
577
+ cursor.execute("""
578
+ SELECT COUNT(*)
579
+ FROM resume_data
580
+ WHERE DATE(created_at) = DATE('now')
581
+ """)
582
+ stats['today_submissions'] = cursor.fetchone()[0]
583
+
584
+ # Database size (approximate)
585
+ cursor.execute("PRAGMA page_count")
586
+ page_count = cursor.fetchone()[0]
587
+ cursor.execute("PRAGMA page_size")
588
+ page_size = cursor.fetchone()[0]
589
+ size_bytes = page_count * page_size
590
+
591
+ if size_bytes < 1024:
592
+ stats['storage_size'] = f"{size_bytes} bytes"
593
+ elif size_bytes < 1024 * 1024:
594
+ stats['storage_size'] = f"{size_bytes/1024:.1f} KB"
595
+ else:
596
+ stats['storage_size'] = f"{size_bytes/(1024*1024):.1f} MB"
597
+
598
+ return stats
599
+
600
+ def get_admin_logs(self):
601
+ """Get admin logs"""
602
+ cursor = self.conn.cursor()
603
+ try:
604
+ cursor.execute('''
605
+ SELECT admin_email, action, timestamp
606
+ FROM admin_logs
607
+ ORDER BY timestamp DESC
608
+ ''')
609
+ return cursor.fetchall()
610
+ except Exception as e:
611
+ print(f"Error fetching admin logs: {str(e)}")
612
+ return []
613
+
614
+ def render_dashboard(self):
615
+ """Main dashboard rendering function"""
616
+ # Apply styling
617
+ st.markdown("""
618
+ <style>
619
+ .dashboard-container {
620
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
621
+ padding: 2rem;
622
+ border-radius: 20px;
623
+ margin: -1rem -1rem 2rem -1rem;
624
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
625
+ }
626
+ .dashboard-title {
627
+ color: #4FD1C5;
628
+ font-size: 2.5rem;
629
+ margin-bottom: 0.5rem;
630
+ display: flex;
631
+ align-items: center;
632
+ gap: 1rem;
633
+ }
634
+ .dashboard-icon {
635
+ background: rgba(79, 209, 197, 0.2);
636
+ padding: 0.5rem;
637
+ border-radius: 12px;
638
+ }
639
+ .stats-grid {
640
+ display: grid;
641
+ grid-template-columns: repeat(4, 1fr);
642
+ gap: 1.5rem;
643
+ margin-top: 2rem;
644
+ }
645
+ .stat-card {
646
+ background: rgba(255, 255, 255, 0.05);
647
+ backdrop-filter: blur(10px);
648
+ padding: 1.5rem;
649
+ border-radius: 16px;
650
+ border: 1px solid rgba(255, 255, 255, 0.1);
651
+ transition: all 0.3s ease;
652
+ }
653
+ .stat-card:hover {
654
+ transform: translateY(-5px);
655
+ background: rgba(255, 255, 255, 0.1);
656
+ }
657
+ .stat-value {
658
+ font-size: 2.5rem;
659
+ font-weight: bold;
660
+ margin: 0;
661
+ color: #4FD1C5;
662
+ }
663
+ .stat-label {
664
+ font-size: 1rem;
665
+ color: rgba(255, 255, 255, 0.7);
666
+ margin: 0.5rem 0 0 0;
667
+ }
668
+ .section-title {
669
+ color: #4FD1C5;
670
+ font-size: 1.5rem;
671
+ margin: 1rem 0 0.5rem 0;
672
+ padding-bottom: 0.5rem;
673
+ border-bottom: 2px solid rgba(79, 209, 197, 0.2);
674
+ }
675
+ .chart-container {
676
+ background: rgba(255, 255, 255, 0.05);
677
+ border-radius: 16px;
678
+ padding: 1rem;
679
+ margin-bottom: 1rem;
680
+ }
681
+ .insights-grid {
682
+ display: grid;
683
+ grid-template-columns: repeat(3, 1fr);
684
+ gap: 1.5rem;
685
+ margin-top: 1rem;
686
+ }
687
+ .insight-card {
688
+ background: rgba(255, 255, 255, 0.05);
689
+ padding: 1.5rem;
690
+ border-radius: 16px;
691
+ border: 1px solid rgba(255, 255, 255, 0.1);
692
+ }
693
+ .trend-indicator {
694
+ display: inline-flex;
695
+ align-items: center;
696
+ padding: 0.25rem 0.5rem;
697
+ border-radius: 12px;
698
+ font-size: 0.875rem;
699
+ margin-left: 0.5rem;
700
+ }
701
+ .trend-up {
702
+ background: rgba(46, 204, 113, 0.2);
703
+ color: #2ecc71;
704
+ }
705
+ .trend-down {
706
+ background: rgba(231, 76, 60, 0.2);
707
+ color: #e74c3c;
708
+ }
709
+ @keyframes fadeInUp {
710
+ from {
711
+ opacity: 0;
712
+ transform: translateY(20px);
713
+ }
714
+ to {
715
+ opacity: 1;
716
+ transform: translateY(0);
717
+ }
718
+ }
719
+ .animate-fade-in {
720
+ animation: fadeInUp 0.5s ease-out forwards;
721
+ }
722
+ </style>
723
+ """, unsafe_allow_html=True)
724
+
725
+ # Dashboard Header
726
+ st.markdown("""
727
+ <div class="dashboard-container animate-fade-in">
728
+ <div style="display: flex; justify-content: space-between; align-items: center;">
729
+ <div class="dashboard-title">
730
+ <span class="dashboard-icon">📊</span>
731
+ Resume Analytics Dashboard
732
+ </div>
733
+ <div style="color: rgba(255, 255, 255, 0.7);">
734
+ Last updated: {}
735
+ </div>
736
+ </div>
737
+ """.format(datetime.now().strftime('%B %d, %Y %I:%M %p')), unsafe_allow_html=True)
738
+
739
+ # Quick Stats
740
+ stats = self.get_quick_stats()
741
+ trend_indicators = self.get_trend_indicators()
742
+
743
+ st.markdown("""
744
+ <div class="stats-grid">
745
+ <div class="stat-card">
746
+ <p class="stat-value">{}</p>
747
+ <p class="stat-label">Total Resumes</p>
748
+ <span class="trend-indicator {}">
749
+ {} {}%
750
+ </span>
751
+ </div>
752
+ <div class="stat-card">
753
+ <p class="stat-value">{}</p>
754
+ <p class="stat-label">Avg ATS Score</p>
755
+ <span class="trend-indicator {}">
756
+ {} {}%
757
+ </span>
758
+ </div>
759
+ <div class="stat-card">
760
+ <p class="stat-value">{}</p>
761
+ <p class="stat-label">High Performing</p>
762
+ <span class="trend-indicator {}">
763
+ {} {}%
764
+ </span>
765
+ </div>
766
+ <div class="stat-card">
767
+ <p class="stat-value">{}</p>
768
+ <p class="stat-label">Success Rate</p>
769
+ <span class="trend-indicator {}">
770
+ {} {}%
771
+ </span>
772
+ </div>
773
+ </div>
774
+ </div>
775
+ """.format(
776
+ stats['Total Resumes'],
777
+ trend_indicators['resumes']['class'], trend_indicators['resumes']['icon'], trend_indicators['resumes']['value'],
778
+ stats['Avg ATS Score'],
779
+ trend_indicators['ats']['class'], trend_indicators['ats']['icon'], trend_indicators['ats']['value'],
780
+ stats['High Performing'],
781
+ trend_indicators['high_performing']['class'], trend_indicators['high_performing']['icon'], trend_indicators['high_performing']['value'],
782
+ stats['Success Rate'],
783
+ trend_indicators['success_rate']['class'], trend_indicators['success_rate']['icon'], trend_indicators['success_rate']['value']
784
+ ), unsafe_allow_html=True)
785
+
786
+ # Performance Analytics Section
787
+ st.markdown('<div class="section-title">📈 Performance Analytics</div>', unsafe_allow_html=True)
788
+
789
+ col1, col2 = st.columns(2)
790
+
791
+ with col1:
792
+ st.markdown('<div class="chart-container">', unsafe_allow_html=True)
793
+ fig = self.create_enhanced_ats_gauge(float(stats['Avg ATS Score'].rstrip('%')))
794
+ st.plotly_chart(fig, use_container_width=True)
795
+ st.markdown('</div>', unsafe_allow_html=True)
796
+
797
+ with col2:
798
+ st.markdown('<div class="chart-container">', unsafe_allow_html=True)
799
+ fig = self.create_skill_distribution_chart()
800
+ st.plotly_chart(fig, use_container_width=True)
801
+ st.markdown('</div>', unsafe_allow_html=True)
802
+
803
+ # Additional Analytics
804
+ col1, col2 = st.columns(2)
805
+
806
+ with col1:
807
+ st.markdown('<div class="chart-container">', unsafe_allow_html=True)
808
+ fig = self.create_submission_trends_chart()
809
+ st.plotly_chart(fig, use_container_width=True)
810
+ st.markdown('</div>', unsafe_allow_html=True)
811
+
812
+ with col2:
813
+ st.markdown('<div class="chart-container">', unsafe_allow_html=True)
814
+ fig = self.create_job_category_chart()
815
+ st.plotly_chart(fig, use_container_width=True)
816
+ st.markdown('</div>', unsafe_allow_html=True)
817
+
818
+ # Key Insights Section
819
+ st.markdown('<div class="section-title">🎯 Key Insights</div>', unsafe_allow_html=True)
820
+ insights = self.get_detailed_insights()
821
+
822
+ st.markdown('<div class="insights-grid">', unsafe_allow_html=True)
823
+ for insight in insights:
824
+ st.markdown(f"""
825
+ <div class="insight-card">
826
+ <h3 style="color: #4FD1C5; margin-bottom: 1rem;">
827
+ {insight['icon']} {insight['title']}
828
+ </h3>
829
+ <p style="color: rgba(255, 255, 255, 0.7); margin: 0;">
830
+ {insight['description']}
831
+ </p>
832
+ <div style="margin-top: 1rem;">
833
+ <span class="trend-indicator {insight['trend_class']}">
834
+ {insight['trend_icon']} {insight['trend_value']}
835
+ </span>
836
+ </div>
837
+ </div>
838
+ """, unsafe_allow_html=True)
839
+ st.markdown('</div>', unsafe_allow_html=True)
840
+
841
+ # Admin logs section with Excel download functionality
842
+ if st.session_state.get('is_admin', False):
843
+ self.render_admin_section()
844
+
845
+ def get_trend_indicators(self):
846
+ """Get trend indicators for stats"""
847
+ cursor = self.conn.cursor()
848
+ indicators = {}
849
+
850
+ # Compare with last week's data
851
+ for metric in ['resumes', 'ats', 'high_performing', 'success_rate']:
852
+ try:
853
+ if metric == 'resumes':
854
+ cursor.execute("""
855
+ SELECT
856
+ (COUNT(*) - (
857
+ SELECT COUNT(*)
858
+ FROM resume_data
859
+ WHERE created_at < date('now', '-7 days')
860
+ )) * 100.0 /
861
+ NULLIF((
862
+ SELECT COUNT(*)
863
+ FROM resume_data
864
+ WHERE created_at < date('now', '-7 days')
865
+ ), 0)
866
+ FROM resume_data
867
+ """)
868
+ elif metric == 'ats':
869
+ cursor.execute("""
870
+ SELECT
871
+ (AVG(ats_score) - (
872
+ SELECT AVG(ats_score)
873
+ FROM resume_analysis
874
+ WHERE created_at < date('now', '-7 days')
875
+ )) * 100.0 /
876
+ NULLIF((
877
+ SELECT AVG(ats_score)
878
+ FROM resume_analysis
879
+ WHERE created_at < date('now', '-7 days')
880
+ ), 0)
881
+ FROM resume_analysis
882
+ """)
883
+
884
+ change = cursor.fetchone()[0] or 0
885
+ indicators[metric] = {
886
+ 'value': abs(round(change, 1)),
887
+ 'icon': '↑' if change >= 0 else '↓',
888
+ 'class': 'trend-up' if change >= 0 else 'trend-down'
889
+ }
890
+ except Exception:
891
+ indicators[metric] = {
892
+ 'value': 0,
893
+ 'icon': '→',
894
+ 'class': 'trend-neutral'
895
+ }
896
+
897
+ return indicators
898
+
899
+ def get_detailed_insights(self):
900
+ """Get detailed insights from the database"""
901
+ cursor = self.conn.cursor()
902
+ insights = []
903
+
904
+ # Most Successful Job Category
905
+ cursor.execute("""
906
+ SELECT target_category, AVG(ats_score) as avg_score,
907
+ COUNT(*) as submission_count
908
+ FROM resume_data rd
909
+ JOIN resume_analysis ra ON rd.id = ra.resume_id
910
+ GROUP BY target_category
911
+ ORDER BY avg_score DESC
912
+ LIMIT 1
913
+ """)
914
+ top_category = cursor.fetchone()
915
+ if top_category:
916
+ insights.append({
917
+ 'title': 'Top Performing Category',
918
+ 'icon': '🏆',
919
+ 'description': f"{top_category[0]} leads with {top_category[1]:.1f}% average ATS score across {top_category[2]} submissions",
920
+ 'trend_class': 'trend-up',
921
+ 'trend_icon': '↑',
922
+ 'trend_value': f"{top_category[1]:.1f}%"
923
+ })
924
+
925
+ # Recent Improvement
926
+ cursor.execute("""
927
+ SELECT
928
+ (SELECT AVG(ats_score) FROM resume_analysis
929
+ WHERE created_at >= date('now', '-7 days')) as recent_score,
930
+ (SELECT AVG(ats_score) FROM resume_analysis
931
+ WHERE created_at < date('now', '-7 days')) as old_score
932
+ """)
933
+ scores = cursor.fetchone()
934
+ if scores and scores[0] and scores[1]:
935
+ change = scores[0] - scores[1]
936
+ insights.append({
937
+ 'title': 'Weekly Trend',
938
+ 'icon': '📈',
939
+ 'description': f"ATS scores have {'improved' if change >= 0 else 'decreased'} by {abs(change):.1f}% in the last week",
940
+ 'trend_class': 'trend-up' if change >= 0 else 'trend-down',
941
+ 'trend_icon': '↑' if change >= 0 else '↓',
942
+ 'trend_value': f"{abs(change):.1f}%"
943
+ })
944
+
945
+ # Most Common Skills
946
+ cursor.execute("""
947
+ WITH RECURSIVE
948
+ split(skill, rest) AS (
949
+ SELECT '', skills || ','
950
+ FROM resume_data
951
+ WHERE skills IS NOT NULL
952
+ UNION ALL
953
+ SELECT
954
+ substr(rest, 0, instr(rest, ',')),
955
+ substr(rest, instr(rest, ',') + 1)
956
+ FROM split
957
+ WHERE rest <> ''
958
+ ),
959
+ cleaned_skills AS (
960
+ SELECT TRIM(REPLACE(REPLACE(skill, '[', ''), ']', '')) as skill
961
+ FROM split
962
+ WHERE skill <> ''
963
+ )
964
+ SELECT skill, COUNT(*) as count
965
+ FROM cleaned_skills
966
+ GROUP BY skill
967
+ ORDER BY count DESC
968
+ LIMIT 3
969
+ """)
970
+ top_skills = cursor.fetchall()
971
+ if top_skills:
972
+ skills_text = f"Most in-demand skills: Python ({top_skills[0][1]} resumes), Java ({top_skills[1][1]} resumes), Express ({top_skills[2][1]} resumes)"
973
+ insights.append({
974
+ 'title': 'Top Skills',
975
+ 'icon': '💡',
976
+ 'description': f"Most in-demand skills: {skills_text}",
977
+ 'trend_class': 'trend-up',
978
+ 'trend_icon': '🔝',
979
+ 'trend_value': f"Top {len(top_skills)}"
980
+ })
981
+
982
+ return insights
983
+
984
+ def get_quick_stats(self):
985
+ """Get quick statistics for the dashboard"""
986
+ cursor = self.conn.cursor()
987
+
988
+ # Total Resumes
989
+ cursor.execute("SELECT COUNT(*) FROM resume_data")
990
+ total_resumes = cursor.fetchone()[0]
991
+
992
+ # Average ATS Score
993
+ cursor.execute("SELECT AVG(ats_score) FROM resume_analysis")
994
+ avg_ats = cursor.fetchone()[0] or 0
995
+
996
+ # High Performing Resumes
997
+ cursor.execute("SELECT COUNT(*) FROM resume_analysis WHERE ats_score >= 70")
998
+ high_performing = cursor.fetchone()[0]
999
+
1000
+ # Success Rate
1001
+ success_rate = (high_performing / total_resumes * 100) if total_resumes > 0 else 0
1002
+
1003
+ return {
1004
+ "Total Resumes": f"{total_resumes:,}",
1005
+ "Avg ATS Score": f"{avg_ats:.1f}%",
1006
+ "High Performing": f"{high_performing:,}",
1007
+ "Success Rate": f"{success_rate:.1f}%"
1008
+ }
1009
+
1010
+ def create_enhanced_ats_gauge(self, value):
1011
+ """Create an enhanced ATS score gauge chart"""
1012
+ reference = 70 # Target score
1013
+ delta = value - reference
1014
+
1015
+ fig = go.Figure(go.Indicator(
1016
+ mode="gauge+number+delta",
1017
+ value=value,
1018
+ delta={
1019
+ 'reference': reference,
1020
+ 'valueformat': '.1f',
1021
+ 'increasing': {'color': '#2ecc71'},
1022
+ 'decreasing': {'color': '#e74c3c'}
1023
+ },
1024
+ number={'font': {'size': 40, 'color': 'white'}},
1025
+ gauge={
1026
+ 'axis': {
1027
+ 'range': [0, 100],
1028
+ 'tickwidth': 1,
1029
+ 'tickcolor': 'white',
1030
+ 'tickfont': {'color': 'white'}
1031
+ },
1032
+ 'bar': {'color': '#3498db'},
1033
+ 'bgcolor': 'rgba(0,0,0,0)',
1034
+ 'borderwidth': 2,
1035
+ 'bordercolor': 'white',
1036
+ 'steps': [
1037
+ {'range': [0, 40], 'color': '#e74c3c'},
1038
+ {'range': [40, 70], 'color': '#f1c40f'},
1039
+ {'range': [70, 100], 'color': '#2ecc71'}
1040
+ ],
1041
+ 'threshold': {
1042
+ 'line': {'color': 'white', 'width': 4},
1043
+ 'thickness': 0.75,
1044
+ 'value': reference
1045
+ }
1046
+ }
1047
+ ))
1048
+
1049
+ fig.update_layout(
1050
+ title={
1051
+ 'text': 'ATS Score Performance',
1052
+ 'font': {'size': 24, 'color': 'white'},
1053
+ 'y': 0.85
1054
+ },
1055
+ paper_bgcolor='rgba(0,0,0,0)',
1056
+ plot_bgcolor='rgba(0,0,0,0)',
1057
+ font={'color': 'white'},
1058
+ height=350,
1059
+ margin=dict(l=20, r=20, t=80, b=20)
1060
+ )
1061
+
1062
+ return fig
1063
+
1064
+ def create_skill_distribution_chart(self):
1065
+ """Create a skill distribution chart"""
1066
+ categories, counts = self.get_skill_distribution()
1067
+
1068
+ fig = go.Figure(data=[
1069
+ go.Bar(
1070
+ x=categories,
1071
+ y=counts,
1072
+ marker_color=self.colors['info'],
1073
+ text=counts,
1074
+ textposition='auto',
1075
+ )
1076
+ ])
1077
+
1078
+ fig.update_layout(
1079
+ title={
1080
+ 'text': 'Skill Distribution',
1081
+ 'y':0.95,
1082
+ 'x':0.5,
1083
+ 'xanchor': 'center',
1084
+ 'yanchor': 'top'
1085
+ },
1086
+ height=350,
1087
+ plot_bgcolor='rgba(0,0,0,0)',
1088
+ paper_bgcolor='rgba(0,0,0,0)',
1089
+ font=dict(color=self.colors['text']),
1090
+ margin=dict(l=40, r=40, t=60, b=40),
1091
+ xaxis=dict(
1092
+ showgrid=False,
1093
+ showline=True,
1094
+ linecolor='rgba(255,255,255,0.2)',
1095
+ tickfont=dict(size=12)
1096
+ ),
1097
+ yaxis=dict(
1098
+ showgrid=True,
1099
+ gridcolor='rgba(255,255,255,0.1)',
1100
+ zeroline=False
1101
+ ),
1102
+ bargap=0.3
1103
+ )
1104
+ return fig
1105
+
1106
+ def create_submission_trends_chart(self):
1107
+ """Create a weekly submission trend chart"""
1108
+ dates, submissions = self.get_weekly_trends()
1109
+ fig = go.Figure()
1110
+ fig.add_trace(go.Scatter(
1111
+ x=dates,
1112
+ y=submissions,
1113
+ mode='lines+markers',
1114
+ line=dict(color=self.colors['info'], width=3),
1115
+ marker=dict(size=8, color=self.colors['info'])
1116
+ ))
1117
+
1118
+ fig.update_layout(
1119
+ title="Weekly Submission Pattern",
1120
+ paper_bgcolor=self.colors['card'],
1121
+ plot_bgcolor=self.colors['card'],
1122
+ font={'color': self.colors['text']},
1123
+ height=300,
1124
+ margin=dict(l=20, r=20, t=50, b=20)
1125
+ )
1126
+ fig.update_xaxes(title_text="Day of Week", color=self.colors['text'])
1127
+ fig.update_yaxes(title_text="Number of Submissions", color=self.colors['text'])
1128
+
1129
+ return fig
1130
+
1131
+ def create_job_category_chart(self):
1132
+ """Create a success rate by category chart"""
1133
+ categories, rates = self.get_job_category_stats()
1134
+ fig = go.Figure(go.Bar(
1135
+ x=categories,
1136
+ y=rates,
1137
+ marker_color=[self.colors['success'], self.colors['info'],
1138
+ self.colors['warning'], self.colors['purple'],
1139
+ self.colors['secondary']],
1140
+ text=[f"{rate}%" for rate in rates],
1141
+ textposition='auto',
1142
+ ))
1143
+
1144
+ fig.update_layout(
1145
+ title="Success Rate by Job Category",
1146
+ paper_bgcolor=self.colors['card'],
1147
+ plot_bgcolor=self.colors['card'],
1148
+ font={'color': self.colors['text']},
1149
+ height=300,
1150
+ margin=dict(l=20, r=20, t=50, b=20)
1151
+ )
1152
+ fig.update_xaxes(title_text="Job Category", color=self.colors['text'])
1153
+ fig.update_yaxes(title_text="Success Rate (%)", color=self.colors['text'])
1154
+
1155
+ return fig
feedback/__pycache__/feedback.cpython-311.pyc ADDED
Binary file (14.6 kB). View file
 
feedback/feedback.db ADDED
Binary file (12.3 kB). View file
 
feedback/feedback.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sqlite3
3
+ from datetime import datetime
4
+ import pandas as pd
5
+ import time
6
+
7
+ class FeedbackManager:
8
+ def __init__(self):
9
+ self.db_path = "feedback/feedback.db"
10
+ self.setup_database()
11
+
12
+ def setup_database(self):
13
+ """Create feedback table if it doesn't exist"""
14
+ conn = sqlite3.connect(self.db_path)
15
+ c = conn.cursor()
16
+ c.execute('''
17
+ CREATE TABLE IF NOT EXISTS feedback (
18
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19
+ rating INTEGER,
20
+ usability_score INTEGER,
21
+ feature_satisfaction INTEGER,
22
+ missing_features TEXT,
23
+ improvement_suggestions TEXT,
24
+ user_experience TEXT,
25
+ timestamp DATETIME
26
+ )
27
+ ''')
28
+ conn.commit()
29
+ conn.close()
30
+
31
+ def save_feedback(self, feedback_data):
32
+ """Save feedback to database"""
33
+ conn = sqlite3.connect(self.db_path)
34
+ c = conn.cursor()
35
+ c.execute('''
36
+ INSERT INTO feedback (
37
+ rating, usability_score, feature_satisfaction,
38
+ missing_features, improvement_suggestions,
39
+ user_experience, timestamp
40
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
41
+ ''', (
42
+ feedback_data['rating'],
43
+ feedback_data['usability_score'],
44
+ feedback_data['feature_satisfaction'],
45
+ feedback_data['missing_features'],
46
+ feedback_data['improvement_suggestions'],
47
+ feedback_data['user_experience'],
48
+ datetime.now()
49
+ ))
50
+ conn.commit()
51
+ conn.close()
52
+
53
+ def get_feedback_stats(self):
54
+ """Get feedback statistics"""
55
+ conn = sqlite3.connect(self.db_path)
56
+ df = pd.read_sql_query("SELECT * FROM feedback", conn)
57
+ conn.close()
58
+
59
+ if df.empty:
60
+ return {
61
+ 'avg_rating': 0,
62
+ 'avg_usability': 0,
63
+ 'avg_satisfaction': 0,
64
+ 'total_responses': 0
65
+ }
66
+
67
+ return {
68
+ 'avg_rating': df['rating'].mean(),
69
+ 'avg_usability': df['usability_score'].mean(),
70
+ 'avg_satisfaction': df['feature_satisfaction'].mean(),
71
+ 'total_responses': len(df)
72
+ }
73
+
74
+ def render_feedback_form(self):
75
+ """Render the feedback form"""
76
+ st.markdown("""
77
+ <style>
78
+ @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css');
79
+
80
+ .feedback-container {
81
+ background: rgba(255, 255, 255, 0.05);
82
+ backdrop-filter: blur(10px);
83
+ padding: 30px;
84
+ border-radius: 20px;
85
+ margin: 20px 0;
86
+ border: 1px solid rgba(255, 255, 255, 0.1);
87
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
88
+ }
89
+
90
+ .feedback-header {
91
+ color: #E0E0E0;
92
+ font-size: 1.5em;
93
+ font-weight: 600;
94
+ margin-bottom: 25px;
95
+ text-align: center;
96
+ padding: 15px;
97
+ background: linear-gradient(135deg, #4CAF50, #2196F3);
98
+ border-radius: 12px;
99
+ box-shadow: 0 4px 15px rgba(76, 175, 80, 0.2);
100
+ }
101
+
102
+ .feedback-section {
103
+ margin: 20px 0;
104
+ padding: 20px;
105
+ border-radius: 15px;
106
+ background: rgba(255, 255, 255, 0.03);
107
+ border: 1px solid rgba(255, 255, 255, 0.1);
108
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
109
+ }
110
+
111
+ .feedback-section:hover {
112
+ transform: translateY(-5px);
113
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
114
+ }
115
+
116
+ .feedback-label {
117
+ color: #E0E0E0;
118
+ font-size: 1.1em;
119
+ font-weight: 500;
120
+ margin-bottom: 10px;
121
+ }
122
+
123
+ .star-rating {
124
+ font-size: 24px;
125
+ color: #FFD700;
126
+ cursor: pointer;
127
+ transition: transform 0.2s ease;
128
+ }
129
+
130
+ .star-rating:hover {
131
+ transform: scale(1.1);
132
+ }
133
+
134
+ .rating-container {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 10px;
138
+ margin: 15px 0;
139
+ }
140
+
141
+ .submit-button {
142
+ background: linear-gradient(135deg, #4CAF50, #2196F3);
143
+ color: white;
144
+ padding: 12px 25px;
145
+ border: none;
146
+ border-radius: 8px;
147
+ font-weight: 600;
148
+ cursor: pointer;
149
+ transition: all 0.3s ease;
150
+ text-transform: uppercase;
151
+ letter-spacing: 1px;
152
+ width: 100%;
153
+ margin-top: 20px;
154
+ }
155
+
156
+ .submit-button:hover {
157
+ transform: translateY(-2px);
158
+ box-shadow: 0 5px 15px rgba(33, 150, 243, 0.3);
159
+ }
160
+
161
+ .textarea-container {
162
+ background: rgba(255, 255, 255, 0.03);
163
+ border: 1px solid rgba(255, 255, 255, 0.1);
164
+ border-radius: 8px;
165
+ padding: 10px;
166
+ margin-top: 10px;
167
+ }
168
+
169
+ .textarea-container textarea {
170
+ width: 100%;
171
+ min-height: 100px;
172
+ background: transparent;
173
+ border: none;
174
+ color: #E0E0E0;
175
+ font-size: 1em;
176
+ resize: vertical;
177
+ }
178
+ </style>
179
+ """, unsafe_allow_html=True)
180
+
181
+ st.markdown('<div class="feedback-container">', unsafe_allow_html=True)
182
+ st.markdown('<h2 class="feedback-header">📝 Share Your Feedback</h2>', unsafe_allow_html=True)
183
+
184
+ # Overall Rating
185
+ st.markdown('<div class="feedback-section">', unsafe_allow_html=True)
186
+ st.markdown('<label class="feedback-label">Overall Experience Rating</label>', unsafe_allow_html=True)
187
+ rating = st.slider("Overall Rating", 1, 5, 5, help="Rate your overall experience with the app", label_visibility="collapsed")
188
+ st.markdown(f'<div class="rating-container">{"⭐" * rating}</div>', unsafe_allow_html=True)
189
+ st.markdown('</div>', unsafe_allow_html=True)
190
+
191
+ # Usability Score
192
+ st.markdown('<div class="feedback-section">', unsafe_allow_html=True)
193
+ st.markdown('<label class="feedback-label">How easy was it to use our app?</label>', unsafe_allow_html=True)
194
+ usability_score = st.slider("Usability Score", 1, 5, 5, help="Rate the app's ease of use", label_visibility="collapsed")
195
+ st.markdown(f'<div class="rating-container">{"⭐" * usability_score}</div>', unsafe_allow_html=True)
196
+ st.markdown('</div>', unsafe_allow_html=True)
197
+
198
+ # Feature Satisfaction
199
+ st.markdown('<div class="feedback-section">', unsafe_allow_html=True)
200
+ st.markdown('<label class="feedback-label">How satisfied are you with our features?</label>', unsafe_allow_html=True)
201
+ feature_satisfaction = st.slider("Feature Satisfaction", 1, 5, 5, help="Rate your satisfaction with the app's features", label_visibility="collapsed")
202
+ st.markdown(f'<div class="rating-container">{"⭐" * feature_satisfaction}</div>', unsafe_allow_html=True)
203
+ st.markdown('</div>', unsafe_allow_html=True)
204
+
205
+ # Text Feedback
206
+ st.markdown('<div class="feedback-section">', unsafe_allow_html=True)
207
+ st.markdown('<label class="feedback-label">What features would you like to see added?</label>', unsafe_allow_html=True)
208
+ missing_features = st.text_area("Missing Features", placeholder="Share your feature requests...", label_visibility="collapsed")
209
+
210
+ st.markdown('<label class="feedback-label">How can we improve?</label>', unsafe_allow_html=True)
211
+ improvement_suggestions = st.text_area("Improvement Suggestions", placeholder="Your suggestions for improvement...", label_visibility="collapsed")
212
+
213
+ st.markdown('<label class="feedback-label">Tell us about your experience</label>', unsafe_allow_html=True)
214
+ user_experience = st.text_area("User Experience", placeholder="Share your experience with us...", label_visibility="collapsed")
215
+ st.markdown('</div>', unsafe_allow_html=True)
216
+
217
+ # Submit Button
218
+ if st.button("Submit Feedback", key="submit_feedback"):
219
+ try:
220
+ # Create progress bar
221
+ progress_bar = st.progress(0)
222
+ status_text = st.empty()
223
+
224
+ # Simulate processing with animation
225
+ for i in range(100):
226
+ progress_bar.progress(i + 1)
227
+ if i < 30:
228
+ status_text.text("Processing feedback... 📝")
229
+ elif i < 60:
230
+ status_text.text("Analyzing responses... 🔍")
231
+ elif i < 90:
232
+ status_text.text("Saving to database... 💾")
233
+ else:
234
+ status_text.text("Finalizing... ✨")
235
+ time.sleep(0.01)
236
+
237
+ # Save feedback
238
+ feedback_data = {
239
+ 'rating': rating,
240
+ 'usability_score': usability_score,
241
+ 'feature_satisfaction': feature_satisfaction,
242
+ 'missing_features': missing_features,
243
+ 'improvement_suggestions': improvement_suggestions,
244
+ 'user_experience': user_experience
245
+ }
246
+ self.save_feedback(feedback_data)
247
+
248
+ # Clear progress elements
249
+ progress_bar.empty()
250
+ status_text.empty()
251
+
252
+ # Show success message with animation
253
+ success_container = st.empty()
254
+ success_container.markdown("""
255
+ <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, rgba(76, 175, 80, 0.1), rgba(33, 150, 243, 0.1)); border-radius: 10px;">
256
+ <h2 style="color: #4CAF50;">Thank You! 🎉</h2>
257
+ <p style="color: #E0E0E0;">Your feedback helps us improve Smart Resume AI</p>
258
+ </div>
259
+ """, unsafe_allow_html=True)
260
+
261
+ # Show balloons animation
262
+ st.balloons()
263
+
264
+ # Keep success message visible
265
+ time.sleep(2)
266
+
267
+ except Exception as e:
268
+ st.error(f"Error submitting feedback: {str(e)}")
269
+
270
+ def render_feedback_stats(self):
271
+ """Render feedback statistics"""
272
+ stats = self.get_feedback_stats()
273
+
274
+ st.markdown("""
275
+ <div style="text-align: center; padding: 15px; background: linear-gradient(90deg, rgba(76, 175, 80, 0.1), rgba(33, 150, 243, 0.1)); border-radius: 10px; margin-bottom: 20px;">
276
+ <h3 style="color: #E0E0E0;">Feedback Overview 📊</h3>
277
+ </div>
278
+ """, unsafe_allow_html=True)
279
+
280
+ cols = st.columns(4)
281
+ metrics = [
282
+ {"label": "Total Responses", "value": f"{stats['total_responses']:,}", "delta": "↗"},
283
+ {"label": "Avg Rating", "value": f"{stats['avg_rating']:.1f}/5.0", "delta": "⭐"},
284
+ {"label": "Usability Score", "value": f"{stats['avg_usability']:.1f}/5.0", "delta": "🎯"},
285
+ {"label": "Satisfaction", "value": f"{stats['avg_satisfaction']:.1f}/5.0", "delta": "😊"}
286
+ ]
287
+
288
+ for col, metric in zip(cols, metrics):
289
+ col.markdown(f"""
290
+ <div style="background: rgba(255, 255, 255, 0.05); padding: 15px; border-radius: 8px; text-align: center;">
291
+ <div style="color: #B0B0B0; font-size: 0.9em;">{metric['label']}</div>
292
+ <div style="font-size: 1.5em; color: #4CAF50; margin: 5px 0;">{metric['value']}</div>
293
+ <div style="color: #E0E0E0; font-size: 1.2em;">{metric['delta']}</div>
294
+ </div>
295
+ """, unsafe_allow_html=True)
feedback/schema.sql ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ CREATE TABLE IF NOT EXISTS feedback (
2
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3
+ rating INTEGER NOT NULL,
4
+ usability_score INTEGER NOT NULL,
5
+ feature_satisfaction INTEGER NOT NULL,
6
+ missing_features TEXT,
7
+ improvement_suggestions TEXT,
8
+ user_experience TEXT,
9
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
10
+ );
jobs/__pycache__/companies.cpython-311.pyc ADDED
Binary file (6.53 kB). View file
 
jobs/__pycache__/job_portals.cpython-311.pyc ADDED
Binary file (10.1 kB). View file
 
jobs/__pycache__/job_search.cpython-311.pyc ADDED
Binary file (26.4 kB). View file
 
jobs/__pycache__/linkedin_scraper.cpython-311.pyc ADDED
Binary file (32.2 kB). View file
 
jobs/__pycache__/suggestions.cpython-311.pyc ADDED
Binary file (9.63 kB). View file
 
jobs/__pycache__/webdriver_utils.cpython-311.pyc ADDED
Binary file (10.2 kB). View file
 
jobs/companies.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Company data and market insights for job search"""
2
+
3
+ FEATURED_COMPANIES = {
4
+ "tech": [
5
+ {
6
+ "name": "Google",
7
+ "icon": "fab fa-google",
8
+ "color": "#4285F4",
9
+ "careers_url": "https://careers.google.com",
10
+ "description": "Leading technology company known for search, cloud, and innovation",
11
+ "categories": ["Software", "AI/ML", "Cloud", "Data Science"]
12
+ },
13
+ {
14
+ "name": "Microsoft",
15
+ "icon": "fab fa-microsoft",
16
+ "color": "#00A4EF",
17
+ "careers_url": "https://careers.microsoft.com",
18
+ "description": "Global leader in software, cloud, and enterprise solutions",
19
+ "categories": ["Software", "Cloud", "Gaming", "Enterprise"]
20
+ },
21
+ {
22
+ "name": "Amazon",
23
+ "icon": "fab fa-amazon",
24
+ "color": "#FF9900",
25
+ "careers_url": "https://www.amazon.jobs",
26
+ "description": "E-commerce and cloud computing giant",
27
+ "categories": ["Software", "Operations", "Cloud", "Retail"]
28
+ },
29
+ {
30
+ "name": "Apple",
31
+ "icon": "fab fa-apple",
32
+ "color": "#555555",
33
+ "careers_url": "https://www.apple.com/careers",
34
+ "description": "Innovation leader in consumer technology",
35
+ "categories": ["Software", "Hardware", "Design", "AI/ML"]
36
+ },
37
+ {
38
+ "name": "Facebook",
39
+ "icon": "fab fa-facebook",
40
+ "color": "#1877F2",
41
+ "careers_url": "https://www.metacareers.com/",
42
+ "description": "Social media and technology company",
43
+ "categories": ["Software", "Marketing", "Networking", "AI/ML"]
44
+ },
45
+ {
46
+ "name": "Netflix",
47
+ "icon": "fas fa-play-circle",
48
+ "color": "#E50914",
49
+ "careers_url": "https://explore.jobs.netflix.net/careers",
50
+ "description": "Streaming media company",
51
+ "categories": ["Software", "Marketing", "Design", "Service"],
52
+ "logo_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Netflix_2015_logo.svg/1920px-Netflix_2015_logo.svg.png",
53
+ "website": "https://jobs.netflix.com/",
54
+ "industry": "Entertainment & Technology"
55
+ }
56
+ ],
57
+ "indian_tech": [
58
+ {
59
+ "name": "TCS",
60
+ "icon": "fas fa-building",
61
+ "color": "#0070C0",
62
+ "careers_url": "https://www.tcs.com/careers",
63
+ "description": "India's largest IT services company",
64
+ "categories": ["IT Services", "Consulting", "Digital"]
65
+ },
66
+ {
67
+ "name": "Infosys",
68
+ "icon": "fas fa-building",
69
+ "color": "#007CC3",
70
+ "careers_url": "https://www.infosys.com/careers",
71
+ "description": "Global leader in digital services and consulting",
72
+ "categories": ["IT Services", "Consulting", "Digital"]
73
+ },
74
+ {
75
+ "name": "Wipro",
76
+ "icon": "fas fa-building",
77
+ "color": "#341F65",
78
+ "careers_url": "https://careers.wipro.com",
79
+ "description": "Leading global information technology company",
80
+ "categories": ["IT Services", "Consulting", "Digital"]
81
+ },
82
+ {
83
+ "name": "HCL",
84
+ "icon": "fas fa-building",
85
+ "color": "#0075C9",
86
+ "careers_url": "https://www.hcltech.com/careers",
87
+ "description": "Global technology company",
88
+ "categories": ["IT Services", "Engineering", "Digital"]
89
+ }
90
+ ],
91
+ "global_corps": [
92
+ {
93
+ "name": "IBM",
94
+ "icon": "fas fa-server",
95
+ "color": "#1F70C1",
96
+ "careers_url": "https://www.ibm.com/careers",
97
+ "description": "Global leader in technology and consulting",
98
+ "categories": ["Software", "Consulting", "AI/ML", "Cloud"],
99
+ "logo_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/51/IBM_logo.svg/1920px-IBM_logo.svg.png",
100
+ "website": "https://www.ibm.com/careers/",
101
+ "industry": "Technology & Consulting"
102
+ },
103
+ {
104
+ "name": "Accenture",
105
+ "icon": "fas fa-building",
106
+ "color": "#A100FF",
107
+ "careers_url": "https://www.accenture.com/careers",
108
+ "description": "Global professional services company",
109
+ "categories": ["Consulting", "Technology", "Digital"]
110
+ },
111
+ {
112
+ "name": "Cognizant",
113
+ "icon": "fas fa-building",
114
+ "color": "#1299D8",
115
+ "careers_url": "https://careers.cognizant.com",
116
+ "description": "Leading professional services company",
117
+ "categories": ["IT Services", "Consulting", "Digital"]
118
+ }
119
+ ]
120
+ }
121
+
122
+ JOB_MARKET_INSIGHTS = {
123
+ "trending_skills": [
124
+ {"name": "Artificial Intelligence", "growth": "+45%", "icon": "fas fa-brain"},
125
+ {"name": "Cloud Computing", "growth": "+38%", "icon": "fas fa-cloud"},
126
+ {"name": "Data Science", "growth": "+35%", "icon": "fas fa-chart-line"},
127
+ {"name": "Cybersecurity", "growth": "+32%", "icon": "fas fa-shield-alt"},
128
+ {"name": "DevOps", "growth": "+30%", "icon": "fas fa-code-branch"},
129
+ {"name": "Machine Learning", "growth": "+28%", "icon": "fas fa-robot"},
130
+ {"name": "Blockchain", "growth": "+25%", "icon": "fas fa-lock"},
131
+ {"name": "Big Data", "growth": "+23%", "icon": "fas fa-database"},
132
+ {"name": "Internet of Things", "growth": "+21%", "icon": "fas fa-wifi"}
133
+ ],
134
+ "top_locations": [
135
+ {"name": "Bangalore", "jobs": "50,000+", "icon": "fas fa-city"},
136
+ {"name": "Mumbai", "jobs": "35,000+", "icon": "fas fa-city"},
137
+ {"name": "Delhi NCR", "jobs": "30,000+", "icon": "fas fa-city"},
138
+ {"name": "Hyderabad", "jobs": "25,000+", "icon": "fas fa-city"},
139
+ {"name": "Pune", "jobs": "20,000+", "icon": "fas fa-city"},
140
+ {"name": "Chennai", "jobs": "15,000+", "icon": "fas fa-city"},
141
+ {"name": "Noida", "jobs": "10,000+", "icon": "fas fa-city"},
142
+ {"name": "Vadodara", "jobs": "7,000+", "icon": "fas fa-city"},
143
+ {"name": "Ahmedabad", "jobs": "6,000+", "icon": "fas fa-city"},
144
+ {"name": "Remote", "jobs": "3,000+", "icon": "fas fa-globe-americas"},
145
+ ],
146
+ "salary_insights": [
147
+ {"role": "Machine Learning Engineer", "range": "10-35 LPA", "experience": "0-5 years"},
148
+ {"role": "Big Data Engineer", "range": "8-30 LPA", "experience": "0-5 years"},
149
+ {"role": "Software Engineer", "range": "5-25 LPA", "experience": "0-5 years"},
150
+ {"role": "Data Scientist", "range": "8-30 LPA", "experience": "0-5 years"},
151
+ {"role": "DevOps Engineer", "range": "6-28 LPA", "experience": "0-5 years"},
152
+ {"role": "UI/UX Designer", "range": "5-25 LPA", "experience": "0-5 years"},
153
+ {"role": "Full Stack Developer", "range": "8-30 LPA", "experience": "0-5 years"},
154
+ {"role": "C++/C#/Python/Java Developer", "range": "6-26 LPA", "experience": "0-5 years"},
155
+ {"role": "Django Developer", "range": "7-27 LPA", "experience": "0-5 years"},
156
+ {"role": "Cloud Engineer", "range": "6-26 LPA", "experience": "0-5 years"},
157
+ {"role": "Google Cloud/AWS/Azure Engineer", "range": "6-26 LPA", "experience": "0-5 years"},
158
+ {"role": "Salesforce Engineer", "range": "6-26 LPA", "experience": "0-5 years"},
159
+ ]
160
+ }
161
+
162
+ def get_featured_companies(category=None):
163
+ """Get featured companies, optionally filtered by category"""
164
+ if category and category in FEATURED_COMPANIES:
165
+ return FEATURED_COMPANIES[category]
166
+ return [company for companies in FEATURED_COMPANIES.values() for company in companies]
167
+
168
+ def get_market_insights():
169
+ """Get job market insights"""
170
+ return JOB_MARKET_INSIGHTS
171
+
172
+ def get_company_info(company_name):
173
+ """Get company information by name"""
174
+ for companies in FEATURED_COMPANIES.values():
175
+ for company in companies:
176
+ if company["name"] == company_name:
177
+ return company
178
+ return None
179
+
180
+ def get_companies_by_industry(industry):
181
+ """Get list of companies by industry"""
182
+ companies = []
183
+ for companies_list in FEATURED_COMPANIES.values():
184
+ for company in companies_list:
185
+ if "industry" in company and company["industry"] == industry:
186
+ companies.append(company)
187
+ return companies
jobs/job_portals.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module for handling job portal integrations"""
2
+ import urllib.parse
3
+ from typing import Dict, List
4
+ from .suggestions import LOCATION_SUGGESTIONS, get_cities_by_state
5
+
6
+ class JobPortal:
7
+ """Class for searching jobs across multiple job portals"""
8
+
9
+ def __init__(self):
10
+ """Initialize job portal URLs and parameters"""
11
+ self.portals = [
12
+ {
13
+ "name": "LinkedIn",
14
+ "icon": "fab fa-linkedin",
15
+ "color": "#0A66C2",
16
+ "url": "https://www.linkedin.com/jobs/search/?keywords={}&location={}&f_E={}",
17
+ "experience_param": ""
18
+ },
19
+ {
20
+ "name": "Naukri",
21
+ "icon": "fas fa-building",
22
+ "color": "#FF7555",
23
+ "url": "https://www.naukri.com/{}-jobs-in-{}?experience={}",
24
+ "experience_param": ""
25
+ },
26
+ {
27
+ "name": "Foundit (Monster)",
28
+ "icon": "fas fa-globe",
29
+ "color": "#5D3FD3",
30
+ "url": "https://www.foundit.in/srp/results?query={}&locations={}",
31
+ "experience_param": ""
32
+ },
33
+ {
34
+ "name": "FreshersWorld",
35
+ "icon": "fas fa-graduation-cap",
36
+ "color": "#003A9B",
37
+ "url": "https://www.freshersworld.com/jobs/jobsearch/{}-jobs-in-{}",
38
+ "experience_param": ""
39
+ },
40
+ {
41
+ "name": "TimesJobs",
42
+ "icon": "fas fa-briefcase",
43
+ "color": "#003A9B",
44
+ "url": "https://www.timesjobs.com/candidate/job-search.html?searchType=personalizedSearch&from=submit&txtKeywords={}&txtLocation={}",
45
+ "experience_param": ""
46
+ },
47
+ {
48
+ "name": "Instahyre",
49
+ "icon": "fas fa-user-tie",
50
+ "color": "#003A9B",
51
+ "url": "https://www.instahyre.com/{}-jobs-in-{}",
52
+ "experience_param": ""
53
+ },
54
+ {
55
+ "name": "Indeed",
56
+ "icon": "fas fa-search-dollar",
57
+ "color": "#003A9B",
58
+ "url": "https://in.indeed.com/jobs?q={}&l={}&explvl={}",
59
+ "experience_param": ""
60
+ }
61
+ ]
62
+
63
+ def get_portal_list(self) -> List[Dict]:
64
+ """Get list of available job portals"""
65
+ return self.portals
66
+
67
+ def format_query(self, query: str) -> str:
68
+ """Format query string for URLs"""
69
+ # Replace spaces with appropriate characters based on portal
70
+ return query.replace(" ", "+")
71
+
72
+ def format_location(self, location: str) -> str:
73
+ """Format location string for URLs"""
74
+ if not location:
75
+ return ""
76
+
77
+ # Check if location is a state
78
+ location = location.strip()
79
+ is_state = False
80
+
81
+ # Check if the location is a state
82
+ for loc in LOCATION_SUGGESTIONS:
83
+ if loc.get("type") == "state" and loc.get("text").lower() == location.lower():
84
+ is_state = True
85
+ break
86
+
87
+ # If it's a state, get the major city in that state for better job results
88
+ if is_state:
89
+ cities = get_cities_by_state(location)
90
+ if cities:
91
+ # Use the first city in the state (usually the capital or major city)
92
+ location = cities[0]["text"]
93
+
94
+ # Convert to lowercase and replace spaces with hyphens
95
+ return location.lower().replace(" ", "-")
96
+
97
+ def format_job_title(self, title: str) -> str:
98
+ """Format job title for URLs"""
99
+ # Remove common words and special characters
100
+ title = title.lower()
101
+ title = title.replace("developer", "").replace("engineer", "").strip()
102
+ title = title.replace(" ", "-")
103
+ return title.strip("-")
104
+
105
+ def format_experience(self, experience: str) -> tuple:
106
+ """Format experience for different job portals"""
107
+ if not experience or experience == "all":
108
+ return "", "0", "0", "entry"
109
+
110
+ try:
111
+ # Handle dictionary input
112
+ if isinstance(experience, dict):
113
+ exp_id = experience.get('id', 'all')
114
+ if exp_id == 'all':
115
+ return "", "0", "0", "entry"
116
+
117
+ # Split experience range (e.g., "1-3" -> ["1", "3"])
118
+ if "-" in exp_id:
119
+ exp_min, exp_max = exp_id.split('-')
120
+ if exp_max == "+":
121
+ exp_max = "15" # Set a reasonable maximum for 10+ years
122
+ else:
123
+ # Handle "fresher" or other non-range values
124
+ exp_min = "0"
125
+ exp_max = "1"
126
+
127
+ # Map to portal-specific format
128
+ exp_level = {
129
+ "fresher": "0",
130
+ "0-1": "0",
131
+ "1-3": "1",
132
+ "3-5": "2",
133
+ "5-7": "3",
134
+ "7-10": "4",
135
+ "10+": "5"
136
+ }.get(exp_id, "0")
137
+
138
+ return exp_level, exp_min, exp_max, "entry" if exp_min == "0" else "experienced"
139
+
140
+ return "", "0", "0", "entry"
141
+
142
+ except Exception as e:
143
+ print(f"Error formatting experience: {str(e)}")
144
+ return "", "0", "0", "entry"
145
+
146
+ def get_experience_param(self, portal_name, experience):
147
+ """Get experience parameter for specific portal"""
148
+ experience_id = experience.get("id", "all")
149
+
150
+ if experience_id == "all":
151
+ if portal_name == "Foundit (Monster)":
152
+ return ""
153
+ elif portal_name == "Naukri":
154
+ return ""
155
+ elif portal_name == "LinkedIn":
156
+ return ""
157
+ elif portal_name == "Indeed":
158
+ return "entry_level"
159
+
160
+ if portal_name == "Foundit (Monster)":
161
+ if experience_id == "fresher":
162
+ return "&experienceRanges=0~0"
163
+ elif experience_id == "0-1":
164
+ return "&experienceRanges=0~1"
165
+ elif experience_id == "1-3":
166
+ return "&experienceRanges=1~3"
167
+ elif experience_id == "3-5":
168
+ return "&experienceRanges=3~5"
169
+ elif experience_id == "5-7":
170
+ return "&experienceRanges=5~7"
171
+ elif experience_id == "7-10":
172
+ return "&experienceRanges=7~10"
173
+ elif experience_id == "10+":
174
+ return "&experienceRanges=10~50"
175
+
176
+ elif portal_name == "Naukri":
177
+ if experience_id == "fresher":
178
+ return "0"
179
+ elif experience_id == "0-1":
180
+ return "0-1"
181
+ elif experience_id == "1-3":
182
+ return "1-3"
183
+ elif experience_id == "3-5":
184
+ return "3-5"
185
+ elif experience_id == "5-7":
186
+ return "5-7"
187
+ elif experience_id == "7-10":
188
+ return "7-10"
189
+ elif experience_id == "10+":
190
+ return "10-50"
191
+
192
+ elif portal_name == "LinkedIn":
193
+ if experience_id == "fresher" or experience_id == "0-1":
194
+ return "1" # Entry level
195
+ elif experience_id == "1-3" or experience_id == "3-5":
196
+ return "2" # Associate
197
+ elif experience_id == "5-7" or experience_id == "7-10":
198
+ return "3" # Mid-Senior level
199
+ elif experience_id == "10+":
200
+ return "4" # Director
201
+
202
+ elif portal_name == "Indeed":
203
+ if experience_id == "fresher" or experience_id == "0-1":
204
+ return "entry_level"
205
+ elif experience_id == "1-3" or experience_id == "3-5":
206
+ return "mid_level"
207
+ elif experience_id == "5-7" or experience_id == "7-10" or experience_id == "10+":
208
+ return "senior_level"
209
+
210
+ return ""
211
+
212
+ def search_jobs(self, job_title, location, experience=None):
213
+ """Search jobs across multiple portals"""
214
+ if not experience:
215
+ experience = {"id": "all", "text": "All Levels"}
216
+
217
+ results = []
218
+
219
+ for portal in self.portals:
220
+ portal_name = portal["name"]
221
+
222
+ # Format job title based on portal
223
+ if portal_name == "Foundit (Monster)":
224
+ formatted_job = job_title.replace(' ', '+')
225
+ elif portal_name == "Naukri":
226
+ formatted_job = self.format_job_title(job_title)
227
+ elif portal_name == "Glassdoor":
228
+ # For Glassdoor, format job title with + signs
229
+ formatted_job = job_title.replace(' ', '+')
230
+ elif portal_name in ["LinkedIn", "Indeed", "TimesJobs"]:
231
+ formatted_job = job_title.replace(' ', '%20')
232
+ elif portal_name in ["FreshersWorld", "Instahyre"]:
233
+ formatted_job = job_title.lower().replace(' ', '-')
234
+ else:
235
+ formatted_job = job_title
236
+
237
+ # Format location based on portal
238
+ if portal_name == "Foundit (Monster)":
239
+ formatted_location = location.replace(' ', '+') if location else "India"
240
+ elif portal_name == "Naukri":
241
+ formatted_location = self.format_location(location) if location else "india"
242
+ elif portal_name == "Glassdoor":
243
+ # For Glassdoor, keep spaces in location name
244
+ formatted_location = location if location else "India"
245
+ elif portal_name in ["LinkedIn", "Indeed", "TimesJobs"]:
246
+ formatted_location = location.replace(' ', '%20') if location else "India"
247
+ elif portal_name in ["FreshersWorld", "Instahyre"]:
248
+ formatted_location = location.lower().replace(' ', '-') if location else "india"
249
+ else:
250
+ formatted_location = location if location else "India"
251
+
252
+ # Get experience parameter
253
+ exp_param = self.get_experience_param(portal_name, experience)
254
+
255
+ # Build URL based on portal
256
+ try:
257
+ if portal_name == "Foundit (Monster)":
258
+ url = portal["url"].format(formatted_job, formatted_location)
259
+ if exp_param:
260
+ url += exp_param
261
+ elif portal_name == "Naukri":
262
+ url = portal["url"].format(formatted_job, formatted_location, exp_param)
263
+ elif portal_name == "LinkedIn":
264
+ url = portal["url"].format(formatted_job, formatted_location, exp_param)
265
+ elif portal_name == "Indeed":
266
+ url = portal["url"].format(formatted_job, formatted_location, exp_param)
267
+ elif portal_name == "Glassdoor":
268
+ # For Glassdoor, location comes first, then job title (occ parameter)
269
+ url = portal["url"].format(formatted_location, formatted_job)
270
+ elif portal_name in ["TimesJobs"]:
271
+ url = portal["url"].format(formatted_job, formatted_location)
272
+ elif portal_name in ["FreshersWorld", "Instahyre"]:
273
+ url = portal["url"].format(formatted_job, formatted_location)
274
+ else:
275
+ url = portal["url"]
276
+
277
+ results.append({
278
+ "portal": portal_name,
279
+ "icon": portal["icon"],
280
+ "color": portal["color"],
281
+ "title": f"{job_title} jobs in {location if location else 'India'}",
282
+ "url": url
283
+ })
284
+ except Exception as e:
285
+ print(f"Error creating URL for {portal_name}: {str(e)}")
286
+ continue
287
+
288
+ return results
jobs/job_search.py ADDED
@@ -0,0 +1,510 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from typing import List, Dict
3
+ from .job_portals import JobPortal
4
+ from .suggestions import (
5
+ JOB_SUGGESTIONS,
6
+ LOCATION_SUGGESTIONS,
7
+ EXPERIENCE_RANGES,
8
+ SALARY_RANGES,
9
+ JOB_TYPES,
10
+ get_cities_by_state,
11
+ get_all_states
12
+ )
13
+ from .companies import get_featured_companies, get_market_insights
14
+ from .linkedin_scraper import render_linkedin_scraper
15
+ from streamlit_extras.add_vertical_space import add_vertical_space
16
+ from streamlit_option_menu import option_menu
17
+
18
+ def filter_suggestions(query: str, suggestions: List[Dict]) -> List[Dict]:
19
+ """Filter suggestions based on user input"""
20
+ if not query:
21
+ return []
22
+ return [
23
+ s for s in suggestions
24
+ if query.lower() in s["text"].lower()
25
+ ][:5]
26
+
27
+ def filter_location_suggestions(query: str, suggestions: List[Dict]) -> List[Dict]:
28
+ """Filter location suggestions based on user input with smart categorization"""
29
+ if not query or len(query) < 2:
30
+ return []
31
+
32
+ # First check if query matches any state
33
+ matching_states = [s for s in suggestions if s.get("type") == "state" and query.lower() in s["text"].lower()]
34
+
35
+ # Then check cities
36
+ matching_cities = [s for s in suggestions if s.get("type") == "city" and query.lower() in s["text"].lower()]
37
+
38
+ # Then check work modes
39
+ matching_work_modes = [s for s in suggestions if s.get("type") == "work_mode" and query.lower() in s["text"].lower()]
40
+
41
+ # Combine results with states first, then major cities, then other matches
42
+ results = matching_states + matching_cities + matching_work_modes
43
+ return results[:7] # Return top 7 matches
44
+
45
+ def get_filter_options():
46
+ """Get filter options for job search"""
47
+ return {
48
+ "experience_levels": [
49
+ {"id": "all", "text": "All Levels"},
50
+ {"id": "fresher", "text": "Fresher"},
51
+ {"id": "0-1", "text": "0-1 years"},
52
+ {"id": "1-3", "text": "1-3 years"},
53
+ {"id": "3-5", "text": "3-5 years"},
54
+ {"id": "5-7", "text": "5-7 years"},
55
+ {"id": "7-10", "text": "7-10 years"},
56
+ {"id": "10+", "text": "10+ years"}
57
+ ],
58
+ "salary_ranges": [
59
+ {"id": "all", "text": "All Ranges"},
60
+ {"id": "0-3", "text": "0-3 LPA"},
61
+ {"id": "3-6", "text": "3-6 LPA"},
62
+ {"id": "6-10", "text": "6-10 LPA"},
63
+ {"id": "10-15", "text": "10-15 LPA"},
64
+ {"id": "15+", "text": "15+ LPA"}
65
+ ],
66
+ "job_types": [
67
+ {"id": "all", "text": "All Types"},
68
+ {"id": "full-time", "text": "Full Time"},
69
+ {"id": "part-time", "text": "Part Time"},
70
+ {"id": "contract", "text": "Contract"},
71
+ {"id": "remote", "text": "Remote"}
72
+ ]
73
+ }
74
+
75
+ def render_company_section():
76
+ """Render the featured companies section"""
77
+ st.markdown("""
78
+ <style>
79
+ .company-grid {
80
+ display: grid;
81
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
82
+ gap: 1rem;
83
+ padding: 1rem 0;
84
+ }
85
+ .company-card {
86
+ background: rgba(255, 255, 255, 0.05);
87
+ border-radius: 10px;
88
+ padding: 1rem;
89
+ transition: transform 0.2s;
90
+ cursor: pointer;
91
+ }
92
+ .company-card:hover {
93
+ transform: translateY(-5px);
94
+ background: rgba(255, 255, 255, 0.08);
95
+ }
96
+ .company-header {
97
+ display: flex;
98
+ align-items: center;
99
+ margin-bottom: 0.5rem;
100
+ }
101
+ .company-icon {
102
+ font-size: 1.5rem;
103
+ margin-right: 0.5rem;
104
+ }
105
+ .company-categories {
106
+ display: flex;
107
+ flex-wrap: wrap;
108
+ gap: 0.5rem;
109
+ margin-top: 0.5rem;
110
+ }
111
+ .company-category {
112
+ background: rgba(255, 255, 255, 0.1);
113
+ padding: 0.2rem 0.5rem;
114
+ border-radius: 15px;
115
+ font-size: 0.8rem;
116
+ }
117
+ </style>
118
+ """, unsafe_allow_html=True)
119
+
120
+ # Featured Companies
121
+ st.markdown("### 🏢 Featured Companies")
122
+
123
+ tabs = st.tabs(["All Companies", "Tech Giants", "Indian Tech", "Global Corps"])
124
+
125
+ categories = [None, "tech", "indian_tech", "global_corps"]
126
+ for tab, category in zip(tabs, categories):
127
+ with tab:
128
+ companies = get_featured_companies(category)
129
+ st.markdown('<div class="company-grid">', unsafe_allow_html=True)
130
+
131
+ for company in companies:
132
+ st.markdown(f"""
133
+ <a href="{company['careers_url']}" target="_blank" style="text-decoration: none; color: inherit;">
134
+ <div class="company-card">
135
+ <div class="company-header">
136
+ <i class="{company['icon']} company-icon" style="color: {company['color']}"></i>
137
+ <h3 style="margin: 0;">{company['name']}</h3>
138
+ </div>
139
+ <p style="margin: 0.5rem 0; color: #888;">{company['description']}</p>
140
+ <div class="company-categories">
141
+ {' '.join(f'<span class="company-category">{cat}</span>' for cat in company['categories'])}
142
+ </div>
143
+ </div>
144
+ </a>
145
+ """, unsafe_allow_html=True)
146
+
147
+ st.markdown('</div>', unsafe_allow_html=True)
148
+
149
+ def render_market_insights():
150
+ """Render job market insights section"""
151
+ insights = get_market_insights()
152
+
153
+ st.markdown("""
154
+ <style>
155
+ .insights-grid {
156
+ display: grid;
157
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
158
+ gap: 1rem;
159
+ padding: 1rem 0;
160
+ }
161
+ .insight-card {
162
+ background: rgba(255, 255, 255, 0.05);
163
+ border-radius: 10px;
164
+ padding: 1rem;
165
+ text-align: center;
166
+ transition: transform 0.3s ease, background 0.3s ease;
167
+ }
168
+ .insight-card:hover {
169
+ transform: translateY(-5px);
170
+ background: rgba(255, 255, 255, 0.08);
171
+ }
172
+ .insight-icon {
173
+ font-size: 2rem;
174
+ margin-bottom: 0.5rem;
175
+ color: #00bfa5;
176
+ }
177
+ .growth-text {
178
+ color: #00c853;
179
+ font-weight: bold;
180
+ }
181
+ .salary-card {
182
+ background: rgba(255, 255, 255, 0.05);
183
+ border-radius: 15px;
184
+ padding: 1.5rem;
185
+ margin-bottom: 1rem;
186
+ transition: all 0.3s ease;
187
+ border-left: 4px solid #00bfa5;
188
+ }
189
+ .salary-card:hover {
190
+ transform: translateX(10px);
191
+ background: rgba(255, 255, 255, 0.08);
192
+ }
193
+ .salary-header {
194
+ display: flex;
195
+ align-items: center;
196
+ margin-bottom: 1rem;
197
+ }
198
+ .role-icon {
199
+ font-size: 1.5rem;
200
+ margin-right: 1rem;
201
+ color: #00bfa5;
202
+ }
203
+ .salary-details {
204
+ display: flex;
205
+ justify-content: space-between;
206
+ align-items: center;
207
+ margin-top: 0.5rem;
208
+ }
209
+ .salary-tag {
210
+ background: rgba(0, 191, 165, 0.1);
211
+ color: #00bfa5;
212
+ padding: 0.3rem 0.8rem;
213
+ border-radius: 20px;
214
+ font-size: 0.9rem;
215
+ }
216
+ .experience-tag {
217
+ background: rgba(255, 255, 255, 0.1);
218
+ padding: 0.3rem 0.8rem;
219
+ border-radius: 20px;
220
+ font-size: 0.9rem;
221
+ }
222
+ .role-title {
223
+ font-size: 1.2rem;
224
+ font-weight: bold;
225
+ margin: 0;
226
+ }
227
+ .salary-range {
228
+ font-size: 1.1rem;
229
+ color: #00bfa5;
230
+ font-weight: bold;
231
+ }
232
+ .role-icons {
233
+ font-family: "Font Awesome 5 Free";
234
+ }
235
+ </style>
236
+ """, unsafe_allow_html=True)
237
+
238
+ st.markdown("### 📊 Job Market Insights")
239
+
240
+ tabs = st.tabs(["Trending Skills", "Top Locations", "Salary Insights"])
241
+
242
+ with tabs[0]:
243
+ st.markdown('<div class="insights-grid">', unsafe_allow_html=True)
244
+ for skill in insights["trending_skills"]:
245
+ st.markdown(f"""
246
+ <div class="insight-card">
247
+ <i class="{skill['icon']} insight-icon"></i>
248
+ <h4>{skill['name']}</h4>
249
+ <p class="growth-text">Growth: {skill['growth']}</p>
250
+ </div>
251
+ """, unsafe_allow_html=True)
252
+ st.markdown('</div>', unsafe_allow_html=True)
253
+
254
+ with tabs[1]:
255
+ st.markdown('<div class="insights-grid">', unsafe_allow_html=True)
256
+ for location in insights["top_locations"]:
257
+ st.markdown(f"""
258
+ <div class="insight-card">
259
+ <i class="{location['icon']} insight-icon"></i>
260
+ <h4>{location['name']}</h4>
261
+ <p>Available Jobs: {location['jobs']}</p>
262
+ </div>
263
+ """, unsafe_allow_html=True)
264
+ st.markdown('</div>', unsafe_allow_html=True)
265
+
266
+ with tabs[2]:
267
+ # Role-specific icons
268
+ role_icons = {
269
+ "Software Engineer": "fas fa-code",
270
+ "Data Scientist": "fas fa-brain",
271
+ "Product Manager": "fas fa-tasks",
272
+ "DevOps Engineer": "fas fa-server",
273
+ "UI/UX Designer": "fas fa-paint-brush"
274
+ }
275
+
276
+ for insight in insights["salary_insights"]:
277
+ role = insight['role']
278
+ icon = role_icons.get(role, "fas fa-briefcase")
279
+
280
+ st.markdown(f"""
281
+ <div class="salary-card">
282
+ <div class="salary-header">
283
+ <i class="{icon} role-icon"></i>
284
+ <div>
285
+ <h3 class="role-title">{role}</h3>
286
+ <div class="salary-details">
287
+ <span class="salary-tag">₹ {insight['range']}</span>
288
+ <span class="experience-tag">
289
+ <i class="fas fa-history"></i> {insight['experience']}
290
+ </span>
291
+ </div>
292
+ </div>
293
+ </div>
294
+ </div>
295
+ """, unsafe_allow_html=True)
296
+
297
+ def render_job_search():
298
+ """Render job search page with enhanced features"""
299
+ st.title("🔍 Smart Job Search")
300
+ st.markdown("Find Your Dream Job Across Multiple Platforms")
301
+
302
+ # Market Insights Section (Above Search)
303
+ render_market_insights()
304
+
305
+ # Job Search Section
306
+ with st.container():
307
+ st.markdown("""
308
+ <style>
309
+ .search-container {
310
+ background: rgba(255, 255, 255, 0.05);
311
+ border-radius: 10px;
312
+ padding: 20px;
313
+ margin-bottom: 20px;
314
+ }
315
+ .search-title {
316
+ color: #00bfa5;
317
+ font-weight: bold;
318
+ margin-bottom: 5px;
319
+ }
320
+ .search-description {
321
+ color: #888;
322
+ font-size: 0.9rem;
323
+ margin-bottom: 20px;
324
+ }
325
+ </style>
326
+ """, unsafe_allow_html=True)
327
+
328
+ st.markdown('<div class="search-container">', unsafe_allow_html=True)
329
+
330
+ # Create tabs with icons
331
+ tabs = option_menu(
332
+ menu_title=None,
333
+ options=["Job Portal", "LinkedIn"],
334
+ icons=["search", "linkedin"],
335
+ menu_icon="cast",
336
+ default_index=0,
337
+ orientation="horizontal",
338
+ styles={
339
+ "container": {"padding": "0px", "margin-bottom": "20px"},
340
+ "icon": {"font-size": "18px"},
341
+ "nav-link": {"font-size": "16px", "text-align": "center", "padding": "10px", "border-radius": "5px"},
342
+ "nav-link-selected": {"background-color": "#00bfa5", "font-weight": "bold"},
343
+ }
344
+ )
345
+
346
+ # Display content based on selected tab
347
+ if tabs == "Job Portal":
348
+ st.markdown('<h3 class="search-title"><i class="fas fa-search-dollar" style="color: #00bfa5;"></i> Search Jobs Across Multiple Platforms</h3>', unsafe_allow_html=True)
349
+ st.markdown('<p class="search-description">Find job opportunities from top job portals like LinkedIn, Indeed, Naukri, and Foundit</p>', unsafe_allow_html=True)
350
+
351
+ # Search inputs
352
+ col1, col2 = st.columns([2, 1])
353
+
354
+ with col1:
355
+ job_query = st.text_input("Job Title / Skills",
356
+ value="",
357
+ placeholder="e.g. Software Engineer, Data Scientist")
358
+
359
+ if job_query and len(job_query) >= 2:
360
+ filtered_jobs = [s["text"] for s in JOB_SUGGESTIONS if job_query.lower() in s["text"].lower()]
361
+ if filtered_jobs:
362
+ job_query = st.selectbox("Select Job Title", filtered_jobs)
363
+
364
+ with col2:
365
+ location = st.text_input("Location",
366
+ value="",
367
+ placeholder="e.g. Bangalore, Karnataka")
368
+
369
+ if location and len(location) >= 2:
370
+ # Use enhanced location filtering
371
+ filtered_locations = filter_location_suggestions(location, LOCATION_SUGGESTIONS)
372
+
373
+ if filtered_locations:
374
+ # Format the display text to show location type
375
+ location_options = []
376
+ location_display = {}
377
+
378
+ for loc in filtered_locations:
379
+ display_text = loc["text"]
380
+ if loc.get("type") == "state":
381
+ display_text = f"{loc['text']} (State)"
382
+ elif loc.get("type") == "city":
383
+ display_text = f"{loc['text']}, {loc.get('state', '')}"
384
+ elif loc.get("type") == "work_mode":
385
+ display_text = f"{loc['text']} (Work Mode)"
386
+
387
+ location_options.append(loc["text"])
388
+ location_display[loc["text"]] = display_text
389
+
390
+ # Create a selectbox with formatted display
391
+ selected_location = st.selectbox(
392
+ "Select Location",
393
+ options=location_options,
394
+ format_func=lambda x: location_display.get(x, x)
395
+ )
396
+
397
+ location = selected_location
398
+
399
+ # If a state is selected, show cities in that state
400
+ selected_loc_type = next((loc.get("type") for loc in filtered_locations if loc["text"] == selected_location), None)
401
+
402
+ if selected_loc_type == "state":
403
+ st.markdown(f"**Cities in {selected_location}:**")
404
+ cities = get_cities_by_state(selected_location)
405
+
406
+ # Display cities as clickable buttons
407
+ city_cols = st.columns(3)
408
+ for i, city in enumerate(cities):
409
+ with city_cols[i % 3]:
410
+ if st.button(f"{city['icon']} {city['text']}", key=f"city_{i}"):
411
+ location = city['text']
412
+
413
+ # Advanced Filters
414
+ with st.expander("🎯 Advanced Filters"):
415
+ st.markdown('<div class="filter-section">', unsafe_allow_html=True)
416
+ filter_cols = st.columns(3)
417
+
418
+ with filter_cols[0]:
419
+ experience = st.selectbox("Experience Level",
420
+ options=get_filter_options()["experience_levels"],
421
+ format_func=lambda x: x["text"])
422
+
423
+ with filter_cols[1]:
424
+ salary_range = st.selectbox("Salary Range",
425
+ options=get_filter_options()["salary_ranges"],
426
+ format_func=lambda x: x["text"])
427
+
428
+ with filter_cols[2]:
429
+ job_type = st.selectbox("Job Type",
430
+ options=get_filter_options()["job_types"],
431
+ format_func=lambda x: x["text"])
432
+
433
+ st.markdown('</div>', unsafe_allow_html=True)
434
+
435
+ # Search button
436
+ if st.button("SEARCH JOBS", type="primary", use_container_width=True):
437
+ if job_query:
438
+ job_portal = JobPortal()
439
+ results = job_portal.search_jobs(job_query, location, experience)
440
+
441
+ if results:
442
+ st.markdown("""
443
+ <style>
444
+ .result-card {
445
+ background: rgba(255, 255, 255, 0.05);
446
+ border-radius: 10px;
447
+ padding: 15px;
448
+ margin-bottom: 10px;
449
+ border-left: 4px solid #00bfa5;
450
+ transition: transform 0.2s;
451
+ }
452
+ .result-card:hover {
453
+ transform: translateX(5px);
454
+ background: rgba(255, 255, 255, 0.08);
455
+ }
456
+ .portal-name {
457
+ color: #00bfa5;
458
+ font-weight: bold;
459
+ font-size: 1.2rem;
460
+ }
461
+ .portal-link {
462
+ display: inline-block;
463
+ background: #00bfa5;
464
+ color: white !important;
465
+ padding: 5px 15px;
466
+ border-radius: 5px;
467
+ text-decoration: none;
468
+ margin-top: 10px;
469
+ font-weight: bold;
470
+ }
471
+ .portal-link:hover {
472
+ background: #00a589;
473
+ }
474
+ </style>
475
+ """, unsafe_allow_html=True)
476
+
477
+ st.markdown("### 🎯 Job Search Results")
478
+ for result in results:
479
+ with st.container():
480
+ st.markdown(f"""
481
+ <div class="result-card">
482
+ <div class="portal-name">
483
+ <i class="{result["icon"]}" style="color: {result["color"]}"></i>
484
+ {result["portal"]}
485
+ </div>
486
+ <p>{result["title"]}</p>
487
+ <a href="{result["url"]}" target="_blank" class="portal-link">
488
+ View Jobs on {result["portal"]} →
489
+ </a>
490
+ </div>
491
+ """, unsafe_allow_html=True)
492
+ else:
493
+ st.warning("No results found. Try different search terms or filters.")
494
+ else:
495
+ st.warning("Please enter a job title or skills to search.")
496
+
497
+ else:
498
+ # LinkedIn Job Scraper - only show the title once
499
+ st.markdown('<h3 class="search-title"><i class="fab fa-linkedin" style="color: #0A66C2;"></i> LinkedIn Job Scraper</h3>', unsafe_allow_html=True)
500
+ st.markdown('<p class="search-description">Find real-time job listings directly from LinkedIn</p>', unsafe_allow_html=True)
501
+
502
+ # Render LinkedIn scraper without showing the title again
503
+ render_linkedin_scraper()
504
+
505
+ st.markdown('</div>', unsafe_allow_html=True)
506
+
507
+ # Featured Companies Section (Below Search)
508
+ render_company_section()
509
+
510
+ # Removed render_job_search() call to prevent automatic rendering
jobs/linkedin_scraper.py ADDED
@@ -0,0 +1,662 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import numpy as np
3
+ import pandas as pd
4
+ import streamlit as st
5
+ from streamlit_extras.add_vertical_space import add_vertical_space
6
+ from selenium import webdriver
7
+ from selenium.webdriver.common.by import By
8
+ from selenium.webdriver.common.keys import Keys
9
+ from selenium.common.exceptions import NoSuchElementException
10
+ import warnings
11
+ warnings.filterwarnings('ignore')
12
+
13
+ # Import our custom webdriver utility
14
+ from .webdriver_utils import setup_webdriver
15
+
16
+ class LinkedInScraper:
17
+ """Class for scraping job listings from LinkedIn"""
18
+
19
+ @staticmethod
20
+ def webdriver_setup():
21
+ """Set up and configure the Chrome webdriver"""
22
+ # Use our custom webdriver setup utility with multiple fallback options
23
+ return setup_webdriver()
24
+
25
+ @staticmethod
26
+ def get_user_input(show_title=True):
27
+ """Get user input for job search parameters"""
28
+ add_vertical_space(1)
29
+
30
+ # Apply custom styling for the form
31
+ if show_title:
32
+ st.markdown("""
33
+ <style>
34
+ .linkedin-form {
35
+ background: rgba(10, 102, 194, 0.05);
36
+ border-radius: 10px;
37
+ padding: 20px;
38
+ border-left: 4px solid #0A66C2;
39
+ margin-bottom: 20px;
40
+ }
41
+ .linkedin-title {
42
+ color: #0A66C2;
43
+ font-weight: bold;
44
+ }
45
+ .linkedin-subtitle {
46
+ color: #666;
47
+ font-size: 0.9rem;
48
+ margin-bottom: 15px;
49
+ }
50
+ </style>
51
+ """, unsafe_allow_html=True)
52
+
53
+ st.markdown('<div class="linkedin-form">', unsafe_allow_html=True)
54
+ st.markdown('<h3 class="linkedin-title"><i class="fab fa-linkedin"></i> LinkedIn Job Scraper</h3>', unsafe_allow_html=True)
55
+ st.markdown('<p class="linkedin-subtitle">Find real-time job listings directly from LinkedIn</p>', unsafe_allow_html=True)
56
+
57
+ with st.form(key='linkedin_scrape'):
58
+ col1, col2, col3 = st.columns([0.5, 0.3, 0.2], gap='medium')
59
+
60
+ with col1:
61
+ job_title_input = st.text_input(
62
+ label='Job Title',
63
+ placeholder='e.g. Data Scientist, Software Engineer',
64
+ help="Enter job titles separated by commas"
65
+ )
66
+ job_title_input = job_title_input.split(',')
67
+
68
+ with col2:
69
+ job_location = st.text_input(
70
+ label='Job Location',
71
+ value='India',
72
+ placeholder='e.g. Bangalore, Mumbai, Remote',
73
+ help="Enter a location or 'India' for nationwide search"
74
+ )
75
+
76
+ with col3:
77
+ job_count = st.number_input(
78
+ label='Number of Jobs',
79
+ min_value=1,
80
+ max_value=10,
81
+ value=3,
82
+ step=1,
83
+ help="Number of job listings to scrape (max 10)"
84
+ )
85
+
86
+ # Submit Button
87
+ add_vertical_space(1)
88
+ submit = st.form_submit_button(
89
+ label='Search LinkedIn Jobs',
90
+ type='primary',
91
+ use_container_width=True
92
+ )
93
+ add_vertical_space(1)
94
+
95
+ if show_title:
96
+ st.markdown('</div>', unsafe_allow_html=True)
97
+
98
+ return job_title_input, job_location, job_count, submit
99
+
100
+ @staticmethod
101
+ def build_url(job_title, job_location):
102
+ """Build LinkedIn search URL from job title and location"""
103
+ # Format job titles
104
+ formatted_titles = []
105
+ for title in job_title:
106
+ if title.strip(): # Skip empty titles
107
+ words = title.strip().split()
108
+ formatted_title = '%20'.join(words)
109
+ formatted_titles.append(formatted_title)
110
+
111
+ # If no valid titles, use a default
112
+ if not formatted_titles:
113
+ formatted_titles = ["jobs"]
114
+
115
+ # Join multiple job titles
116
+ job_title_param = '%2C%20'.join(formatted_titles)
117
+
118
+ # Format location
119
+ location_param = job_location.replace(' ', '%20')
120
+
121
+ # Build the LinkedIn search URL
122
+ link = f"https://in.linkedin.com/jobs/search?keywords={job_title_param}&location={location_param}&geoId=102713980&f_TPR=r604800&position=1&pageNum=0"
123
+
124
+ return link
125
+
126
+ @staticmethod
127
+ def open_link(driver, link):
128
+ """Open LinkedIn link and wait for page to load"""
129
+ max_attempts = 3
130
+ attempts = 0
131
+
132
+ while attempts < max_attempts:
133
+ try:
134
+ driver.get(link)
135
+ driver.implicitly_wait(5)
136
+ time.sleep(3)
137
+
138
+ # Check if page loaded correctly
139
+ if "LinkedIn" in driver.title:
140
+ return True
141
+
142
+ # Alternative check for elements
143
+ try:
144
+ driver.find_element(by=By.CSS_SELECTOR, value='.jobs-search-results')
145
+ return True
146
+ except:
147
+ pass
148
+
149
+ try:
150
+ driver.find_element(by=By.CSS_SELECTOR, value='.jobs-search-results-list')
151
+ return True
152
+ except:
153
+ pass
154
+
155
+ # One more attempt with a different selector
156
+ try:
157
+ driver.find_element(by=By.CSS_SELECTOR, value='.base-search-card')
158
+ return True
159
+ except:
160
+ pass
161
+
162
+ attempts += 1
163
+ if attempts >= max_attempts:
164
+ st.warning("Could not load LinkedIn jobs page. Please try again.")
165
+ return False
166
+
167
+ time.sleep(2)
168
+
169
+ except Exception as e:
170
+ attempts += 1
171
+ if attempts >= max_attempts:
172
+ st.warning(f"Error loading LinkedIn page: {str(e)}")
173
+ return False
174
+ time.sleep(2)
175
+
176
+ return False
177
+
178
+ @staticmethod
179
+ def link_open_scrolldown(driver, link, job_count):
180
+ """Open LinkedIn link and scroll down to load more jobs"""
181
+ # Open the link
182
+ if not LinkedInScraper.open_link(driver, link):
183
+ return False
184
+
185
+ # Scroll down to load more jobs
186
+ scroll_attempts = min(job_count + 5, 15) # Add extra scrolls to ensure we get enough jobs
187
+
188
+ for i in range(scroll_attempts):
189
+ try:
190
+ # Handle sign-in modal if it appears
191
+ try:
192
+ dismiss_buttons = driver.find_elements(
193
+ by=By.CSS_SELECTOR,
194
+ value="button[data-tracking-control-name='public_jobs_contextual-sign-in-modal_modal_dismiss']"
195
+ )
196
+ if dismiss_buttons:
197
+ dismiss_buttons[0].click()
198
+ except:
199
+ pass
200
+
201
+ # Scroll down to load more content
202
+ driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
203
+ time.sleep(1.5)
204
+
205
+ # Try to click "See more jobs" button if present
206
+ try:
207
+ see_more_buttons = driver.find_elements(
208
+ by=By.CSS_SELECTOR,
209
+ value="button[aria-label='See more jobs']"
210
+ )
211
+ if see_more_buttons:
212
+ see_more_buttons[0].click()
213
+ time.sleep(2)
214
+ except:
215
+ pass
216
+
217
+ except Exception as e:
218
+ continue
219
+
220
+ return True
221
+
222
+ @staticmethod
223
+ def job_title_filter(scrap_job_title, user_job_title_input):
224
+ """Filter job titles based on user input"""
225
+ # Skip filtering if job title input is empty or contains only empty strings
226
+ if not user_job_title_input or all(not title.strip() for title in user_job_title_input):
227
+ return scrap_job_title
228
+
229
+ # User job titles converted to lowercase
230
+ user_input = [title.lower().strip() for title in user_job_title_input if title.strip()]
231
+
232
+ # If no valid user input after cleaning, return the original title
233
+ if not user_input:
234
+ return scrap_job_title
235
+
236
+ # Scraped job title converted to lowercase
237
+ scrap_title = scrap_job_title.lower().strip()
238
+
239
+ # Check if any user job title matches the scraped job title
240
+ for user_title in user_input:
241
+ # Check if all words in user title are in scraped title
242
+ if all(word in scrap_title for word in user_title.split()):
243
+ return scrap_job_title
244
+
245
+ # No match found
246
+ return np.nan
247
+
248
+ @staticmethod
249
+ def scrap_company_data(driver, job_title_input, job_location):
250
+ """Scrape company data from LinkedIn job listings"""
251
+ try:
252
+ # Scrape company names
253
+ company_elements = driver.find_elements(
254
+ by=By.CSS_SELECTOR,
255
+ value='h4.base-search-card__subtitle'
256
+ )
257
+ company_names = [element.text for element in company_elements if element.text.strip()]
258
+
259
+ # Scrape job locations
260
+ location_elements = driver.find_elements(
261
+ by=By.CSS_SELECTOR,
262
+ value='span.job-search-card__location'
263
+ )
264
+ company_locations = [element.text for element in location_elements if element.text.strip()]
265
+
266
+ # Scrape job titles
267
+ title_elements = driver.find_elements(
268
+ by=By.CSS_SELECTOR,
269
+ value='h3.base-search-card__title'
270
+ )
271
+ job_titles = [element.text for element in title_elements if element.text.strip()]
272
+
273
+ # Scrape job URLs
274
+ url_elements = driver.find_elements(
275
+ by=By.XPATH,
276
+ value='//a[contains(@href, "/jobs/view/")]'
277
+ )
278
+ job_urls = [element.get_attribute('href') for element in url_elements if element.get_attribute('href')]
279
+
280
+ # Check if we have any data
281
+ if not company_names or not job_titles or not company_locations or not job_urls:
282
+ st.warning("No job listings found on LinkedIn. Try different search terms.")
283
+ return pd.DataFrame()
284
+
285
+ # Ensure all arrays have the same length by truncating to the shortest length
286
+ min_length = min(len(company_names), len(job_titles), len(company_locations), len(job_urls))
287
+
288
+ if min_length == 0:
289
+ st.warning("No job listings found on LinkedIn. Try different search terms.")
290
+ return pd.DataFrame()
291
+
292
+ company_names = company_names[:min_length]
293
+ job_titles = job_titles[:min_length]
294
+ company_locations = company_locations[:min_length]
295
+ job_urls = job_urls[:min_length]
296
+
297
+ # Create DataFrame
298
+ df = pd.DataFrame({
299
+ 'Company Name': company_names,
300
+ 'Job Title': job_titles,
301
+ 'Location': company_locations,
302
+ 'Website URL': job_urls
303
+ })
304
+
305
+ # Filter job titles based on user input if provided
306
+ if job_title_input and job_title_input != ['']:
307
+ filtered_titles = []
308
+ for title in df['Job Title']:
309
+ if any(user_title.lower().strip() in title.lower() for user_title in job_title_input if user_title.strip()):
310
+ filtered_titles.append(title)
311
+ else:
312
+ filtered_titles.append(np.nan)
313
+ df['Job Title'] = filtered_titles
314
+
315
+ # Filter locations based on user input if provided and not "India"
316
+ if job_location and job_location.lower() != "india":
317
+ filtered_locations = []
318
+ for loc in df['Location']:
319
+ if job_location.lower() in loc.lower():
320
+ filtered_locations.append(loc)
321
+ else:
322
+ filtered_locations.append(np.nan)
323
+ df['Location'] = filtered_locations
324
+
325
+ # Drop rows with NaN values and reset index
326
+ df = df.dropna()
327
+ df = df.reset_index(drop=True)
328
+
329
+ return df
330
+
331
+ except Exception as e:
332
+ st.error(f"Error scraping company data: {str(e)}")
333
+ st.info("Try refreshing the page or using different search terms.")
334
+ return pd.DataFrame()
335
+
336
+ @staticmethod
337
+ def scrap_job_description(driver, df, job_count):
338
+ """Scrape job descriptions for each job listing"""
339
+ if df.empty:
340
+ return df
341
+
342
+ # Get job URLs
343
+ job_urls = df['Website URL'].tolist()
344
+
345
+ # Limit to requested job count
346
+ job_urls = job_urls[:min(len(job_urls), job_count)]
347
+
348
+ # Initialize list for job descriptions
349
+ job_descriptions = []
350
+
351
+ # Progress bar for scraping job descriptions
352
+ progress_bar = st.progress(0)
353
+ status_text = st.empty()
354
+
355
+ for i, url in enumerate(job_urls):
356
+ try:
357
+ # Update progress
358
+ progress = int((i + 1) / len(job_urls) * 100)
359
+ progress_bar.progress(progress)
360
+ status_text.text(f"Scraping job {i+1} of {len(job_urls)}...")
361
+
362
+ # Open job listing page
363
+ driver.get(url)
364
+ driver.implicitly_wait(5)
365
+ time.sleep(2)
366
+
367
+ # Try to click "Show more" button to expand job description
368
+ try:
369
+ show_more_buttons = driver.find_elements(
370
+ by=By.CSS_SELECTOR,
371
+ value='button[data-tracking-control-name="public_jobs_show-more-html-btn"]'
372
+ )
373
+ if show_more_buttons:
374
+ show_more_buttons[0].click()
375
+ time.sleep(1)
376
+ except:
377
+ pass
378
+
379
+ # Get job description
380
+ description_elements = driver.find_elements(
381
+ by=By.CSS_SELECTOR,
382
+ value='div.show-more-less-html__markup'
383
+ )
384
+
385
+ if description_elements and description_elements[0].text.strip():
386
+ description_text = description_elements[0].text
387
+
388
+ # Process and structure the job description
389
+ processed_description = LinkedInScraper.process_job_description(description_text)
390
+ job_descriptions.append(processed_description)
391
+ else:
392
+ # Try alternative selectors
393
+ alt_description = driver.find_elements(
394
+ by=By.CSS_SELECTOR,
395
+ value='div.description__text'
396
+ )
397
+ if alt_description and alt_description[0].text.strip():
398
+ description_text = alt_description[0].text
399
+ processed_description = LinkedInScraper.process_job_description(description_text)
400
+ job_descriptions.append(processed_description)
401
+ else:
402
+ job_descriptions.append("Description not available")
403
+
404
+ except Exception as e:
405
+ job_descriptions.append("Description not available")
406
+ st.warning(f"Error scraping job description {i+1}: {str(e)}")
407
+
408
+ # Clear progress indicators
409
+ progress_bar.empty()
410
+ status_text.empty()
411
+
412
+ # Filter DataFrame to include only rows with descriptions
413
+ df = df.iloc[:len(job_descriptions), :]
414
+
415
+ # Add job descriptions to DataFrame
416
+ df['Job Description'] = job_descriptions
417
+
418
+ # Filter out rows with unavailable descriptions
419
+ df['Job Description'] = df['Job Description'].apply(
420
+ lambda x: np.nan if x == "Description not available" else x
421
+ )
422
+ df = df.dropna()
423
+ df = df.reset_index(drop=True)
424
+
425
+ return df
426
+
427
+ @staticmethod
428
+ def process_job_description(text):
429
+ """Process and structure job description text"""
430
+ if not text or text == "Description not available":
431
+ return text
432
+
433
+ # Split into sections
434
+ sections = text.split('\n\n')
435
+ processed_sections = []
436
+
437
+ # Common section headers to identify
438
+ section_headers = [
439
+ "responsibilities", "requirements", "qualifications", "skills",
440
+ "about the job", "about the role", "what you'll do", "what you'll need",
441
+ "about us", "about the company", "who we are", "benefits", "perks",
442
+ "job description", "role description", "experience", "education",
443
+ "job summary", "job overview", "job requirements", "job responsibilities",
444
+ "job qualifications", "job skills", "job benefits", "job perks",
445
+ "job description", "role description", "experience", "education",
446
+ "job summary", "job overview", "job requirements", "job responsibilities",
447
+ "job qualifications", "job skills", "job benefits", "job perks",
448
+ "Education Qualification and Experience", "Required Skills", "Preferred Qualifications", "Key Responsibilities",
449
+ "About Us", "About the Company", "About the Role", "About the Job",
450
+ "About the Team", "About the Organization", "About the Industry", "About the Location",
451
+ "Position", "Job Description", "Job Summary", "Job Overview"
452
+ ]
453
+
454
+ # Process each section
455
+ current_section = ""
456
+ for section in sections:
457
+ if not section.strip():
458
+ continue
459
+
460
+ # Check if this is a new section header
461
+ is_header = False
462
+ section_lower = section.lower().strip()
463
+
464
+ # Check if section starts with a header
465
+ for header in section_headers:
466
+ if section_lower.startswith(header) or section_lower.startswith("• " + header) or section_lower.startswith("- " + header):
467
+ # Format as a header
468
+ current_section = section.strip()
469
+ is_header = True
470
+ processed_sections.append(f"\n**{current_section}**\n")
471
+ break
472
+
473
+ if not is_header:
474
+ # Check if it's a bullet point list
475
+ if section.strip().startswith('•') or section.strip().startswith('-') or section.strip().startswith('*'):
476
+ lines = section.split('\n')
477
+ formatted_lines = []
478
+
479
+ for line in lines:
480
+ line = line.strip()
481
+ if line:
482
+ if line.startswith('•') or line.startswith('-') or line.startswith('*'):
483
+ # Format as bullet point
484
+ formatted_lines.append(f"• {line.lstrip('•').lstrip('-').lstrip('*').strip()}")
485
+ else:
486
+ formatted_lines.append(line)
487
+
488
+ processed_sections.append('\n'.join(formatted_lines))
489
+ else:
490
+ # Regular paragraph
491
+ processed_sections.append(section.strip())
492
+
493
+ # Join all processed sections
494
+ return '\n\n'.join(processed_sections)
495
+
496
+ @staticmethod
497
+ def display_data_userinterface(df_final):
498
+ """Display scraped job data in the user interface"""
499
+ if df_final.empty:
500
+ st.warning("No matching jobs found. Try different search terms or location.")
501
+ return
502
+
503
+ # Apply custom styling for job cards
504
+ st.markdown("""
505
+ <style>
506
+ .job-card {
507
+ background: rgba(255, 255, 255, 0.05);
508
+ border-radius: 10px;
509
+ padding: 1.5rem;
510
+ margin-bottom: 1rem;
511
+ border-left: 4px solid #0A66C2;
512
+ transition: transform 0.2s;
513
+ }
514
+ .job-card:hover {
515
+ background: rgba(255, 255, 255, 0.08);
516
+ }
517
+ .job-title {
518
+ color: #0A66C2;
519
+ font-size: 1.3rem;
520
+ margin-bottom: 0.5rem;
521
+ }
522
+ .company-name {
523
+ font-weight: bold;
524
+ font-size: 1.1rem;
525
+ }
526
+ .job-location {
527
+ color: #888;
528
+ margin-bottom: 1rem;
529
+ }
530
+ .job-url-button {
531
+ display: inline-block;
532
+ background: #0A66C2;
533
+ color: white;
534
+ padding: 0.5rem 1rem;
535
+ border-radius: 5px;
536
+ text-decoration: none;
537
+ margin-top: 1rem;
538
+ font-weight: bold;
539
+ }
540
+ .job-url-button:hover {
541
+ background: #084d8e;
542
+ }
543
+ .job-count {
544
+ background: rgba(10, 102, 194, 0.1);
545
+ color: #0A66C2;
546
+ padding: 0.5rem 1rem;
547
+ border-radius: 5px;
548
+ margin-bottom: 1rem;
549
+ font-weight: bold;
550
+ }
551
+ .job-section {
552
+ margin-top: 1rem;
553
+ padding-top: 0.5rem;
554
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
555
+ }
556
+ .job-section-title {
557
+ font-weight: bold;
558
+ color: #0A66C2;
559
+ margin-bottom: 0.5rem;
560
+ }
561
+ </style>
562
+ """, unsafe_allow_html=True)
563
+
564
+ # Display job count
565
+ st.markdown(f'<div class="job-count">🎯 Found {len(df_final)} matching jobs on LinkedIn</div>', unsafe_allow_html=True)
566
+
567
+ # Display each job
568
+ for i in range(len(df_final)):
569
+ company_name = df_final.iloc[i, 0]
570
+ job_title = df_final.iloc[i, 1]
571
+ location = df_final.iloc[i, 2]
572
+ url = df_final.iloc[i, 3]
573
+ description = df_final.iloc[i, 4]
574
+
575
+ # Create job card
576
+ st.markdown(f"""
577
+ <div class="job-card">
578
+ <div class="job-title">{job_title}</div>
579
+ <div class="company-name">{company_name}</div>
580
+ <div class="job-location">📍 {location}</div>
581
+ </div>
582
+ """, unsafe_allow_html=True)
583
+
584
+ # Job description in expander with better formatting
585
+ with st.expander("View Job Description"):
586
+ st.markdown(description)
587
+ st.markdown(f"<a href='{url}' target='_blank' class='job-url-button'>Apply on LinkedIn</a>", unsafe_allow_html=True)
588
+
589
+ st.markdown("<hr>", unsafe_allow_html=True)
590
+
591
+ @staticmethod
592
+ def main(show_title=True):
593
+ """Main function to run the LinkedIn job scraper"""
594
+ # Initialize driver to None
595
+ driver = None
596
+
597
+ try:
598
+ # Get user input
599
+ job_title_input, job_location, job_count, submit = LinkedInScraper.get_user_input(show_title)
600
+
601
+ if submit:
602
+ if job_title_input != [''] and job_location:
603
+ try:
604
+ # Set up Chrome webdriver
605
+ with st.spinner('Setting up Chrome webdriver...'):
606
+ driver = LinkedInScraper.webdriver_setup()
607
+
608
+ if not driver:
609
+ st.error("Failed to initialize Chrome webdriver. Please make sure Chrome is installed.")
610
+ return
611
+
612
+ # Build URL and open LinkedIn
613
+ with st.spinner('Loading LinkedIn jobs page...'):
614
+ link = LinkedInScraper.build_url(job_title_input, job_location)
615
+ st.info(f"Searching for: {', '.join([t for t in job_title_input if t.strip()])} in {job_location}")
616
+ success = LinkedInScraper.link_open_scrolldown(driver, link, job_count)
617
+
618
+ if not success:
619
+ st.error("Failed to load LinkedIn jobs page. Please try again.")
620
+ return
621
+
622
+ # Scrape job data
623
+ with st.spinner('Scraping job listings...'):
624
+ df = LinkedInScraper.scrap_company_data(driver, job_title_input, job_location)
625
+
626
+ if df.empty:
627
+ st.warning("No jobs found matching your criteria. Try different search terms.")
628
+ return
629
+
630
+ # Scrape job descriptions
631
+ with st.spinner('Fetching job descriptions...'):
632
+ df_final = LinkedInScraper.scrap_job_description(driver, df, job_count)
633
+
634
+ if df_final.empty:
635
+ st.warning("Could not retrieve job descriptions. Try different search terms.")
636
+ return
637
+
638
+ # Display results
639
+ LinkedInScraper.display_data_userinterface(df_final)
640
+
641
+ except Exception as e:
642
+ st.error(f"An error occurred: {str(e)}")
643
+ st.info("Try refreshing the page or using different search terms.")
644
+
645
+ elif job_title_input == ['']:
646
+ st.warning("Please enter a job title to search.")
647
+
648
+ elif not job_location:
649
+ st.warning("Please enter a job location to search.")
650
+
651
+ except Exception as e:
652
+ st.error(f"An unexpected error occurred: {str(e)}")
653
+
654
+ finally:
655
+ # Close the webdriver
656
+ if driver:
657
+ driver.quit()
658
+
659
+ def render_linkedin_scraper():
660
+ """Render the LinkedIn job scraper interface"""
661
+ # Don't show the title again, as it's already shown in the job_search.py file
662
+ LinkedInScraper.main(show_title=False)
jobs/suggestions.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module containing job-related data and configurations"""
2
+
3
+ # Job titles and skills suggestions
4
+ JOB_SUGGESTIONS = [
5
+ {"text": "Software Engineer", "icon": "💻"},
6
+ {"text": "Full Stack Developer", "icon": "🔧"},
7
+ {"text": "Data Scientist", "icon": "📊"},
8
+ {"text": "Product Manager", "icon": "📱"},
9
+ {"text": "DevOps Engineer", "icon": "⚙️"},
10
+ {"text": "UI/UX Designer", "icon": "🎨"},
11
+ {"text": "Python Developer", "icon": "🐍"},
12
+ {"text": "Java Developer", "icon": "☕"},
13
+ {"text": "React Developer", "icon": "⚛️"},
14
+ {"text": "Machine Learning Engineer", "icon": "🤖"},
15
+ {"text": "Backend Developer", "icon": "🖧"},
16
+ {"text": "Frontend Developer", "icon": "🎨"},
17
+ {"text": "Node.js Developer", "icon": "🌿"},
18
+ {"text": "Angular Developer", "icon": "📐"},
19
+ {"text": "PHP Developer", "icon": "🐘"},
20
+ {"text": "Ruby Developer", "icon": "💎"},
21
+ {"text": "Go Developer", "icon": "🚀"},
22
+ {"text": "C++ Developer", "icon": "🖥️"},
23
+ {"text": "C# Developer", "icon": "🎮"},
24
+ {"text": "Django Developer", "icon": "🛠️"},
25
+ {"text": "Data Analyst", "icon": "📈"},
26
+ {"text": "Big Data Engineer", "icon": "📡"},
27
+ {"text": "Database Administrator", "icon": "🗄️"},
28
+ {"text": "Business Intelligence Analyst", "icon": "📊"},
29
+ {"text": "Cloud Engineer", "icon": "☁️"},
30
+ {"text": "AWS Engineer", "icon": "☁️🔧"},
31
+ {"text": "Azure Engineer", "icon": "☁️🖥️"},
32
+ {"text": "Google Cloud Engineer", "icon": "☁️📡"},
33
+ {"text": "Network Engineer", "icon": "🔌"},
34
+ {"text": "AI Researcher", "icon": "🧠"},
35
+ {"text": "NLP Engineer", "icon": "🗣️"},
36
+ {"text": "Computer Vision Engineer", "icon": "👁️"},
37
+ {"text": "Deep Learning Engineer", "icon": "🧠📚"},
38
+ {"text": "Cybersecurity Analyst", "icon": "🔒"},
39
+ {"text": "Ethical Hacker", "icon": "🕵️‍♂️"},
40
+ {"text": "Security Engineer", "icon": "🛡️"},
41
+ {"text": "Penetration Tester", "icon": "🔍"},
42
+ {"text": "Cryptography Engineer", "icon": "🔑"},
43
+ {"text": "Game Developer", "icon": "🎮"},
44
+ {"text": "Embedded Systems Engineer", "icon": "🖧⚙️"},
45
+ {"text": "Mobile App Developer", "icon": "📱"},
46
+ {"text": "iOS Developer", "icon": "🍏"},
47
+ {"text": "Android Developer", "icon": "🤖"},
48
+ {"text": "Blockchain Developer", "icon": "🔗"},
49
+ {"text": "IoT Developer", "icon": "🌐"},
50
+ {"text": "AR/VR Developer", "icon": "🕶️"},
51
+ {"text": "Project Manager", "icon": "📋"},
52
+ {"text": "Technical Writer", "icon": "✍️"},
53
+ {"text": "QA Engineer", "icon": "✅"},
54
+ {"text": "Scrum Master", "icon": "🔄"},
55
+ {"text": "Support Engineer", "icon": "📞"},
56
+ {"text": "IT Consultant", "icon": "🧑‍💼"},
57
+ {"text": "Technical Support Specialist", "icon": "🎧"}
58
+ ]
59
+
60
+
61
+ # Location suggestions - organized by states and major cities
62
+ LOCATION_SUGGESTIONS = [
63
+ # Work modes
64
+ {"text": "Remote", "icon": "🏠", "type": "work_mode"},
65
+ {"text": "Work from Home", "icon": "🏠", "type": "work_mode"},
66
+ {"text": "Hybrid", "icon": "🏢", "type": "work_mode"},
67
+
68
+ # Major tech hubs
69
+ {"text": "Bangalore", "icon": "📍", "type": "city", "state": "Karnataka"},
70
+ {"text": "Mumbai", "icon": "📍", "type": "city", "state": "Maharashtra"},
71
+ {"text": "Delhi", "icon": "📍", "type": "city", "state": "Delhi"},
72
+ {"text": "Hyderabad", "icon": "📍", "type": "city", "state": "Telangana"},
73
+ {"text": "Pune", "icon": "📍", "type": "city", "state": "Maharashtra"},
74
+ {"text": "Chennai", "icon": "📍", "type": "city", "state": "Tamil Nadu"},
75
+ {"text": "Noida", "icon": "📍", "type": "city", "state": "Uttar Pradesh"},
76
+ {"text": "Gurgaon", "icon": "📍", "type": "city", "state": "Haryana"},
77
+
78
+ # States
79
+ {"text": "Karnataka", "icon": "🗺️", "type": "state"},
80
+ {"text": "Maharashtra", "icon": "🗺️", "type": "state"},
81
+ {"text": "Tamil Nadu", "icon": "🗺️", "type": "state"},
82
+ {"text": "Telangana", "icon": "🗺️", "type": "state"},
83
+ {"text": "Delhi", "icon": "🗺️", "type": "state"},
84
+ {"text": "Uttar Pradesh", "icon": "🗺️", "type": "state"},
85
+ {"text": "Gujarat", "icon": "🗺️", "type": "state"},
86
+ {"text": "Rajasthan", "icon": "🗺️", "type": "state"},
87
+ {"text": "Kerala", "icon": "🗺️", "type": "state"},
88
+ {"text": "West Bengal", "icon": "🗺️", "type": "state"},
89
+ {"text": "Punjab", "icon": "🗺️", "type": "state"},
90
+ {"text": "Haryana", "icon": "🗺️", "type": "state"},
91
+ {"text": "Andhra Pradesh", "icon": "🗺️", "type": "state"},
92
+ {"text": "Madhya Pradesh", "icon": "🗺️", "type": "state"},
93
+ {"text": "Bihar", "icon": "🗺️", "type": "state"},
94
+
95
+ # Karnataka cities
96
+ {"text": "Mysore", "icon": "📍", "type": "city", "state": "Karnataka"},
97
+ {"text": "Hubli", "icon": "📍", "type": "city", "state": "Karnataka"},
98
+ {"text": "Mangalore", "icon": "📍", "type": "city", "state": "Karnataka"},
99
+ {"text": "Belgaum", "icon": "📍", "type": "city", "state": "Karnataka"},
100
+ {"text": "Davangere", "icon": "📍", "type": "city", "state": "Karnataka"},
101
+
102
+ # Maharashtra cities
103
+ {"text": "Nagpur", "icon": "📍", "type": "city", "state": "Maharashtra"},
104
+ {"text": "Nashik", "icon": "📍", "type": "city", "state": "Maharashtra"},
105
+ {"text": "Aurangabad", "icon": "📍", "type": "city", "state": "Maharashtra"},
106
+ {"text": "Kolhapur", "icon": "📍", "type": "city", "state": "Maharashtra"},
107
+ {"text": "Solapur", "icon": "📍", "type": "city", "state": "Maharashtra"},
108
+
109
+ # Tamil Nadu cities
110
+ {"text": "Coimbatore", "icon": "📍", "type": "city", "state": "Tamil Nadu"},
111
+ {"text": "Madurai", "icon": "📍", "type": "city", "state": "Tamil Nadu"},
112
+ {"text": "Salem", "icon": "📍", "type": "city", "state": "Tamil Nadu"},
113
+ {"text": "Tiruchirappalli", "icon": "📍", "type": "city", "state": "Tamil Nadu"},
114
+ {"text": "Vellore", "icon": "📍", "type": "city", "state": "Tamil Nadu"},
115
+
116
+ # Uttar Pradesh cities
117
+ {"text": "Lucknow", "icon": "📍", "type": "city", "state": "Uttar Pradesh"},
118
+ {"text": "Kanpur", "icon": "📍", "type": "city", "state": "Uttar Pradesh"},
119
+ {"text": "Agra", "icon": "📍", "type": "city", "state": "Uttar Pradesh"},
120
+ {"text": "Varanasi", "icon": "📍", "type": "city", "state": "Uttar Pradesh"},
121
+ {"text": "Meerut", "icon": "📍", "type": "city", "state": "Uttar Pradesh"},
122
+
123
+ # Andhra Pradesh cities
124
+ {"text": "Vijayawada", "icon": "📍", "type": "city", "state": "Andhra Pradesh"},
125
+ {"text": "Visakhapatnam", "icon": "📍", "type": "city", "state": "Andhra Pradesh"},
126
+ {"text": "Tirupati", "icon": "📍", "type": "city", "state": "Andhra Pradesh"},
127
+ {"text": "Guntur", "icon": "📍", "type": "city", "state": "Andhra Pradesh"},
128
+ {"text": "Nellore", "icon": "📍", "type": "city", "state": "Andhra Pradesh"},
129
+
130
+ # West Bengal cities
131
+ {"text": "Kolkata", "icon": "📍", "type": "city", "state": "West Bengal"},
132
+ {"text": "Darjeeling", "icon": "📍", "type": "city", "state": "West Bengal"},
133
+ {"text": "Siliguri", "icon": "📍", "type": "city", "state": "West Bengal"},
134
+ {"text": "Durgapur", "icon": "📍", "type": "city", "state": "West Bengal"},
135
+ {"text": "Asansol", "icon": "📍", "type": "city", "state": "West Bengal"},
136
+
137
+ # Gujarat cities
138
+ {"text": "Ahmedabad", "icon": "📍", "type": "city", "state": "Gujarat"},
139
+ {"text": "Surat", "icon": "📍", "type": "city", "state": "Gujarat"},
140
+ {"text": "Vadodara", "icon": "📍", "type": "city", "state": "Gujarat"},
141
+ {"text": "Rajkot", "icon": "📍", "type": "city", "state": "Gujarat"},
142
+ {"text": "Bhavnagar", "icon": "📍", "type": "city", "state": "Gujarat"},
143
+
144
+ # Rajasthan cities
145
+ {"text": "Jaipur", "icon": "📍", "type": "city", "state": "Rajasthan"},
146
+ {"text": "Jodhpur", "icon": "📍", "type": "city", "state": "Rajasthan"},
147
+ {"text": "Udaipur", "icon": "📍", "type": "city", "state": "Rajasthan"},
148
+ {"text": "Kota", "icon": "📍", "type": "city", "state": "Rajasthan"},
149
+ {"text": "Ajmer", "icon": "📍", "type": "city", "state": "Rajasthan"},
150
+
151
+ # Kerala cities
152
+ {"text": "Kochi", "icon": "📍", "type": "city", "state": "Kerala"},
153
+ {"text": "Thiruvananthapuram", "icon": "📍", "type": "city", "state": "Kerala"},
154
+ {"text": "Kozhikode", "icon": "📍", "type": "city", "state": "Kerala"},
155
+ {"text": "Thrissur", "icon": "📍", "type": "city", "state": "Kerala"},
156
+ {"text": "Alappuzha", "icon": "📍", "type": "city", "state": "Kerala"},
157
+
158
+ # Punjab cities
159
+ {"text": "Amritsar", "icon": "📍", "type": "city", "state": "Punjab"},
160
+ {"text": "Ludhiana", "icon": "📍", "type": "city", "state": "Punjab"},
161
+ {"text": "Jalandhar", "icon": "📍", "type": "city", "state": "Punjab"},
162
+ {"text": "Patiala", "icon": "📍", "type": "city", "state": "Punjab"},
163
+ {"text": "Bathinda", "icon": "📍", "type": "city", "state": "Punjab"},
164
+
165
+ # Haryana cities
166
+ {"text": "Faridabad", "icon": "📍", "type": "city", "state": "Haryana"},
167
+ {"text": "Panipat", "icon": "📍", "type": "city", "state": "Haryana"},
168
+ {"text": "Ambala", "icon": "📍", "type": "city", "state": "Haryana"},
169
+ {"text": "Karnal", "icon": "📍", "type": "city", "state": "Haryana"},
170
+ {"text": "Hisar", "icon": "📍", "type": "city", "state": "Haryana"},
171
+
172
+ # Northeast cities
173
+ {"text": "Guwahati", "icon": "📍", "type": "city", "state": "Assam"},
174
+ {"text": "Shillong", "icon": "📍", "type": "city", "state": "Meghalaya"},
175
+ {"text": "Imphal", "icon": "📍", "type": "city", "state": "Manipur"},
176
+ {"text": "Aizawl", "icon": "📍", "type": "city", "state": "Mizoram"},
177
+ {"text": "Gangtok", "icon": "📍", "type": "city", "state": "Sikkim"},
178
+
179
+ # Union Territories
180
+ {"text": "Chandigarh", "icon": "📍", "type": "city", "state": "Chandigarh"},
181
+ {"text": "Port Blair", "icon": "📍", "type": "city", "state": "Andaman and Nicobar Islands"},
182
+ {"text": "Shimla", "icon": "📍", "type": "city", "state": "Himachal Pradesh"},
183
+ {"text": "Dehradun", "icon": "📍", "type": "city", "state": "Uttarakhand"},
184
+ {"text": "Itanagar", "icon": "📍", "type": "city", "state": "Arunachal Pradesh"}
185
+ ]
186
+
187
+ # Function to get cities by state
188
+ def get_cities_by_state(state_name):
189
+ """Get list of cities for a specific state"""
190
+ return [loc for loc in LOCATION_SUGGESTIONS if loc.get("type") == "city" and loc.get("state") == state_name]
191
+
192
+ # Function to get all states
193
+ def get_all_states():
194
+ """Get list of all states"""
195
+ return [loc for loc in LOCATION_SUGGESTIONS if loc.get("type") == "state"]
196
+
197
+ # Job types
198
+ JOB_TYPES = [
199
+ {"id": "all", "text": "All Types"},
200
+ {"id": "full-time", "text": "Full Time"},
201
+ {"id": "part-time", "text": "Part Time"},
202
+ {"id": "contract", "text": "Contract"},
203
+ {"id": "internship", "text": "Internship"},
204
+ {"id": "remote", "text": "Remote"}
205
+ ]
206
+
207
+ # Experience levels
208
+ EXPERIENCE_RANGES = [
209
+ {"id": "all", "text": "All Levels"},
210
+ {"id": "fresher", "text": "Fresher"},
211
+ {"id": "1-3", "text": "1-3 years"},
212
+ {"id": "3-5", "text": "3-5 years"},
213
+ {"id": "5-7", "text": "5-7 years"},
214
+ {"id": "7+", "text": "7+ years"}
215
+ ]
216
+
217
+ # Salary ranges
218
+ SALARY_RANGES = [
219
+ {"id": "all", "text": "All Ranges"},
220
+ {"id": "0-3", "text": "0-3 LPA"},
221
+ {"id": "3-6", "text": "3-6 LPA"},
222
+ {"id": "6-10", "text": "6-10 LPA"},
223
+ {"id": "10-15", "text": "10-15 LPA"},
224
+ {"id": "15+", "text": "15+ LPA"}
225
+ ]
jobs/webdriver_utils.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Utility functions for webdriver setup and management"""
2
+ import os
3
+ import sys
4
+ import platform
5
+ import tempfile
6
+ import subprocess
7
+ import streamlit as st
8
+ from selenium import webdriver
9
+ from selenium.webdriver.chrome.service import Service
10
+ from selenium.webdriver.chrome.options import Options
11
+
12
+ # Try to import various webdriver managers with fallbacks
13
+ try:
14
+ from webdriver_manager.chrome import ChromeDriverManager
15
+ from webdriver_manager.core.utils import ChromeType
16
+ webdriver_manager_available = True
17
+ except ImportError:
18
+ webdriver_manager_available = False
19
+
20
+ try:
21
+ import chromedriver_autoinstaller
22
+ autoinstaller_available = True
23
+ except ImportError:
24
+ autoinstaller_available = False
25
+
26
+ def get_chrome_version():
27
+ """Get the installed Chrome/Chromium version"""
28
+ system = platform.system()
29
+
30
+ if system == "Windows":
31
+ # Windows-specific Chrome detection
32
+ try:
33
+ # Try to find Chrome in Program Files
34
+ chrome_paths = [
35
+ r"C:\Program Files\Google\Chrome\Application\chrome.exe",
36
+ r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
37
+ os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe")
38
+ ]
39
+
40
+ for path in chrome_paths:
41
+ if os.path.exists(path):
42
+ try:
43
+ # Try using registry/wmic to get version
44
+ escaped_path = path.replace("\\", "\\\\")
45
+ output = subprocess.check_output(
46
+ ['wmic', 'datafile', 'where', f'name="{escaped_path}"', 'get', 'Version', '/value'],
47
+ stderr=subprocess.STDOUT
48
+ )
49
+ version_str = output.decode('utf-8').strip()
50
+ if "Version=" in version_str:
51
+ version = version_str.split('=')[1].split('.')[0]
52
+ return version
53
+ except:
54
+ # Try alternative method
55
+ try:
56
+ output = subprocess.check_output([path, '--version'], stderr=subprocess.STDOUT)
57
+ version = output.decode('utf-8').strip().split()[-1].split('.')[0]
58
+ return version
59
+ except:
60
+ pass
61
+ except Exception:
62
+ # Silently fail and continue with default
63
+ pass
64
+ else:
65
+ # Linux/Mac detection
66
+ try:
67
+ # Try different Chrome/Chromium binaries
68
+ for binary in ['/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser']:
69
+ if os.path.exists(binary):
70
+ version = subprocess.check_output([binary, '--version'], stderr=subprocess.STDOUT)
71
+ version = version.decode('utf-8').strip().split()[-1].split('.')[0]
72
+ return version
73
+ except Exception:
74
+ # Silently fail and continue with default
75
+ pass
76
+
77
+ # Default to latest if all else fails
78
+ return "120"
79
+
80
+ def run_setup_script():
81
+ """Run the setup_chromedriver.py script to install the correct chromedriver"""
82
+ try:
83
+ # Get the path to the setup script
84
+ script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
85
+ setup_script = os.path.join(script_dir, "setup_chromedriver.py")
86
+
87
+ if os.path.exists(setup_script):
88
+ st.info("Running chromedriver setup script...")
89
+ result = subprocess.run([sys.executable, setup_script],
90
+ capture_output=True, text=True)
91
+
92
+ if result.returncode == 0:
93
+ st.success("Chromedriver setup completed successfully!")
94
+ # Extract the chromedriver path from the output
95
+ for line in result.stdout.split('\n'):
96
+ if "Chromedriver path:" in line:
97
+ chromedriver_path = line.split("Chromedriver path:")[1].strip()
98
+ return chromedriver_path
99
+ else:
100
+ st.warning(f"Chromedriver setup failed: {result.stderr}")
101
+ else:
102
+ st.warning(f"Setup script not found at {setup_script}")
103
+ except Exception as e:
104
+ st.warning(f"Error running setup script: {str(e)}")
105
+
106
+ return None
107
+
108
+ def get_chromedriver_path():
109
+ """Get the path to the chromedriver executable based on the platform"""
110
+ system = platform.system()
111
+
112
+ if system == "Windows":
113
+ # Check in LocalAppData
114
+ local_app_data = os.environ.get('LOCALAPPDATA', '')
115
+ if local_app_data:
116
+ chromedriver_path = os.path.join(local_app_data, "ChromeDriver", "chromedriver.exe")
117
+ if os.path.exists(chromedriver_path):
118
+ return chromedriver_path
119
+ else:
120
+ # Check in ~/.chromedriver
121
+ home_dir = os.path.expanduser("~")
122
+ chromedriver_path = os.path.join(home_dir, ".chromedriver", "chromedriver")
123
+ if os.path.exists(chromedriver_path):
124
+ return chromedriver_path
125
+
126
+ return None
127
+
128
+ def setup_webdriver():
129
+ """
130
+ Set up and configure Chrome webdriver with multiple fallback options
131
+
132
+ Returns:
133
+ webdriver.Chrome or None: Configured Chrome webdriver or None if setup fails
134
+ """
135
+ options = Options()
136
+ options.add_argument('--headless')
137
+ options.add_argument('--no-sandbox')
138
+ options.add_argument('--disable-dev-shm-usage')
139
+ options.add_argument('--disable-gpu')
140
+ options.add_argument('--window-size=1920,1080')
141
+ options.add_argument('--disable-blink-features=AutomationControlled')
142
+ options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36')
143
+
144
+ # Method 1: Try direct initialization first since it's working
145
+ try:
146
+ driver = webdriver.Chrome(options=options)
147
+ st.success("Chrome webdriver initialized successfully!")
148
+ return driver
149
+ except Exception:
150
+ # If direct initialization fails, try other methods
151
+ pass
152
+
153
+ # Method 2: Check if we already have a chromedriver installed
154
+ chromedriver_path = get_chromedriver_path()
155
+ if chromedriver_path:
156
+ try:
157
+ service = Service(executable_path=chromedriver_path)
158
+ driver = webdriver.Chrome(service=service, options=options)
159
+ return driver
160
+ except Exception:
161
+ # Silently fail and continue with other methods
162
+ pass
163
+
164
+ # Method 3: Try using webdriver-manager
165
+ if webdriver_manager_available:
166
+ try:
167
+ service = Service(ChromeDriverManager().install())
168
+ driver = webdriver.Chrome(service=service, options=options)
169
+ return driver
170
+ except Exception:
171
+ # Silently fail and continue with other methods
172
+ pass
173
+
174
+ # Method 4: Try platform-specific approaches
175
+ system = platform.system()
176
+ if system == "Windows":
177
+ try:
178
+ # Try with Chrome binary path
179
+ chrome_paths = [
180
+ r"C:\Program Files\Google\Chrome\Application\chrome.exe",
181
+ r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
182
+ os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe")
183
+ ]
184
+
185
+ for path in chrome_paths:
186
+ if os.path.exists(path):
187
+ options.binary_location = path
188
+ try:
189
+ driver = webdriver.Chrome(options=options)
190
+ return driver
191
+ except Exception:
192
+ continue
193
+ except Exception:
194
+ # Silently fail and continue with other methods
195
+ pass
196
+ elif system == "Linux":
197
+ try:
198
+ # Try with Chromium binary path
199
+ options.binary_location = "/usr/bin/chromium"
200
+ try:
201
+ driver = webdriver.Chrome(options=options)
202
+ return driver
203
+ except Exception:
204
+ pass
205
+
206
+ # Try with Google Chrome binary path
207
+ options.binary_location = "/usr/bin/google-chrome"
208
+ try:
209
+ driver = webdriver.Chrome(options=options)
210
+ return driver
211
+ except Exception:
212
+ pass
213
+ except Exception:
214
+ # Silently fail and continue with other methods
215
+ pass
216
+
217
+ # All methods failed
218
+ st.error("Failed to initialize Chrome webdriver. Please make sure Chrome is installed.")
219
+ return None
packages.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ chromium
2
+ chromium-driver
3
+ libglib2.0-0
4
+ libnss3
5
+ libgconf-2-4
6
+ libfontconfig1
7
+ xvfb
8
+ wget
9
+ unzip
poppler/poppler-24.08.0/Library/bin/Lerc.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3383b3f5df29c5b556ccbff8b5c1cbcdcaa423a68cb22bc3bf84c4c36a1bcdbc
3
+ size 519680
poppler/poppler-24.08.0/Library/bin/cairo.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c690978c5be8486d06e4363cbc802fe800a3c454d66b64dd19f172292bd20a3f
3
+ size 1016320
poppler/poppler-24.08.0/Library/bin/charset.dll ADDED
Binary file (11.8 kB). View file
 
poppler/poppler-24.08.0/Library/bin/deflate.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b0c144c1bcb58b16d25f43cfb36e5e7fb03f39414fbb2f5b2e14b1874f6c7f27
3
+ size 177152
poppler/poppler-24.08.0/Library/bin/expat.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1eef16676ab7cabca520ed0b809bb7f38b23250a7d051942cf1b288aebdb9bfe
3
+ size 402432
poppler/poppler-24.08.0/Library/bin/fontconfig-1.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cfce15422bcf0fce28278b2e517741ef718de4e9f86ee8e9234805695005d018
3
+ size 282624
poppler/poppler-24.08.0/Library/bin/freetype.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f878121d560f43c398671bc1d0d7a61bf719fa7894eac5a1db665414bcd4096f
3
+ size 670720
poppler/poppler-24.08.0/Library/bin/iconv.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cb108fa99613c60b732221c6edf9a7ee0484c2758c63252708969616912c256c
3
+ size 937472
poppler/poppler-24.08.0/Library/bin/jpeg8.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b98a020fe2161efb9f8cfe991f8f77c5d497b290422328fe351948d2234838e9
3
+ size 803328
poppler/poppler-24.08.0/Library/bin/lcms2.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2f864b9f1bf5839fbc963265a0c9c94a4709124439e5ab5bfcae5f522483a1c4
3
+ size 560128
poppler/poppler-24.08.0/Library/bin/libcrypto-3-x64.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:124c5f40a6cf0e642f9d92784dd66314fd548b4fdf93c543bf478e71e1209f9d
3
+ size 6459392
poppler/poppler-24.08.0/Library/bin/libcurl.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a70c1cee2d55cf029eedc1a304e087d9276995d7caa0970832faf856d3bd4ae7
3
+ size 617984
poppler/poppler-24.08.0/Library/bin/libexpat.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1eef16676ab7cabca520ed0b809bb7f38b23250a7d051942cf1b288aebdb9bfe
3
+ size 402432
poppler/poppler-24.08.0/Library/bin/liblzma.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:07a4148f1972c843ddb31dbf1d058a7f73cb375f74473c9220f1476b8b973518
3
+ size 154624
poppler/poppler-24.08.0/Library/bin/libpng16.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:54789e18aceb43b5ad8a1c6a3a496b9379dd63d3ce46aefeddbeec87b8da3a08
3
+ size 196608
poppler/poppler-24.08.0/Library/bin/libssh2.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4a37f850e18c32d9606a9c5b38039bc4079b9d9f0346a3aab4c34ad7e2e4b5d9
3
+ size 244224
poppler/poppler-24.08.0/Library/bin/libtiff.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:58498f3e9e5f8444c9e315de6f35dae090e630978326838770187b5946303bda
3
+ size 493056