razaali10's picture
Update app.py
23ee159 verified
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()