Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
# app.py
|
2 |
-
|
3 |
import streamlit as st
|
4 |
import requests
|
5 |
from langchain_groq import ChatGroq
|
@@ -14,9 +12,6 @@ from streamlit_option_menu import option_menu
|
|
14 |
import fitz # PyMuPDF
|
15 |
from bs4 import BeautifulSoup
|
16 |
|
17 |
-
# -------------------------------
|
18 |
-
# API Key Retrieval
|
19 |
-
# -------------------------------
|
20 |
|
21 |
GROQ_API_KEY = st.secrets["GROQ_API_KEY"]
|
22 |
RAPIDAPI_KEY = st.secrets["RAPIDAPI_KEY"]
|
@@ -24,9 +19,6 @@ YOUTUBE_API_KEY = st.secrets["YOUTUBE_API_KEY"]
|
|
24 |
THE_MUSE_API_KEY = st.secrets.get("THE_MUSE_API_KEY", "")
|
25 |
BLS_API_KEY = st.secrets.get("BLS_API_KEY", "")
|
26 |
|
27 |
-
# -------------------------------
|
28 |
-
# Initialize Language Model
|
29 |
-
# -------------------------------
|
30 |
|
31 |
llm = ChatGroq(
|
32 |
temperature=0,
|
@@ -34,9 +26,7 @@ llm = ChatGroq(
|
|
34 |
model_name="llama-3.1-70b-versatile"
|
35 |
)
|
36 |
|
37 |
-
|
38 |
-
# Function Definitions
|
39 |
-
# -------------------------------
|
40 |
|
41 |
@st.cache_data(ttl=3600)
|
42 |
def extract_text_from_pdf(pdf_file):
|
@@ -359,44 +349,51 @@ def fetch_muse_jobs_api(job_title, location=None, category=None, max_results=50)
|
|
359 |
st.error(f"Error fetching jobs from The Muse: {e}")
|
360 |
return []
|
361 |
|
362 |
-
# Indeed API Integration
|
363 |
@st.cache_data(ttl=86400) # Cache results for 1 day
|
364 |
-
def
|
365 |
"""
|
366 |
-
Fetches job
|
367 |
|
368 |
Args:
|
369 |
-
job_title (str): The job title to search for
|
370 |
-
location (str, optional): The
|
371 |
-
|
372 |
-
|
|
|
|
|
|
|
|
|
|
|
373 |
|
374 |
Returns:
|
375 |
-
list: A list of job
|
376 |
"""
|
377 |
-
url = "https://
|
378 |
-
|
379 |
-
# Encode job title to handle spaces and special characters
|
380 |
-
encoded_job_title = re.sub(r'\s+', '+', job_title.strip())
|
381 |
|
382 |
querystring = {
|
383 |
-
"
|
384 |
-
"
|
385 |
-
"
|
386 |
-
"
|
|
|
|
|
|
|
|
|
|
|
387 |
}
|
388 |
|
389 |
headers = {
|
390 |
-
"x-rapidapi-key": RAPIDAPI_KEY,
|
391 |
-
"x-rapidapi-host": "
|
392 |
}
|
393 |
|
394 |
try:
|
395 |
response = requests.get(url, headers=headers, params=querystring)
|
396 |
-
response.raise_for_status()
|
397 |
data = response.json()
|
398 |
-
|
399 |
-
return
|
400 |
except requests.exceptions.HTTPError as http_err:
|
401 |
if response.status_code == 400:
|
402 |
st.error("❌ Bad Request: Please check the parameters you're sending.")
|
@@ -414,6 +411,52 @@ def fetch_indeed_jobs_api(job_title, location="CA", sort="-1", page_size=50):
|
|
414 |
st.error(f"❌ An unexpected error occurred: {e}")
|
415 |
return []
|
416 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
417 |
def recommend_indeed_jobs(user_skills, user_preferences):
|
418 |
"""
|
419 |
Recommends jobs from Indeed API based on user skills and preferences.
|
@@ -426,19 +469,30 @@ def recommend_indeed_jobs(user_skills, user_preferences):
|
|
426 |
list: Recommended job listings.
|
427 |
"""
|
428 |
job_title = user_preferences.get("job_title", "")
|
429 |
-
|
430 |
-
category = user_preferences.get("category")
|
|
|
431 |
|
432 |
-
|
|
|
433 |
|
434 |
-
# Simple matching based on skills appearing in job description
|
435 |
recommended_jobs = []
|
436 |
-
|
437 |
-
job_description = job.get("description", "").lower()
|
438 |
-
match_score = sum(skill.lower() in job_description for skill in user_skills)
|
439 |
-
if match_score > 0:
|
440 |
-
recommended_jobs.append((match_score, job))
|
441 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
442 |
# Sort jobs based on match_score
|
443 |
recommended_jobs.sort(reverse=True, key=lambda x: x[0])
|
444 |
|
@@ -546,6 +600,97 @@ def display_bls_data(series_id, title):
|
|
546 |
fig = px.line(df, x="Year", y="Value", title=series_title, markers=True)
|
547 |
st.plotly_chart(fig, use_container_width=True)
|
548 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
549 |
# -------------------------------
|
550 |
# Application Tracking Database Functions
|
551 |
# -------------------------------
|
@@ -711,7 +856,32 @@ def embed_youtube_videos(video_urls, module_name):
|
|
711 |
# Job Recommendations and BLS Integration
|
712 |
# -------------------------------
|
713 |
|
714 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
715 |
|
716 |
# -------------------------------
|
717 |
# Page Functions
|
@@ -931,6 +1101,7 @@ def application_tracking_dashboard():
|
|
931 |
|
932 |
# Initialize database
|
933 |
init_db()
|
|
|
934 |
|
935 |
# Form to add a new application
|
936 |
st.subheader("➕ Add New Application")
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import requests
|
3 |
from langchain_groq import ChatGroq
|
|
|
12 |
import fitz # PyMuPDF
|
13 |
from bs4 import BeautifulSoup
|
14 |
|
|
|
|
|
|
|
15 |
|
16 |
GROQ_API_KEY = st.secrets["GROQ_API_KEY"]
|
17 |
RAPIDAPI_KEY = st.secrets["RAPIDAPI_KEY"]
|
|
|
19 |
THE_MUSE_API_KEY = st.secrets.get("THE_MUSE_API_KEY", "")
|
20 |
BLS_API_KEY = st.secrets.get("BLS_API_KEY", "")
|
21 |
|
|
|
|
|
|
|
22 |
|
23 |
llm = ChatGroq(
|
24 |
temperature=0,
|
|
|
26 |
model_name="llama-3.1-70b-versatile"
|
27 |
)
|
28 |
|
29 |
+
|
|
|
|
|
30 |
|
31 |
@st.cache_data(ttl=3600)
|
32 |
def extract_text_from_pdf(pdf_file):
|
|
|
349 |
st.error(f"Error fetching jobs from The Muse: {e}")
|
350 |
return []
|
351 |
|
352 |
+
# Indeed API Integration using /list and /get
|
353 |
@st.cache_data(ttl=86400) # Cache results for 1 day
|
354 |
+
def fetch_indeed_jobs_list_api(job_title, location="United States", distance="1.0", language="en_GB", remoteOnly="false", datePosted="month", employmentTypes="fulltime;parttime;intern;contractor", index=0, page_size=10):
|
355 |
"""
|
356 |
+
Fetches a list of job IDs from Indeed API based on user preferences.
|
357 |
|
358 |
Args:
|
359 |
+
job_title (str): The job title to search for.
|
360 |
+
location (str, optional): The job location. Defaults to "United States".
|
361 |
+
distance (str, optional): Search radius in miles. Defaults to "1.0".
|
362 |
+
language (str, optional): Language code. Defaults to "en_GB".
|
363 |
+
remoteOnly (str, optional): "true" or "false". Defaults to "false".
|
364 |
+
datePosted (str, optional): e.g., "month". Defaults to "month".
|
365 |
+
employmentTypes (str, optional): e.g., "fulltime;parttime;intern;contractor". Defaults to "fulltime;parttime;intern;contractor".
|
366 |
+
index (int, optional): Pagination index. Defaults to 0.
|
367 |
+
page_size (int, optional): Number of jobs to fetch. Defaults to 10.
|
368 |
|
369 |
Returns:
|
370 |
+
list: A list of job IDs.
|
371 |
"""
|
372 |
+
url = "https://jobs-api14.p.rapidapi.com/list"
|
|
|
|
|
|
|
373 |
|
374 |
querystring = {
|
375 |
+
"query": job_title,
|
376 |
+
"location": location,
|
377 |
+
"distance": distance,
|
378 |
+
"language": language,
|
379 |
+
"remoteOnly": remoteOnly,
|
380 |
+
"datePosted": datePosted,
|
381 |
+
"employmentTypes": employmentTypes,
|
382 |
+
"index": str(index),
|
383 |
+
"page_size": str(page_size)
|
384 |
}
|
385 |
|
386 |
headers = {
|
387 |
+
"x-rapidapi-key": RAPIDAPI_KEY,
|
388 |
+
"x-rapidapi-host": "jobs-api14.p.rapidapi.com"
|
389 |
}
|
390 |
|
391 |
try:
|
392 |
response = requests.get(url, headers=headers, params=querystring)
|
393 |
+
response.raise_for_status()
|
394 |
data = response.json()
|
395 |
+
job_ids = [job["id"] for job in data.get("jobs", [])]
|
396 |
+
return job_ids
|
397 |
except requests.exceptions.HTTPError as http_err:
|
398 |
if response.status_code == 400:
|
399 |
st.error("❌ Bad Request: Please check the parameters you're sending.")
|
|
|
411 |
st.error(f"❌ An unexpected error occurred: {e}")
|
412 |
return []
|
413 |
|
414 |
+
@st.cache_data(ttl=86400) # Cache results for 1 day
|
415 |
+
def fetch_indeed_job_details_api(job_id, language="en_GB"):
|
416 |
+
"""
|
417 |
+
Fetches job details from Indeed API based on job ID.
|
418 |
+
|
419 |
+
Args:
|
420 |
+
job_id (str): The job ID.
|
421 |
+
language (str, optional): Language code. Defaults to "en_GB".
|
422 |
+
|
423 |
+
Returns:
|
424 |
+
dict: Job details.
|
425 |
+
"""
|
426 |
+
url = "https://jobs-api14.p.rapidapi.com/get"
|
427 |
+
|
428 |
+
querystring = {
|
429 |
+
"id": job_id,
|
430 |
+
"language": language
|
431 |
+
}
|
432 |
+
|
433 |
+
headers = {
|
434 |
+
"x-rapidapi-key": RAPIDAPI_KEY,
|
435 |
+
"x-rapidapi-host": "jobs-api14.p.rapidapi.com"
|
436 |
+
}
|
437 |
+
|
438 |
+
try:
|
439 |
+
response = requests.get(url, headers=headers, params=querystring)
|
440 |
+
response.raise_for_status()
|
441 |
+
job_details = response.json()
|
442 |
+
return job_details
|
443 |
+
except requests.exceptions.HTTPError as http_err:
|
444 |
+
if response.status_code == 400:
|
445 |
+
st.error("❌ Bad Request: Please check the job ID and parameters.")
|
446 |
+
elif response.status_code == 403:
|
447 |
+
st.error("❌ Access Forbidden: Check your API key and permissions.")
|
448 |
+
elif response.status_code == 404:
|
449 |
+
st.error("❌ Job Not Found: Verify the job ID.")
|
450 |
+
else:
|
451 |
+
st.error(f"❌ HTTP error occurred: {http_err}")
|
452 |
+
return {}
|
453 |
+
except requests.exceptions.RequestException as req_err:
|
454 |
+
st.error(f"❌ Request Exception: {req_err}")
|
455 |
+
return {}
|
456 |
+
except Exception as e:
|
457 |
+
st.error(f"❌ An unexpected error occurred: {e}")
|
458 |
+
return {}
|
459 |
+
|
460 |
def recommend_indeed_jobs(user_skills, user_preferences):
|
461 |
"""
|
462 |
Recommends jobs from Indeed API based on user skills and preferences.
|
|
|
469 |
list: Recommended job listings.
|
470 |
"""
|
471 |
job_title = user_preferences.get("job_title", "")
|
472 |
+
location = user_preferences.get("location", "United States")
|
473 |
+
category = user_preferences.get("category", "")
|
474 |
+
language = "en_GB"
|
475 |
|
476 |
+
# Fetch job IDs
|
477 |
+
job_ids = fetch_indeed_jobs_list_api(job_title, location=location, category=category, page_size=5) # Limiting to 5 for API call count
|
478 |
|
|
|
479 |
recommended_jobs = []
|
480 |
+
api_calls_needed = len(job_ids) # Each /get call counts as one
|
|
|
|
|
|
|
|
|
481 |
|
482 |
+
# Check if enough API calls are left
|
483 |
+
if not can_make_api_calls(api_calls_needed):
|
484 |
+
st.error("❌ You have reached your monthly API request limit. Please try again next month.")
|
485 |
+
return []
|
486 |
+
|
487 |
+
for job_id in job_ids:
|
488 |
+
job_details = fetch_indeed_job_details_api(job_id, language=language)
|
489 |
+
if job_details and not job_details.get("hasError", True):
|
490 |
+
job_description = job_details.get("description", "").lower()
|
491 |
+
match_score = sum(skill.lower() in job_description for skill in user_skills)
|
492 |
+
if match_score > 0:
|
493 |
+
recommended_jobs.append((match_score, job_details))
|
494 |
+
decrement_api_calls(1)
|
495 |
+
|
496 |
# Sort jobs based on match_score
|
497 |
recommended_jobs.sort(reverse=True, key=lambda x: x[0])
|
498 |
|
|
|
600 |
fig = px.line(df, x="Year", y="Value", title=series_title, markers=True)
|
601 |
st.plotly_chart(fig, use_container_width=True)
|
602 |
|
603 |
+
# -------------------------------
|
604 |
+
# API Usage Counter Functions
|
605 |
+
# -------------------------------
|
606 |
+
|
607 |
+
def init_api_usage_db():
|
608 |
+
"""
|
609 |
+
Initializes the SQLite database and creates the api_usage table if it doesn't exist.
|
610 |
+
"""
|
611 |
+
conn = sqlite3.connect('applications.db')
|
612 |
+
c = conn.cursor()
|
613 |
+
c.execute('''
|
614 |
+
CREATE TABLE IF NOT EXISTS api_usage (
|
615 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
616 |
+
count INTEGER,
|
617 |
+
last_reset DATE
|
618 |
+
)
|
619 |
+
''')
|
620 |
+
# Check if a row exists, if not, initialize
|
621 |
+
c.execute('SELECT COUNT(*) FROM api_usage')
|
622 |
+
if c.fetchone()[0] == 0:
|
623 |
+
# Initialize with 25 requests and current date
|
624 |
+
c.execute('INSERT INTO api_usage (count, last_reset) VALUES (?, ?)', (25, datetime.now().date()))
|
625 |
+
conn.commit()
|
626 |
+
conn.close()
|
627 |
+
|
628 |
+
def get_api_usage():
|
629 |
+
"""
|
630 |
+
Retrieves the current API usage count and last reset date.
|
631 |
+
|
632 |
+
Returns:
|
633 |
+
tuple: (count, last_reset_date)
|
634 |
+
"""
|
635 |
+
conn = sqlite3.connect('applications.db')
|
636 |
+
c = conn.cursor()
|
637 |
+
c.execute('SELECT count, last_reset FROM api_usage WHERE id = 1')
|
638 |
+
row = c.fetchone()
|
639 |
+
conn.close()
|
640 |
+
if row:
|
641 |
+
return row[0], datetime.strptime(row[1], "%Y-%m-%d").date()
|
642 |
+
else:
|
643 |
+
return 25, datetime.now().date()
|
644 |
+
|
645 |
+
def reset_api_usage():
|
646 |
+
"""
|
647 |
+
Resets the API usage count to 25 and updates the last reset date.
|
648 |
+
"""
|
649 |
+
conn = sqlite3.connect('applications.db')
|
650 |
+
c = conn.cursor()
|
651 |
+
c.execute('UPDATE api_usage SET count = ?, last_reset = ? WHERE id = 1', (25, datetime.now().date()))
|
652 |
+
conn.commit()
|
653 |
+
conn.close()
|
654 |
+
|
655 |
+
def can_make_api_calls(requests_needed):
|
656 |
+
"""
|
657 |
+
Checks if there are enough API calls remaining.
|
658 |
+
|
659 |
+
Args:
|
660 |
+
requests_needed (int): Number of API calls required.
|
661 |
+
|
662 |
+
Returns:
|
663 |
+
bool: True if allowed, False otherwise.
|
664 |
+
"""
|
665 |
+
count, last_reset = get_api_usage()
|
666 |
+
today = datetime.now().date()
|
667 |
+
if today >= last_reset + timedelta(days=30):
|
668 |
+
reset_api_usage()
|
669 |
+
count, last_reset = get_api_usage()
|
670 |
+
if count >= requests_needed:
|
671 |
+
return True
|
672 |
+
else:
|
673 |
+
return False
|
674 |
+
|
675 |
+
def decrement_api_calls(requests_used):
|
676 |
+
"""
|
677 |
+
Decrements the API usage count by the number of requests used.
|
678 |
+
|
679 |
+
Args:
|
680 |
+
requests_used (int): Number of API calls used.
|
681 |
+
"""
|
682 |
+
conn = sqlite3.connect('applications.db')
|
683 |
+
c = conn.cursor()
|
684 |
+
c.execute('SELECT count FROM api_usage WHERE id = 1')
|
685 |
+
row = c.fetchone()
|
686 |
+
if row:
|
687 |
+
new_count = row[0] - requests_used
|
688 |
+
if new_count < 0:
|
689 |
+
new_count = 0
|
690 |
+
c.execute('UPDATE api_usage SET count = ? WHERE id = 1', (new_count,))
|
691 |
+
conn.commit()
|
692 |
+
conn.close()
|
693 |
+
|
694 |
# -------------------------------
|
695 |
# Application Tracking Database Functions
|
696 |
# -------------------------------
|
|
|
856 |
# Job Recommendations and BLS Integration
|
857 |
# -------------------------------
|
858 |
|
859 |
+
def labor_market_insights_module():
|
860 |
+
st.header("📈 Labor Market Insights")
|
861 |
+
|
862 |
+
st.write("""
|
863 |
+
Gain valuable insights into the current labor market trends, employment rates, and industry growth to make informed career decisions.
|
864 |
+
""")
|
865 |
+
|
866 |
+
# Define BLS Series IDs based on desired data
|
867 |
+
# Example: Unemployment rate (Series ID: LNS14000000)
|
868 |
+
# Reference: https://www.bls.gov/web/laus/laumstrk.htm
|
869 |
+
unemployment_series_id = "LNS14000000" # Unemployment Rate
|
870 |
+
employment_series_id = "CEU0000000001" # Total Employment
|
871 |
+
|
872 |
+
# Display Unemployment Rate
|
873 |
+
display_bls_data(unemployment_series_id, "Unemployment Rate (%)")
|
874 |
+
|
875 |
+
# Display Total Employment
|
876 |
+
display_bls_data(employment_series_id, "Total Employment")
|
877 |
+
|
878 |
+
# Additional Insights
|
879 |
+
st.subheader("💡 Additional Insights")
|
880 |
+
st.write("""
|
881 |
+
- **Industry Growth:** Understanding which industries are growing can help you target your job search effectively.
|
882 |
+
- **Salary Trends:** Keeping an eye on salary trends ensures that you negotiate effectively and align your expectations.
|
883 |
+
- **Geographical Demand:** Some regions may have higher demand for certain roles, guiding your location preferences.
|
884 |
+
""")
|
885 |
|
886 |
# -------------------------------
|
887 |
# Page Functions
|
|
|
1101 |
|
1102 |
# Initialize database
|
1103 |
init_db()
|
1104 |
+
init_api_usage_db()
|
1105 |
|
1106 |
# Form to add a new application
|
1107 |
st.subheader("➕ Add New Application")
|