-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstreamlit_app.py
More file actions
129 lines (117 loc) · 5.26 KB
/
Copy pathstreamlit_app.py
File metadata and controls
129 lines (117 loc) · 5.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
from __future__ import annotations
import streamlit as st
from job_matcher.search import search_jobs_for_cv
SOURCE_LABELS = {
"linkedin": "LinkedIn",
"etat_geneve": "État de Genève",
"jobup": "JobUp",
"indeed": "Indeed",
}
SOURCE_ICONS = {
"linkedin": "app/static/source-icons/linkedin.png",
"etat_geneve": "app/static/source-icons/etat-geneve.png",
"jobup": "app/static/source-icons/jobup.png",
"indeed": "app/static/source-icons/indeed.png",
}
SORT_ORDER_OPTIONS = {
"Title Relevance": "relevance",
"Job Description Relevance": "text_score_final",
"Newest first": "newest",
"Title A–Z": "title_asc",
}
def clean_company_name(company: str | None) -> str:
company = (company or "").strip()
if not company:
return "Unknown company"
cutoff = min(
(company.index(separator) for separator in (",", "(", ":") if separator in company),
default=len(company),
)
return company[:cutoff].strip() or "Unknown company"
st.set_page_config(page_title="CV Job Matcher", page_icon=":briefcase:", layout="wide")
st.title("CV Job Matcher")
st.caption("Upload a CV PDF, pick a time window, and retrieve the closest job offers from the database.")
uploaded_file = st.file_uploader("CV PDF", type=["pdf"])
filters_col, limit_col, sort_col = st.columns(3)
with filters_col:
lookback_hours = st.slider("Lookback window (hours)", min_value=1, max_value=168, value=24)
with limit_col:
result_limit = st.segmented_control(
"Matching offers",
options=[25, 50, 100, 200],
default=25,
selection_mode="single",
)
with sort_col:
sort_order_label = st.selectbox(
"Sort offers by",
options=list(SORT_ORDER_OPTIONS),
index=0,
)
sort_order = SORT_ORDER_OPTIONS[sort_order_label]
if st.button("Find matching offers", type="primary", use_container_width=True):
if uploaded_file is None:
st.error("Upload a PDF before starting the search.")
else:
with st.spinner("Computing CV embeddings and querying the database..."):
try:
cv_text, cv_chunks, results = search_jobs_for_cv(
uploaded_file.getvalue(),
lookback_hours=lookback_hours,
result_limit=result_limit,
sort_order=sort_order,
)
except Exception as exc:
st.exception(exc)
else:
left, middle, right = st.columns(3)
left.metric("CV chars", len(cv_text))
middle.metric("CV chunks", len(cv_chunks))
right.metric("Matching offers", len(results))
if not results:
st.warning("No offers matched the selected time window.")
else:
for index, result in enumerate(results, start=1):
source_label = SOURCE_LABELS.get(
result.source or "",
result.source or "Source inconnue",
)
source_icon = SOURCE_ICONS.get(result.source or "")
source_prefix = (
f" "
if source_icon
else ""
)
company_name = clean_company_name(result.company)
with st.expander(
(
f"{index}. {source_prefix}"
f"{result.title or 'Untitled'} - "
f"{company_name}"
),
expanded=index <= 3,
):
st.markdown(
f"""
**Location:** {result.location or "N/A"}
**Employment type:** {result.employment_type or "N/A"}
**Industry:** {result.industry or "N/A"}
**Posted at:** {result.date_posted or "N/A"}
**URL:** {result.canonical_url}
"""
)
score_a, score_b, score_c, score_d = st.columns(4)
score_a.metric("Title score", f"{result.title_score:.4f}")
score_b.metric("Text score final", f"{result.score_final:.4f}")
score_c.metric("Text score max", f"{result.score_max:.4f}")
score_d.metric("Text score top5", f"{result.score_top5_mean:.4f}")
left_col, right_col = st.columns(2)
with left_col:
st.markdown("**Top matching paragraph**")
if result.top_paragraph:
st.markdown(result.top_paragraph, unsafe_allow_html=True)
else:
st.write("N/A")
with right_col:
st.markdown("**Best CV chunk**")
st.write(result.top_cv_chunk or "N/A")