MrugajaJ commited on
Commit
63e0dfb
·
verified ·
1 Parent(s): a63d10c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -58
app.py CHANGED
@@ -17,34 +17,54 @@ from src.precompute import build_candidate_text, build_jd_text
17
 
18
  # Set Page Config
19
  st.set_page_config(
20
- page_title="Vettly Talent Intelligence Portal",
21
- page_icon="💼",
22
  layout="wide",
23
  initial_sidebar_state="expanded"
24
  )
25
 
26
- # Custom HR-Themed Styling
27
  st.markdown("""
28
  <style>
29
- /* Light Offwhite & Green Theme */
 
 
30
  .stApp {
31
- background-color: #F8FAF8;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
  /* Candidate Cards */
35
  .candidate-card {
36
- background-color: #FFFFFF;
37
- border: 1px solid #E2E8F0;
38
- border-left: 5px solid #2E7D32; /* Green accent */
39
  padding: 1.5rem;
40
  border-radius: 8px;
41
  margin-bottom: 1.2rem;
42
- box-shadow: 0 2px 4px rgba(0,0,0,0.02);
43
- transition: transform 0.2s ease, box-shadow 0.2s ease;
44
  }
45
  .candidate-card:hover {
46
  transform: translateY(-2px);
47
- box-shadow: 0 6px 12px rgba(0,0,0,0.05);
48
  }
49
 
50
  /* Badges & Metrics */
@@ -55,70 +75,81 @@ st.markdown("""
55
  font-size: 0.8rem;
56
  font-weight: 600;
57
  margin-right: 0.5rem;
 
 
 
58
  }
59
- .badge-rose {
60
- background-color: #E8F5E9;
61
- color: #2E7D32;
62
- }
63
- .badge-indigo {
64
- background-color: #F1F8E9;
65
- color: #33691E;
66
- }
67
- .badge-emerald {
68
- background-color: #E0F2F1;
69
- color: #00695C;
70
  }
71
 
72
  /* Stats Layout */
73
  .stat-box {
74
- background-color: #FFFFFF;
75
- border: 1px solid #C8E6C9;
76
- padding: 1rem;
77
  border-radius: 8px;
78
  text-align: center;
79
- box-shadow: 0 1px 3px rgba(0,0,0,0.01);
 
 
 
 
 
 
 
 
 
 
 
80
  }
81
 
82
  /* Typography adjustments */
83
- h1, h2, h3 {
84
- color: #1B5E20 !important;
85
  font-family: 'Inter', sans-serif;
86
  }
 
 
 
 
 
 
 
87
  </style>
88
  """, unsafe_allow_html=True)
89
 
90
  # App Title & Welcome Banner
91
- st.title("Vettly Talent Portal")
92
- st.markdown("#### AI-Assisted Candidate Discovery, Fit Analysis, and Talent Alignment")
93
- st.markdown("<hr style='border-color: #C8E6C9;'>", unsafe_allow_html=True)
94
 
95
  # Center Uploads
96
- st.markdown("### 📋 Upload Datasets")
97
  col_up1, col_up2 = st.columns(2)
98
  with col_up1:
99
- uploaded_jd = st.file_uploader("Upload Job Description (JSON)", type=["json"])
100
  with col_up2:
101
- uploaded_candidates = st.file_uploader("Upload Candidates Dataset (JSONL)", type=["jsonl"])
102
  use_default_candidates = st.checkbox("Use Demo Candidates Dataset (100,000 Profiles) - Instant Load", value=False)
103
 
104
  if use_default_candidates:
105
- # Use the pre-existing local file directly to skip browser uploading
106
  uploaded_candidates = open("data/candidates.jsonl", "rb")
107
 
108
  # Sidebar Setup
109
  with st.sidebar:
110
- st.markdown("<h4 style='color: #1B5E20 !important;'>⚙️ Score Weights</h4>", unsafe_allow_html=True)
111
  w_A = st.slider("Career Fit Weight (A)", 0.0, 1.0, 0.40, 0.05)
112
  w_B = st.slider("Skill Trust Weight (B)", 0.0, 1.0, 0.35, 0.05)
113
  w_C = st.slider("Semantic Similarity Weight (C)", 0.0, 1.0, 0.25, 0.05)
114
 
115
- # Check normalization
116
  if abs((w_A + w_B + w_C) - 1.0) > 0.001:
117
  st.warning(f"Weights sum to {w_A+w_B+w_C:.2f}. They will be normalized to 1.0 internally.")
118
 
119
  # Check if inputs are uploaded
120
  if not uploaded_jd or not uploaded_candidates:
121
- st.info("👋 Welcome! Please **upload the Job Description** and the **Candidates Dataset** (or check the Demo Dataset box) above to begin. The Start button will appear once files are loaded.")
122
  else:
123
  jd_data = json.load(uploaded_jd)
124
 
@@ -142,7 +173,7 @@ else:
142
  st.write(", ".join([f"`{k}`" for k in kws]))
143
 
144
  # Start Button
145
- if st.button("🚀 Start Talent Search & Vetting Pipeline", use_container_width=True):
146
  # Normalize weights
147
  total_w = w_A + w_B + w_C
148
  nw_A, nw_B, nw_C = w_A/total_w, w_B/total_w, w_C/total_w
@@ -260,24 +291,27 @@ else:
260
  progress_bar.empty()
261
 
262
  # Display Stats Summary Dashboard
 
263
  st.markdown("### Talent Pipeline Summary Dashboard")
264
  d_col1, d_col2, d_col3, d_col4 = st.columns(4)
265
  with d_col1:
266
- st.markdown(f"<div class='stat-box'><h4>Total Profiles</h4><h2 style='color:#1B5E20;'>100,000</h2></div>", unsafe_allow_html=True)
267
  with d_col2:
268
- st.markdown(f"<div class='stat-box'><h4>Filtered Out</h4><h2 style='color:#4CAF50;'>{100000 - len(survivors):,}</h2></div>", unsafe_allow_html=True)
269
  with d_col3:
270
- st.markdown(f"<div class='stat-box'><h4>Qualified Survivors</h4><h2 style='color:#2E7D32;'>{len(survivors):,}</h2></div>", unsafe_allow_html=True)
271
  with d_col4:
272
- st.markdown(f"<div class='stat-box'><h4>Pruned Ratio</h4><h2 style='color:#81C784;'>{((100000 - len(survivors))/100000)*100:.2f}%</h2></div>", unsafe_allow_html=True)
273
 
274
  # Draw Bar chart of filtering reasons
275
- st.markdown("#### Primary Reasons for Candidate Disqualification")
276
  df_reasons = pd.DataFrame(list(killed_reasons.items()), columns=["Disqualification Category", "Candidate Count"])
277
- st.bar_chart(df_reasons.set_index("Disqualification Category"), color="#4CAF50")
 
 
278
 
279
  # Output Top Candidates list in a gorgeous card design
280
- st.markdown("### 🏆 Top 50 Matched Candidates")
281
 
282
  for rank, cand_item in enumerate(top_candidates, 1):
283
  cand = cand_item["candidate"]
@@ -299,28 +333,28 @@ else:
299
  <div class="candidate-card">
300
  <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
301
  <div>
302
- <span class="metric-badge badge-rose" style="font-size:1rem; padding: 0.4rem 0.8rem;">Rank #{rank}</span>
303
- <strong style="font-size:1.2rem; color:#1B5E20; margin-left: 0.5rem;">{anom_name}</strong>
304
- <span style="color:#64748B; margin-left:1rem;">{curr_title} @ {curr_company}</span>
305
  </div>
306
  <div>
307
- <span style="font-size:1.6rem; font-weight:700; color:#2E7D32;">{final_pct}% Match</span>
308
  </div>
309
  </div>
310
- <div style="margin-bottom: 0.8rem;">
311
- <span class="metric-badge badge-indigo">💼 {yoe} Years Experience</span>
312
- <span class="metric-badge badge-indigo">📍 {loc}</span>
313
- <span class="metric-badge badge-emerald">Career Fit: {score_A_pct}%</span>
314
- <span class="metric-badge badge-emerald">Skills Trust: {score_B_pct}%</span>
315
- <span class="metric-badge badge-emerald">Semantic Sim: {score_C_pct}%</span>
316
  </div>
317
  </div>
318
  """, unsafe_allow_html=True)
319
 
320
  # Details Expander
321
- with st.expander(f"Inspect profile details & hiring alignment for {anom_name}"):
322
  st.markdown("**Core Fit Analysis:**")
323
- st.write(f"Candidate has a match score of {final_pct}%. They possess {yoe} years of relevant industry experience in {profile.get('current_industry', 'tech')}. Matched locations include {loc}.")
324
 
325
  # Show career history
326
  st.markdown("**Career History Summary:**")
 
17
 
18
  # Set Page Config
19
  st.set_page_config(
20
+ page_title="Vettly Talent Portal",
 
21
  layout="wide",
22
  initial_sidebar_state="expanded"
23
  )
24
 
25
+ # Custom Premium Dark Theme Styling
26
  st.markdown("""
27
  <style>
28
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
29
+
30
+ /* Global Backgrounds */
31
  .stApp {
32
+ background-color: #0f111a;
33
+ color: #ffffff;
34
+ font-family: 'Inter', sans-serif;
35
+ }
36
+
37
+ [data-testid="stSidebar"] {
38
+ background-color: #161925;
39
+ border-right: 1px solid #24293e;
40
+ }
41
+
42
+ /* File Uploader Customization */
43
+ [data-testid="stFileUploader"] {
44
+ background-color: #1c2035;
45
+ border: 1px dashed #24293e;
46
+ border-radius: 8px;
47
+ padding: 1rem;
48
+ }
49
+
50
+ /* Slider & Accent styling */
51
+ .stSlider > div > div > div > div {
52
+ background-color: #ff7b00 !important;
53
  }
54
 
55
  /* Candidate Cards */
56
  .candidate-card {
57
+ background-color: #161925;
58
+ border: 1px solid #24293e;
59
+ border-left: 4px solid #ff7b00;
60
  padding: 1.5rem;
61
  border-radius: 8px;
62
  margin-bottom: 1.2rem;
63
+ transition: transform 0.2s ease, border-color 0.2s ease;
 
64
  }
65
  .candidate-card:hover {
66
  transform: translateY(-2px);
67
+ border-color: #ff7b00;
68
  }
69
 
70
  /* Badges & Metrics */
 
75
  font-size: 0.8rem;
76
  font-weight: 600;
77
  margin-right: 0.5rem;
78
+ background-color: #24293e;
79
+ color: #e2e8f0;
80
+ border: 1px solid #333a56;
81
  }
82
+ .badge-accent {
83
+ color: #ff7b00;
84
+ background-color: rgba(255, 123, 0, 0.1);
85
+ border-color: rgba(255, 123, 0, 0.2);
 
 
 
 
 
 
 
86
  }
87
 
88
  /* Stats Layout */
89
  .stat-box {
90
+ background-color: #161925;
91
+ border: 1px solid #24293e;
92
+ padding: 1.5rem;
93
  border-radius: 8px;
94
  text-align: center;
95
+ }
96
+ .stat-box h4 {
97
+ color: #94a3b8 !important;
98
+ font-size: 0.9rem;
99
+ text-transform: uppercase;
100
+ letter-spacing: 0.05em;
101
+ margin-bottom: 0.5rem;
102
+ }
103
+ .stat-box h2 {
104
+ color: #ffffff !important;
105
+ font-size: 2rem;
106
+ margin: 0;
107
  }
108
 
109
  /* Typography adjustments */
110
+ h1, h2, h3, h4, h5, p, span {
 
111
  font-family: 'Inter', sans-serif;
112
  }
113
+ h1 { color: #ffffff !important; font-weight: 700; }
114
+ h2, h3 { color: #e2e8f0 !important; font-weight: 600; }
115
+ p { color: #cbd5e1 !important; }
116
+
117
+ /* Override markdown text colors */
118
+ .stMarkdown p { color: #cbd5e1; }
119
+ .stMarkdown strong { color: #ffffff; }
120
  </style>
121
  """, unsafe_allow_html=True)
122
 
123
  # App Title & Welcome Banner
124
+ st.markdown("<h1 style='font-size: 2.8rem; margin-bottom: 0;'>Vettly Talent Portal</h1>", unsafe_allow_html=True)
125
+ st.markdown("<h3 style='color: #94a3b8 !important; margin-top: 0.5rem; font-weight: 400;'>AI-Assisted Candidate Discovery, Fit Analysis, and Role Alignment</h3>", unsafe_allow_html=True)
126
+ st.markdown("<hr style='border-color: #24293e; margin: 2rem 0;'>", unsafe_allow_html=True)
127
 
128
  # Center Uploads
129
+ st.markdown("### Upload Datasets")
130
  col_up1, col_up2 = st.columns(2)
131
  with col_up1:
132
+ uploaded_jd = st.file_uploader("Job Description (JSON)", type=["json"])
133
  with col_up2:
134
+ uploaded_candidates = st.file_uploader("Candidates Dataset (JSONL)", type=["jsonl"])
135
  use_default_candidates = st.checkbox("Use Demo Candidates Dataset (100,000 Profiles) - Instant Load", value=False)
136
 
137
  if use_default_candidates:
 
138
  uploaded_candidates = open("data/candidates.jsonl", "rb")
139
 
140
  # Sidebar Setup
141
  with st.sidebar:
142
+ st.markdown("### Score Weights Configuration")
143
  w_A = st.slider("Career Fit Weight (A)", 0.0, 1.0, 0.40, 0.05)
144
  w_B = st.slider("Skill Trust Weight (B)", 0.0, 1.0, 0.35, 0.05)
145
  w_C = st.slider("Semantic Similarity Weight (C)", 0.0, 1.0, 0.25, 0.05)
146
 
 
147
  if abs((w_A + w_B + w_C) - 1.0) > 0.001:
148
  st.warning(f"Weights sum to {w_A+w_B+w_C:.2f}. They will be normalized to 1.0 internally.")
149
 
150
  # Check if inputs are uploaded
151
  if not uploaded_jd or not uploaded_candidates:
152
+ st.info("Welcome. Please upload the Job Description and the Candidates Dataset (or check the Demo Dataset box) above to begin. The start action will appear once files are loaded.")
153
  else:
154
  jd_data = json.load(uploaded_jd)
155
 
 
173
  st.write(", ".join([f"`{k}`" for k in kws]))
174
 
175
  # Start Button
176
+ if st.button("Start Talent Search & Vetting Pipeline", type="primary", use_container_width=True):
177
  # Normalize weights
178
  total_w = w_A + w_B + w_C
179
  nw_A, nw_B, nw_C = w_A/total_w, w_B/total_w, w_C/total_w
 
291
  progress_bar.empty()
292
 
293
  # Display Stats Summary Dashboard
294
+ st.markdown("<hr style='border-color: #24293e; margin: 2rem 0;'>", unsafe_allow_html=True)
295
  st.markdown("### Talent Pipeline Summary Dashboard")
296
  d_col1, d_col2, d_col3, d_col4 = st.columns(4)
297
  with d_col1:
298
+ st.markdown(f"<div class='stat-box'><h4>Total Profiles</h4><h2>100,000</h2></div>", unsafe_allow_html=True)
299
  with d_col2:
300
+ st.markdown(f"<div class='stat-box'><h4>Filtered Out</h4><h2>{100000 - len(survivors):,}</h2></div>", unsafe_allow_html=True)
301
  with d_col3:
302
+ st.markdown(f"<div class='stat-box'><h4>Qualified Survivors</h4><h2 style='color: #ff7b00 !important;'>{len(survivors):,}</h2></div>", unsafe_allow_html=True)
303
  with d_col4:
304
+ st.markdown(f"<div class='stat-box'><h4>Pruned Ratio</h4><h2>{((100000 - len(survivors))/100000)*100:.2f}%</h2></div>", unsafe_allow_html=True)
305
 
306
  # Draw Bar chart of filtering reasons
307
+ st.markdown("<br><h4>Primary Reasons for Candidate Disqualification</h4>", unsafe_allow_html=True)
308
  df_reasons = pd.DataFrame(list(killed_reasons.items()), columns=["Disqualification Category", "Candidate Count"])
309
+ st.bar_chart(df_reasons.set_index("Disqualification Category"), color="#ff7b00")
310
+
311
+ st.markdown("<hr style='border-color: #24293e; margin: 2rem 0;'>", unsafe_allow_html=True)
312
 
313
  # Output Top Candidates list in a gorgeous card design
314
+ st.markdown("### Top 50 Matched Candidates")
315
 
316
  for rank, cand_item in enumerate(top_candidates, 1):
317
  cand = cand_item["candidate"]
 
333
  <div class="candidate-card">
334
  <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
335
  <div>
336
+ <span class="metric-badge badge-accent" style="font-size:1rem; padding: 0.4rem 0.8rem;">Rank #{rank}</span>
337
+ <strong style="font-size:1.2rem; color:#ffffff; margin-left: 0.5rem;">{anom_name}</strong>
338
+ <span style="color:#94a3b8; margin-left:1rem;">{curr_title} @ {curr_company}</span>
339
  </div>
340
  <div>
341
+ <span style="font-size:1.6rem; font-weight:700; color:#ff7b00;">{final_pct}% Match</span>
342
  </div>
343
  </div>
344
+ <div style="margin-bottom: 0.4rem;">
345
+ <span class="metric-badge">Exp: {yoe} Yrs</span>
346
+ <span class="metric-badge">Loc: {loc}</span>
347
+ <span class="metric-badge">Career Fit: {score_A_pct}%</span>
348
+ <span class="metric-badge">Skills Trust: {score_B_pct}%</span>
349
+ <span class="metric-badge">Semantic Sim: {score_C_pct}%</span>
350
  </div>
351
  </div>
352
  """, unsafe_allow_html=True)
353
 
354
  # Details Expander
355
+ with st.expander(f"Inspect Profile Details & Alignment: {anom_name}"):
356
  st.markdown("**Core Fit Analysis:**")
357
+ st.write(f"Candidate has a match score of {final_pct}%. They possess {yoe} years of relevant industry experience in {profile.get('current_industry', 'tech')}. Matched locations include {loc}.")
358
 
359
  # Show career history
360
  st.markdown("**Career History Summary:**")