resumeradar / app.py
Kartik-work's picture
Fix file upload permissions and temp directory on HF
8f98f93
Raw
History Blame Contribute Delete
6.72 kB
import tempfile
import pandas as pd
import streamlit as st
from pathlib import Path
from src.extractor import extract_text, get_pdf_preview
from src.matcher import compute_match
from config import APP_ICON, APP_TITLE
from core.logger import get_logger
logger = get_logger(__name__)
st.set_page_config(page_title = APP_TITLE, page_icon = APP_ICON, layout = "wide" )
#initalising the session state
if "resumes" not in st.session_state:
st.session_state.resume = {}
if "selected_resume" not in st.session_state:
st.session_state.selected_resume = None
if "jd_text" not in st.session_state:
st.session_state.jd_text = ""
if "results" not in st.session_state:
st.session_state.results = {}
if "previews" not in st.session_state:
st.session_state.previews = {}
st.title(f"{APP_ICON} {APP_TITLE}")
st.caption("Upload resume · Paste a job description · Get instant match analysis")
st.divider()
col_left, col_right = st.columns(2)
with col_left:
st.subheader("Resumes")
upload_files = st.file_uploader("Upload one or more resumes",
type = ["pdf"],
accept_multiple_files = True)
#Process the upload files
if upload_files:
for file in upload_files:
name = file.name
if name not in st.session_state.resume:
with st.spinner(f"Extracting {name}..."):
try:
pdf_bytes = file.read()
preview_bytes = get_pdf_preview(pdf_bytes)
st.session_state.previews[name] = preview_bytes
with tempfile.NamedTemporaryFile(suffix = ".pdf", delete = False, dir="/tmp" ) as temp:
temp.write(pdf_bytes)
temp_path = Path(temp.name)
text = extract_text(temp_path)
temp_path.unlink()
st.session_state.resume[name] = text
logger.info(f"Resume stored: {name}")
st.success(f"{name} extracted")
except Exception as e:
logger.error(f"Failed to extract: {name} : {e}")
st.error(f"Failed to rad: {name}")
if st.session_state.resume:
st.divider()
resume_names = list(st.session_state.resume.keys())
selected = st.selectbox("Select resume to preview",
options = resume_names,
key = "selected_resume")
if selected:
st.markdown("**Fist page Preview: **")
if selected in st.session_state.previews:
st.image(
st.session_state.previews[selected],
caption = selected,
use_container_width = True
)
st.caption(f"Extracted: {len(st.session_state.resume[selected])} chars")
with col_right:
st.subheader("Job Discription")
jd_text = st.text_area(
"Paste the job discription here",
height = 400,
placeholder = "e.g. We are looking for AI Engineer ...",
key = "jd_text"
)
if len(jd_text) < 500:
st.warning(f"JD seems too short — add more details for better matching")
elif len(jd_text) > 5000:
st.warning(f"JD is very long — consider pasting key requirements only")
else:
st.caption(f"{len(jd_text.split())} words — good length")
st.divider()
col_empty_left, col_btn, ccol_empty_right = st.columns([3, 4, 3])
with col_btn:
analyze_clicked = st.button(
"Analyze Match",
type = "primary",
use_container_width = True
)
if analyze_clicked:
if not st.session_state.resume:
st.warning("Please upload at least one Resume")
elif not jd_text.strip():
st.warning("Please paste a job description")
else:
selected = st.session_state.selected_resume
resume_text = st.session_state.resume[selected]
with st.spinner(f"Analysing {selected}..."):
try:
result = compute_match(resume_text, jd_text)
st.session_state.results[selected] = result
logger.info(f"Analysis complete: {selected}")
except Exception as e:
logger.error(f"Analysis failed for {selected}: {e}")
st.error("Analysis failed - please try again")
if st.session_state.results:
selected = st.session_state.selected_resume
if selected in st.session_state.results:
results = st.session_state.results[selected]
st.divider()
st.subheader(f"Results - {selected}")
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
label = "match Score",
value = f"{result['match_percentage']}%"
)
with col2:
st.metric(
label = "Missing Skills",
value = len(result['missing_skills'])
)
with col3:
st.metric(
label="Matched Skills",
value=len(result['match_skills'])
)
verdict = result['verdict']
if verdict == "Strong Match":
st.success(f"{verdict}")
elif verdict == "Good Match":
st.info(f"{verdict}")
elif verdict == "Weak Match":
st.warning(f"{verdict}")
else:
st.error(f"{verdict}")
col_match, col_miss = st.columns(2)
with col_match:
st.markdown("**Matched Skills**")
for skill in result['match_skills']:
st.markdown(f"- {skill}")
with col_miss:
st.markdown("**Missing Skills**")
for skill in result["missing_skills"]:
st.markdown(f"- {skill}")
st.divider()
st.subheader("Summary - All Resumes")
if not st.session_state.results:
st.info("Analyse resume to see summary here")
else:
row = []
for resume_name, result in st.session_state.results.items():
row.append({
"Resume": resume_name,
"Match%": result["match_percentage"],
"Matched Skills": len(result["match_skills"]),
"Missing Skills": len(result["missing_skills"]),
"Verdict": result["verdict"]
})
df = pd.DataFrame(row)
df = df.sort_values("Match%", ascending = False)
st.dataframe(
df,
use_container_width = True,
hide_index = True
)