Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- app.py +60 -0
- job_api.py +24 -0
- utils.py +44 -0
app.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from utils import (
|
| 3 |
+
extract_text_from_pdf,
|
| 4 |
+
analyze_cv_text,
|
| 5 |
+
get_personalized_roadmap,
|
| 6 |
+
get_skill_score,
|
| 7 |
+
generate_job_recommendations,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
st.set_page_config(page_title="Smart CV Analyzer", layout="wide")
|
| 11 |
+
|
| 12 |
+
st.title("📄 Smart CV Analyzer & Career Roadmap Generator")
|
| 13 |
+
|
| 14 |
+
uploaded_file = st.file_uploader("Upload your CV (PDF)", type=["pdf"])
|
| 15 |
+
|
| 16 |
+
if uploaded_file:
|
| 17 |
+
text = extract_text_from_pdf(uploaded_file)
|
| 18 |
+
with st.spinner("Analyzing your CV..."):
|
| 19 |
+
analysis = analyze_cv_text(text)
|
| 20 |
+
roadmap = get_personalized_roadmap(text)
|
| 21 |
+
score = get_skill_score(text)
|
| 22 |
+
|
| 23 |
+
st.success("✅ CV Analysis Completed")
|
| 24 |
+
|
| 25 |
+
st.subheader("🔍 Career Field & Key Insights")
|
| 26 |
+
st.write(analysis)
|
| 27 |
+
|
| 28 |
+
st.subheader("📈 Skill Score")
|
| 29 |
+
st.metric(label="Skill Score (1-100)", value=score)
|
| 30 |
+
|
| 31 |
+
st.subheader("🗺️ Personalized Career Roadmap")
|
| 32 |
+
st.text_area("6-Month Roadmap", roadmap, height=300)
|
| 33 |
+
|
| 34 |
+
# Job Filters
|
| 35 |
+
st.subheader("💼 Job Listings (via Adzuna API)")
|
| 36 |
+
with st.form("job_filters"):
|
| 37 |
+
col1, col2 = st.columns(2)
|
| 38 |
+
with col1:
|
| 39 |
+
location = st.text_input("Preferred Location", value="United States")
|
| 40 |
+
with col2:
|
| 41 |
+
job_type = st.selectbox("Job Type", ["", "full_time", "part_time", "contract", "internship"])
|
| 42 |
+
experience = st.selectbox("Experience Level", ["", "entry", "mid", "senior"])
|
| 43 |
+
submitted = st.form_submit_button("Search Jobs")
|
| 44 |
+
|
| 45 |
+
if submitted:
|
| 46 |
+
jobs = generate_job_recommendations(text, location)
|
| 47 |
+
if jobs:
|
| 48 |
+
for job in jobs:
|
| 49 |
+
st.markdown(f"### [{job.get('title')}]({job.get('redirect_url')})")
|
| 50 |
+
st.write(f"**Company:** {job.get('company', {}).get('display_name', 'N/A')}")
|
| 51 |
+
st.write(f"**Location:** {job.get('location', {}).get('display_name', 'N/A')}")
|
| 52 |
+
st.write(f"**Salary:** ${job.get('salary_min', 0)} - ${job.get('salary_max', 0)}")
|
| 53 |
+
st.markdown("---")
|
| 54 |
+
else:
|
| 55 |
+
st.warning("No jobs found matching your profile.")
|
| 56 |
+
|
| 57 |
+
# Report Download
|
| 58 |
+
st.subheader("📥 Download Report")
|
| 59 |
+
report = f"Skill Score: {score}/100\n\nInsights:\n{analysis}\n\nRoadmap:\n{roadmap}"
|
| 60 |
+
st.download_button("Download Full Report", report, file_name="cv_analysis_report.txt")
|
job_api.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
|
| 4 |
+
ADZUNA_APP_ID = os.getenv("ADZUNA_APP_ID")
|
| 5 |
+
ADZUNA_APP_KEY = os.getenv("ADZUNA_APP_KEY")
|
| 6 |
+
|
| 7 |
+
def fetch_job_listings_adzuna(keywords, location="United States", results_limit=10):
|
| 8 |
+
url = f"https://api.adzuna.com/v1/api/jobs/us/search/1"
|
| 9 |
+
params = {
|
| 10 |
+
"app_id": ADZUNA_APP_ID,
|
| 11 |
+
"app_key": ADZUNA_APP_KEY,
|
| 12 |
+
"what": keywords,
|
| 13 |
+
"where": location,
|
| 14 |
+
"results_per_page": results_limit,
|
| 15 |
+
"content-type": "application/json",
|
| 16 |
+
}
|
| 17 |
+
try:
|
| 18 |
+
response = requests.get(url, params=params)
|
| 19 |
+
response.raise_for_status()
|
| 20 |
+
data = response.json()
|
| 21 |
+
return data.get("results", [])
|
| 22 |
+
except Exception as e:
|
| 23 |
+
print(f"Error fetching job listings: {e}")
|
| 24 |
+
return []
|
utils.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import spacy
|
| 4 |
+
import PyPDF2
|
| 5 |
+
import openai
|
| 6 |
+
from transformers import pipeline
|
| 7 |
+
from job_api import fetch_job_listings_adzuna
|
| 8 |
+
|
| 9 |
+
openai.api_key = os.getenv("OPENAI_API_KEY")
|
| 10 |
+
|
| 11 |
+
nlp_spacy = spacy.load("en_core_web_sm")
|
| 12 |
+
|
| 13 |
+
llm = pipeline("text-generation", model="mistralai/Mistral-7B-Instruct-v0.1", device="cpu", max_new_tokens=512)
|
| 14 |
+
|
| 15 |
+
def extract_text_from_pdf(pdf_file):
|
| 16 |
+
reader = PyPDF2.PdfReader(pdf_file)
|
| 17 |
+
return " ".join([page.extract_text() for page in reader.pages if page.extract_text()])
|
| 18 |
+
|
| 19 |
+
def extract_keywords(text):
|
| 20 |
+
doc = nlp_spacy(text)
|
| 21 |
+
return list(set([token.lemma_.lower() for token in doc if token.pos_ in ["NOUN", "PROPN", "VERB"] and len(token.text) > 2]))
|
| 22 |
+
|
| 23 |
+
def analyze_cv_text(text):
|
| 24 |
+
prompt = f"Analyze this CV and suggest the top skills and job domain:
|
| 25 |
+
{text[:1500]}"
|
| 26 |
+
result = llm(prompt)[0]["generated_text"]
|
| 27 |
+
return result
|
| 28 |
+
|
| 29 |
+
def get_personalized_roadmap(text):
|
| 30 |
+
prompt = f"Based on this CV, generate a 6-month career improvement roadmap including skills, certifications, education, and goals:
|
| 31 |
+
{text[:1500]}"
|
| 32 |
+
response = llm(prompt)[0]["generated_text"]
|
| 33 |
+
return response
|
| 34 |
+
|
| 35 |
+
def get_skill_score(text):
|
| 36 |
+
prompt = f"Give a skill strength score (1-100) based on this CV:
|
| 37 |
+
{text[:1500]}"
|
| 38 |
+
result = llm(prompt)[0]["generated_text"]
|
| 39 |
+
score = re.findall(r"(\d+)", result)
|
| 40 |
+
return int(score[0]) if score else 50
|
| 41 |
+
|
| 42 |
+
def generate_job_recommendations(text, location):
|
| 43 |
+
keywords = " ".join(extract_keywords(text))
|
| 44 |
+
return fetch_job_listings_adzuna(keywords, location)
|