razaali10's picture
Update app.py
23ee159 verified
Raw
History Blame Contribute Delete
9.58 kB
import streamlit as st
import pandas as pd
import json
import smtplib
from email.message import EmailMessage
from typing import Dict, List
from jobspy import scrape_jobs
import groq
# ======================================================
# Utilities
# ======================================================
def remove_duplicates(df: pd.DataFrame) -> pd.DataFrame:
df["__dedup__"] = (
df.get("title", "").astype(str) + "|" +
df.get("company", "").astype(str) + "|" +
df.get("location", "").astype(str) + "|" +
df.get("job_url", "").astype(str)
)
return df.drop_duplicates("__dedup__").drop(columns="__dedup__")
def compute_keyword_score(text: str, keywords: List[str]) -> int:
text_l = (text or "").lower()
return sum(text_l.count(k.lower()) for k in keywords if k)
# ======================================================
# Optional Email Helper
# ======================================================
def email_secrets_available() -> bool:
required = [
"SMTP_SERVER",
"SMTP_PORT",
"SMTP_USER",
"SMTP_PASSWORD",
"EMAIL_FROM",
]
return all(key in st.secrets for key in required)
def send_email_with_csv(recipient_email: str, df: pd.DataFrame):
smtp_server = st.secrets["SMTP_SERVER"]
smtp_port = int(st.secrets["SMTP_PORT"])
smtp_user = st.secrets["SMTP_USER"]
smtp_password = st.secrets["SMTP_PASSWORD"]
email_from = st.secrets["EMAIL_FROM"]
msg = EmailMessage()
msg["Subject"] = "Your Job Search Results"
msg["From"] = email_from
msg["To"] = recipient_email
msg.set_content(
"Hello,\n\nAttached is the CSV file containing your job search results.\n\nRegards,\nPrivate Job Search Tool"
)
csv_data = df.to_csv(index=False)
msg.add_attachment(csv_data, subtype="csv", filename="job_results.csv")
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(smtp_user, smtp_password)
server.send_message(msg)
# ======================================================
# AI helper (intent extraction)
# ======================================================
def extract_search_parameters(client, prompt: str) -> Dict[str, str]:
system_prompt = """
Extract job search parameters.
Return JSON ONLY:
{
"search_term": "<job title or keywords>",
"location": "<city, province/state, or country>"
}
"""
response = client.chat.completions.create(
model="meta-llama/llama-4-scout-17b-16e-instruct",
temperature=0.2,
max_tokens=200,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
try:
return json.loads(response.choices[0].message.content)
except Exception:
return {"search_term": prompt, "location": "Canada"}
# ======================================================
# Job scraping
# ======================================================
@st.cache_data(ttl=3600)
def get_indeed_jobs(
search_term: str,
location: str,
radius_km: int,
posted_within_days: int
) -> pd.DataFrame:
try:
jobs = scrape_jobs(
site_name=["indeed"],
search_term=search_term,
location=location,
results_wanted=100,
hours_old=posted_within_days * 24,
country_indeed="Canada",
radius=radius_km
)
return pd.DataFrame(jobs)
except Exception:
return pd.DataFrame()
# ======================================================
# Streamlit App
# ======================================================
def main():
st.set_page_config(page_title="Private Job Search", layout="centered")
st.title("📄 Private Job Search, Rank & Download")
# --------------------------------------------------
# Job description
# --------------------------------------------------
job_prompt = st.text_area(
"Describe the job you are looking for",
placeholder="e.g. Civil Engineer, Water Resources, Transportation",
height=120
)
api_key = st.text_input("Groq API Key", type="password")
# --------------------------------------------------
# City selection
# --------------------------------------------------
st.subheader("Location")
predefined_cities = [
"Use AI / Prompt Location",
"Calgary, AB",
"Edmonton, AB",
"Toronto, ON",
"Vancouver, BC",
"Mississauga, ON",
"Brampton, ON",
"Ottawa, ON",
"Hamilton, ON",
"Custom city..."
]
selected_city = st.selectbox("Select city", predefined_cities)
custom_city = ""
if selected_city == "Custom city...":
custom_city = st.text_input(
"Enter city (e.g., Red Deer, AB or Surrey, BC)"
)
# --------------------------------------------------
# Job boards
# --------------------------------------------------
st.subheader("Job Boards")
use_indeed = st.checkbox("Indeed", value=True)
# --------------------------------------------------
# Filters
# --------------------------------------------------
st.subheader("Filters")
posted_within_days = st.slider(
"Posted within last (days)",
min_value=1,
max_value=30,
value=7
)
radius_km = st.slider(
"Search radius (km)",
min_value=5,
max_value=100,
value=25,
step=5
)
# --------------------------------------------------
# Keyword ranking
# --------------------------------------------------
keywords_raw = st.text_input(
"Keyword ranking (comma-separated)",
placeholder="water, wastewater, stormwater, EPANET"
)
keywords = [k.strip() for k in keywords_raw.split(",") if k.strip()]
# --------------------------------------------------
# Optional email
# --------------------------------------------------
send_email = st.checkbox("📧 Send results by email (optional)")
email_address = st.text_input("Email address") if send_email else None
# --------------------------------------------------
# Action
# --------------------------------------------------
if st.button(
"🔍 Search Jobs",
disabled=not job_prompt or not api_key
):
client = groq.Client(api_key=api_key)
with st.spinner("Understanding your request..."):
params = extract_search_parameters(client, job_prompt)
# Resolve final location
if selected_city == "Use AI / Prompt Location":
location = params.get("location", "Canada")
elif selected_city == "Custom city...":
location = custom_city if custom_city else params.get("location", "Canada")
else:
location = selected_city
if not use_indeed:
st.warning("No job boards selected.")
return
with st.spinner("Searching jobs..."):
jobs_df = get_indeed_jobs(
params["search_term"],
location,
radius_km,
posted_within_days
)
if jobs_df.empty:
st.warning("No jobs found.")
return
jobs_df.fillna("", inplace=True)
jobs_df = remove_duplicates(jobs_df)
# Keyword ranking
jobs_df["keyword_score"] = jobs_df.apply(
lambda r: compute_keyword_score(
f"{r.get('title','')} {r.get('description','')}",
keywords
),
axis=1
)
jobs_df = jobs_df.sort_values(
by="keyword_score",
ascending=False
)
st.success(f"✅ Found {len(jobs_df)} jobs for **{location}**")
# --------------------------------------------------
# Download
# --------------------------------------------------
csv_data = jobs_df.to_csv(index=False).encode("utf-8")
st.download_button(
label="⬇️ Download Jobs (CSV)",
data=csv_data,
file_name="job_results.csv",
mime="text/csv"
)
# --------------------------------------------------
# Optional email
# --------------------------------------------------
if send_email:
if not email_address:
st.warning("Please enter an email address.")
elif not email_secrets_available():
st.warning("Email not configured. Download is still available.")
else:
with st.spinner("Sending email..."):
try:
send_email_with_csv(email_address, jobs_df)
st.success(f"📧 Results emailed to {email_address}")
except Exception as e:
st.error(f"Failed to send email: {e}")
# --------------------------------------------------
# Preview
# --------------------------------------------------
st.subheader("Preview (Top 20 Results)")
preview_cols = [
c for c in [
"title", "company", "location",
"keyword_score", "date_posted", "job_url"
] if c in jobs_df.columns
]
st.dataframe(
jobs_df[preview_cols].head(20),
use_container_width=True
)
if __name__ == "__main__":
main()