umarch commited on
Commit
c32bc18
·
verified ·
1 Parent(s): a648533

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +353 -346
app.py CHANGED
@@ -1,346 +1,353 @@
1
- import streamlit as st
2
- import pandas as pd
3
- import os
4
- import logging
5
- import re
6
- from chromadb import PersistentClient
7
- from sentence_transformers import SentenceTransformer
8
- from langchain_groq import ChatGroq
9
- from rag_utils_updated import extract_text, preprocess_text, get_embeddings, is_image_pdf, assess_cv, extract_job_requirements
10
- import plotly.graph_objects as go
11
- from dotenv import load_dotenv
12
-
13
- # Logging setup
14
- logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
15
- logger = logging.getLogger(__name__)
16
-
17
- load_dotenv()
18
-
19
- # Test if LLM_PROMPT is loaded correctly
20
- if os.environ.get("LLM_PROMPT") is None:
21
- st.error("LLM_PROMPT is missing. Check your .env file!")
22
-
23
- # Initialize session state (ONLY for job description and flags)
24
- if "job_description" not in st.session_state:
25
- st.session_state.job_description = ""
26
- if "continue_to_detailed_assessment" not in st.session_state:
27
- st.session_state.continue_to_detailed_assessment = False
28
- if "requirements" not in st.session_state:
29
- st.session_state.requirements = None
30
- if "detailed_assessments" not in st.session_state:
31
- st.session_state.detailed_assessments = {} # Initialize as an empty dictionary
32
- if "chromadb_initialized" not in st.session_state:
33
- st.session_state.chromadb_initialized = False
34
- if "cvs" not in st.session_state:
35
- st.session_state.cvs = {}
36
- if "job_description_embedding" not in st.session_state:
37
- st.session_state.job_description_embedding = None
38
- # Initialize session state variable
39
- if "assessment_completed" not in st.session_state:
40
- st.session_state.assessment_completed = False
41
-
42
- # Persistent Storage for Embeddings
43
- PERMANENT_DB_PATH = "./cv_db"
44
- if "collection" not in st.session_state:
45
- db_client = PersistentClient(path=PERMANENT_DB_PATH)
46
- st.session_state.collection = db_client.get_or_create_collection("cv_embeddings")
47
-
48
- if "embedding_model" not in st.session_state:
49
- st.session_state.embedding_model = SentenceTransformer('all-mpnet-base-v2')
50
-
51
- if "groq_client" not in st.session_state:
52
- st.session_state.groq_client = ChatGroq(api_key=os.environ.get("GROQ_API_KEY"))
53
-
54
- st.title("CV Assessment and Ranking App")
55
-
56
- # 1. Input Job Description
57
- st.subheader("Enter Job Description")
58
- requirements_source = st.radio("Source:", ("File Upload", "Web Page Link", "Text Input"))
59
-
60
- job_description_text = ""
61
- if requirements_source == "File Upload":
62
- uploaded_file = st.file_uploader("Upload Job Requirements (PDF/DOCX)", type=["pdf", "docx"])
63
- if uploaded_file:
64
- job_description_text = extract_text(uploaded_file)
65
- elif requirements_source == "Web Page Link":
66
- # webpage_url = st.text_input("Enter Web Page URL")
67
- # if webpage_url:
68
- # job_description_text = extract_text(webpage_url)
69
- st.warning("This function is not available in MVP yet.")
70
- elif requirements_source == "Text Input":
71
- job_description_text = st.text_area("Enter Job Requirements", height=200)
72
-
73
- st.session_state.job_description = job_description_text
74
-
75
- if st.session_state.job_description:
76
- st.success("Job description uploaded successfully!")
77
-
78
- # 2. Upload CVs (Folder Upload)
79
- st.subheader("Upload CVs (Folder)")
80
- uploaded_files = st.file_uploader("Choose a folder containing CV files", accept_multiple_files=True)
81
-
82
- if uploaded_files and not st.session_state.assessment_completed:
83
- st.write(f"{len(uploaded_files)} CV(s) uploaded.")
84
-
85
- st.session_state.cvs = {}
86
- cv_embeddings_created = 0
87
-
88
- if not st.session_state.chromadb_initialized:
89
- try:
90
- ids_in_collection = st.session_state.collection.get()['ids']
91
- if ids_in_collection:
92
- st.session_state.collection.delete(ids=ids_in_collection)
93
- logger.info("ChromaDB collection cleared.")
94
- else:
95
- logger.info("ChromaDB collection is already empty. Skipping deletion.")
96
- except Exception as e:
97
- st.error(f"Error clearing ChromaDB collection: {e}")
98
- st.stop()
99
- st.session_state.chromadb_initialized = True
100
-
101
- for uploaded_file in uploaded_files:
102
- filename = uploaded_file.name
103
- if filename in st.session_state.cvs:
104
- continue
105
-
106
- for attempt in range(2):
107
- try:
108
- if is_image_pdf(uploaded_file):
109
- st.warning(f"{filename} appears to be an image-based PDF and cannot be processed.")
110
- break
111
-
112
- text = extract_text(uploaded_file)
113
- if not text.strip():
114
- raise ValueError("No text extracted.")
115
-
116
- preprocessed_text = preprocess_text(text)
117
- embedding = get_embeddings(preprocessed_text, st.session_state.embedding_model)
118
-
119
- st.session_state.cvs[filename] = {
120
- "text": preprocessed_text,
121
- "embedding": embedding,
122
- }
123
- cv_embeddings_created += 1
124
-
125
- try:
126
- st.session_state.collection.add(
127
- embeddings=[embedding],
128
- documents=[preprocessed_text],
129
- ids=[filename],
130
- metadatas=[{"filename": filename}]
131
- )
132
- logger.info(f"Embedding for {filename} added to ChromaDB.")
133
- except Exception as e:
134
- st.error(f"Error adding embedding to ChromaDB for {filename}: {e}")
135
- st.stop()
136
-
137
- break
138
-
139
- except Exception as e:
140
- logger.error(f"Text extraction failed for {filename} on attempt {attempt + 1}: {e}")
141
- if attempt == 1:
142
- st.error(f"Failed to process {filename} after multiple attempts.")
143
-
144
- if cv_embeddings_created > 0:
145
- st.success(f"{cv_embeddings_created} CV embeddings created successfully!")
146
-
147
- num_errors = len(uploaded_files) - cv_embeddings_created
148
- if num_errors > 0:
149
- st.error(f"Error in CV embeddings creation for {num_errors} CV(s).")
150
-
151
- if st.button("Continue Assessment"):
152
- st.session_state.continue_to_detailed_assessment = True
153
-
154
- elif uploaded_files and st.session_state.assessment_completed:
155
- st.warning("This is an MVP. Please refresh the page before uploading and assessing new files.")
156
-
157
- if st.session_state.continue_to_detailed_assessment:
158
- st.session_state.continue_to_detailed_assessment = False # reset value
159
- st.write("Performing detailed assessments...")
160
-
161
- # Extract Job Requirements
162
- if st.session_state.job_description and st.session_state.requirements is None:
163
- st.session_state.requirements = extract_job_requirements(st.session_state.job_description, st.session_state.groq_client)
164
- if st.session_state.requirements:
165
- with st.expander("Extracted Job Requirements:"):
166
- for req in st.session_state.requirements:
167
- st.write(f"- {req}")
168
- # st.write("Extracted Job Requirements:")
169
- # for req in st.session_state.requirements:
170
- # st.write(f"- {req}")
171
- else:
172
- st.warning("Could not extract job requirements.")
173
-
174
- # Generate job description embedding if not already done
175
- if st.session_state.job_description and st.session_state.job_description_embedding is None:
176
- try:
177
- job_description_embedding = get_embeddings(st.session_state.job_description, st.session_state.embedding_model)
178
- st.session_state.job_description_embedding = job_description_embedding
179
- except Exception as e:
180
- st.error(f"Error creating job description embedding: {e}")
181
- st.stop()
182
-
183
- # Detailed CV Assessments
184
- selected_cvs = list(st.session_state.cvs.keys())
185
-
186
- if not st.session_state.detailed_assessments:
187
- st.session_state.detailed_assessments = {}
188
- with st.spinner("Performing detailed assessments..."):
189
- for filename in selected_cvs:
190
- if filename in st.session_state.cvs:
191
- cv_text = st.session_state.cvs[filename]["text"]
192
- try:
193
- assessment = assess_cv(cv_text, st.session_state.requirements, filename, st.session_state.groq_client)
194
- st.session_state.detailed_assessments[filename] = assessment
195
- except Exception as e:
196
- st.error(f"Error during detailed assessment of {filename}: {e}")
197
-
198
- # Display Results (Remaining part of the code)
199
- st.session_state.assessment_completed = True
200
- st.success("Detailed assessments complete!")
201
-
202
- st.subheader("Candidates Assessment and Ranking")
203
-
204
- def parse_assessment(raw_response, requirements):
205
- """Parses the LLM's assessment with robust error handling."""
206
- matches = {
207
- "technical_lead": "Not Found",
208
- "hr_specialist": "Not Found",
209
- "project_manager": "Not Found",
210
- "final_assessment": "Not Found",
211
- "recommendation": "Not Found",
212
- "technical_lead_score": "Not Found",
213
- "hr_specialist_score": "Not Found",
214
- "project_manager_score": "Not Found",
215
- "final_assessment_score": "Not Found",
216
- }
217
-
218
- try:
219
- # Parse labeled scores
220
- technical_lead_match = re.search(r"Technical Lead Assessment:\s*(.*?)\s*Technical Lead Score:\s*(\d+)", raw_response, re.IGNORECASE | re.DOTALL)
221
- if technical_lead_match:
222
- matches["technical_lead"] = technical_lead_match.group(1).strip()
223
- matches["technical_lead_score"] = technical_lead_match.group(2)
224
-
225
- hr_specialist_match = re.search(r"HR Specialist Assessment:\s*(.*?)\s*HR Specialist Score:\s*(\d+)", raw_response, re.IGNORECASE | re.DOTALL)
226
- if hr_specialist_match:
227
- matches["hr_specialist"] = hr_specialist_match.group(1).strip()
228
- matches["hr_specialist_score"] = hr_specialist_match.group(2)
229
-
230
- project_manager_match = re.search(r"Project Manager Assessment:\s*(.*?)\s*Project Manager Score:\s*(\d+)", raw_response, re.IGNORECASE | re.DOTALL)
231
- if project_manager_match:
232
- matches["project_manager"] = project_manager_match.group(1).strip()
233
- matches["project_manager_score"] = project_manager_match.group(2)
234
-
235
- final_assessment_match = re.search(r"Final Assessment:\s*(.*?)\s*Final Assessment Score:\s*(\d+)", raw_response, re.IGNORECASE | re.DOTALL)
236
- if final_assessment_match:
237
- matches["final_assessment"] = final_assessment_match.group(1).strip()
238
- matches["final_assessment_score"] = final_assessment_match.group(2)
239
-
240
- recommendation_match = re.search(r"Recommendation:\s*(.*?)$", raw_response, re.IGNORECASE | re.DOTALL)
241
- if recommendation_match:
242
- matches["recommendation"] = recommendation_match.group(1).strip()
243
-
244
- # Fallback mechanism: extract scores from raw response if labels are not found
245
- if matches["technical_lead_score"] == "Not Found":
246
- score_match = re.search(r"Technical Lead Assessment:.*?score(?:s)?\s*(?:of)?\s*(\d+)\s*(?:out\s*of|\/)\s*100", raw_response, re.IGNORECASE | re.DOTALL)
247
- if score_match:
248
- matches["technical_lead_score"] = score_match.group(1)
249
- if matches["hr_specialist_score"] == "Not Found":
250
- score_match = re.search(r"HR Specialist Assessment:.*?score(?:s)?\s*(?:of)?\s*(\d+)\s*(?:out\s*of|\/)\s*100", raw_response, re.IGNORECASE | re.DOTALL)
251
- if score_match:
252
- matches["hr_specialist_score"] = score_match.group(1)
253
- if matches["project_manager_score"] == "Not Found":
254
- score_match = re.search(r"Project Manager Assessment:.*?score(?:s)?\s*(?:of)?\s*(\d+)\s*(?:out\s*of|\/)\s*100", raw_response, re.IGNORECASE | re.DOTALL)
255
- if score_match:
256
- matches["project_manager_score"] = score_match.group(1)
257
- if matches["final_assessment_score"] == "Not Found":
258
- score_match = re.search(r"Final Assessment:.*?(?:Consensus Score|total of|final score).*?(\d+)\s*(?:out of)?\s*100", raw_response, re.IGNORECASE | re.DOTALL)
259
- if score_match:
260
- matches["final_assessment_score"] = score_match.group(1)
261
-
262
- except Exception as e:
263
- print(f"Error parsing assessment: {e}")
264
-
265
- return matches
266
-
267
- # Data frame logic
268
- if st.session_state.detailed_assessments:
269
- assessments_df = pd.DataFrame(columns=["filename",
270
- "final_assessment_score", "final_assessment",
271
- "technical_lead_score", "technical_lead",
272
- "hr_specialist_score", "hr_specialist",
273
- "project_manager_score", "project_manager",
274
- "recommendation"
275
- ])
276
- for filename, assessment in st.session_state.detailed_assessments.items():
277
- if "error" in assessment:
278
- st.error(assessment["error"])
279
- elif "raw_response" in assessment:
280
- parsed_data = parse_assessment(assessment["raw_response"], st.session_state.requirements)
281
- # Append the new dictionary as a row
282
- assessments_df = pd.concat([assessments_df, pd.DataFrame([parsed_data])], ignore_index=True)
283
- assessments_df.loc[assessments_df.index[-1], 'filename'] = filename
284
- #st.write("---")
285
-
286
- # Sort the DataFrame by 'final_assessment_score' in descending order
287
- # Convert the column to numeric before sorting
288
- assessments_df['final_assessment_score'] = pd.to_numeric(assessments_df['final_assessment_score'], errors='coerce') #coerce turns non numeric values to NaN.
289
- assessments_df = assessments_df.sort_values(by='final_assessment_score', ascending=False)
290
-
291
- st.dataframe(assessments_df)
292
-
293
-
294
- st.subheader("Detailed Assessment Results")
295
- # Iterate through the DataFrame rows to display the UI for each assessment
296
- for index, row in assessments_df.iterrows():
297
- st.write(f"**Filename:** {row['filename']}")
298
- scores = {
299
- "Technical Lead": int(row["technical_lead_score"]),
300
- "HR Specialist": int(row["hr_specialist_score"]),
301
- "Project Manager": int(row["project_manager_score"]),
302
- "Final Assessment": int(row["final_assessment_score"]),
303
- }
304
- scores_df = pd.DataFrame(list(scores.items()), columns=["Expert", "Score"])
305
-
306
- # Create Plotly bar chart with annotations
307
- fig = go.Figure(data=[go.Bar(
308
- x=scores_df["Expert"],
309
- y=scores_df["Score"],
310
- text=scores_df["Score"],
311
- textposition='auto',
312
- )])
313
- fig.update_layout(yaxis_range=[0, 100])
314
-
315
- # Create columns layout
316
- col1, col2 = st.columns([1, 3])
317
-
318
- # Display bar chart in the first column
319
- with col1:
320
- st.plotly_chart(fig, use_container_width=True)
321
-
322
- # Display collapsed panels in the second column
323
- with col2:
324
- with st.expander("Technical Lead Assessment"):
325
- st.write(f"{row['technical_lead']}")
326
- st.write(f"**Technical Lead Score:** {row['technical_lead_score']}")
327
-
328
- with st.expander("HR Specialist Assessment"):
329
- st.write(f"{row['hr_specialist']}")
330
- st.write(f"**HR Specialist Score:** {row['hr_specialist_score']}")
331
-
332
- with st.expander("Project Manager Assessment"):
333
- st.write(f"{row['project_manager']}")
334
- st.write(f"**Project Manager Score:** {row['project_manager_score']}")
335
-
336
- with st.expander("Final Assessment"):
337
- st.write(f"{row['final_assessment']}")
338
- st.write(f"**Final Assessment Score:** {row['final_assessment_score']}")
339
-
340
- with st.expander("Recommendation"):
341
- st.write(f"{row['recommendation']}")
342
-
343
- st.write("---")
344
-
345
- else:
346
- st.write("No detailed assessments were performed.")
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import os
4
+ import logging
5
+ import re
6
+ from chromadb import PersistentClient
7
+ from sentence_transformers import SentenceTransformer
8
+ from langchain_groq import ChatGroq
9
+ from rag_utils_updated import extract_text, preprocess_text, get_embeddings, is_image_pdf, assess_cv, extract_job_requirements
10
+ import plotly.graph_objects as go
11
+ from dotenv import load_dotenv
12
+ from huggingface_hub import get_secret, SecretNotFoundError
13
+
14
+ # Logging setup
15
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # load_dotenv()
19
+ # Test if LLM_PROMPT is loaded correctly
20
+ # if os.environ.get("LLM_PROMPT") is None:
21
+ # st.error("LLM_PROMPT is missing. Check your .env file!")
22
+
23
+ try:
24
+ llm_prompt = get_secret("LLM_PROMPT") # Ensure "LLM_PROMPT" matches your secret name.
25
+ # Now you can use llm_prompt in your code
26
+ print("LLM_PROMPT was successfully retrieved from HuggingFace Secrets.")
27
+ except SecretNotFoundError:
28
+ st.error("LLM_PROMPT secret not found in Hugging Face secrets.")
29
+
30
+ # Initialize session state (ONLY for job description and flags)
31
+ if "job_description" not in st.session_state:
32
+ st.session_state.job_description = ""
33
+ if "continue_to_detailed_assessment" not in st.session_state:
34
+ st.session_state.continue_to_detailed_assessment = False
35
+ if "requirements" not in st.session_state:
36
+ st.session_state.requirements = None
37
+ if "detailed_assessments" not in st.session_state:
38
+ st.session_state.detailed_assessments = {} # Initialize as an empty dictionary
39
+ if "chromadb_initialized" not in st.session_state:
40
+ st.session_state.chromadb_initialized = False
41
+ if "cvs" not in st.session_state:
42
+ st.session_state.cvs = {}
43
+ if "job_description_embedding" not in st.session_state:
44
+ st.session_state.job_description_embedding = None
45
+ # Initialize session state variable
46
+ if "assessment_completed" not in st.session_state:
47
+ st.session_state.assessment_completed = False
48
+
49
+ # Persistent Storage for Embeddings
50
+ PERMANENT_DB_PATH = "./cv_db"
51
+ if "collection" not in st.session_state:
52
+ db_client = PersistentClient(path=PERMANENT_DB_PATH)
53
+ st.session_state.collection = db_client.get_or_create_collection("cv_embeddings")
54
+
55
+ if "embedding_model" not in st.session_state:
56
+ st.session_state.embedding_model = SentenceTransformer('all-mpnet-base-v2')
57
+
58
+ if "groq_client" not in st.session_state:
59
+ st.session_state.groq_client = ChatGroq(api_key=os.environ.get("GROQ_API_KEY"))
60
+
61
+ st.title("CV Assessment and Ranking App")
62
+
63
+ # 1. Input Job Description
64
+ st.subheader("Enter Job Description")
65
+ requirements_source = st.radio("Source:", ("File Upload", "Web Page Link", "Text Input"))
66
+
67
+ job_description_text = ""
68
+ if requirements_source == "File Upload":
69
+ uploaded_file = st.file_uploader("Upload Job Requirements (PDF/DOCX)", type=["pdf", "docx"])
70
+ if uploaded_file:
71
+ job_description_text = extract_text(uploaded_file)
72
+ elif requirements_source == "Web Page Link":
73
+ # webpage_url = st.text_input("Enter Web Page URL")
74
+ # if webpage_url:
75
+ # job_description_text = extract_text(webpage_url)
76
+ st.warning("This function is not available in MVP yet.")
77
+ elif requirements_source == "Text Input":
78
+ job_description_text = st.text_area("Enter Job Requirements", height=200)
79
+
80
+ st.session_state.job_description = job_description_text
81
+
82
+ if st.session_state.job_description:
83
+ st.success("Job description uploaded successfully!")
84
+
85
+ # 2. Upload CVs (Folder Upload)
86
+ st.subheader("Upload CVs (Folder)")
87
+ uploaded_files = st.file_uploader("Choose a folder containing CV files", accept_multiple_files=True)
88
+
89
+ if uploaded_files and not st.session_state.assessment_completed:
90
+ st.write(f"{len(uploaded_files)} CV(s) uploaded.")
91
+
92
+ st.session_state.cvs = {}
93
+ cv_embeddings_created = 0
94
+
95
+ if not st.session_state.chromadb_initialized:
96
+ try:
97
+ ids_in_collection = st.session_state.collection.get()['ids']
98
+ if ids_in_collection:
99
+ st.session_state.collection.delete(ids=ids_in_collection)
100
+ logger.info("ChromaDB collection cleared.")
101
+ else:
102
+ logger.info("ChromaDB collection is already empty. Skipping deletion.")
103
+ except Exception as e:
104
+ st.error(f"Error clearing ChromaDB collection: {e}")
105
+ st.stop()
106
+ st.session_state.chromadb_initialized = True
107
+
108
+ for uploaded_file in uploaded_files:
109
+ filename = uploaded_file.name
110
+ if filename in st.session_state.cvs:
111
+ continue
112
+
113
+ for attempt in range(2):
114
+ try:
115
+ if is_image_pdf(uploaded_file):
116
+ st.warning(f"{filename} appears to be an image-based PDF and cannot be processed.")
117
+ break
118
+
119
+ text = extract_text(uploaded_file)
120
+ if not text.strip():
121
+ raise ValueError("No text extracted.")
122
+
123
+ preprocessed_text = preprocess_text(text)
124
+ embedding = get_embeddings(preprocessed_text, st.session_state.embedding_model)
125
+
126
+ st.session_state.cvs[filename] = {
127
+ "text": preprocessed_text,
128
+ "embedding": embedding,
129
+ }
130
+ cv_embeddings_created += 1
131
+
132
+ try:
133
+ st.session_state.collection.add(
134
+ embeddings=[embedding],
135
+ documents=[preprocessed_text],
136
+ ids=[filename],
137
+ metadatas=[{"filename": filename}]
138
+ )
139
+ logger.info(f"Embedding for {filename} added to ChromaDB.")
140
+ except Exception as e:
141
+ st.error(f"Error adding embedding to ChromaDB for {filename}: {e}")
142
+ st.stop()
143
+
144
+ break
145
+
146
+ except Exception as e:
147
+ logger.error(f"Text extraction failed for {filename} on attempt {attempt + 1}: {e}")
148
+ if attempt == 1:
149
+ st.error(f"Failed to process {filename} after multiple attempts.")
150
+
151
+ if cv_embeddings_created > 0:
152
+ st.success(f"{cv_embeddings_created} CV embeddings created successfully!")
153
+
154
+ num_errors = len(uploaded_files) - cv_embeddings_created
155
+ if num_errors > 0:
156
+ st.error(f"Error in CV embeddings creation for {num_errors} CV(s).")
157
+
158
+ if st.button("Continue Assessment"):
159
+ st.session_state.continue_to_detailed_assessment = True
160
+
161
+ elif uploaded_files and st.session_state.assessment_completed:
162
+ st.warning("This is an MVP. Please refresh the page before uploading and assessing new files.")
163
+
164
+ if st.session_state.continue_to_detailed_assessment:
165
+ st.session_state.continue_to_detailed_assessment = False # reset value
166
+ st.write("Performing detailed assessments...")
167
+
168
+ # Extract Job Requirements
169
+ if st.session_state.job_description and st.session_state.requirements is None:
170
+ st.session_state.requirements = extract_job_requirements(st.session_state.job_description, st.session_state.groq_client)
171
+ if st.session_state.requirements:
172
+ with st.expander("Extracted Job Requirements:"):
173
+ for req in st.session_state.requirements:
174
+ st.write(f"- {req}")
175
+ # st.write("Extracted Job Requirements:")
176
+ # for req in st.session_state.requirements:
177
+ # st.write(f"- {req}")
178
+ else:
179
+ st.warning("Could not extract job requirements.")
180
+
181
+ # Generate job description embedding if not already done
182
+ if st.session_state.job_description and st.session_state.job_description_embedding is None:
183
+ try:
184
+ job_description_embedding = get_embeddings(st.session_state.job_description, st.session_state.embedding_model)
185
+ st.session_state.job_description_embedding = job_description_embedding
186
+ except Exception as e:
187
+ st.error(f"Error creating job description embedding: {e}")
188
+ st.stop()
189
+
190
+ # Detailed CV Assessments
191
+ selected_cvs = list(st.session_state.cvs.keys())
192
+
193
+ if not st.session_state.detailed_assessments:
194
+ st.session_state.detailed_assessments = {}
195
+ with st.spinner("Performing detailed assessments..."):
196
+ for filename in selected_cvs:
197
+ if filename in st.session_state.cvs:
198
+ cv_text = st.session_state.cvs[filename]["text"]
199
+ try:
200
+ assessment = assess_cv(cv_text, st.session_state.requirements, filename, st.session_state.groq_client)
201
+ st.session_state.detailed_assessments[filename] = assessment
202
+ except Exception as e:
203
+ st.error(f"Error during detailed assessment of {filename}: {e}")
204
+
205
+ # Display Results (Remaining part of the code)
206
+ st.session_state.assessment_completed = True
207
+ st.success("Detailed assessments complete!")
208
+
209
+ st.subheader("Candidates Assessment and Ranking")
210
+
211
+ def parse_assessment(raw_response, requirements):
212
+ """Parses the LLM's assessment with robust error handling."""
213
+ matches = {
214
+ "technical_lead": "Not Found",
215
+ "hr_specialist": "Not Found",
216
+ "project_manager": "Not Found",
217
+ "final_assessment": "Not Found",
218
+ "recommendation": "Not Found",
219
+ "technical_lead_score": "Not Found",
220
+ "hr_specialist_score": "Not Found",
221
+ "project_manager_score": "Not Found",
222
+ "final_assessment_score": "Not Found",
223
+ }
224
+
225
+ try:
226
+ # Parse labeled scores
227
+ technical_lead_match = re.search(r"Technical Lead Assessment:\s*(.*?)\s*Technical Lead Score:\s*(\d+)", raw_response, re.IGNORECASE | re.DOTALL)
228
+ if technical_lead_match:
229
+ matches["technical_lead"] = technical_lead_match.group(1).strip()
230
+ matches["technical_lead_score"] = technical_lead_match.group(2)
231
+
232
+ hr_specialist_match = re.search(r"HR Specialist Assessment:\s*(.*?)\s*HR Specialist Score:\s*(\d+)", raw_response, re.IGNORECASE | re.DOTALL)
233
+ if hr_specialist_match:
234
+ matches["hr_specialist"] = hr_specialist_match.group(1).strip()
235
+ matches["hr_specialist_score"] = hr_specialist_match.group(2)
236
+
237
+ project_manager_match = re.search(r"Project Manager Assessment:\s*(.*?)\s*Project Manager Score:\s*(\d+)", raw_response, re.IGNORECASE | re.DOTALL)
238
+ if project_manager_match:
239
+ matches["project_manager"] = project_manager_match.group(1).strip()
240
+ matches["project_manager_score"] = project_manager_match.group(2)
241
+
242
+ final_assessment_match = re.search(r"Final Assessment:\s*(.*?)\s*Final Assessment Score:\s*(\d+)", raw_response, re.IGNORECASE | re.DOTALL)
243
+ if final_assessment_match:
244
+ matches["final_assessment"] = final_assessment_match.group(1).strip()
245
+ matches["final_assessment_score"] = final_assessment_match.group(2)
246
+
247
+ recommendation_match = re.search(r"Recommendation:\s*(.*?)$", raw_response, re.IGNORECASE | re.DOTALL)
248
+ if recommendation_match:
249
+ matches["recommendation"] = recommendation_match.group(1).strip()
250
+
251
+ # Fallback mechanism: extract scores from raw response if labels are not found
252
+ if matches["technical_lead_score"] == "Not Found":
253
+ score_match = re.search(r"Technical Lead Assessment:.*?score(?:s)?\s*(?:of)?\s*(\d+)\s*(?:out\s*of|\/)\s*100", raw_response, re.IGNORECASE | re.DOTALL)
254
+ if score_match:
255
+ matches["technical_lead_score"] = score_match.group(1)
256
+ if matches["hr_specialist_score"] == "Not Found":
257
+ score_match = re.search(r"HR Specialist Assessment:.*?score(?:s)?\s*(?:of)?\s*(\d+)\s*(?:out\s*of|\/)\s*100", raw_response, re.IGNORECASE | re.DOTALL)
258
+ if score_match:
259
+ matches["hr_specialist_score"] = score_match.group(1)
260
+ if matches["project_manager_score"] == "Not Found":
261
+ score_match = re.search(r"Project Manager Assessment:.*?score(?:s)?\s*(?:of)?\s*(\d+)\s*(?:out\s*of|\/)\s*100", raw_response, re.IGNORECASE | re.DOTALL)
262
+ if score_match:
263
+ matches["project_manager_score"] = score_match.group(1)
264
+ if matches["final_assessment_score"] == "Not Found":
265
+ score_match = re.search(r"Final Assessment:.*?(?:Consensus Score|total of|final score).*?(\d+)\s*(?:out of)?\s*100", raw_response, re.IGNORECASE | re.DOTALL)
266
+ if score_match:
267
+ matches["final_assessment_score"] = score_match.group(1)
268
+
269
+ except Exception as e:
270
+ print(f"Error parsing assessment: {e}")
271
+
272
+ return matches
273
+
274
+ # Data frame logic
275
+ if st.session_state.detailed_assessments:
276
+ assessments_df = pd.DataFrame(columns=["filename",
277
+ "final_assessment_score", "final_assessment",
278
+ "technical_lead_score", "technical_lead",
279
+ "hr_specialist_score", "hr_specialist",
280
+ "project_manager_score", "project_manager",
281
+ "recommendation"
282
+ ])
283
+ for filename, assessment in st.session_state.detailed_assessments.items():
284
+ if "error" in assessment:
285
+ st.error(assessment["error"])
286
+ elif "raw_response" in assessment:
287
+ parsed_data = parse_assessment(assessment["raw_response"], st.session_state.requirements)
288
+ # Append the new dictionary as a row
289
+ assessments_df = pd.concat([assessments_df, pd.DataFrame([parsed_data])], ignore_index=True)
290
+ assessments_df.loc[assessments_df.index[-1], 'filename'] = filename
291
+ #st.write("---")
292
+
293
+ # Sort the DataFrame by 'final_assessment_score' in descending order
294
+ # Convert the column to numeric before sorting
295
+ assessments_df['final_assessment_score'] = pd.to_numeric(assessments_df['final_assessment_score'], errors='coerce') #coerce turns non numeric values to NaN.
296
+ assessments_df = assessments_df.sort_values(by='final_assessment_score', ascending=False)
297
+
298
+ st.dataframe(assessments_df)
299
+
300
+
301
+ st.subheader("Detailed Assessment Results")
302
+ # Iterate through the DataFrame rows to display the UI for each assessment
303
+ for index, row in assessments_df.iterrows():
304
+ st.write(f"**Filename:** {row['filename']}")
305
+ scores = {
306
+ "Technical Lead": int(row["technical_lead_score"]),
307
+ "HR Specialist": int(row["hr_specialist_score"]),
308
+ "Project Manager": int(row["project_manager_score"]),
309
+ "Final Assessment": int(row["final_assessment_score"]),
310
+ }
311
+ scores_df = pd.DataFrame(list(scores.items()), columns=["Expert", "Score"])
312
+
313
+ # Create Plotly bar chart with annotations
314
+ fig = go.Figure(data=[go.Bar(
315
+ x=scores_df["Expert"],
316
+ y=scores_df["Score"],
317
+ text=scores_df["Score"],
318
+ textposition='auto',
319
+ )])
320
+ fig.update_layout(yaxis_range=[0, 100])
321
+
322
+ # Create columns layout
323
+ col1, col2 = st.columns([1, 3])
324
+
325
+ # Display bar chart in the first column
326
+ with col1:
327
+ st.plotly_chart(fig, use_container_width=True)
328
+
329
+ # Display collapsed panels in the second column
330
+ with col2:
331
+ with st.expander("Technical Lead Assessment"):
332
+ st.write(f"{row['technical_lead']}")
333
+ st.write(f"**Technical Lead Score:** {row['technical_lead_score']}")
334
+
335
+ with st.expander("HR Specialist Assessment"):
336
+ st.write(f"{row['hr_specialist']}")
337
+ st.write(f"**HR Specialist Score:** {row['hr_specialist_score']}")
338
+
339
+ with st.expander("Project Manager Assessment"):
340
+ st.write(f"{row['project_manager']}")
341
+ st.write(f"**Project Manager Score:** {row['project_manager_score']}")
342
+
343
+ with st.expander("Final Assessment"):
344
+ st.write(f"{row['final_assessment']}")
345
+ st.write(f"**Final Assessment Score:** {row['final_assessment_score']}")
346
+
347
+ with st.expander("Recommendation"):
348
+ st.write(f"{row['recommendation']}")
349
+
350
+ st.write("---")
351
+
352
+ else:
353
+ st.write("No detailed assessments were performed.")