Update app.py
Browse files
app.py
CHANGED
|
@@ -19,7 +19,6 @@ from twilio.rest import Client
|
|
| 19 |
import logging
|
| 20 |
import whisper
|
| 21 |
import speech_recognition as sr
|
| 22 |
-
from gtts import gTTS
|
| 23 |
#model = whisper.load_model("base")
|
| 24 |
|
| 25 |
|
|
@@ -87,33 +86,12 @@ def get_questions(prompt, input_text, num_questions=3, max_retries=10):
|
|
| 87 |
|
| 88 |
return new_questions
|
| 89 |
|
| 90 |
-
async def generate_question_audio(question, voice="en-
|
| 91 |
clean_question = re.sub(r'[^A-Za-z0-9.,?! ]+', '', question)
|
| 92 |
tts = edge_tts.Communicate(text=clean_question, voice=voice)
|
| 93 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
tts = edge_tts.Communicate(text=clean_question, voice=voice)
|
| 97 |
-
await tts.save(tmp_file.name)
|
| 98 |
-
print("β
Edge-TTS audio generated successfully.")
|
| 99 |
-
return tmp_file.name
|
| 100 |
-
|
| 101 |
-
except Exception as e:
|
| 102 |
-
print(f"β οΈ Edge-TTS failed: {e}")
|
| 103 |
-
print("βͺ Falling back to Google TTS...")
|
| 104 |
-
|
| 105 |
-
try:
|
| 106 |
-
# πΉ Fallback: Google TTS (works inside Hugging Face)
|
| 107 |
-
tts = gTTS(text=clean_question, lang="en")
|
| 108 |
-
tts.save(tmp_file.name)
|
| 109 |
-
print("β
gTTS fallback audio generated successfully.")
|
| 110 |
-
return tmp_file.name
|
| 111 |
-
|
| 112 |
-
except Exception as e2:
|
| 113 |
-
print(f"β gTTS also failed: {e2}")
|
| 114 |
-
return None
|
| 115 |
-
|
| 116 |
-
|
| 117 |
|
| 118 |
########################################///////////////////////////////////////////////////#########################################
|
| 119 |
|
|
@@ -413,41 +391,91 @@ def evaluate_answers():
|
|
| 413 |
base_assessment_criteria_qualitative_non_hr = """
|
| 414 |
For the OVERALL qualitative summary, assess responses based on:
|
| 415 |
- Conceptual Understanding (effort and relevance more than perfect accuracy for the level)
|
| 416 |
-
- Communication Clarity (
|
| 417 |
- Depth of Explanation (relative to expected level)
|
| 418 |
-
- Use of Examples (if any, and
|
| 419 |
-
- Logical Flow (
|
|
|
|
|
|
|
| 420 |
"""
|
| 421 |
per_question_scoring_guidelines_non_hr = f"""
|
| 422 |
-
For EACH question and its answer,
|
| 423 |
The candidate is at a {level_string} level.
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
-
|
| 428 |
-
-
|
| 429 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
"""
|
| 431 |
if level_string == "beginner":
|
| 432 |
level_specific_instructions_non_hr = """
|
| 433 |
-
You are
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
- **
|
| 439 |
-
- **
|
| 440 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
"""
|
| 442 |
elif level_string == "intermediate":
|
| 443 |
-
level_specific_instructions_non_hr = """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
else: # Advanced
|
| 445 |
-
level_specific_instructions_non_hr = """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 446 |
|
| 447 |
evaluation_prompt_template_non_hr = f"""
|
| 448 |
{level_specific_instructions_non_hr}
|
| 449 |
{per_question_scoring_guidelines_non_hr}
|
| 450 |
{base_assessment_criteria_qualitative_non_hr}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
**YOUR RESPONSE MUST STRICTLY FOLLOW THIS FORMAT. PROVIDE SCORES FOR EACH QUESTION.**
|
| 452 |
Output format:
|
| 453 |
|
|
@@ -461,7 +489,10 @@ def evaluate_answers():
|
|
| 461 |
- Depth of Explanation: [Overall qualitative feedback here]
|
| 462 |
- Examples: [Overall qualitative feedback here]
|
| 463 |
- Logical Flow: [Overall qualitative feedback here]
|
|
|
|
| 464 |
[Any additional overall encouraging remarks can optionally follow here]
|
|
|
|
|
|
|
| 465 |
"""
|
| 466 |
candidate_responses_formatted_non_hr = "\n\n".join(
|
| 467 |
[f"Question {i+1}: {entry['question']}\nAnswer {i+1}: {str(entry.get('response', '[No response provided]'))}" for i, entry in enumerate(st.session_state["answers"])]
|
|
@@ -966,7 +997,7 @@ if st.session_state.get("generated_questions"):
|
|
| 966 |
|
| 967 |
# Phase 2: Waiting to Start Recording
|
| 968 |
elif st.session_state["record_phase"] == "waiting_to_start":
|
| 969 |
-
remaining =
|
| 970 |
if remaining > 0:
|
| 971 |
st.markdown(f"<h4 class='timer-text'>β³ {remaining} seconds to click 'Start Recording'...</h4>", unsafe_allow_html=True)
|
| 972 |
if st.button("ποΈ Start Recording"):
|
|
@@ -1078,23 +1109,6 @@ if st.session_state.get("show_summary", False):
|
|
| 1078 |
current_percentage_score = st.session_state.get('percentage_score', 0.0)
|
| 1079 |
current_overall_score = st.session_state.get('overall_score', 0.0)
|
| 1080 |
|
| 1081 |
-
# --- Retrieve stored configuration info ---
|
| 1082 |
-
selected_domain = st.session_state.get("selected_domain", "N/A")
|
| 1083 |
-
input_type = st.session_state.get("section_choice", st.session_state.get("soft_skill_mode", "N/A"))
|
| 1084 |
-
difficulty_level = st.session_state.get("difficulty_level_select", "N/A")
|
| 1085 |
-
total_questions_selected = st.session_state.get("num_qs", num_qs_in_session)
|
| 1086 |
-
selected_company = st.session_state.get("selected_company")
|
| 1087 |
-
selected_job_role = st.session_state.get("selected_job_role")
|
| 1088 |
-
|
| 1089 |
-
# --- Display configuration summary ---
|
| 1090 |
-
st.markdown("### βοΈ Test Configuration Summary")
|
| 1091 |
-
st.markdown(f"""
|
| 1092 |
-
- **Domain Selected:** {selected_domain}
|
| 1093 |
-
- **Input Type / Mode:** {section_choice}
|
| 1094 |
-
- **Difficulty Level / Job Role:** {difficulty_level if selected_domain != "Finance" else job_roles}
|
| 1095 |
-
- **Total Questions Selected:** {total_questions_selected}
|
| 1096 |
-
""")
|
| 1097 |
-
|
| 1098 |
if st.session_state["selected_domain"] == "Soft Skills":
|
| 1099 |
hr_table_data = []
|
| 1100 |
for param, config in HR_PARAMETERS_CONFIG.items():
|
|
@@ -1181,19 +1195,10 @@ if st.session_state.get("show_summary", False):
|
|
| 1181 |
|
| 1182 |
# Helper function to prepare summary text for download
|
| 1183 |
def prepare_summary_for_download():
|
| 1184 |
-
#download_text = f"# GrillMaster Mock Interview Summary\n\n"
|
| 1185 |
-
#download_text += f"**Selected Domain:** {st.session_state.get('selected_domain', 'N/A')}\n"
|
| 1186 |
-
#dl_difficulty = st.session_state.get('difficulty_level_select', 'N/A')
|
| 1187 |
-
#download_text += f"**Difficulty Level:** {dl_difficulty}\n"
|
| 1188 |
download_text = f"# GrillMaster Mock Interview Summary\n\n"
|
| 1189 |
-
download_text += f"**Selected Domain:** {selected_domain}\n"
|
| 1190 |
-
|
| 1191 |
-
download_text += f"**Difficulty Level
|
| 1192 |
-
download_text += f"**Total Questions Selected:** {total_questions_selected}\n"
|
| 1193 |
-
download_text += f"**Company Selected:** {selected_company}\n"
|
| 1194 |
-
download_text += f"**Job Role:** {selected_job_role}\n"
|
| 1195 |
-
#download_text += f"**Calculated Overall Score:** {current_overall_score:.1f} / {max_score_possible_for_session:.1f} ({current_percentage_score:.1f}%)\n\n"
|
| 1196 |
-
download_text += "## Questions & Candidate's Answers:\n"
|
| 1197 |
|
| 1198 |
num_q_for_max_score = len(st.session_state.get("generated_questions", st.session_state.get("answers",[])))
|
| 1199 |
max_s_for_dl = num_q_for_max_score * 5.0
|
|
|
|
| 19 |
import logging
|
| 20 |
import whisper
|
| 21 |
import speech_recognition as sr
|
|
|
|
| 22 |
#model = whisper.load_model("base")
|
| 23 |
|
| 24 |
|
|
|
|
| 86 |
|
| 87 |
return new_questions
|
| 88 |
|
| 89 |
+
async def generate_question_audio(question, voice="en-IE-EmilyNeural"):
|
| 90 |
clean_question = re.sub(r'[^A-Za-z0-9.,?! ]+', '', question)
|
| 91 |
tts = edge_tts.Communicate(text=clean_question, voice=voice)
|
| 92 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
|
| 93 |
+
await tts.save(tmp_file.name)
|
| 94 |
+
return tmp_file.name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
########################################///////////////////////////////////////////////////#########################################
|
| 97 |
|
|
|
|
| 391 |
base_assessment_criteria_qualitative_non_hr = """
|
| 392 |
For the OVERALL qualitative summary, assess responses based on:
|
| 393 |
- Conceptual Understanding (effort and relevance more than perfect accuracy for the level)
|
| 394 |
+
- Communication Clarity (is the idea understandable and logically stated?)
|
| 395 |
- Depth of Explanation (relative to expected level)
|
| 396 |
+
- Use of Examples (if any, and appropriate for the level)
|
| 397 |
+
- Logical Flow (basic structure or reasoning flow)
|
| 398 |
+
|
| 399 |
+
Focus on both understanding and reasoning. Responses should demonstrate thinking, not memorization.
|
| 400 |
"""
|
| 401 |
per_question_scoring_guidelines_non_hr = f"""
|
| 402 |
+
For EACH question and its answer, assign a score from 0 to 5 points.
|
| 403 |
The candidate is at a {level_string} level.
|
| 404 |
+
Use the numeric scale and notes below for calibration.
|
| 405 |
+
|
| 406 |
+
**Scoring Scale (per question):**
|
| 407 |
+
- **5 (Excellent / 90β100%)** β Comprehensive, accurate, and well-structured. Includes reasoning or an example. Rare and well-deserved.
|
| 408 |
+
- **4 (Good / 75β89%)** β Mostly correct, relevant, and clear. Minor conceptual gaps but good structure.
|
| 409 |
+
- **3 (Fair / 60β74%)** β Partially correct or lacks depth, but shows understanding and effort.
|
| 410 |
+
- **2 (Basic / 45β59%)** β One-line or short answer with minimal reasoning; incomplete or overly generic.
|
| 411 |
+
- **1 (Poor / 30β44%)** β Attempted but largely irrelevant or unclear.
|
| 412 |
+
- **0 (No Effort / <30%)** β Incorrect, off-topic, or explicitly βI donβt knowβ.
|
| 413 |
+
|
| 414 |
+
**Important Rules:**
|
| 415 |
+
- *One-word or one-line answers* (e.g., just definitions or keywords) must NOT score more than **2 out of 5**, regardless of correctness, because they lack reasoning and depth.
|
| 416 |
+
- Encourage clarity, structure, and explanation over memorized phrases.
|
| 417 |
+
"""
|
| 418 |
+
scoring_tightness_guidelines = """
|
| 419 |
+
**Scoring Calibration (Strictness Guidance):**
|
| 420 |
+
- Maintain a slightly tight scoring approach.
|
| 421 |
+
- Incomplete or short one-line answers score **below 60% (1β2 out of 5)**.
|
| 422 |
+
- Scores of **5/5 (100%)** should be **rare** β reserved for comprehensive, insightful, and well-reasoned answers.
|
| 423 |
+
- Most competent answers should fall between **3 and 4**.
|
| 424 |
+
- When unsure, choose the **lower score** to maintain scoring consistency.
|
| 425 |
"""
|
| 426 |
if level_string == "beginner":
|
| 427 |
level_specific_instructions_non_hr = """
|
| 428 |
+
You are a **supportive, understanding evaluator** for a **BEGINNER/FRESHER**.
|
| 429 |
+
Focus on clarity, effort, and attempt β not perfection.
|
| 430 |
+
Encourage learning through feedback, but ensure fair scoring.
|
| 431 |
+
|
| 432 |
+
**Scoring Guidelines (0β5):**
|
| 433 |
+
- **5** β Accurate, clear, well-structured, and shows strong effort and reasoning. Rare.
|
| 434 |
+
- **4** β Mostly correct, relevant, and shows basic reasoning or understanding.
|
| 435 |
+
- **3** β Partial correctness with effort; may lack completeness or flow.
|
| 436 |
+
- **1β2** β One-line or definition-only answers; minimal reasoning. (Below 60%)
|
| 437 |
+
- **0** β No effort or irrelevant response.
|
| 438 |
+
Avoid giving high scores to short, memorized, or definition-only responses.
|
| 439 |
+
Provide motivating feedback that highlights areas of improvement.
|
| 440 |
"""
|
| 441 |
elif level_string == "intermediate":
|
| 442 |
+
level_specific_instructions_non_hr = """
|
| 443 |
+
You are a **balanced and fair evaluator** for an **INTERMEDIATE** candidate.
|
| 444 |
+
Expect conceptual clarity, structured reasoning, and relevant examples.
|
| 445 |
+
Be encouraging yet objective in scoring.
|
| 446 |
+
|
| 447 |
+
**Scoring Guidelines (0β5):**
|
| 448 |
+
- **5** β Clear, accurate, structured response with reasoning and relevance. Rare.
|
| 449 |
+
- **4** β Mostly correct with some logical structure and explanation.
|
| 450 |
+
- **3** β Some understanding; missing clarity or key detail.
|
| 451 |
+
- **1β2** β Short, definition-like, or minimal response. (Below 60%)
|
| 452 |
+
- **0** β Irrelevant or incorrect.
|
| 453 |
+
Never assign high scores to one-line or superficial answers.
|
| 454 |
+
"""
|
| 455 |
else: # Advanced
|
| 456 |
+
level_specific_instructions_non_hr = """
|
| 457 |
+
You are a **discerning but fair evaluator** for an **ADVANCED** professional.
|
| 458 |
+
Expect precision, applied understanding, and structured reasoning.
|
| 459 |
+
Maintain fairness without excessive strictness.
|
| 460 |
+
|
| 461 |
+
**Scoring Guidelines (0β5):**
|
| 462 |
+
- **5** β Exceptionally comprehensive, insightful, and accurate. (Rare)
|
| 463 |
+
- **4** β Correct and well-reasoned; may lack minor nuance or application.
|
| 464 |
+
- **3** β Adequate but missing depth, structure, or examples.
|
| 465 |
+
- **1β2** β Generic, incomplete, or one-line responses without reasoning. (Below 60%)
|
| 466 |
+
- **0** β Fundamentally incorrect or irrelevant.
|
| 467 |
+
Be concise and consistent in judgment; reward depth, not brevity.
|
| 468 |
+
"""
|
| 469 |
|
| 470 |
evaluation_prompt_template_non_hr = f"""
|
| 471 |
{level_specific_instructions_non_hr}
|
| 472 |
{per_question_scoring_guidelines_non_hr}
|
| 473 |
{base_assessment_criteria_qualitative_non_hr}
|
| 474 |
+
{scoring_tightness_guidelines}
|
| 475 |
+
|
| 476 |
+
When evaluating, be supportive yet fair. Encourage clarity and effort but avoid over-rewarding shallow or memorized answers.
|
| 477 |
+
Maintain a balanced tone β neither too strict nor too lenient.
|
| 478 |
+
|
| 479 |
**YOUR RESPONSE MUST STRICTLY FOLLOW THIS FORMAT. PROVIDE SCORES FOR EACH QUESTION.**
|
| 480 |
Output format:
|
| 481 |
|
|
|
|
| 489 |
- Depth of Explanation: [Overall qualitative feedback here]
|
| 490 |
- Examples: [Overall qualitative feedback here]
|
| 491 |
- Logical Flow: [Overall qualitative feedback here]
|
| 492 |
+
- Final Remarks: [Brief encouraging but fair closing note]
|
| 493 |
[Any additional overall encouraging remarks can optionally follow here]
|
| 494 |
+
|
| 495 |
+
Provide the final tone as **professional, balanced, and confidence-building**.
|
| 496 |
"""
|
| 497 |
candidate_responses_formatted_non_hr = "\n\n".join(
|
| 498 |
[f"Question {i+1}: {entry['question']}\nAnswer {i+1}: {str(entry.get('response', '[No response provided]'))}" for i, entry in enumerate(st.session_state["answers"])]
|
|
|
|
| 997 |
|
| 998 |
# Phase 2: Waiting to Start Recording
|
| 999 |
elif st.session_state["record_phase"] == "waiting_to_start":
|
| 1000 |
+
remaining = 15 - int(elapsed)
|
| 1001 |
if remaining > 0:
|
| 1002 |
st.markdown(f"<h4 class='timer-text'>β³ {remaining} seconds to click 'Start Recording'...</h4>", unsafe_allow_html=True)
|
| 1003 |
if st.button("ποΈ Start Recording"):
|
|
|
|
| 1109 |
current_percentage_score = st.session_state.get('percentage_score', 0.0)
|
| 1110 |
current_overall_score = st.session_state.get('overall_score', 0.0)
|
| 1111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1112 |
if st.session_state["selected_domain"] == "Soft Skills":
|
| 1113 |
hr_table_data = []
|
| 1114 |
for param, config in HR_PARAMETERS_CONFIG.items():
|
|
|
|
| 1195 |
|
| 1196 |
# Helper function to prepare summary text for download
|
| 1197 |
def prepare_summary_for_download():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1198 |
download_text = f"# GrillMaster Mock Interview Summary\n\n"
|
| 1199 |
+
download_text += f"**Selected Domain:** {st.session_state.get('selected_domain', 'N/A')}\n"
|
| 1200 |
+
dl_difficulty = st.session_state.get('difficulty_level_select', 'N/A')
|
| 1201 |
+
download_text += f"**Difficulty Level:** {dl_difficulty}\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1202 |
|
| 1203 |
num_q_for_max_score = len(st.session_state.get("generated_questions", st.session_state.get("answers",[])))
|
| 1204 |
max_s_for_dl = num_q_for_max_score * 5.0
|