scmlewis commited on
Commit
bac5ff8
·
verified ·
1 Parent(s): 34cbce4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -86
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # app.py
2
- # Business-Focused Streamlit Application for AI Talent Screening (FIXED)
3
 
4
  import streamlit as st
5
  from transformers import BertTokenizer, BertForSequenceClassification, T5Tokenizer, T5ForConditionalGeneration
@@ -13,65 +13,71 @@ from docx import Document
13
  import time
14
  import pandas as pd
15
 
16
- # Set page config with professional theme and wide layout
17
  st.set_page_config(
18
  page_title="AI Talent Screening Tool",
19
- page_icon="💼",
20
  layout="wide",
21
  initial_sidebar_state="expanded",
22
  )
23
 
24
- # --- CUSTOM PROFESSIONAL CSS OVERHAUL (UNCHANGED) ---
25
  st.markdown("""
26
  <style>
27
- /* 0. GLOBAL CONFIG & LIGHT THEME */
28
  :root {
29
- --primary-color: #007BFF; /* Corporate Blue */
30
- --success-color: #28A745; /* Corporate Green */
31
- --warning-color: #FFC107; /* Corporate Yellow */
32
- --danger-color: #DC3545; /* Corporate Red */
33
- --background-color: #F8F9FA; /* Light Gray Background */
34
- --container-background: #FFFFFF; /* White Container */
35
- --text-color: #212529; /* Dark Text */
 
36
  }
37
 
38
  .main {
39
  background-color: var(--background-color);
40
  color: var(--text-color);
41
- font-family: 'Arial', sans-serif;
 
 
 
 
 
42
  }
43
 
44
  /* 1. HEADER & TITLES */
45
  h1 {
46
  text-align: center;
47
  color: var(--primary-color);
48
- font-size: 2.5em;
49
- font-weight: 700;
50
- border-bottom: 3px solid rgba(0, 123, 255, 0.2);
51
- padding-bottom: 10px;
52
  margin-bottom: 30px;
53
  }
54
  h2, h3, h4 {
55
  color: var(--text-color);
56
- border-left: 5px solid var(--success-color); /* Green marker for clarity */
57
- padding-left: 10px;
58
- margin-top: 25px;
59
  font-weight: 600;
60
  }
61
 
62
  /* 2. BUTTONS & HOVER EFFECTS */
63
  .stButton>button {
64
- color: var(--primary-color) !important;
65
- border: 1px solid var(--primary-color) !important;
66
  background-color: var(--container-background) !important;
67
- border-radius: 8px; /* Rounded corners for friendly look */
68
  transition: all 0.3s ease;
69
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
70
  font-weight: 600;
71
  }
72
  .stButton>button:hover {
73
- background-color: rgba(0, 123, 255, 0.1) !important;
74
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); /* Subtle shadow lift */
75
  transform: translateY(-2px);
76
  }
77
  /* Primary Button (Run Screening) */
@@ -81,70 +87,95 @@ st.markdown("""
81
  border-color: var(--success-color) !important;
82
  }
83
  .stButton>button[kind="primary"]:hover {
84
- background-color: #1e7e34 !important; /* Darker green on hover */
85
- border-color: #1e7e34 !important;
86
  }
87
 
88
- /* 3. INPUTS, CONTAINERS & TABS */
89
  .stTextArea, .stTextInput, .stFileUploader {
90
  border-radius: 8px;
91
- border: 1px solid #CED4DA;
92
- background-color: var(--container-background);
93
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);
 
94
  }
95
  .stTabs [aria-selected="true"] {
96
- color: var(--success-color) !important;
97
- border-bottom: 3px solid var(--success-color) !important;
98
  font-weight: bold;
99
  }
100
  .stSidebar {
101
- background-color: #E9ECEF; /* Sidebar background color matching light theme */
102
- border-right: 1px solid #DEE2E6;
 
103
  }
104
 
105
- /* Custom Scorecard Style (Clean, White Tiles) */
 
 
 
 
 
 
 
106
  .scorecard-block {
107
- border: 1px solid #DEE2E6;
108
- border-radius: 10px; /* Highly rounded for friendly look */
109
- padding: 15px;
110
  margin: 5px 0;
111
- background-color: var(--container-background);
112
  transition: all 0.3s;
113
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
114
  }
115
  .scorecard-block:hover {
116
- box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
117
  }
118
  .scorecard-value {
119
- font-size: 36px;
120
- font-weight: 700;
121
  color: var(--primary-color);
122
  }
123
  .scorecard-label {
124
  font-size: 14px;
125
- color: #6C757D;
126
  }
127
  /* Color override for specific blocks */
128
  .block-relevant { border-left: 5px solid var(--success-color); }
129
  .block-uncertain { border-left: 5px solid var(--warning-color); }
130
  .block-irrelevant { border-left: 5px solid var(--danger-color); }
131
 
132
- /* Streamlit's Info/Success/Warning blocks made friendlier */
133
  [data-testid="stAlert"] {
134
  border-radius: 8px;
135
  padding: 15px;
136
  font-size: 16px;
 
 
137
  }
138
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  </style>
140
  """, unsafe_allow_html=True)
141
 
142
- # --- (Model and Helper Functions - Core logic remains the same) ---
143
- # ... (skills_list, skills_pattern, extract_text_from_pdf/docx/file, normalize_text, check_experience_mismatch, validate_input, load_models, tokenize_inputs, extract_skills, classify_and_summarize_batch, generate_skill_pie_chart functions remain unchanged) ...
144
-
145
- # NOTE: Since the file content is large, I'm only including the modified function `render_sidebar`
146
- # and the affected part of `main` for brevity. The full code block at the end contains the complete, fixed file.
147
 
 
 
 
148
  skills_list = [
149
  'python', 'sql', 'c++', 'java', 'tableau', 'machine learning', 'data analysis',
150
  'business intelligence', 'r', 'tensorflow', 'pandas', 'spark', 'scikit-learn', 'aws',
@@ -181,7 +212,7 @@ def extract_text_from_docx(file):
181
  text += paragraph.text + "\n"
182
  return text.strip()
183
  except: return ""
184
-
185
  def extract_text_from_file(uploaded_file):
186
  if uploaded_file.name.endswith('.pdf'): return extract_text_from_pdf(uploaded_file)
187
  elif uploaded_file.name.endswith('.docx'): return extract_text_from_docx(uploaded_file)
@@ -296,13 +327,13 @@ def classify_and_summarize_batch(resume, job_description, _bert_tok, _t5_input,
296
  elif detected_skills: final_summary = f"Key Skills: {', '.join(detected_skills)}"
297
  else: final_summary = f"Experience: {exp_match.group(0) if exp_match else 'Unknown'}"
298
 
299
- if suitability == "Relevant": color = "#28A745"
300
- elif suitability == "Irrelevant": color = "#DC3545"
301
  else: color = "#FFC107"
302
 
303
  return {"Suitability": suitability, "Key Skills & Experience Summary": final_summary, "Flagging Reason": warning, "Suitability_Color": color}
304
  except Exception as e:
305
- return {"Suitability": "Error", "Key Skills & Experience Summary": "Failed to process profile", "Flagging Reason": str(e), "Suitability_Color": "#DC3545"}
306
 
307
  @st.cache_data
308
  def generate_skill_pie_chart(resumes):
@@ -326,29 +357,30 @@ def generate_skill_pie_chart(resumes):
326
  labels = list(top_skills.keys())
327
  sizes = [(count / sum(top_skills.values())) * 100 for count in top_skills.values()]
328
 
329
- plt.style.use('default')
 
330
  fig, ax = plt.subplots(figsize=(6, 4))
331
- colors = plt.cm.tab10(np.linspace(0, 1, len(labels)))
332
- plt.rcParams['text.color'] = 'black'
333
- wedges, texts, autotexts = ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors, textprops={'fontsize': 10, 'color': 'black'})
334
  ax.axis('equal')
335
- plt.title("Top Candidate Skill Frequency", fontsize=14, color='#007BFF', pad=10)
336
  return fig
337
 
338
  def render_sidebar():
339
- """Render sidebar content with professional HR language. FIXED: Replaced st.get_style_color with hex codes."""
340
- # Define hex colors to replace st.get_style_color() calls
341
- SUCCESS_COLOR = "#28A745" # Corporate Green
342
- WARNING_COLOR = "#FFC107" # Corporate Yellow
343
- DANGER_COLOR = "#DC3545" # Corporate Red
344
- PRIMARY_COLOR = "#007BFF" # Corporate Blue
345
 
346
  with st.sidebar:
347
  st.markdown(f"""
348
  <h2 style='text-align: center; border-left: none; padding-left: 0; color: {PRIMARY_COLOR};'>
349
- Talent Screening Assistant
350
  </h2>
351
- <p style='text-align: center; font-size: 14px; margin-top: 0; color: #6C757D;'>
352
  Powered by Advanced NLP (BERT + T5)
353
  </p>
354
  """, unsafe_allow_html=True)
@@ -388,7 +420,7 @@ def main():
388
  if 'valid_resumes' not in st.session_state: st.session_state.valid_resumes = []
389
  if 'models' not in st.session_state: st.session_state.models = None
390
 
391
- st.markdown("<h1>AI TALENT SCREENING TOOL</h1>", unsafe_allow_html=True)
392
 
393
  # HR-friendly Tab Names
394
  tab_setup, tab_resumes, tab_results = st.tabs(["1. Job Requirement Setup", "2. Candidate Profile Upload", "3. Screening Report & Analytics"])
@@ -503,17 +535,8 @@ def main():
503
 
504
  for i, resume in enumerate(valid_resumes):
505
  status_text.text(f"Status: Analyzing Profile {i+1} of {total_steps}...")
506
-
507
- # Create single-batch tensors for BERT and T5
508
- bert_tok_single = {
509
- 'input_ids': bert_tokenized['input_ids'][i].unsqueeze(0),
510
- 'attention_mask': bert_tokenized['attention_mask'][i].unsqueeze(0)
511
- }
512
- t5_tok_single = {
513
- 'input_ids': t5_tokenized['input_ids'][i].unsqueeze(0),
514
- 'attention_mask': t5_tokenized['attention_mask'][i].unsqueeze(0)
515
- }
516
-
517
  result = classify_and_summarize_batch(resume, job_description, bert_tok_single, t5_inputs[i], t5_tok_single, job_skills_set)
518
  result["Profile ID"] = f"Candidate {i+1}"
519
  results.append(result)
@@ -541,10 +564,10 @@ def main():
541
  st.markdown(f"#### Overview: {total} Candidate Profiles Processed")
542
 
543
  # Define hex colors again for the scorecard blocks
544
- PRIMARY_COLOR = "#007BFF" # Corporate Blue
545
- SUCCESS_COLOR = "#28A745" # Corporate Green
546
- WARNING_COLOR = "#FFC107" # Corporate Yellow
547
- DANGER_COLOR = "#DC3545" # Corporate Red
548
 
549
  col1, col2, col3, col4 = st.columns(4)
550
 
 
1
  # app.py
2
+ # Modern Dark Mode Streamlit Application for AI Talent Screening
3
 
4
  import streamlit as st
5
  from transformers import BertTokenizer, BertForSequenceClassification, T5Tokenizer, T5ForConditionalGeneration
 
13
  import time
14
  import pandas as pd
15
 
16
+ # Set page config with modern dark theme and wide layout
17
  st.set_page_config(
18
  page_title="AI Talent Screening Tool",
19
+ page_icon="🚀",
20
  layout="wide",
21
  initial_sidebar_state="expanded",
22
  )
23
 
24
+ # --- CUSTOM MODERN DARK MODE CSS OVERHAUL ---
25
  st.markdown("""
26
  <style>
27
+ /* 0. GLOBAL CONFIG & DARK THEME */
28
  :root {
29
+ --primary-color: #9C27B0; /* Vibrant Purple/Magenta (Accent) */
30
+ --success-color: #4CAF50; /* Green (Good Match) */
31
+ --warning-color: #FFC107; /* Amber/Yellow (Review) */
32
+ --danger-color: #F44336; /* Red (Irrelevant/Error) */
33
+ --background-color: #1E1E1E; /* Deep Dark Background */
34
+ --container-background: #2D2D2D; /* Slightly Lighter Container */
35
+ --text-color: #F8F8F8; /* Light Text */
36
+ --secondary-text-color: #B0B0B0; /* Muted Light Gray */
37
  }
38
 
39
  .main {
40
  background-color: var(--background-color);
41
  color: var(--text-color);
42
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
43
+ }
44
+
45
+ /* Overall Streamlit container background (Dark) */
46
+ .stApp {
47
+ background-color: var(--background-color);
48
  }
49
 
50
  /* 1. HEADER & TITLES */
51
  h1 {
52
  text-align: center;
53
  color: var(--primary-color);
54
+ font-size: 2.8em;
55
+ font-weight: 800;
56
+ border-bottom: 3px solid rgba(156, 39, 176, 0.3);
57
+ padding-bottom: 15px;
58
  margin-bottom: 30px;
59
  }
60
  h2, h3, h4 {
61
  color: var(--text-color);
62
+ border-left: 5px solid var(--primary-color); /* Purple marker for clarity */
63
+ padding-left: 15px;
64
+ margin-top: 30px;
65
  font-weight: 600;
66
  }
67
 
68
  /* 2. BUTTONS & HOVER EFFECTS */
69
  .stButton>button {
70
+ color: var(--text-color) !important;
71
+ border: 1px solid var(--container-background) !important;
72
  background-color: var(--container-background) !important;
73
+ border-radius: 12px; /* More rounded for modern look */
74
  transition: all 0.3s ease;
75
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
76
  font-weight: 600;
77
  }
78
  .stButton>button:hover {
79
+ background-color: #404040 !important;
80
+ box-shadow: 0 6px 15px rgba(0, 0, 0, 0.5);
81
  transform: translateY(-2px);
82
  }
83
  /* Primary Button (Run Screening) */
 
87
  border-color: var(--success-color) !important;
88
  }
89
  .stButton>button[kind="primary"]:hover {
90
+ background-color: #388E3C !important; /* Darker green on hover */
91
+ border-color: #388E3C !important;
92
  }
93
 
94
+ /* 3. INPUTS, CONTAINERS, TABS & SIDEBAR */
95
  .stTextArea, .stTextInput, .stFileUploader {
96
  border-radius: 8px;
97
+ border: 1px solid #444444;
98
+ background-color: #333333; /* Darker input background */
99
+ color: var(--text-color); /* Ensure input text is light */
100
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
101
  }
102
  .stTabs [aria-selected="true"] {
103
+ color: var(--primary-color) !important;
104
+ border-bottom: 3px solid var(--primary-color) !important;
105
  font-weight: bold;
106
  }
107
  .stSidebar {
108
+ background-color: #252525; /* Slightly lighter dark sidebar for contrast */
109
+ border-right: 1px solid #3A3A3A;
110
+ color: var(--text-color);
111
  }
112
 
113
+ /* Fix: Ensure text in sidebar expanders is visible */
114
+ [data-testid="stSidebar"] p,
115
+ [data-testid="stSidebar"] li,
116
+ [data-testid="stSidebar"] [data-testid="stExpander"] {
117
+ color: var(--secondary-text-color) !important;
118
+ }
119
+
120
+ /* Custom Scorecard Style (Dark Tiles) */
121
  .scorecard-block {
122
+ border: 1px solid #3A3A3A;
123
+ border-radius: 12px;
124
+ padding: 20px;
125
  margin: 5px 0;
126
+ background-color: #333333;
127
  transition: all 0.3s;
128
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
129
  }
130
  .scorecard-block:hover {
131
+ box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
132
  }
133
  .scorecard-value {
134
+ font-size: 38px;
135
+ font-weight: 800;
136
  color: var(--primary-color);
137
  }
138
  .scorecard-label {
139
  font-size: 14px;
140
+ color: var(--secondary-text-color);
141
  }
142
  /* Color override for specific blocks */
143
  .block-relevant { border-left: 5px solid var(--success-color); }
144
  .block-uncertain { border-left: 5px solid var(--warning-color); }
145
  .block-irrelevant { border-left: 5px solid var(--danger-color); }
146
 
147
+ /* Streamlit's Info/Success/Warning blocks made darker */
148
  [data-testid="stAlert"] {
149
  border-radius: 8px;
150
  padding: 15px;
151
  font-size: 16px;
152
+ background-color: #333333 !important; /* Darker alert backgrounds */
153
+ color: var(--text-color) !important;
154
  }
155
+ /* Override specific alert colors for dark theme contrast */
156
+ [data-testid="stAlert"] div[role="alert"] {
157
+ background-color: #333333 !important;
158
+ }
159
+ [data-testid="stAlert"] div[role="alert"].st-emotion-cache-1f81d5m { /* Info */
160
+ border-left: 5px solid #2196F3;
161
+ }
162
+ [data-testid="stAlert"] div[role="alert"].st-emotion-cache-1218yph { /* Warning */
163
+ border-left: 5px solid var(--warning-color);
164
+ }
165
+ [data-testid="stAlert"] div[role="alert"].st-emotion-cache-22lkyf { /* Error */
166
+ border-left: 5px solid var(--danger-color);
167
+ }
168
+ [data-testid="stAlert"] div[role="alert"].st-emotion-cache-5lq06g { /* Success */
169
+ border-left: 5px solid var(--success-color);
170
+ }
171
+
172
  </style>
173
  """, unsafe_allow_html=True)
174
 
 
 
 
 
 
175
 
176
+ # --- (Model and Helper Functions - Core logic remains the same) ---
177
+ # NOTE: The core ML logic and utility functions for PDF/DOCX parsing remain unchanged
178
+ # as they are robust and purely functional.
179
  skills_list = [
180
  'python', 'sql', 'c++', 'java', 'tableau', 'machine learning', 'data analysis',
181
  'business intelligence', 'r', 'tensorflow', 'pandas', 'spark', 'scikit-learn', 'aws',
 
212
  text += paragraph.text + "\n"
213
  return text.strip()
214
  except: return ""
215
+
216
  def extract_text_from_file(uploaded_file):
217
  if uploaded_file.name.endswith('.pdf'): return extract_text_from_pdf(uploaded_file)
218
  elif uploaded_file.name.endswith('.docx'): return extract_text_from_docx(uploaded_file)
 
327
  elif detected_skills: final_summary = f"Key Skills: {', '.join(detected_skills)}"
328
  else: final_summary = f"Experience: {exp_match.group(0) if exp_match else 'Unknown'}"
329
 
330
+ if suitability == "Relevant": color = "#4CAF50"
331
+ elif suitability == "Irrelevant": color = "#F44336"
332
  else: color = "#FFC107"
333
 
334
  return {"Suitability": suitability, "Key Skills & Experience Summary": final_summary, "Flagging Reason": warning, "Suitability_Color": color}
335
  except Exception as e:
336
+ return {"Suitability": "Error", "Key Skills & Experience Summary": "Failed to process profile", "Flagging Reason": str(e), "Suitability_Color": "#F44336"}
337
 
338
  @st.cache_data
339
  def generate_skill_pie_chart(resumes):
 
357
  labels = list(top_skills.keys())
358
  sizes = [(count / sum(top_skills.values())) * 100 for count in top_skills.values()]
359
 
360
+ # Use dark theme settings for the chart
361
+ plt.style.use('dark_background')
362
  fig, ax = plt.subplots(figsize=(6, 4))
363
+ colors = plt.cm.magma(np.linspace(0.3, 0.9, len(labels))) # Vibrant color map for dark mode
364
+ plt.rcParams['text.color'] = '#F8F8F8'
365
+ wedges, texts, autotexts = ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors, textprops={'fontsize': 10, 'color': '#F8F8F8'})
366
  ax.axis('equal')
367
+ plt.title("Top Candidate Skill Frequency", fontsize=14, color='#9C27B0', pad=10)
368
  return fig
369
 
370
  def render_sidebar():
371
+ """Render sidebar content with professional HR language."""
372
+ # Define hex colors
373
+ SUCCESS_COLOR = "#4CAF50"
374
+ WARNING_COLOR = "#FFC107"
375
+ DANGER_COLOR = "#F44336"
376
+ PRIMARY_COLOR = "#9C27B0"
377
 
378
  with st.sidebar:
379
  st.markdown(f"""
380
  <h2 style='text-align: center; border-left: none; padding-left: 0; color: {PRIMARY_COLOR};'>
381
+ TALENT SCREENING ASSISTANT
382
  </h2>
383
+ <p style='text-align: center; font-size: 14px; margin-top: 0; color: #B0B0B0;'>
384
  Powered by Advanced NLP (BERT + T5)
385
  </p>
386
  """, unsafe_allow_html=True)
 
420
  if 'valid_resumes' not in st.session_state: st.session_state.valid_resumes = []
421
  if 'models' not in st.session_state: st.session_state.models = None
422
 
423
+ st.markdown("<h1>🚀 AI TALENT SCREENING TOOL</h1>", unsafe_allow_html=True)
424
 
425
  # HR-friendly Tab Names
426
  tab_setup, tab_resumes, tab_results = st.tabs(["1. Job Requirement Setup", "2. Candidate Profile Upload", "3. Screening Report & Analytics"])
 
535
 
536
  for i, resume in enumerate(valid_resumes):
537
  status_text.text(f"Status: Analyzing Profile {i+1} of {total_steps}...")
538
+ bert_tok_single = {'input_ids': bert_tokenized['input_ids'][i].unsqueeze(0), 'attention_mask': bert_tokenized['attention_mask'][i].unsqueeze(0)}
539
+ t5_tok_single = {'input_ids': t5_tokenized['input_ids'][i].unsqueeze(0), 'attention_mask': t5_tokenized['attention_mask'][i].unsqueeze(0)}
 
 
 
 
 
 
 
 
 
540
  result = classify_and_summarize_batch(resume, job_description, bert_tok_single, t5_inputs[i], t5_tok_single, job_skills_set)
541
  result["Profile ID"] = f"Candidate {i+1}"
542
  results.append(result)
 
564
  st.markdown(f"#### Overview: {total} Candidate Profiles Processed")
565
 
566
  # Define hex colors again for the scorecard blocks
567
+ PRIMARY_COLOR = "#9C27B0"
568
+ SUCCESS_COLOR = "#4CAF50"
569
+ WARNING_COLOR = "#FFC107"
570
+ DANGER_COLOR = "#F44336"
571
 
572
  col1, col2, col3, col4 = st.columns(4)
573